2583 lines
95 KiB
Python
2583 lines
95 KiB
Python
from datetime import timedelta
|
||
from pandas import DataFrame
|
||
from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR, Chan_SEG_DIR, Chan_ZS_DIR, Chan_BSP_DIR, Chan_BSP_TYPE, Chan_KLC_FX, Chan_PRICE_TREND, Chan_KLU_PATTERN, Chan_K_DIR, Chan_KLC_STATE
|
||
from ChanKLU import ChanKLU
|
||
from ChanKLC import ChanKLC
|
||
from ChanBI import ChanBI
|
||
from ChanSBI import ChanSBI
|
||
from ChanSEG import ChanSEG
|
||
from ChanZS import ChanZS, ChanZS_Big
|
||
from ChanBIZS import ChanBIZS
|
||
from ChanBSP import ChanBSP
|
||
import talib.abstract as ta
|
||
import pandas as pd
|
||
from technical.util import resample_to_interval
|
||
from decimal import Decimal
|
||
import numpy as np
|
||
from ChanMACD import ChanMACD
|
||
|
||
class TF_DF():
|
||
def __init__(self, df=None, interval=0, timeframe=None):
|
||
if df is not None:
|
||
self.init_TF_DF(df, interval, timeframe)
|
||
def init_TF_DF(self, df, interval, timeframe):
|
||
self.timeframe = timeframe
|
||
self.interval = interval
|
||
# 检查 DataFrame 是否为空或没有 date 列
|
||
if df is None or df.empty:
|
||
raise ValueError(f"DataFrame for {timeframe} is empty. Please download data first.")
|
||
if 'date' not in df.columns:
|
||
raise ValueError(f"DataFrame for {timeframe} missing 'date' column. Columns: {df.columns.tolist()}")
|
||
# interval=1 时不需要重采样
|
||
if interval == 1:
|
||
self.dataframe = df.copy()
|
||
else:
|
||
self.dataframe = resample_to_interval(df, interval)
|
||
#print(self.timeframe, len(self.dataframe))
|
||
self.dataframe = self.add_indicators(self.dataframe)
|
||
self.klu_list = []
|
||
self.klc_list = []
|
||
self.bi_list = []
|
||
self.zs_list = []
|
||
self.bsp_list = []
|
||
self.seg_list = []
|
||
self.klc_fx_list = []
|
||
self.klu_list = self.cal_kl_data(self.dataframe)
|
||
self.klc_list = self.get_klc_list(self.klu_list)
|
||
self.bi_list = self.cal_bi_list(self.klc_list)
|
||
self.seg_list = self.get_seg_list(self.bi_list)
|
||
self.zs_list = self.get_zs_list(self.bi_list, self.seg_list)
|
||
self.big_zs_list = self.get_big_zs_list(self.zs_list)
|
||
self.chanmacd = ChanMACD(self.klu_list)
|
||
self.klu_list = self.chanmacd.cal_macd_state()
|
||
|
||
|
||
def get_ema52(self, index=-1):
|
||
if self.klu_list:
|
||
ema52_value = self.klu_list[index].ema52
|
||
# 处理NaN值
|
||
if pd.isna(ema52_value) or ema52_value is None:
|
||
return None
|
||
return float(ema52_value)
|
||
return None
|
||
def get_ema24(self, index=-1):
|
||
if self.klu_list:
|
||
ema24_value = self.klu_list[index].ema24
|
||
# 处理NaN值
|
||
if pd.isna(ema24_value) or ema24_value is None:
|
||
return None
|
||
return float(ema24_value)
|
||
return None
|
||
def get_current_klc(self):
|
||
if len(self.klc_list) > 0:
|
||
return self.klc_list[-2]
|
||
return None
|
||
def add_indicators(self, df):
|
||
fast = 26
|
||
slow = 52
|
||
period = 9
|
||
macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period)
|
||
bb365 = ta.BBANDS(df, timeperiod=365, nbdevup=3.0, nbdevdn=3.0, matype=0)
|
||
bb120 = ta.BBANDS(df, timeperiod=120, nbdevup=3.0, nbdevdn=3.0, matype=0)
|
||
bb30 = ta.BBANDS(df, timeperiod=41, nbdevup=2.3, nbdevdn=2.3, matype=0)
|
||
bb302 = ta.BBANDS(df, timeperiod=41, nbdevup=2.0, nbdevdn=2.0, matype=0)
|
||
bb30 = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0)
|
||
bb302 = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0)
|
||
bb2633 = ta.BBANDS(df, timeperiod=26, nbdevup=3.0, nbdevdn=3.0, matype=0)
|
||
# 计算布林带中轨(移动平均线)
|
||
bb30_middle = ta.SMA(df, timeperiod=90)
|
||
|
||
# 手动计算布林带 %B 指标 (BBP)
|
||
# %B = (Price - Lower Band) / (Upper Band - Lower Band)
|
||
bbp365 = (df['close'] - bb365['lowerband']) / (bb365['upperband'] - bb365['lowerband'])
|
||
bbp120 = (df['close'] - bb120['lowerband']) / (bb120['upperband'] - bb120['lowerband'])
|
||
bbp30 = (df['close'] - bb30['lowerband']) / (bb30['upperband'] - bb30['lowerband'])
|
||
bbp302 = (df['close'] - bb302['lowerband']) / (bb302['upperband'] - bb302['lowerband'])
|
||
bbp2633 = (df['close'] - bb2633['lowerband']) / (bb2633['upperband'] - bb2633['lowerband'])
|
||
df['bb2633upper'] = bb2633['upperband']
|
||
df['bb2633lower'] = bb2633['lowerband']
|
||
df['bbp2633'] = bbp2633
|
||
df['bb2633middle'] = bb2633['middleband']
|
||
df['atr'] = ta.ATR(df, timeperiod=14)
|
||
df['bbup365'] = bb365['upperband']
|
||
df['bblow365'] = bb365['lowerband']
|
||
df['bbp365'] = bbp365
|
||
df['bbup120'] = bb120['upperband']
|
||
df['bblow120'] = bb120['lowerband']
|
||
df['bbp120'] = bbp120
|
||
df['bbup30'] = bb30['upperband']
|
||
df['bblow30'] = bb30['lowerband']
|
||
df['bbmiddle30'] = bb30_middle # 添加bb30中轨
|
||
df['bbp30'] = bbp30
|
||
df['bbup302'] = bb302['upperband']
|
||
df['bblow302'] = bb302['lowerband']
|
||
df['bbp302'] = bbp302
|
||
df['macd'] = macd['macd']
|
||
df['macdsignal'] = macd['macdsignal']
|
||
df['macdhist'] = macd['macdhist']
|
||
df['ema5'] = ta.EMA(df, timeperiod=5)
|
||
df['ema10'] = ta.EMA(df, timeperiod=10)
|
||
df['ema24'] = ta.EMA(df, timeperiod=24)
|
||
df['ema52'] = ta.EMA(df, timeperiod=52)
|
||
df['ema104'] = ta.EMA(df, timeperiod=104)
|
||
df['ema156'] = ta.EMA(df, timeperiod=156)
|
||
df['ema208'] = ta.EMA(df, timeperiod=208)
|
||
df['ema26'] = ta.EMA(df, timeperiod=26)
|
||
df['ema13'] = ta.EMA(df, timeperiod=13)
|
||
df['ema7'] = ta.EMA(df, timeperiod=7)
|
||
df['rsi'] = ta.RSI(df, timeperiod=14)
|
||
df['volume_ratio'] = self.cal_volume_ratio(df)
|
||
return df
|
||
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 get_bsp_state(self, dataframe):
|
||
klu_list = self.get_klu_list(dataframe)
|
||
klc_list = self.get_klc_list(klu_list)
|
||
bi_list = self.cal_bi_list(klc_list)
|
||
seg_list = self.get_seg_list(bi_list)
|
||
bi_zs_list = self.cal_bi_zs(seg_list)
|
||
bsp_list = self.find_all_bsp(bi_list, bi_zs_list)
|
||
bsp_state_list = [0] * len(dataframe)
|
||
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_fx_type == Chan_KLC_FX.TOP2:
|
||
bi = klc.bi.pre
|
||
if bi and bi.is_sure and bi.end_klc.bsp_type == Chan_BSP_TYPE.B3:
|
||
# 第三类买点
|
||
bsp_state_list[index] = -1
|
||
#print(klc.end_time, "B3")
|
||
else:
|
||
bsp_state_list[index] = 0
|
||
elif klc.klc_fx_type == Chan_KLC_FX.BOTTOM2:
|
||
bi = klc.bi.pre
|
||
if bi and bi.is_sure and bi.end_klc.bsp_type == Chan_BSP_TYPE.S3:
|
||
# 第三类卖点
|
||
bsp_state_list[index] = 1
|
||
#print(klc.end_time, "S3")
|
||
else:
|
||
bsp_state_list[index] = 0
|
||
klc_index += 1
|
||
else:
|
||
bsp_state_list[index] = 0
|
||
return bsp_state_list
|
||
def get_ema_state(self, dataframe):
|
||
klu_list = self.get_klu_list(dataframe)
|
||
klc_list = self.get_klc_list(klu_list)
|
||
bi_list = self.cal_bi_list(klc_list)
|
||
klu_state_list = []
|
||
for klu in klu_list:
|
||
if klu.near0_return == 1:
|
||
klu_state_list.append("1")
|
||
elif klu.near0_return == 9:
|
||
klu_state_list.append("-1")
|
||
elif klu.candle_dir == Chan_K_DIR.BULL:
|
||
klu_state_list.append("2")
|
||
elif klu.candle_dir == Chan_K_DIR.BEAR:
|
||
klu_state_list.append("-2")
|
||
else:
|
||
klu_state_list.append("0")
|
||
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):
|
||
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.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_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_trend(self, klc_list):
|
||
"""
|
||
基于价格与EMA24/EMA52的位置关系、以及MACD/Signal/Hist的方向,
|
||
为每个KLC打上趋势标签:'UP' / 'DOWN' / 'FLAT'。
|
||
仅设置 klc.trend,不影响其它字段。
|
||
"""
|
||
if not klc_list:
|
||
return klc_list
|
||
last_trend = Chan_PRICE_TREND.UNKNOWN
|
||
# 趋势延续性:参考近 N 根已完成的KLC
|
||
lookback_n = 5
|
||
prev_klcs = []
|
||
for klc in klc_list:
|
||
price = getattr(klc, 'close', None)
|
||
ema24 = getattr(klc, 'ema24', None)
|
||
ema52 = getattr(klc, 'ema52', None)
|
||
macd_raw = getattr(klc, 'macd', None)
|
||
signal_raw = getattr(klc, 'signal', None)
|
||
hist_raw = getattr(klc, 'macdhist', None)
|
||
macd = macd_raw if macd_raw is not None else 0
|
||
signal = signal_raw if signal_raw is not None else 0
|
||
hist = hist_raw if hist_raw is not None else 0
|
||
rsi = getattr(klc, 'rsi', None)
|
||
macd_ready = macd_raw is not None and signal_raw is not None
|
||
hist_ready = hist_raw is not None
|
||
trend = Chan_PRICE_TREND.UNKNOWN
|
||
score = 0
|
||
try:
|
||
# 有效性
|
||
price_valid = price is not None and price != 0
|
||
ema24_valid = ema24 is not None and ema24 != 0
|
||
ema52_valid = ema52 is not None and ema52 != 0
|
||
# 多因子投票
|
||
|
||
# 1) 均线结构 + 价位
|
||
if ema24_valid or ema52_valid:
|
||
ma_votes = 0
|
||
if ema24_valid and ema52_valid:
|
||
ma_votes += 1 if ema24 > ema52 else -1
|
||
if price_valid and ema24_valid:
|
||
ma_votes += 1 if price > ema24 else 0
|
||
if price_valid and ema52_valid:
|
||
ma_votes += 1 if price > ema52 else -1
|
||
# 限幅,避免相关因子重复计分
|
||
score += max(-2, min(2, ma_votes))
|
||
# 2) MACD结构
|
||
if macd_ready:
|
||
score += 1 if macd >= signal else -1
|
||
if hist_ready and hist != 0:
|
||
score += 1 if hist > 0 else -1
|
||
# 3) 动量与均线差分斜率
|
||
pre = getattr(klc, 'pre', None)
|
||
pre_hist = getattr(pre, 'macdhist', None) if pre else None
|
||
if pre:
|
||
pre_close = getattr(pre, 'close', None)
|
||
if price_valid and pre_close is not None:
|
||
score += 1 if price >= pre_close else -1
|
||
pre_ema24 = getattr(pre, 'ema24', None)
|
||
pre_ema52 = getattr(pre, 'ema52', None)
|
||
if ema24_valid and ema52_valid and pre_ema24 not in (None, 0) and pre_ema52 not in (None, 0):
|
||
spread_now = ema24 - ema52
|
||
spread_pre = pre_ema24 - pre_ema52
|
||
score += 1 if spread_now >= spread_pre else -1
|
||
# 3.1) MACD柱体动量趋势:考虑 macdhist 的斜率与过零
|
||
if hist_ready and pre_hist is not None:
|
||
# 柱体斜率:上升加分,下降减分
|
||
if hist > pre_hist:
|
||
score += 1
|
||
elif hist < pre_hist:
|
||
score -= 1
|
||
# 过零加权:负转正更偏多,正转负更偏空
|
||
if pre_hist < 0 and hist > 0:
|
||
score += 1
|
||
elif pre_hist > 0 and hist < 0:
|
||
score -= 1
|
||
# 3.2) EMA52 突破/跌破加权
|
||
if ema52_valid and price_valid and pre_close is not None and pre_ema52 not in (None, 0):
|
||
# 看多突破:从均线下方上破且动量配合
|
||
if pre_close <= pre_ema52 and price > ema52 and (hist is None or pre_hist is None or hist >= pre_hist):
|
||
score += 1
|
||
# 看空跌破:从均线上方下破且动量配合
|
||
if pre_close >= pre_ema52 and price < ema52 and (hist is None or pre_hist is None or hist <= pre_hist):
|
||
score -= 1
|
||
# 3.3) EMA52 支撑/阻力触碰(非强穿越)
|
||
if ema52_valid and price_valid:
|
||
low_v = getattr(klc, 'low', None)
|
||
high_v = getattr(klc, 'high', None)
|
||
if low_v is not None and high_v is not None and ema52 not in (None, 0):
|
||
# 触碰容差(相对EMA52的0.15%)
|
||
touch_tol = 0.0015
|
||
# 作为支撑:收盘在上,最低靠近EMA52
|
||
near_support_touch = (price > ema52) and (abs(low_v - ema52) / abs(ema52) <= touch_tol)
|
||
# 作为阻力:收盘在下,最高靠近EMA52
|
||
near_resistance_touch = (price < ema52) and (abs(high_v - ema52) / abs(ema52) <= touch_tol)
|
||
if near_support_touch:
|
||
# 若动量不弱,则更偏多
|
||
score += 1 if (hist is None or pre_hist is None or hist >= pre_hist) else 0
|
||
if near_resistance_touch:
|
||
# 若动量不强,则更偏空
|
||
score -= 1 if (hist is None or pre_hist is None or hist <= pre_hist) else 0
|
||
# 3.4) 多次对 EMA52 的"拒绝"配合 MACD 逆向:易形成压/支并反向
|
||
# 统计近窗口内的上/下拒绝次数:
|
||
# - 上拒绝:价格位于 EMA52 下方,最高触及/越过 EMA52 但收盘仍在下方
|
||
# - 下拒绝:价格位于 EMA52 上方,最低触及/跌破 EMA52 但收盘仍在上方
|
||
recent_up_rejects = 0
|
||
recent_down_rejects = 0
|
||
if ema52_valid:
|
||
window_rej = prev_klcs[-lookback_n:] if len(prev_klcs) > 0 else []
|
||
rej_tol = 0.0015
|
||
for wk in window_rej:
|
||
wk_close = getattr(wk, 'close', None)
|
||
wk_ema52 = getattr(wk, 'ema52', None)
|
||
wk_high = getattr(wk, 'high', None)
|
||
wk_low = getattr(wk, 'low', None)
|
||
if wk_close is None or wk_ema52 in (None, 0):
|
||
continue
|
||
# 上拒绝(阻力):下方多次试图上破但未站上
|
||
if wk_close < wk_ema52 and wk_high is not None:
|
||
if wk_high >= wk_ema52 or abs(wk_high - wk_ema52) / abs(wk_ema52) <= rej_tol:
|
||
recent_up_rejects += 1
|
||
# 下拒绝(支撑):上方多次试图下破但未跌破
|
||
if wk_close > wk_ema52 and wk_low is not None:
|
||
if wk_low <= wk_ema52 or abs(wk_low - wk_ema52) / abs(wk_ema52) <= rej_tol:
|
||
recent_down_rejects += 1
|
||
# 定义 MACD 的方向偏好
|
||
macd_bias_up = macd_ready and (macd >= signal) and (not hist_ready or pre_hist is None or hist >= pre_hist)
|
||
macd_bias_down = macd_ready and (macd <= signal) and (not hist_ready or pre_hist is None or hist <= pre_hist)
|
||
# 若多次上拒绝且 MACD 偏空,则更偏向下行;若多次下拒绝且 MACD 偏多,则更偏向上行
|
||
if recent_up_rejects >= 2 and macd_bias_down:
|
||
score -= 2
|
||
if recent_down_rejects >= 2 and macd_bias_up:
|
||
score += 2
|
||
# 4) RSI 辅助
|
||
if rsi is not None:
|
||
if rsi >= 55:
|
||
score += 1
|
||
elif rsi <= 45:
|
||
score -= 1
|
||
# 5) 指标未就绪回退(EMA/MACD缺失时,用动量与RSI辅助,延续趋势)
|
||
has_full_ind = ema24_valid and ema52_valid and not (macd == 0 and signal == 0 and hist == 0)
|
||
if not has_full_ind:
|
||
# 仅根据价动量/RSI做轻量判断,默认延续 last_trend,除非出现强反向
|
||
strong_up = False
|
||
strong_down = False
|
||
pre = getattr(klc, 'pre', None)
|
||
if pre:
|
||
pre_close = getattr(pre, 'close', None)
|
||
if price_valid and pre_close is not None:
|
||
strong_up = (price >= pre_close)
|
||
strong_down = (price < pre_close)
|
||
if rsi is not None:
|
||
if rsi >= 60:
|
||
strong_up = True
|
||
elif rsi <= 40:
|
||
strong_down = True
|
||
if last_trend == Chan_PRICE_TREND.UP and not strong_down:
|
||
trend = Chan_PRICE_TREND.UP
|
||
elif last_trend == Chan_PRICE_TREND.DOWN and not strong_up:
|
||
trend = Chan_PRICE_TREND.DOWN
|
||
else:
|
||
trend = Chan_PRICE_TREND.UP if strong_up and not strong_down else (Chan_PRICE_TREND.DOWN if strong_down and not strong_up else Chan_PRICE_TREND.FLAT)
|
||
else:
|
||
# 6) 震荡过滤(仅当极近EMA52且MACD贴合时判作震荡)
|
||
near_flat = False
|
||
if price_valid and ema52_valid:
|
||
near_ema52 = abs(price - ema52) / abs(ema52) <= 0.0005 # 0.05%
|
||
if macd_ready:
|
||
macd_scale = max(abs(macd), abs(signal), 1e-6)
|
||
near_macd = abs(macd - signal) / macd_scale <= 0.05
|
||
else:
|
||
near_macd = False
|
||
near_flat = near_ema52 and near_macd
|
||
# 7) 动态阈值 + 趋势记忆(更强粘滞:趋势中容忍小幅反分)
|
||
# 引入过去 N 根KLC 的趋势延续性来动态调整翻转阈值,并结合 EMA52 支撑/阻力触碰强化门槛
|
||
force_flip_down = False
|
||
force_flip_up = False
|
||
if near_flat:
|
||
trend = Chan_PRICE_TREND.FLAT
|
||
else:
|
||
# 计算过去窗口的趋势一致性
|
||
window = prev_klcs[-lookback_n:] if len(prev_klcs) > 0 else []
|
||
persist_up = 0
|
||
persist_down = 0
|
||
for wk in window:
|
||
if getattr(wk, 'trend', None) == Chan_PRICE_TREND.UP:
|
||
persist_up += 1
|
||
elif getattr(wk, 'trend', None) == Chan_PRICE_TREND.DOWN:
|
||
persist_down += 1
|
||
persist_ratio_up = (persist_up / len(window)) if len(window) > 0 else 0
|
||
persist_ratio_down = (persist_down / len(window)) if len(window) > 0 else 0
|
||
# 基准阈值
|
||
down_flip_threshold = -2
|
||
up_flip_threshold = 2
|
||
# 若最近多为UP,则从UP翻转需更强反向信号;同理对DOWN
|
||
if last_trend == Chan_PRICE_TREND.UP and persist_ratio_up >= 0.6:
|
||
down_flip_threshold = -3
|
||
elif last_trend == Chan_PRICE_TREND.DOWN and persist_ratio_down >= 0.6:
|
||
up_flip_threshold = 3
|
||
# EMA52 触碰强化门槛:UP时若出现支撑触碰,下翻更难;DOWN时若出现阻力触碰,上翻更难
|
||
if ema52_valid and price_valid:
|
||
low_v = getattr(klc, 'low', None)
|
||
high_v = getattr(klc, 'high', None)
|
||
if low_v is not None and high_v is not None and ema52 not in (None, 0):
|
||
touch_tol = 0.0015
|
||
near_support_touch = (price > ema52) and (abs(low_v - ema52) / abs(ema52) <= touch_tol)
|
||
near_resistance_touch = (price < ema52) and (abs(high_v - ema52) / abs(ema52) <= touch_tol)
|
||
if last_trend == Chan_PRICE_TREND.UP and near_support_touch:
|
||
# 强化维持UP:进一步降低向下翻转阈值
|
||
down_flip_threshold = min(down_flip_threshold - 1, -3)
|
||
if last_trend == Chan_PRICE_TREND.DOWN and near_resistance_touch:
|
||
# 强化维持DOWN:进一步提高向上翻转阈值
|
||
up_flip_threshold = max(up_flip_threshold + 1, 3)
|
||
# 7.1) 复合拐头信号:MACD/Signal 同向拐头 + hist 连续减弱 + 多次未能越过 EMA52
|
||
pre_macd = getattr(pre, 'macd', None) if pre else None
|
||
pre_signal = getattr(pre, 'signal', None) if pre else None
|
||
macd_slope = (macd - pre_macd) if (macd_ready and pre_macd is not None) else 0
|
||
signal_slope = (signal - pre_signal) if (macd_ready and pre_signal is not None) else 0
|
||
# hist 连续减弱(绝对值缩小)
|
||
hist_seq = []
|
||
for wk in prev_klcs[-2:]:
|
||
val = getattr(wk, 'macdhist', None)
|
||
if val is not None:
|
||
hist_seq.append(val)
|
||
if hist is not None:
|
||
hist_seq.append(hist)
|
||
weaken_steps = 0
|
||
for i in range(1, len(hist_seq)):
|
||
if abs(hist_seq[i]) < abs(hist_seq[i-1]):
|
||
weaken_steps += 1
|
||
# 近窗口对 EMA52 的"未能站上/跌破"统计(放宽窗口与条件)
|
||
window_ema = prev_klcs[-4:] if len(prev_klcs) > 0 else []
|
||
no_up_break = False
|
||
no_down_break = False
|
||
if ema52_valid:
|
||
# 未能有效上破:最近若干根收盘大多数不在 EMA52 上方,且高点多次触及/接近
|
||
cnt_touch_up = 0
|
||
cnt_close_above = 0
|
||
for wk in window_ema:
|
||
wk_close = getattr(wk, 'close', None)
|
||
wk_high = getattr(wk, 'high', None)
|
||
wk_ema = getattr(wk, 'ema52', None)
|
||
if wk_close is not None and wk_ema not in (None, 0):
|
||
if wk_close > wk_ema:
|
||
cnt_close_above += 1
|
||
if wk_high is not None and (wk_high >= wk_ema or abs(wk_high - wk_ema) / abs(wk_ema) <= 0.0015):
|
||
cnt_touch_up += 1
|
||
no_up_break = (cnt_close_above <= 1 and cnt_touch_up >= 1 and price <= ema52)
|
||
# 未能有效下破:最近若干根收盘大多数不在 EMA52 下方,且低点多次触及/接近
|
||
cnt_touch_down = 0
|
||
cnt_close_below = 0
|
||
for wk in window_ema:
|
||
wk_close = getattr(wk, 'close', None)
|
||
wk_low = getattr(wk, 'low', None)
|
||
wk_ema = getattr(wk, 'ema52', None)
|
||
if wk_close is not None and wk_ema not in (None, 0):
|
||
if wk_close < wk_ema:
|
||
cnt_close_below += 1
|
||
if wk_low is not None and (wk_low <= wk_ema or abs(wk_low - wk_ema) / abs(wk_ema) <= 0.0015):
|
||
cnt_touch_down += 1
|
||
no_down_break = (cnt_close_below <= 1 and cnt_touch_down >= 1 and price >= ema52)
|
||
# 若当前为UP趋势,出现明显拐头+hist减弱+未能上破EMA52,则加速看空
|
||
if last_trend == Chan_PRICE_TREND.UP and macd_slope < 0 and signal_slope < 0 and weaken_steps >= 1 and no_up_break and macd_bias_down:
|
||
score -= 3
|
||
down_flip_threshold = max(down_flip_threshold, 0)
|
||
force_flip_down = True
|
||
# 若当前为DOWN趋势,出现明显拐头+hist减弱+未能下破EMA52,则加速看多
|
||
if last_trend == Chan_PRICE_TREND.DOWN and macd_slope > 0 and signal_slope > 0 and weaken_steps >= 1 and no_down_break and macd_bias_up:
|
||
score += 3
|
||
up_flip_threshold = min(up_flip_threshold, 0)
|
||
force_flip_up = True
|
||
# 多次对 EMA52 的拒绝配合 MACD 逆向:加速反向翻转(降低相反方向阈值)
|
||
if recent_up_rejects >= 2 and macd_bias_down:
|
||
# 从 UP 向 DOWN 的翻转更容易
|
||
down_flip_threshold = max(down_flip_threshold, -1)
|
||
if recent_down_rejects >= 2 and macd_bias_up:
|
||
# 从 DOWN 向 UP 的翻转更容易
|
||
up_flip_threshold = min(up_flip_threshold, 1)
|
||
if force_flip_down:
|
||
trend = Chan_PRICE_TREND.DOWN
|
||
elif force_flip_up:
|
||
trend = Chan_PRICE_TREND.UP
|
||
elif last_trend == Chan_PRICE_TREND.UP:
|
||
if score <= down_flip_threshold:
|
||
trend = Chan_PRICE_TREND.DOWN
|
||
else:
|
||
trend = Chan_PRICE_TREND.UP
|
||
elif last_trend == Chan_PRICE_TREND.DOWN:
|
||
if score >= up_flip_threshold:
|
||
trend = Chan_PRICE_TREND.UP
|
||
else:
|
||
trend = Chan_PRICE_TREND.DOWN
|
||
else:
|
||
# 初始无记忆时,降低进入门槛
|
||
if score >= 1:
|
||
trend = Chan_PRICE_TREND.UP
|
||
elif score <= -1:
|
||
trend = Chan_PRICE_TREND.DOWN
|
||
else:
|
||
trend = Chan_PRICE_TREND.FLAT
|
||
except Exception:
|
||
trend = Chan_PRICE_TREND.UNKNOWN
|
||
# 写回趋势
|
||
if klc.end_time is None:
|
||
trend = Chan_PRICE_TREND.FLAT
|
||
if hasattr(klc, 'set_trend'):
|
||
klc.set_trend(trend)
|
||
else:
|
||
setattr(klc, 'trend', trend)
|
||
last_trend = trend
|
||
# 更新滑窗:仅向后看
|
||
prev_klcs.append(klc)
|
||
price_diff = klc.close - klc.pre.close if klc.pre else 0
|
||
#if klc.index > len(klc_list) - 10:
|
||
#print(klc.start_time, klc.end_time, klc.close, klc.ema24, klc.ema52, klc.macd, klc.signal, klc.macdhist, klc.trend, price_diff, score)
|
||
#print(klc.start_time, klc.end_time, klc.trend, price_diff, score)
|
||
return klc_list
|
||
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_bi_list(self, dataframe):
|
||
bi_list = self.cal_bi_list(self.get_klc_list(dataframe))
|
||
#bi_list = self.cal_bi_list_chanlun(self.get_klc_list(dataframe))
|
||
return bi_list
|
||
def get_kl_data(self, dataframe:DataFrame):
|
||
return self.cal_kl_data(dataframe)
|
||
def get_klc_list(self, klu_list):
|
||
klc_list = []
|
||
last_klu = None
|
||
macd = ChanMACD(klu_list)
|
||
klu_list = macd.cal_macd_state()
|
||
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
|
||
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()
|
||
#print(klu.time, klu.high, klu.low, klu.close, klu.open, klu.exception)
|
||
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)
|
||
last_klu = klu
|
||
klc_list = self.cal_trend(klc_list)
|
||
#print(ema52_up_list, ema52_down_list)
|
||
return klc_list
|
||
|
||
def get_seg_list(self, bi_list):
|
||
seg_list = []
|
||
up_bi_list = []
|
||
down_bi_list = []
|
||
last_up_bi = None
|
||
last_down_bi = None
|
||
last_up_sbi = None
|
||
last_down_sbi = None
|
||
last_seg = None
|
||
up_sbi_list = []
|
||
down_sbi_list = []
|
||
look_for_bottom = False
|
||
look_for_top = False
|
||
for bi in bi_list:
|
||
#print(len(up_sbi_list), len(down_sbi_list))
|
||
if len(seg_list) > 0:
|
||
# Last seg is up
|
||
if last_seg.dir == Chan_SEG_DIR.UP:
|
||
if bi.dir == Chan_BI_DIR.DOWN:
|
||
if len(down_sbi_list) > 1:
|
||
# Check down sbi inclusion
|
||
included = last_down_sbi.check_bi_included(bi)
|
||
if not included:
|
||
down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
|
||
last_down_sbi.set_next(down_sbi)
|
||
last_down_sbi.set_end_bi(last_down_bi)
|
||
down_sbi.set_pre(last_down_sbi)
|
||
down_sbi_list.append(down_sbi)
|
||
fx = last_down_sbi.check_fx()
|
||
# Found top
|
||
if fx == Chan_FX_TYPE.TOP:
|
||
if look_for_top:
|
||
seg_list[-2].set_sure(bi)
|
||
look_for_top = False
|
||
#print(bi.start_time, look_for_top, "UP 1")
|
||
# Has gap and search for bottom fx
|
||
if last_down_sbi.has_fx_gap:
|
||
look_for_bottom = True
|
||
last_seg.pre_set_end_bi(bi_list[last_down_sbi.start_bi.index - 1])
|
||
seg = ChanSEG(last_down_sbi.start_bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
|
||
seg_list.append(seg)
|
||
last_seg.set_next(seg)
|
||
seg.set_pre(last_seg)
|
||
last_seg = seg
|
||
up_sbi_list = []
|
||
last_up_sbi = ChanSBI(last_up_bi, len(up_sbi_list), last_up_bi.dir)
|
||
up_sbi_list.append(last_up_sbi)
|
||
#up_sbi_list.append(last_up_sbi)
|
||
#print(last_up_bi.start_time, last_up_sbi.start_bi.start_time, "Reset up sbi list 1")
|
||
#print(bi.start_time, look_for_top, "UP 2")
|
||
# No gap end SEG
|
||
else:
|
||
if look_for_bottom:
|
||
look_for_bottom = False
|
||
last_seg.set_start_bi(last_down_sbi.start_bi)
|
||
seg_list[-2].set_end_bi(bi_list[last_down_sbi.start_bi.index - 1], bi)
|
||
up_sbi_list = []
|
||
last_up_sbi = ChanSBI(last_up_bi, len(up_sbi_list), last_up_bi.dir)
|
||
up_sbi_list.append(last_up_sbi)
|
||
last_seg.add_bi(bi)
|
||
#up_sbi_list.append(last_up_sbi)
|
||
#print(last_up_bi.start_time, last_up_sbi.start_bi.start_time, "Reset up sbi list 2")
|
||
#print(bi.start_time, look_for_top, "UP 3")
|
||
else:
|
||
last_seg.set_end_bi(bi_list[last_down_sbi.start_bi.index - 1], bi)
|
||
seg = ChanSEG(last_down_sbi.start_bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
|
||
seg_list.append(seg)
|
||
last_seg.set_next(seg)
|
||
seg.set_pre(last_seg)
|
||
last_seg = seg
|
||
#print(last_down_sbi.end_bi.start_time, "Normal UP SEG", last_up_sbi.start_bi.start_time, bi.start_time)
|
||
#l_up_sbi = up_sbi_list[-1]
|
||
up_sbi_list = []
|
||
last_up_sbi = ChanSBI(last_up_bi, len(up_sbi_list), last_up_bi.dir)
|
||
up_sbi_list.append(last_up_sbi)
|
||
#up_sbi_list.append(last_up_sbi)
|
||
#print(last_up_bi.start_time, last_up_sbi.start_bi.start_time, "Reset up sbi list 3")
|
||
last_down_sbi = down_sbi
|
||
last_seg.add_bi(bi)
|
||
else:
|
||
if len(down_sbi_list) == 1:
|
||
included = last_down_sbi.check_bi_included(bi)
|
||
if not included:
|
||
down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
|
||
last_down_sbi.set_next(down_sbi)
|
||
last_down_sbi.set_end_bi(last_down_bi)
|
||
down_sbi.set_pre(last_down_sbi)
|
||
down_sbi_list.append(down_sbi)
|
||
last_down_sbi = down_sbi
|
||
#print(bi.start_time, look_for_top, "UP 4")
|
||
last_seg.add_bi(bi)
|
||
|
||
else:
|
||
last_down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
|
||
down_sbi_list.append(last_down_sbi)
|
||
last_seg.add_bi(bi)
|
||
#print(bi.start_time, look_for_top, "UP 5")
|
||
else:
|
||
if last_up_sbi:
|
||
included = last_up_sbi.check_bi_included(bi)
|
||
if not included:
|
||
up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
|
||
last_up_sbi.set_next(up_sbi)
|
||
last_up_sbi.set_end_bi(last_up_bi)
|
||
up_sbi.set_pre(last_up_sbi)
|
||
up_sbi_list.append(up_sbi)
|
||
last_up_sbi = up_sbi
|
||
#print(bi.start_time, look_for_top, "UP 6")
|
||
last_seg.add_bi(bi)
|
||
|
||
# Last seg is down
|
||
else:
|
||
if bi.dir == Chan_BI_DIR.UP:
|
||
if len(up_sbi_list) > 1:
|
||
# Check down sbi inclusion
|
||
included = last_up_sbi.check_bi_included(bi)
|
||
if not included:
|
||
up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
|
||
last_up_sbi.set_next(up_sbi)
|
||
last_up_sbi.set_end_bi(last_up_bi)
|
||
up_sbi.set_pre(last_up_sbi)
|
||
up_sbi_list.append(up_sbi)
|
||
fx = last_up_sbi.check_fx()
|
||
# Found bottom
|
||
if fx == Chan_FX_TYPE.BOTTOM:
|
||
if look_for_bottom:
|
||
seg_list[-2].set_sure(bi)
|
||
look_for_bottom = False
|
||
#print(bi.start_time, look_for_top, "DOWN 1")
|
||
# Has gap and search for bottom fx
|
||
if last_up_sbi.has_fx_gap:
|
||
look_for_top = True
|
||
last_seg.pre_set_end_bi(bi_list[last_up_sbi.start_bi.index - 1])
|
||
seg = ChanSEG(last_up_sbi.start_bi, len(seg_list), Chan_SEG_DIR.UP, bi)
|
||
seg_list.append(seg)
|
||
last_seg.set_next(seg)
|
||
seg.set_pre(last_seg)
|
||
last_seg = seg
|
||
down_sbi_list = []
|
||
last_down_sbi = ChanSBI(last_down_bi, len(down_sbi_list), last_down_bi.dir)
|
||
down_sbi_list.append(last_down_sbi)
|
||
#down_sbi_list.append(last_down_sbi)
|
||
#print(last_down_bi.start_time, last_down_sbi.start_bi.start_time, "Reset down sbi list 1")
|
||
#print(bi.start_time, look_for_top, "DOWN 2")
|
||
# No gap end SEG
|
||
else:
|
||
if look_for_top:
|
||
look_for_top = False
|
||
last_seg.set_start_bi(last_up_sbi.start_bi)
|
||
seg_list[-2].set_end_bi(bi_list[last_up_sbi.start_bi.index - 1], bi)
|
||
down_sbi_list = []
|
||
last_down_sbi = ChanSBI(last_down_bi, len(down_sbi_list), last_down_bi.dir)
|
||
down_sbi_list.append(last_down_sbi)
|
||
last_seg.add_bi(bi)
|
||
#down_sbi_list.append(last_down_sbi)
|
||
#print(last_down_bi.start_time, last_down_sbi.start_bi.start_time, "Reset down sbi list 2")
|
||
#print(bi.start_time, look_for_top, "DOWN 3")
|
||
else:
|
||
last_seg.set_end_bi(bi_list[last_up_sbi.start_bi.index - 1], bi)
|
||
seg = ChanSEG(last_up_sbi.start_bi, len(seg_list), Chan_SEG_DIR.UP, bi)
|
||
#print(last_up_sbi.start_bi.start_time)
|
||
last_seg.set_next(seg)
|
||
seg.set_pre(last_seg)
|
||
seg_list.append(seg)
|
||
last_seg = seg
|
||
#print(last_up_sbi.end_bi.start_time, "Normal DOWN SEG", last_down_sbi.start_bi.start_time, bi.start_time)
|
||
down_sbi_list = []
|
||
last_down_sbi = ChanSBI(last_down_bi, len(down_sbi_list), last_down_bi.dir)
|
||
down_sbi_list.append(last_down_sbi)
|
||
#down_sbi_list.append(last_down_sbi)
|
||
#print(last_down_bi.start_time, last_down_sbi.start_bi.start_time, "Reset down sbi list 3")
|
||
last_up_sbi = up_sbi
|
||
last_seg.add_bi(bi)
|
||
else:
|
||
if len(up_sbi_list) == 1:
|
||
#last_up_sbi = up_sbi_list[-1]
|
||
included = last_up_sbi.check_bi_included(bi)
|
||
if not included:
|
||
up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
|
||
last_up_sbi.set_next(up_sbi)
|
||
last_up_sbi.set_end_bi(last_up_bi)
|
||
up_sbi.set_pre(last_up_sbi)
|
||
up_sbi_list.append(up_sbi)
|
||
last_up_sbi = up_sbi
|
||
last_seg.add_bi(bi)
|
||
#print(bi.start_time, look_for_top, "DOWN 4")
|
||
else:
|
||
last_up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
|
||
up_sbi_list.append(last_up_sbi)
|
||
last_seg.add_bi(bi)
|
||
#print(bi.start_time, look_for_top, "DOWN 5")
|
||
else:
|
||
if last_down_sbi:
|
||
included = last_down_sbi.check_bi_included(bi)
|
||
if not included:
|
||
down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
|
||
last_down_sbi.set_next(down_sbi)
|
||
last_down_sbi.set_end_bi(last_down_bi)
|
||
down_sbi.set_pre(last_down_sbi)
|
||
down_sbi_list.append(down_sbi)
|
||
last_down_sbi = down_sbi
|
||
last_seg.add_bi(bi)
|
||
#print(bi.start_time, look_for_top, look_for_bottom, "DOWN 6")
|
||
# len(seg_list) = 0
|
||
else:
|
||
if bi.check_overlap():
|
||
if bi.dir == Chan_BI_DIR.UP:
|
||
seg = ChanSEG(bi, len(seg_list), Chan_SEG_DIR.UP, bi)
|
||
last_up_bi = bi
|
||
last_up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
|
||
seg_list.append(seg)
|
||
last_seg = seg
|
||
#print(bi.start_time, 'Create first UP SEG')
|
||
else:
|
||
seg = ChanSEG(bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
|
||
last_down_bi = bi
|
||
last_down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
|
||
seg_list.append(seg)
|
||
last_seg = seg
|
||
#print(bi.start_time, 'Create first DOWN SEG')
|
||
if bi.dir == Chan_BI_DIR.UP:
|
||
last_up_bi = bi
|
||
up_bi_list.append(bi)
|
||
else:
|
||
last_down_bi = bi
|
||
down_bi_list.append(bi)
|
||
"""
|
||
if len(seg_list) > 1:
|
||
seg = seg_list[-1]
|
||
last_seg = seg_list[-2]
|
||
last_seg_bi = last_seg.bi_list[-3]
|
||
bi_index = seg.start_bi.index
|
||
for i in range(bi_index, len(bi_list) - 1):
|
||
# last seg is down
|
||
if seg.dir == Chan_SEG_DIR.UP:
|
||
if bi_list[i].dir == Chan_BI_DIR.UP:
|
||
last_seg_peak = last_seg_bi.high
|
||
if bi_list[i].high > last_seg_peak:
|
||
# The confirmed
|
||
print("Last UP seg is broken, create a new seg. 1")
|
||
seg.pre_set_end_bi(bi_list[i])
|
||
seg = ChanSEG(bi_list[i+1], len(seg_list), Chan_SEG_DIR.DOWN, bi)
|
||
seg_list.append(seg)
|
||
last_seg = seg_list[-2]
|
||
if len(last_seg.bi_list) > 3:
|
||
last_seg_bi = last_seg.bi_list[-3]
|
||
|
||
else:
|
||
if bi_list[i].dir == Chan_BI_DIR.DOWN:
|
||
last_seg_peak = last_seg_bi.low
|
||
if bi_list[i].low < last_seg_peak:
|
||
print("Last DOWN seg is broken, create a new seg. 1")
|
||
seg.pre_set_end_bi(bi_list[i])
|
||
seg = ChanSEG(bi_list[i+1], len(seg_list), Chan_SEG_DIR.UP, bi)
|
||
seg_list.append(seg)
|
||
last_seg = seg_list[-2]
|
||
if len(last_seg.bi_list) > 3:
|
||
last_seg_bi = last_seg.bi_list[-3]
|
||
else:
|
||
if len(seg_list) == 1:
|
||
last_seg = seg_list[-1]
|
||
bi_index = last_seg.bi_list[0].index
|
||
for i in range(bi_index, len(bi_list) - 1):
|
||
if i > bi_index + 2:
|
||
last_seg_peak = bi_list[i-2].high
|
||
# last seg is down
|
||
if last_seg.dir == Chan_SEG_DIR.DOWN:
|
||
if bi_list[i].dir == Chan_BI_DIR.UP:
|
||
if bi_list[i].high > last_seg_peak:
|
||
print("Last seg is broken, create a new seg. 2")
|
||
last_seg.pre_set_end_bi(bi_list[i-1])
|
||
seg = ChanSEG(bi_list[i], len(seg_list), Chan_SEG_DIR.UP, bi)
|
||
seg_list.append(seg)
|
||
last_seg = seg
|
||
last_seg_bi = bi_list[i]
|
||
break
|
||
"""
|
||
#self.cal_bi_zs(seg_list)
|
||
return seg_list
|
||
def get_zs_state(self, df):
|
||
bi_list = self.cal_bi_list(self.get_klc_list(self.get_kl_data(df)))
|
||
seg_list = self.get_seg_list(bi_list)
|
||
zs_list = self.calculate_zs(seg_list)
|
||
for zs in zs_list:
|
||
last_zs = zs
|
||
return zs_list
|
||
def cal_bi_list(self, klc_list):
|
||
bi_list = []
|
||
last_top = None
|
||
last_bottom = None
|
||
bi_klc_min = 4
|
||
last_fx_klc = None
|
||
for klc in klc_list:
|
||
if last_fx_klc:
|
||
klc.check_klc_state(last_fx_klc)
|
||
klc.check_fx_confirmed(last_top, last_bottom)
|
||
fx = self.check_fx(klc)
|
||
if fx == Chan_FX_TYPE.TOP:
|
||
if last_bottom:
|
||
if self.check_top_fx(last_bottom, klc) == False:
|
||
fx = Chan_FX_TYPE.UNKNOWN
|
||
if fx == Chan_FX_TYPE.BOTTOM:
|
||
if last_top:
|
||
if self.check_bottom_fx(last_top, klc) == False:
|
||
#print(klc.end_time, last_top.end_time, "---")
|
||
fx = Chan_FX_TYPE.UNKNOWN
|
||
# Do nothing
|
||
if fx == Chan_FX_TYPE.UNKNOWN:
|
||
if len(bi_list) > 0:
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
#continue
|
||
if len(bi_list) > 0 and klc.end_klu:
|
||
last_bi = bi_list[-1]
|
||
#print(klc.start_time, last_bi.start_time, last_bi.end_time, last_bi.dir, last_bi.high, last_bi.low, last_bottom.end_time, "last bi")
|
||
if last_top and last_bi.dir == Chan_BI_DIR.DOWN:
|
||
if last_bottom and klc.high > last_bi.high:
|
||
#print(klc.end_time, "Top 7, 1", last_bi.start_time, klc.high, last_bi.high)
|
||
#klc.klc_fx_type = Chan_KLC_FX.TOP7
|
||
#klc.fx = Chan_FX_TYPE.TOP
|
||
"""
|
||
last_bi.set_end_klc(last_bottom, klc)
|
||
bi = ChanBI(last_bottom, len(bi_list), Chan_BI_DIR.UP)
|
||
#klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM7)
|
||
#klc.bb_out = True
|
||
last_bi.set_next(bi)
|
||
bi.set_pre(last_bi)
|
||
for klc_index in range(last_bi.end_klc.index, len(klc_list)):
|
||
bi.add_klc(klc_list[klc_index])
|
||
bi_list.append(bi)
|
||
last_top = klc
|
||
klc.set_bi(bi)
|
||
#print(klc.start_time, bi.start_time, bi.end_time, bi.dir, bi.high, bi.low, bi.is_sure)
|
||
"""
|
||
else:
|
||
if last_bottom and last_bi.dir == Chan_BI_DIR.UP:
|
||
if last_top and klc.low < last_bi.low:
|
||
#print(klc.end_time, "Bottom 8, 2", last_bi.start_time)
|
||
#klc.klc_fx_type = Chan_KLC_FX.BOTTOM8
|
||
#klc.fx = Chan_FX_TYPE.BOTTOM
|
||
"""
|
||
last_bi.set_end_klc(last_top, klc)
|
||
bi = ChanBI(last_top, len(bi_list), Chan_BI_DIR.DOWN)
|
||
#klc.set_klc_fx_type(Chan_KLC_FX.TOP6)
|
||
#klc.bb_out = True
|
||
last_bi.set_next(bi)
|
||
bi.set_pre(last_bi)
|
||
for klc_index in range(last_bi.end_klc.index, len(klc_list)):
|
||
bi.add_klc(klc_list[klc_index])
|
||
bi_list.append(bi)
|
||
last_bottom = klc
|
||
klc.set_bi(bi)
|
||
#print(klc.start_time, bi.start_time, bi.end_time, bi.dir, bi.high, bi.low, bi.is_sure)
|
||
"""
|
||
else:
|
||
last_fx_klc = klc
|
||
if fx == Chan_FX_TYPE.TOP:
|
||
#print(klc.end_time, fx, klc.pre.high, klc.high, klc.pre.start_time, klc.pre.end_time)
|
||
if last_top:
|
||
if last_bottom:
|
||
#print(klc.start_time, last_bottom.start_time, last_top.start_time)
|
||
if last_bottom.index < last_top.index:
|
||
# Second top lower to be second sell point
|
||
if last_top.high > klc.high:
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
#klc.set_klc_fx_type(Chan_KLC_FX.TOP3)
|
||
#print(klc.end_time, klc.fx, "二类卖点Sell 1")
|
||
else:
|
||
# A new top found
|
||
#last_top.set_fx(Chan_FX_TYPE.UNKNOWN)
|
||
last_top = klc
|
||
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 1")
|
||
klc.set_klc_fx_type(Chan_KLC_FX.TOP1)
|
||
self.check_fx_pattern(klc)
|
||
#print(klc.end_time, klc.fx, "一类卖点Sell 1")
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
# 不满足结合律的分型
|
||
else:
|
||
#klc.set_klc_fx_type(Chan_KLC_FX.TOP0)
|
||
#print(klc.end_time, klc.klc_fx_type)
|
||
if last_bottom.index + bi_klc_min > klc.index:
|
||
if last_top.high > klc.high:
|
||
#print(klc.start_time, klc.fx, "二类卖点Sell 1")
|
||
#klc.set_klc_fx_type(Chan_KLC_FX.TOP8)
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
# New TOP Found前面的UKNOWN可能出现TOP7,但是这里的也可能出现TOP8分型
|
||
else:
|
||
# 顶分型在出现2之前超过前一个笔的顶 TOP8
|
||
if last_top.index + bi_klc_min < klc.index and len(bi_list) > 1:
|
||
pre_last_bi = bi_list[-2]
|
||
last_bi = bi_list[-1]
|
||
if pre_last_bi.is_sure and not last_bi.is_sure and pre_last_bi.dir == Chan_BI_DIR.UP and False:
|
||
pre_last_bi.update_bi(klc)
|
||
bi_list.remove(last_bi)
|
||
pre_last_bi.set_next(None)
|
||
#last_top.set_fx(Chan_FX_TYPE.PTOP)
|
||
last_top = klc
|
||
last_bottom = pre_last_bi.start_klc
|
||
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Bottom Change 1")
|
||
klc.set_klc_fx_type(Chan_KLC_FX.TOP2)
|
||
#print(klc.start_time, last_bi.start_klc.start_time, "New TOP Found reset last bi")
|
||
#klc.set_state("10")
|
||
#print(klc.start_time, klc.fx, "笔卖点Sell 1")
|
||
###klc.set_klc_fx_type(Chan_KLC_FX.TOP2) # when bi is down but the fx is top
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
#klc.set_klc_fx_type(Chan_KLC_FX.TOP8)
|
||
#print(klc.start_time, last_bi.start_klc.start_time, "New TOP Found reset last bi")
|
||
else:
|
||
#klc.set_fx(Chan_FX_TYPE.PTOP)
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
#print(klc.end_time, klc.fx, "无效顶分型")
|
||
# 满足结合律
|
||
else:
|
||
# New Temp TOP and last bottom confirmed ***** confirm last down bi(last bottom and last top)
|
||
last_bi = bi_list[-1]
|
||
if not last_bi.is_sure:
|
||
last_bi.set_end_klc(last_bottom, klc)
|
||
bi = ChanBI(last_bottom, len(bi_list), Chan_BI_DIR.UP)
|
||
last_bi.set_next(bi)
|
||
bi.set_pre(last_bi)
|
||
bi.add_klc(klc)
|
||
bi_list.append(bi)
|
||
last_top = klc
|
||
#print(klc.end_time, klc.fx, bi_list[-1].dir, "Last Top Change 2")
|
||
klc.set_klc_fx_type(Chan_KLC_FX.TOP2)
|
||
self.check_fx_pattern(klc)
|
||
#bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
#print(klc.start_time, last_bottom.start_time, "Normal TOP Found, Confirm down bi 4")
|
||
# last bottom = None 初始化的时候用,其他时间不用
|
||
else:
|
||
# 初始化的时候用,其他时间不用
|
||
if last_top.high < klc.high:
|
||
last_bi = bi_list[-1]
|
||
last_bi.set_start_klc(klc, Chan_BI_DIR.DOWN)
|
||
last_top = klc
|
||
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 3")
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
# 初始化的时候用,其他时间不用
|
||
else:
|
||
#klc.set_fx(Chan_FX_TYPE.TT)
|
||
#print(klc.start_time, klc.fx, "二类卖点Sell 2")
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
# last_top == None 初始化的时候用,其他时间不用
|
||
else:
|
||
if last_bottom:
|
||
# 不满足结合律的分型
|
||
if last_bottom.index + bi_klc_min > klc.index:
|
||
#klc.set_fx(Chan_FX_TYPE.PTOP)
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
#print(klc.start_time, klc.fx, "中枢卖点Sell 1")
|
||
else:
|
||
# First temp top and last bottom confirmed
|
||
last_top = klc
|
||
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 4")
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
# Last top = None, last bottom = None, create first down bi 初始化的时候用,其他时间不用
|
||
else:
|
||
# First temp top
|
||
last_top = klc
|
||
bi = ChanBI(klc, len(bi_list), Chan_BI_DIR.DOWN)
|
||
bi_list.append(bi)
|
||
bi.add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 5")
|
||
#klc.fx = Bottom ========================
|
||
else:
|
||
if last_bottom:
|
||
if last_top:
|
||
# Bottom after top and find a new bottom
|
||
if last_top.index < last_bottom.index:
|
||
# Second bottom uppper to be second buy point and confirm last bi
|
||
if last_bottom.low < klc.low:
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
#klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM3)
|
||
#print(last_bottom.start_time, last_bottom.end_time, "--------------------------------1")
|
||
#print(klc.end_time, klc.fx, "二类买点Buy 1")
|
||
else:
|
||
# A new bottom found
|
||
last_bottom = klc
|
||
#print(klc.end_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 1")
|
||
klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM1)
|
||
self.check_fx_pattern(klc)
|
||
#print(klc.end_time, klc.fx, "一类买点Buy 1")
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
# 不满足结合律的分型
|
||
else:
|
||
#klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM0)
|
||
#print(klc.end_time, klc.klc_fx_type)
|
||
if last_top.index + bi_klc_min > klc.index:
|
||
if last_bottom.low < klc.low:
|
||
#print(klc.end_time, klc.fx, "中枢买点Buy 1")
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
#klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM8)
|
||
# Found new bottom没有意义,上面UNKNOWN的时候已经是笔破坏了
|
||
else:
|
||
#print(klc.end_time, last_bottom.end_time, "Found a new bottom")
|
||
if last_bottom.index + bi_klc_min < klc.index and len(bi_list) > 1:
|
||
pre_last_bi = bi_list[-2]
|
||
last_bi = bi_list[-1]
|
||
if pre_last_bi.is_sure and not last_bi.is_sure and pre_last_bi.dir == Chan_BI_DIR.DOWN and False:
|
||
pre_last_bi.update_bi(klc)
|
||
bi_list.remove(last_bi)
|
||
pre_last_bi.set_next(None)
|
||
last_bottom = klc
|
||
last_top = pre_last_bi.start_klc
|
||
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Bottom Change 2")
|
||
klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM2)
|
||
#print(klc.start_time, last_bi.start_klc.start_time, "New BOTTOM Found reset last bi")
|
||
#print(klc.start_time, klc.fx, "笔买点Buy 1")
|
||
###klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM2) # when bi is up but the fx is bottom
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
#klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM8)
|
||
else:
|
||
#klc.set_fx(Chan_FX_TYPE.UNKNOWN)
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
print(klc.end_time, klc.fx, "无效底分型")
|
||
# 满足结合律的分型
|
||
else:
|
||
# New Temp Bottom and last top confirmed ***** confirm last up bi(last bottom and last top)
|
||
last_bi = bi_list[-1]
|
||
if not last_bi.is_sure:
|
||
last_bi.set_end_klc(last_top, klc)
|
||
bi = ChanBI(last_top, len(bi_list), Chan_BI_DIR.DOWN)
|
||
last_bi.set_next(bi)
|
||
bi.set_pre(last_bi)
|
||
bi.add_klc(klc)
|
||
bi_list.append(bi)
|
||
last_bottom = klc
|
||
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 2")
|
||
klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM2)
|
||
self.check_fx_pattern(klc)
|
||
#bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
#print(klc.start_time, last_top.start_time, "Normal Bottom Found, Confirm up bi 6")
|
||
# last_top = None 初始化的时候用,其他时间不用
|
||
else:
|
||
if last_bottom.low > klc.low:
|
||
last_bi = bi_list[-1]
|
||
last_bi.set_start_klc(klc, Chan_BI_DIR.UP)
|
||
#last_bottom.set_fx(Chan_FX_TYPE.UNKNOWN)
|
||
last_bottom = klc
|
||
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 3")
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
#print(klc.start_time, klc.fx, "笔买点Buy 3")
|
||
else:
|
||
#klc.set_fx(Chan_FX_TYPE.BB)
|
||
#klc.set_state('-20')
|
||
#print(klc.start_time, klc.fx, "二类买点Buy 2")
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
# last_bottom = None 初始化的时候用,其他时间不用
|
||
else:
|
||
if last_top:
|
||
# 不满足结合律的分型
|
||
if last_top.index + bi_klc_min > klc.index:
|
||
#klc.set_fx(Chan_FX_TYPE.PBOTTOM)
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
#print(klc.start_time, klc.fx, "中枢买点Buy 1")
|
||
else:
|
||
# First temp bottom and last top confirmed
|
||
last_bottom = klc
|
||
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 4")
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
#print(klc.start_time, klc.fx, "一类买点Buy 1")
|
||
# Last top = None, last bottom = None, create first up bi
|
||
else:
|
||
# First temp bottom and no top yet
|
||
last_bottom = klc
|
||
bi = ChanBI(klc, len(bi_list), Chan_BI_DIR.UP)
|
||
#klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM7)
|
||
bi_list.append(bi)
|
||
bi_list[-1].add_klc(klc)
|
||
klc.set_bi(bi_list[-1])
|
||
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 5")
|
||
#print(klc.start_time, klc.fx, "笔买点Buy 4")
|
||
self.get_above_zero_bsp(klc_list)
|
||
#print(bi_list[-1].start_time, bi_list[-1].end_time, len(bi_list[-1].klc_list))
|
||
return bi_list
|
||
def get_above_zero_bsp(self, klc_list):
|
||
buy_bsp_list = []
|
||
sell_bsp_list = []
|
||
above_zero = False
|
||
buy_bsp = None
|
||
sell_bsp = None
|
||
for klc in klc_list:
|
||
if klc.pre and klc.pre.signal < 0 and klc.signal > 0:
|
||
above_zero = True
|
||
if klc.pre and klc.pre.signal > 0 and klc.signal < 0:
|
||
above_zero = False
|
||
if above_zero and klc.klc_fx_type == Chan_KLC_FX.BOTTOM2 and klc.macd > 0:
|
||
buy_bsp = klc
|
||
buy_bsp_list.append(klc)
|
||
#print(klc.end_time, "MACD 0轴上穿,回调笔底分型做多")
|
||
if buy_bsp and klc.pre and klc.pre.macdhist > 0 and klc.macdhist < 0:
|
||
sell_bsp = klc
|
||
sell_bsp_list.append(klc)
|
||
buy_bsp = None
|
||
#print(klc.end_time, "Sell BSP Found")
|
||
return buy_bsp_list
|
||
def check_top_fx(self, last_bottom, klc):
|
||
if (last_bottom.high > klc.pre.low or last_bottom.high > klc.next.low) and (klc.index - last_bottom.index < 100):
|
||
return False
|
||
return True
|
||
|
||
def check_bottom_fx(self, last_top, klc):
|
||
if (last_top.low < klc.pre.high or last_top.low < klc.next.high) and (klc.index - last_top.index < 100):
|
||
return False
|
||
return True
|
||
# 线段内的中枢
|
||
def cal_bi_zs(self, seg_list):
|
||
bi_zs_list = []
|
||
for seg in seg_list:
|
||
zs_list = seg.cal_bi_zs()
|
||
if len(zs_list) > 0:
|
||
bi_zs_list = list(bi_zs_list) + list(zs_list)
|
||
return bi_zs_list
|
||
# 跨段不相连的中枢
|
||
def cal_bi_zs_list(self, bi_list):
|
||
"""
|
||
根据缠论笔中枢定义计算中枢(参照 get_zs_list 线段中枢判断规则)
|
||
从第4根笔开始(索引3),每3根笔为一组检查
|
||
上涨中枢:后中枢 zd > 前中枢 zg(不重叠上移)
|
||
下跌中枢:后中枢 zg < 前中枢 zd(不重叠下移)
|
||
中枢可按两笔一组继续扩展到5根、7根...
|
||
"""
|
||
bi_zs_list = []
|
||
if len(bi_list) < 3:
|
||
return bi_zs_list
|
||
|
||
last_zs = None
|
||
start_idx = 3
|
||
|
||
while start_idx < len(bi_list):
|
||
if start_idx + 2 >= len(bi_list):
|
||
break
|
||
|
||
bi1 = bi_list[start_idx]
|
||
bi2 = bi_list[start_idx + 1]
|
||
bi3 = bi_list[start_idx + 2]
|
||
|
||
if not (bi1.is_sure and bi2.is_sure and bi3.is_sure):
|
||
start_idx += 1
|
||
continue
|
||
|
||
zg = min(bi1.high, bi2.high, bi3.high)
|
||
zd = max(bi1.low, bi2.low, bi3.low)
|
||
|
||
if zg <= zd:
|
||
start_idx += 1
|
||
continue
|
||
|
||
valid = False
|
||
if last_zs is None:
|
||
if bi1.dir == Chan_BI_DIR.DOWN:
|
||
zs_dir = Chan_ZS_DIR.UP
|
||
valid = (bi2.dir == Chan_BI_DIR.UP and bi3.dir == Chan_BI_DIR.DOWN)
|
||
else:
|
||
zs_dir = Chan_ZS_DIR.DOWN
|
||
valid = (bi2.dir == Chan_BI_DIR.DOWN and bi3.dir == Chan_BI_DIR.UP)
|
||
else:
|
||
is_up_zs = zg > last_zs.zg
|
||
is_down_zs = zd < last_zs.zd
|
||
|
||
if is_up_zs:
|
||
zs_dir = Chan_ZS_DIR.UP
|
||
valid = (bi1.dir == Chan_BI_DIR.DOWN and bi2.dir == Chan_BI_DIR.UP and bi3.dir == Chan_BI_DIR.DOWN)
|
||
elif is_down_zs:
|
||
zs_dir = Chan_ZS_DIR.DOWN
|
||
valid = (bi1.dir == Chan_BI_DIR.UP and bi2.dir == Chan_BI_DIR.DOWN and bi3.dir == Chan_BI_DIR.UP)
|
||
|
||
if not valid:
|
||
start_idx += 1
|
||
continue
|
||
gg = max(bi1.high, bi2.high, bi3.high)
|
||
dd = min(bi1.low, bi2.low, bi3.low)
|
||
zs = ChanBIZS(bi1, len(bi_zs_list), zs_dir)
|
||
zs.set_zg(zg)
|
||
zs.set_zd(zd)
|
||
zs.set_gg(gg)
|
||
zs.set_dd(dd)
|
||
zs.is_sure = False
|
||
zs.bi_list = [bi1, bi2, bi3]
|
||
|
||
added_after_leave = []
|
||
leave_index = start_idx + 4
|
||
while leave_index < len(bi_list):
|
||
b = bi_list[leave_index]
|
||
if not b.is_sure:
|
||
break
|
||
if b.high >= zs.zd and b.low <= zs.zg:
|
||
added_after_leave.append(b.pre)
|
||
added_after_leave.append(b)
|
||
else:
|
||
break
|
||
leave_index += 2
|
||
|
||
if added_after_leave:
|
||
bis_for_zs = list(zs.bi_list) + list(added_after_leave)
|
||
bi_highs = [bi.high for bi in bis_for_zs]
|
||
bi_lows = [bi.low for bi in bis_for_zs]
|
||
zs.set_gg(max(bi_highs))
|
||
zs.set_dd(min(bi_lows))
|
||
zs.bi_list = bis_for_zs
|
||
bi = bis_for_zs[-1]
|
||
if bi.is_sure:
|
||
zs.set_end_bi(bi, bi.sure_time)
|
||
|
||
start_idx = start_idx + len(added_after_leave)
|
||
else:
|
||
zs.set_end_bi(bi3, bi3.sure_time)
|
||
|
||
if last_zs:
|
||
last_zs.set_next(zs)
|
||
zs.set_pre(last_zs)
|
||
|
||
bi_zs_list.append(zs)
|
||
last_zs = zs
|
||
|
||
start_idx += 4
|
||
|
||
if last_zs:
|
||
last_zs.is_sure = bi_list[-1].is_sure
|
||
|
||
if last_zs and not last_zs.is_sure:
|
||
if last_zs.bi_list and len(last_zs.bi_list) > 0:
|
||
last_bi_of_zs = last_zs.bi_list[-1]
|
||
last_bi_idx = -1
|
||
for i, bi in enumerate(bi_list):
|
||
if bi == last_bi_of_zs:
|
||
last_bi_idx = i
|
||
break
|
||
|
||
has_leave = False
|
||
if last_bi_idx >= 0 and last_bi_idx + 1 < len(bi_list):
|
||
for i in range(last_bi_idx + 1, len(bi_list)):
|
||
bi = bi_list[i]
|
||
if bi.is_sure:
|
||
leave = (bi.low > last_zs.zg and bi.high > last_zs.zg) or \
|
||
(bi.high < last_zs.zd and bi.low < last_zs.zd)
|
||
if leave:
|
||
has_leave = True
|
||
break
|
||
|
||
if has_leave:
|
||
if last_bi_of_zs.is_sure:
|
||
last_zs.set_end_bi(last_bi_of_zs, last_bi_of_zs.sure_time)
|
||
return bi_zs_list
|
||
def get_bi_zs_list(self, bi_list):
|
||
"""
|
||
根据缠论笔中枢定义计算中枢(完全参照 get_seg_zs_list 线段中枢判断规则)
|
||
从第4根笔开始(索引3),每3根笔为一组检查
|
||
上涨中枢:后中枢 zd > 前中枢 zg(不重叠上移)
|
||
下跌中枢:后中枢 zg < 前中枢 zd(不重叠下移)
|
||
盘整/扩张:后中枢与前中枢整体区间有交集 → 合并扩展
|
||
中枢可按两笔一组继续扩展到5根、7根...
|
||
"""
|
||
bi_zs_list = []
|
||
if len(bi_list) < 3:
|
||
return bi_zs_list
|
||
|
||
last_zs = None
|
||
start_idx = 3
|
||
|
||
while start_idx < len(bi_list):
|
||
if start_idx + 2 >= len(bi_list):
|
||
break
|
||
|
||
bi1 = bi_list[start_idx]
|
||
bi2 = bi_list[start_idx + 1]
|
||
bi3 = bi_list[start_idx + 2]
|
||
|
||
if not (bi1.is_sure and bi2.is_sure and bi3.is_sure):
|
||
start_idx += 1
|
||
continue
|
||
|
||
zg = min(bi1.high, bi2.high, bi3.high)
|
||
zd = max(bi1.low, bi2.low, bi3.low)
|
||
|
||
if zg <= zd:
|
||
start_idx += 1
|
||
continue
|
||
|
||
valid = False
|
||
if last_zs is None:
|
||
if bi1.dir == Chan_BI_DIR.DOWN:
|
||
zs_dir = Chan_ZS_DIR.UP
|
||
valid = (bi2.dir == Chan_BI_DIR.UP and bi3.dir == Chan_BI_DIR.DOWN)
|
||
else:
|
||
zs_dir = Chan_ZS_DIR.DOWN
|
||
valid = (bi2.dir == Chan_BI_DIR.DOWN and bi3.dir == Chan_BI_DIR.UP)
|
||
else:
|
||
is_up_zs = zd > last_zs.zg
|
||
is_down_zs = zg < last_zs.zd
|
||
|
||
if is_up_zs:
|
||
zs_dir = Chan_ZS_DIR.UP
|
||
valid = (bi1.dir == Chan_BI_DIR.DOWN and bi2.dir == Chan_BI_DIR.UP and bi3.dir == Chan_BI_DIR.DOWN)
|
||
elif is_down_zs:
|
||
zs_dir = Chan_ZS_DIR.DOWN
|
||
valid = (bi1.dir == Chan_BI_DIR.UP and bi2.dir == Chan_BI_DIR.DOWN and bi3.dir == Chan_BI_DIR.UP)
|
||
|
||
create_new_zs = False
|
||
if not valid:
|
||
# 如果新中枢和前一个中枢的中枢区间有重叠,不形成新中枢,合并扩展
|
||
if last_zs is not None:
|
||
is_in_last_zs = (zd > last_zs.zd and zd < last_zs.zg) or \
|
||
(zg < last_zs.zg and zg > last_zs.zd) or \
|
||
(zg > last_zs.zg and zd < last_zs.zd) or \
|
||
(zg < last_zs.zg and zd > last_zs.zd)
|
||
if is_in_last_zs:
|
||
# 扩展当前中枢:将 bi1-bi3 加入 last_zs
|
||
for bi in [bi1, bi2, bi3]:
|
||
if bi not in last_zs.bi_list:
|
||
last_zs.add_bi(bi)
|
||
create_new_zs = False
|
||
else:
|
||
start_idx += 1
|
||
continue
|
||
else:
|
||
start_idx += 1
|
||
continue
|
||
else:
|
||
create_new_zs = True
|
||
|
||
# 新中枢形成时确认前一个中枢
|
||
if last_zs and create_new_zs:
|
||
last_bi = last_zs.bi_list[-1]
|
||
if last_bi and last_bi.is_sure:
|
||
last_zs.is_sure = True
|
||
last_zs.set_end_bi(last_bi, last_bi.sure_time)
|
||
|
||
zs = last_zs
|
||
if create_new_zs:
|
||
gg = max(bi1.high, bi2.high, bi3.high)
|
||
dd = min(bi1.low, bi2.low, bi3.low)
|
||
zs = ChanBIZS(bi1, len(bi_zs_list), zs_dir)
|
||
zs.set_zg(zg)
|
||
zs.set_zd(zd)
|
||
zs.set_gg(gg)
|
||
zs.set_dd(dd)
|
||
zs.is_sure = False
|
||
zs.bi_list = [bi1, bi2, bi3]
|
||
|
||
# 离开后回抽扩展检查
|
||
added_after_leave = []
|
||
leave_index = start_idx + 4
|
||
while leave_index < len(bi_list):
|
||
b = bi_list[leave_index]
|
||
if not b.is_sure:
|
||
break
|
||
if b.high >= zs.zd and b.low <= zs.zg:
|
||
added_after_leave.append(b.pre)
|
||
added_after_leave.append(b)
|
||
else:
|
||
break
|
||
leave_index += 2
|
||
|
||
if added_after_leave:
|
||
bis_for_zs = list(zs.bi_list) + list(added_after_leave)
|
||
bi_highs = [bi.high for bi in bis_for_zs]
|
||
bi_lows = [bi.low for bi in bis_for_zs]
|
||
zs.set_gg(max(bi_highs))
|
||
zs.set_dd(min(bi_lows))
|
||
zs.bi_list = bis_for_zs
|
||
bi = bis_for_zs[-1]
|
||
if bi.is_sure:
|
||
zs.set_end_bi(bi, bi.sure_time)
|
||
start_idx = start_idx + len(added_after_leave)
|
||
else:
|
||
if create_new_zs:
|
||
zs.set_end_bi(bi3, bi3.sure_time)
|
||
|
||
if create_new_zs:
|
||
if last_zs:
|
||
last_zs.set_next(zs)
|
||
zs.set_pre(last_zs)
|
||
bi_zs_list.append(zs)
|
||
last_zs = zs
|
||
|
||
start_idx += 4
|
||
|
||
# 最后一个中枢:根据 bi_list 最后一笔确认状态
|
||
if last_zs:
|
||
last_zs.is_sure = bi_list[-1].is_sure
|
||
|
||
if last_zs and not last_zs.is_sure:
|
||
if last_zs.bi_list and len(last_zs.bi_list) > 0:
|
||
last_bi_of_zs = last_zs.bi_list[-1]
|
||
last_bi_idx = -1
|
||
for i, bi in enumerate(bi_list):
|
||
if bi == last_bi_of_zs:
|
||
last_bi_idx = i
|
||
break
|
||
|
||
has_leave = False
|
||
if last_bi_idx >= 0 and last_bi_idx + 1 < len(bi_list):
|
||
for i in range(last_bi_idx + 1, len(bi_list)):
|
||
bi = bi_list[i]
|
||
if bi.is_sure:
|
||
leave = (bi.low > last_zs.zg and bi.high > last_zs.zg) or \
|
||
(bi.high < last_zs.zd and bi.low < last_zs.zd)
|
||
if leave:
|
||
has_leave = True
|
||
break
|
||
|
||
if has_leave:
|
||
if last_bi_of_zs.is_sure:
|
||
last_zs.set_end_bi(last_bi_of_zs, last_bi_of_zs.sure_time)
|
||
|
||
return bi_zs_list
|
||
|
||
def cal_bi_zs_list_pure(self, bi_list):
|
||
bi_zs_list = []
|
||
if len(bi_list) < 3:
|
||
return bi_zs_list
|
||
|
||
def get_zs_range(bis):
|
||
zg = min(bi.high for bi in bis)
|
||
zd = max(bi.low for bi in bis)
|
||
return zg, zd
|
||
|
||
def is_bi_overlap_range(bi, zg, zd):
|
||
return bi.high >= zd and bi.low <= zg
|
||
|
||
def check_zs_position_filter(last_zs, zg, zd, bis):
|
||
if last_zs is None:
|
||
return True
|
||
if zg <= last_zs.zd:
|
||
return bis[0].dir == Chan_BI_DIR.UP and bis[-1].dir == Chan_BI_DIR.UP
|
||
if zd >= last_zs.zg:
|
||
return bis[0].dir == Chan_BI_DIR.DOWN and bis[-1].dir == Chan_BI_DIR.DOWN
|
||
return True
|
||
|
||
def set_zs_bi_list(zs, bis):
|
||
zs.bi_list = list(bis)
|
||
for bi in zs.bi_list:
|
||
bi.set_bi_zs(zs)
|
||
zs.set_gg(max(bi.high for bi in zs.bi_list))
|
||
zs.set_dd(min(bi.low for bi in zs.bi_list))
|
||
zs.classify_zs()
|
||
|
||
last_zs = None
|
||
start_idx = 0
|
||
while start_idx + 2 < len(bi_list):
|
||
bi1 = bi_list[start_idx]
|
||
bi2 = bi_list[start_idx + 1]
|
||
bi3 = bi_list[start_idx + 2]
|
||
|
||
if not (bi1.is_sure and bi2.is_sure and bi3.is_sure):
|
||
start_idx += 1
|
||
continue
|
||
|
||
if not (bi1.dir != bi2.dir and bi1.dir == bi3.dir):
|
||
start_idx += 1
|
||
continue
|
||
|
||
zg, zd = get_zs_range([bi1, bi2, bi3])
|
||
if zg <= zd:
|
||
start_idx += 1
|
||
continue
|
||
|
||
bis_for_zs = [bi1, bi2, bi3]
|
||
extend_idx = start_idx + 3
|
||
while extend_idx + 1 < len(bi_list):
|
||
leave_bi = bi_list[extend_idx]
|
||
back_bi = bi_list[extend_idx + 1]
|
||
if not (leave_bi.is_sure and back_bi.is_sure):
|
||
break
|
||
if not is_bi_overlap_range(back_bi, zg, zd):
|
||
break
|
||
bis_for_zs.append(leave_bi)
|
||
bis_for_zs.append(back_bi)
|
||
extend_idx += 2
|
||
|
||
if not check_zs_position_filter(last_zs, zg, zd, bis_for_zs):
|
||
start_idx += 1
|
||
continue
|
||
|
||
zs_dir = Chan_ZS_DIR.UP if bi1.dir == Chan_BI_DIR.DOWN else Chan_ZS_DIR.DOWN
|
||
zs = ChanBIZS(bi1, len(bi_zs_list), zs_dir)
|
||
zs.set_zg(zg)
|
||
zs.set_zd(zd)
|
||
|
||
set_zs_bi_list(zs, bis_for_zs)
|
||
zs.set_end_bi(bis_for_zs[-1], bis_for_zs[-1].sure_time)
|
||
|
||
if last_zs:
|
||
last_zs.set_next(zs)
|
||
zs.set_pre(last_zs)
|
||
|
||
bi_zs_list.append(zs)
|
||
last_zs = zs
|
||
start_idx = start_idx + len(bis_for_zs)
|
||
|
||
# 与 cal_bi_zs_list 一致:最后一笔未确认时末中枢标为未完成;若其后已出现确认的离开笔,仍按离开前最后一笔确认中枢结束
|
||
if last_zs:
|
||
last_zs.is_sure = bi_list[-1].is_sure
|
||
|
||
if last_zs and not last_zs.is_sure:
|
||
if last_zs.bi_list and len(last_zs.bi_list) > 0:
|
||
last_bi_of_zs = last_zs.bi_list[-1]
|
||
last_bi_idx = -1
|
||
for i, bi in enumerate(bi_list):
|
||
if bi == last_bi_of_zs:
|
||
last_bi_idx = i
|
||
break
|
||
|
||
has_leave = False
|
||
if last_bi_idx >= 0 and last_bi_idx + 1 < len(bi_list):
|
||
for i in range(last_bi_idx + 1, len(bi_list)):
|
||
bi = bi_list[i]
|
||
if bi.is_sure:
|
||
leave = (bi.low > last_zs.zg and bi.high > last_zs.zg) or \
|
||
(bi.high < last_zs.zd and bi.low < last_zs.zd)
|
||
if leave:
|
||
has_leave = True
|
||
break
|
||
|
||
if has_leave:
|
||
if last_bi_of_zs.is_sure:
|
||
last_zs.set_end_bi(last_bi_of_zs, last_bi_of_zs.sure_time)
|
||
|
||
return bi_zs_list
|
||
def find_all_bsp(self, bi_list, bi_zs_list):
|
||
"""
|
||
笔中枢的三类买卖点识别
|
||
|
||
三类买点:中枢形成后,一笔向上离开中枢(低点 > zg),
|
||
随后回拉的一笔低点不跌回中枢(低点 >= zg),确认支撑有效。
|
||
三类卖点:中枢形成后,一笔向下离开中枢(高点 < zd),
|
||
随后反弹的一笔高点不回到中枢(高点 <= zd),确认压力有效。
|
||
|
||
参数:
|
||
bi_list: 笔列表
|
||
bi_zs_list: 笔中枢列表(二维列表,每个seg内的中枢列表)
|
||
|
||
返回:
|
||
bsp_list: ChanBSP 列表,包含所有识别到的三类买卖点
|
||
"""
|
||
bsp_list = []
|
||
if len(bi_list) < 4 or len(bi_zs_list) == 0:
|
||
return bsp_list
|
||
|
||
for zs in bi_zs_list:
|
||
if not zs.is_sure or len(zs.bi_list) < 3:
|
||
continue
|
||
#print(zs.start_time, zs.end_time, zs.dir, zs.is_sure, len(zs.bi_list))
|
||
# 中枢结束后的第一笔(离开笔)
|
||
last_zs_bi = zs.bi_list[-1]
|
||
if last_zs_bi.dir == Chan_BI_DIR.UP:
|
||
if last_zs_bi.is_sure and last_zs_bi.end_klc.high <= zs.zg or (last_zs_bi.next and last_zs_bi.next.is_sure and last_zs_bi.next.end_klc.low < zs.zd):
|
||
leave_bi = last_zs_bi.next
|
||
else:
|
||
leave_bi = last_zs_bi
|
||
else:
|
||
if last_zs_bi.is_sure and last_zs_bi.end_klc.low >= zs.zd or (last_zs_bi.next and last_zs_bi.next.is_sure and last_zs_bi.next.end_klc.high > zs.zg):
|
||
leave_bi = last_zs_bi.next
|
||
else:
|
||
leave_bi = last_zs_bi
|
||
#print(zs.zg, zs.zd)
|
||
if leave_bi is None or not leave_bi.is_sure:
|
||
continue
|
||
if (zs.dir == Chan_ZS_DIR.UP and leave_bi.dir == Chan_BI_DIR.UP and leave_bi.end_klc.high < zs.zg and leave_bi.end_klc.high > zs.zd) or (zs.dir == Chan_ZS_DIR.DOWN and leave_bi.dir == Chan_BI_DIR.DOWN and leave_bi.end_klc.low < zs.zg and leave_bi.end_klc.low > zs.zd):
|
||
#print("--------------------", leave_bi.dir, leave_bi.end_klc.high, leave_bi.end_klc.low, zs.zg, zs.zd)
|
||
leave_bi = leave_bi.next
|
||
# 三类买点:向上离开中枢后回拉不破 zg
|
||
#print("Leave bi:", leave_bi.start_time, leave_bi.end_time, leave_bi.dir, leave_bi.is_sure, leave_bi.low, leave_bi.high)
|
||
if leave_bi.dir == Chan_BI_DIR.UP:
|
||
first_bsp_bi_div = self.check_bi_div(zs, leave_bi)
|
||
# 确认一类卖点:离开断能量小于进入段能量
|
||
if first_bsp_bi_div:
|
||
bsp = ChanBSP(
|
||
leave_bi, len(bsp_list),
|
||
Chan_BSP_TYPE.S1,
|
||
Chan_BSP_DIR.SELL,
|
||
leave_bi.sure_time,
|
||
zs.index+1, zs, None
|
||
)
|
||
leave_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.S1)
|
||
bsp_list.append(bsp)
|
||
# 回拉笔
|
||
pullback_bi = leave_bi.next
|
||
#print(pullback_bi.start_klc.start_time, pullback_bi.dir, pullback_bi.is_sure, pullback_bi.low, pullback_bi.high)
|
||
if pullback_bi and pullback_bi.is_sure and pullback_bi.dir == Chan_BI_DIR.DOWN:
|
||
if pullback_bi.low >= zs.zg:
|
||
# 确认三类买点:回拉笔的低点不跌回中枢
|
||
bsp = ChanBSP(
|
||
pullback_bi, len(bsp_list),
|
||
Chan_BSP_TYPE.B3,
|
||
Chan_BSP_DIR.BUY,
|
||
pullback_bi.sure_time,
|
||
zs.index+1, zs, None
|
||
)
|
||
pullback_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.B3)
|
||
bsp_list.append(bsp)
|
||
# 二类卖点
|
||
if first_bsp_bi_div:
|
||
second_bsp_bi = pullback_bi.next
|
||
if second_bsp_bi and second_bsp_bi.is_sure and second_bsp_bi.end_klc.high < leave_bi.end_klc.high:
|
||
# 确认二类卖点:一类卖点后回拉不超过一类卖点高点
|
||
bsp = ChanBSP(
|
||
second_bsp_bi, len(bsp_list),
|
||
Chan_BSP_TYPE.S2,
|
||
Chan_BSP_DIR.SELL,
|
||
second_bsp_bi.sure_time,
|
||
zs.index+1, zs, None
|
||
)
|
||
second_bsp_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.B2)
|
||
bsp_list.append(bsp)
|
||
# 三类卖点:向下离开中枢后反弹不破 zd
|
||
elif leave_bi.dir == Chan_BI_DIR.DOWN:
|
||
first_bsp_bi_div = self.check_bi_div(zs, leave_bi)
|
||
# 确认一类买点:离开段能量小于进入段
|
||
if first_bsp_bi_div:
|
||
bsp = ChanBSP(
|
||
leave_bi, len(bsp_list),
|
||
Chan_BSP_TYPE.B1,
|
||
Chan_BSP_DIR.BUY,
|
||
leave_bi.sure_time,
|
||
zs.index+1, zs, None
|
||
)
|
||
leave_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.B1)
|
||
bsp_list.append(bsp)
|
||
# 反弹笔
|
||
bounce_bi = leave_bi.next
|
||
#print(bounce_bi.start_klc.start_time, bounce_bi.dir, bounce_bi.is_sure, bounce_bi.low, bounce_bi.high)
|
||
if bounce_bi and bounce_bi.is_sure and bounce_bi.dir == Chan_BI_DIR.UP:
|
||
if bounce_bi.high <= zs.zd:
|
||
# 确认三类卖点:反弹笔的高点不回到中枢
|
||
bsp = ChanBSP(
|
||
bounce_bi, len(bsp_list),
|
||
Chan_BSP_TYPE.S3,
|
||
Chan_BSP_DIR.SELL,
|
||
bounce_bi.sure_time,
|
||
zs.index+1, zs, None
|
||
)
|
||
bounce_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.S3)
|
||
bsp_list.append(bsp)
|
||
# 二类卖点
|
||
if first_bsp_bi_div:
|
||
second_bsp_bi = bounce_bi.next
|
||
if second_bsp_bi and second_bsp_bi.is_sure and second_bsp_bi.end_klc.low > leave_bi.end_klc.low:
|
||
# 确认二类买点:一类买点后回拉不超过一类卖点高点
|
||
bsp = ChanBSP(
|
||
second_bsp_bi, len(bsp_list),
|
||
Chan_BSP_TYPE.B2,
|
||
Chan_BSP_DIR.BUY,
|
||
second_bsp_bi.sure_time,
|
||
zs.index+1, zs, None
|
||
)
|
||
second_bsp_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.B2)
|
||
bsp_list.append(bsp)
|
||
return bsp_list
|
||
def check_bi_div(self, zs, leave_bi):
|
||
enter_bi = zs.bi_list[0].pre
|
||
macdhist_div = 0
|
||
if enter_bi and enter_bi.dir == leave_bi.dir:
|
||
macdhist_div = abs(leave_bi.macd_hist) - abs(enter_bi.macd_hist)
|
||
#print(enter_bi.end_time, leave_bi.end_time, macdhist_div < 0)
|
||
return macdhist_div < 0
|
||
def find_first_bsp(self, bi_list, bi_zs_list):
|
||
"""
|
||
笔中枢的一类买卖点识别
|
||
|
||
一类买点:下跌趋势中,最后一个中枢完成后,向下离开中枢的笔创新低,
|
||
但该笔与进入中枢前的最后一笔下跌形成底背驰(力度减弱),
|
||
即趋势力竭的转折点。
|
||
一类卖点:上涨趋势中,最后一个中枢完成后,向上离开中枢的笔创新高,
|
||
但该笔与进入中枢前的最后一笔上涨形成顶背驰(力度减弱),
|
||
即趋势力竭的转折点。
|
||
|
||
简化判断:中枢形成后,离开中枢的笔(突破笔)本身即为一类买卖点的触发笔。
|
||
|
||
参数:
|
||
bi_list: 笔列表
|
||
bi_zs_list: 笔中枢列表(扁平列表,每个元素是一个中枢对象)
|
||
|
||
返回:
|
||
bsp_list: ChanBSP 列表,包含所有识别到的一类买卖点
|
||
"""
|
||
bsp_list = []
|
||
if len(bi_list) < 4 or len(bi_zs_list) == 0:
|
||
return bsp_list
|
||
|
||
for zs in bi_zs_list:
|
||
if not zs.is_sure or len(zs.bi_list) < 3:
|
||
continue
|
||
|
||
# 找到中枢的最后一笔
|
||
last_zs_bi = zs.bi_list[-1]
|
||
|
||
# 确定离开笔:中枢最后一笔之后的第一笔
|
||
if last_zs_bi.dir == Chan_BI_DIR.UP:
|
||
# 中枢最后一笔向上,如果没有真正离开中枢,取下一笔
|
||
if last_zs_bi.is_sure and last_zs_bi.end_klc.high <= zs.zg:
|
||
leave_bi = last_zs_bi.next
|
||
else:
|
||
leave_bi = last_zs_bi
|
||
else:
|
||
# 中枢最后一笔向下,如果没有真正离开中枢,取下一笔
|
||
if last_zs_bi.is_sure and last_zs_bi.end_klc.low >= zs.zd:
|
||
leave_bi = last_zs_bi.next
|
||
else:
|
||
leave_bi = last_zs_bi
|
||
|
||
if leave_bi is None or not leave_bi.is_sure:
|
||
continue
|
||
|
||
# 一类买点:向下离开中枢(leave_bi向下,低点 < zd),趋势力竭
|
||
if leave_bi.dir == Chan_BI_DIR.DOWN and leave_bi.low < zs.zd:
|
||
# 背驰判断:比较离开笔与中枢内最后一笔同向笔的MACD柱状累积面积
|
||
# 缠论原文:两段同向走势的MACD柱状面积比较,面积缩小即为背驰
|
||
compare_bi = None
|
||
for bi in reversed(zs.bi_list):
|
||
if bi.dir == Chan_BI_DIR.DOWN and bi is not leave_bi:
|
||
compare_bi = bi
|
||
break
|
||
|
||
is_divergence = False
|
||
if compare_bi:
|
||
# 笔的macd_hist是该笔内所有KLU的macdhist累积面积
|
||
leave_macd_area = abs(leave_bi.macd_hist)
|
||
compare_macd_area = abs(compare_bi.macd_hist)
|
||
|
||
# 价格创新低但MACD面积缩小 = 底背驰
|
||
if leave_bi.low <= compare_bi.low and leave_macd_area < compare_macd_area:
|
||
is_divergence = True
|
||
# 即使没创新低,MACD面积明显缩小也算背驰
|
||
elif leave_macd_area < compare_macd_area * 0.5:
|
||
is_divergence = True
|
||
else:
|
||
# 没有对比笔时,只要离开中枢就算一类买点
|
||
is_divergence = True
|
||
|
||
if is_divergence:
|
||
bsp = ChanBSP(
|
||
leave_bi, len(bsp_list),
|
||
Chan_BSP_TYPE.T1,
|
||
Chan_BSP_DIR.BUY,
|
||
leave_bi.sure_time,
|
||
1, zs, None
|
||
)
|
||
bsp_list.append(bsp)
|
||
|
||
# 一类卖点:向上离开中枢(leave_bi向上,高点 > zg),趋势力竭
|
||
elif leave_bi.dir == Chan_BI_DIR.UP and leave_bi.high > zs.zg:
|
||
# 背驰判断:比较离开笔与中枢内最后一笔同向笔的MACD柱状累积面积
|
||
compare_bi = None
|
||
for bi in reversed(zs.bi_list):
|
||
if bi.dir == Chan_BI_DIR.UP and bi is not leave_bi:
|
||
compare_bi = bi
|
||
break
|
||
|
||
is_divergence = False
|
||
if compare_bi:
|
||
leave_macd_area = abs(leave_bi.macd_hist)
|
||
compare_macd_area = abs(compare_bi.macd_hist)
|
||
|
||
# 价格创新高但MACD面积缩小 = 顶背驰
|
||
if leave_bi.high >= compare_bi.high and leave_macd_area < compare_macd_area:
|
||
is_divergence = True
|
||
# 即使没创新高,MACD面积明显缩小也算背驰
|
||
elif leave_macd_area < compare_macd_area * 0.5:
|
||
is_divergence = True
|
||
else:
|
||
is_divergence = True
|
||
|
||
if is_divergence:
|
||
bsp = ChanBSP(
|
||
leave_bi, len(bsp_list),
|
||
Chan_BSP_TYPE.T1,
|
||
Chan_BSP_DIR.SELL,
|
||
leave_bi.sure_time,
|
||
1, zs, None
|
||
)
|
||
bsp_list.append(bsp)
|
||
|
||
return bsp_list
|
||
|
||
def find_second_bsp(self, bi_list, first_bsp_list):
|
||
"""
|
||
笔中枢的二类买卖点识别
|
||
|
||
二类买点:一类买点出现后,价格向上反弹一笔,再回落一笔,
|
||
回落笔的低点不跌破一类买点的低点,确认底部成立。
|
||
二类卖点:一类卖点出现后,价格向下回落一笔,再反弹一笔,
|
||
反弹笔的高点不超过一类卖点的高点,确认顶部成立。
|
||
|
||
参数:
|
||
bi_list: 笔列表
|
||
first_bsp_list: 一类买卖点列表(find_first_bsp 的返回值)
|
||
|
||
返回:
|
||
bsp_list: ChanBSP 列表,包含所有识别到的二类买卖点
|
||
"""
|
||
bsp_list = []
|
||
if not first_bsp_list or len(bi_list) < 4:
|
||
return bsp_list
|
||
|
||
for first_bsp in first_bsp_list:
|
||
trigger_bi = first_bsp.bi # 一类买卖点的触发笔
|
||
|
||
if first_bsp.dir == Chan_BSP_DIR.BUY:
|
||
# 一买之后:trigger_bi 向下 -> 反弹笔(向上) -> 回落笔(向下)
|
||
# 回落笔的低点 > trigger_bi 的低点 => 二类买点
|
||
bounce_bi = trigger_bi.next # 反弹笔(向上)
|
||
if bounce_bi and bounce_bi.is_sure and bounce_bi.dir == Chan_BI_DIR.UP:
|
||
pullback_bi = bounce_bi.next # 回落笔(向下)
|
||
if pullback_bi and pullback_bi.is_sure and pullback_bi.dir == Chan_BI_DIR.DOWN:
|
||
if pullback_bi.low > trigger_bi.low:
|
||
bsp = ChanBSP(
|
||
pullback_bi, len(bsp_list),
|
||
Chan_BSP_TYPE.T2,
|
||
Chan_BSP_DIR.BUY,
|
||
pullback_bi.sure_time,
|
||
1, first_bsp.zs, None
|
||
)
|
||
bsp_list.append(bsp)
|
||
|
||
elif first_bsp.dir == Chan_BSP_DIR.SELL:
|
||
# 一卖之后:trigger_bi 向上 -> 回落笔(向下) -> 反弹笔(向上)
|
||
# 反弹笔的高点 < trigger_bi 的高点 => 二类卖点
|
||
drop_bi = trigger_bi.next # 回落笔(向下)
|
||
if drop_bi and drop_bi.is_sure and drop_bi.dir == Chan_BI_DIR.DOWN:
|
||
bounce_bi = drop_bi.next # 反弹笔(向上)
|
||
if bounce_bi and bounce_bi.is_sure and bounce_bi.dir == Chan_BI_DIR.UP:
|
||
if bounce_bi.high < trigger_bi.high:
|
||
bsp = ChanBSP(
|
||
bounce_bi, len(bsp_list),
|
||
Chan_BSP_TYPE.T2,
|
||
Chan_BSP_DIR.SELL,
|
||
bounce_bi.sure_time,
|
||
1, first_bsp.zs, None
|
||
)
|
||
bsp_list.append(bsp)
|
||
|
||
return bsp_list
|
||
def calculate_seg_zs(self, seg_list):
|
||
return self.get_seg_zs_list(seg_list)
|
||
def get_seg_zs_list(self, seg_list):
|
||
"""
|
||
根据缠论线段中枢定义计算中枢
|
||
从第4根线段开始(索引3),每3根线段为一组检查
|
||
上涨中枢:后中枢 zd > 前中枢 zg(不重叠上移)
|
||
下跌中枢:后中枢 zg < 前中枢 zd(不重叠下移)
|
||
盘整/扩张:后中枢与前中枢整体区间(GG/DD)有交集
|
||
中枢可按两段一组继续扩展到5根、7根...
|
||
"""
|
||
zs_list = []
|
||
if len(seg_list) < 3:
|
||
return zs_list
|
||
|
||
last_zs = None
|
||
|
||
# 从第4根线段开始(索引3),每3根为一组
|
||
start_idx = 3
|
||
|
||
while start_idx < len(seg_list):
|
||
# 取连续3个线段
|
||
if start_idx + 2 >= len(seg_list):
|
||
break
|
||
|
||
seg1 = seg_list[start_idx]
|
||
seg2 = seg_list[start_idx + 1]
|
||
seg3 = seg_list[start_idx + 2]
|
||
|
||
# 三个线段都必须是已确认的
|
||
if not (seg1.is_sure and seg2.is_sure and seg3.is_sure):
|
||
start_idx += 1
|
||
continue
|
||
|
||
# 计算这3个线段的中枢区间
|
||
zg = min(seg1.high, seg2.high, seg3.high)
|
||
zd = max(seg1.low, seg2.low, seg3.low)
|
||
|
||
if zg <= zd:
|
||
start_idx += 1
|
||
#print(seg1.start_bi.start_klc.end_time, "not valid", zg, zd)
|
||
continue
|
||
|
||
# 判断中枢类型(按注释定义)
|
||
# 上涨中枢:后中枢 zd > 前中枢 zg(不重叠上移)
|
||
# 下跌中枢:后中枢 zg < 前中枢 zd(不重叠下移)
|
||
# 盘整/扩张:后中枢与前中枢区间有交集
|
||
if last_zs is None:
|
||
# 第一个中枢仅按线段形态判定方向
|
||
if seg1.dir == Chan_SEG_DIR.DOWN:
|
||
# 下跌+上涨+下跌,对应上涨中枢
|
||
zs_dir = Chan_ZS_DIR.UP
|
||
valid = (seg2.dir == Chan_SEG_DIR.UP and seg3.dir == Chan_SEG_DIR.DOWN)
|
||
else:
|
||
# 上涨+下跌+上涨,对应下跌中枢
|
||
zs_dir = Chan_ZS_DIR.DOWN
|
||
valid = (seg2.dir == Chan_SEG_DIR.DOWN and seg3.dir == Chan_SEG_DIR.UP)
|
||
else:
|
||
is_up_zs = zd > last_zs.zg
|
||
is_down_zs = zg < last_zs.zd
|
||
|
||
if is_up_zs:
|
||
# 不重叠上移
|
||
zs_dir = Chan_ZS_DIR.UP
|
||
valid = (seg1.dir == Chan_SEG_DIR.DOWN and seg2.dir == Chan_SEG_DIR.UP and seg3.dir == Chan_SEG_DIR.DOWN)
|
||
elif is_down_zs:
|
||
# 不重叠下移
|
||
zs_dir = Chan_ZS_DIR.DOWN
|
||
valid = (seg1.dir == Chan_SEG_DIR.UP and seg2.dir == Chan_SEG_DIR.DOWN and seg3.dir == Chan_SEG_DIR.UP)
|
||
create_new_zs = False
|
||
# 验证是否有效
|
||
if not valid:
|
||
# 如果新中枢和前一个中枢的中枢区间有重叠,不行成新中枢需要合并两个中枢
|
||
is_in_last_zs = (zd > last_zs.zd and zd < last_zs.zg) or (zg < last_zs.zg and zg > last_zs.zd) or (zg > last_zs.zg and zd < last_zs.zd) or (zg < last_zs.zg and zd > last_zs.zd)
|
||
if is_in_last_zs:
|
||
#print(seg1.start_time, "New zs is in last zs, not valid")
|
||
last_zs.extend_zs(seg_list[last_zs.seg_list[-1].index:(seg3.index + 1)])
|
||
create_new_zs = False
|
||
else:
|
||
start_idx += 1
|
||
continue
|
||
else:
|
||
create_new_zs = True
|
||
if last_zs and create_new_zs:
|
||
last_seg = last_zs.seg_list[-1]
|
||
last_bi = last_seg.end_bi
|
||
if last_bi:
|
||
last_zs.is_sure = True
|
||
last_zs.set_end_klc(last_bi.end_klc, last_bi.sure_time, 0, last_seg)
|
||
last_zs.set_end_seg(last_seg)
|
||
zs = last_zs
|
||
if create_new_zs:
|
||
# 创建新中枢
|
||
gg = max(seg1.high, seg2.high, seg3.high)
|
||
dd = min(seg1.low, seg2.low, seg3.low)
|
||
|
||
zs = ChanZS(seg1, len(zs_list), zs_dir)
|
||
zs.set_zg(zg)
|
||
zs.set_zd(zd)
|
||
zs.set_gg(gg)
|
||
zs.set_dd(dd)
|
||
zs.is_sure = False
|
||
zs.seg_list = [seg1, seg2, seg3]
|
||
# 若第二线段与 [zd,zg] 重叠(如离开后回抽回到前中枢)则并入扩展
|
||
added_after_leave = []
|
||
leave_index = start_idx + 4
|
||
is_break = False
|
||
while leave_index < len(seg_list):
|
||
s = seg_list[leave_index]
|
||
if not s.is_sure:
|
||
break
|
||
sh = max(s.start_bi.high, s.end_bi.high) if s.end_bi else s.start_bi.high
|
||
sl = min(s.start_bi.low, s.end_bi.low) if s.end_bi else s.start_bi.low
|
||
if sh >= zs.zd and sl <= zs.zg:
|
||
added_after_leave.append(s.pre)
|
||
added_after_leave.append(s)
|
||
leave_index += 2
|
||
else:
|
||
next_seg = s.next
|
||
if next_seg and next_seg.is_sure:
|
||
if next_seg.dir == Chan_SEG_DIR.UP:
|
||
if next_seg.high <= zs.zg and next_seg.low >= zs.zd:
|
||
leave_index += 2
|
||
continue
|
||
else:
|
||
is_break = True
|
||
else:
|
||
if next_seg.low >= zs.zd and next_seg.low <= zs.zg:
|
||
leave_index += 2
|
||
continue
|
||
else:
|
||
is_break = True
|
||
else:
|
||
break
|
||
if is_break:
|
||
break
|
||
if added_after_leave:
|
||
#print(len(added_after_leave))
|
||
segs_for_zs = list(zs.seg_list) + list(added_after_leave)
|
||
seg_highs = [s.high for s in segs_for_zs]
|
||
seg_lows = [s.low for s in segs_for_zs]
|
||
zs.set_gg(max(seg_highs))
|
||
zs.set_dd(min(seg_lows))
|
||
zs.seg_list = segs_for_zs
|
||
seg = segs_for_zs[-1]
|
||
#if seg.end_bi:
|
||
#zs.set_end_klc(seg.end_bi.end_klc, seg.sure_time, 0, seg)
|
||
#zs.set_end_seg(seg)
|
||
#zs.is_sure = True
|
||
start_idx = start_idx + len(added_after_leave)
|
||
if last_zs and last_zs.index != zs.index:
|
||
last_zs.set_next(zs)
|
||
zs.set_pre(last_zs)
|
||
|
||
zs_list.append(zs)
|
||
last_zs = zs
|
||
|
||
# 移动到下一组
|
||
start_idx += 4
|
||
if last_zs:
|
||
last_zs.is_sure = seg_list[-1].is_sure
|
||
"""
|
||
# 处理最后一个未确认的中枢 - 不自动扩展,保持未完成状态
|
||
if last_zs and not last_zs.is_sure:
|
||
# 获取中枢最后一个线段的索引
|
||
if last_zs.seg_list and len(last_zs.seg_list) > 0:
|
||
last_seg_of_zs = last_zs.seg_list[-1]
|
||
# 找到这个线段在seg_list中的索引
|
||
last_seg_idx = -1
|
||
for i, seg in enumerate(seg_list):
|
||
if seg == last_seg_of_zs:
|
||
last_seg_idx = i
|
||
break
|
||
|
||
# 从中枢最后一个线段之后检查是否有离开
|
||
has_leave = False
|
||
if last_seg_idx >= 0 and last_seg_idx + 1 < len(seg_list):
|
||
for i in range(last_seg_idx + 1, len(seg_list)):
|
||
seg = seg_list[i]
|
||
if seg.is_sure:
|
||
# 检查是否离开中枢
|
||
leave = (seg.low > last_zs.zg and seg.high > last_zs.zg) or \
|
||
(seg.high < last_zs.zd and seg.low < last_zs.zd)
|
||
if leave:
|
||
has_leave = True
|
||
break
|
||
|
||
if not has_leave:
|
||
# 没有离开,保持未完成状态
|
||
pass
|
||
else:
|
||
# 有离开,确认中枢
|
||
if last_seg_of_zs.end_bi:
|
||
#print(last_seg_of_zs.start_time, "last_seg_of_zs.end_time", last_seg_of_zs.end_time)
|
||
last_zs.set_end_klc(last_seg_of_zs.end_bi.end_klc, last_seg_of_zs.sure_time, 0, last_seg_of_zs)
|
||
last_zs.set_end_seg(last_seg_of_zs)
|
||
last_zs.is_sure = True
|
||
"""
|
||
return zs_list
|
||
|
||
def get_big_zs_list(self, zs_list):
|
||
"""
|
||
中枢扩张:将区间重叠的连续中枢合并为大级别中枢,便于显示更大级别的震荡区间。
|
||
重叠定义:两中枢 [zd,zg] 有交集,即 (zs_i.zg >= zs_j.zd and zs_i.zd <= zs_j.zg)。
|
||
"""
|
||
big_list = []
|
||
if len(zs_list) < 2:
|
||
return big_list
|
||
i = 0
|
||
while i < len(zs_list):
|
||
group = [zs_list[i]]
|
||
j = i + 1
|
||
while j < len(zs_list):
|
||
cur = zs_list[j]
|
||
# 与当前组内任一中枢有重叠即算扩张(通常只需与组内最后一个比)
|
||
last_in_group = group[-1]
|
||
overlap = (last_in_group.zg >= cur.zd and last_in_group.zd <= cur.zg)
|
||
if overlap:
|
||
group.append(cur)
|
||
j += 1
|
||
else:
|
||
break
|
||
if len(group) >= 2:
|
||
big = ChanZS_Big(group)
|
||
big.index = len(big_list)
|
||
big_list.append(big)
|
||
i = j if len(group) >= 2 else i + 1
|
||
return big_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
|
||
|
||
def get_decimal(self, value):
|
||
return Decimal("{:.2f}".format(value)) |