Merge pull request #17 from jackyu66git/dev

添加了很多识别功能,特别是中枢相关的
This commit is contained in:
jackyu66git
2026-04-05 17:56:14 +08:00
committed by GitHub
16 changed files with 1425 additions and 163 deletions
+21
View File
@@ -20,8 +20,19 @@ class ChanBI():
self.macd_hist = 0 self.macd_hist = 0
self.macd_div = 0 self.macd_div = 0
self.seg = None self.seg = None
self.height = 0
self.width = 0
self.slop = 0
self.fib_list = []
self.seg_index = 0
self.bi_zs = None
self.seg_zs = None
def set_bi_zs(self, bi_zs):
for klc in self.klc_list:
klc.set_bi_zs(bi_zs)
def set_seg(self, seg): def set_seg(self, seg):
self.seg = seg self.seg = seg
self.seg_index = len(seg.bi_list)-1
def set_macdhist(self, macd_hist): def set_macdhist(self, macd_hist):
self.macd_hist = macd_hist self.macd_hist = macd_hist
def set_macd_div(self, macd_div): def set_macd_div(self, macd_div):
@@ -66,7 +77,17 @@ class ChanBI():
self.end_klc = klc self.end_klc = klc
self.set_is_sure(True, sure_klc.end_time) self.set_is_sure(True, sure_klc.end_time)
self.end_time = klc.end_time self.end_time = klc.end_time
self.cal_properties()
#print(self.start_time, klc.fx, "This bi is ended", len(self.klc_list), klc.index - self.start_klc.index) #print(self.start_time, klc.fx, "This bi is ended", len(self.klc_list), klc.index - self.start_klc.index)
def cal_properties(self):
if self.is_sure:
self.height = float(format(self.high - self.low, ".2f"))
self.width = self.end_klc.index - self.start_klc.index
self.slop = float(format(self.height / self.width, ".2f"))
fib_list = [0.0, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0]
for fib in fib_list:
self.fib_list.append(float(format(self.height * fib + self.low, ".2f")))
#print(self.end_time, self.height, self.width, self.slop, self.fib_list)
def set_is_sure(self, is_sure, time): def set_is_sure(self, is_sure, time):
self.is_sure = is_sure self.is_sure = is_sure
self.sure_time = time self.sure_time = time
+9 -3
View File
@@ -21,11 +21,13 @@ class ChanBIZS():
self.sure_time = None self.sure_time = None
self.end_klc = None self.end_klc = None
self.zs_type = Chan_ZS_TYPE.NORMAL self.zs_type = Chan_ZS_TYPE.NORMAL
def set_end_bi(self, end_bi, sure_bi): start_bi.set_bi_zs(self)
def set_end_bi(self, end_bi, sure_time):
self.end_bi = end_bi self.end_bi = end_bi
self.set_end_time(end_bi.end_klc.end_time) self.set_end_time(end_bi.end_klc.end_time)
self.is_sure = True self.is_sure = True
self.sure_time = sure_bi.sure_time self.sure_time = sure_time
end_bi.set_bi_zs(self)
#print(self.start_time, self.is_sure, len(self.bi_list), self.dir, self.zs_type) #print(self.start_time, self.is_sure, len(self.bi_list), self.dir, self.zs_type)
def set_end_time(self, end_time): def set_end_time(self, end_time):
self.end_time = end_time self.end_time = end_time
@@ -40,8 +42,12 @@ class ChanBIZS():
def add_bi(self, bi: ChanBI): def add_bi(self, bi: ChanBI):
if bi: if bi:
self.bi_list.append(bi) self.bi_list.append(bi)
bi.set_bi_zs(self)
self.classify_zs() self.classify_zs()
def set_pre(self, pre):
self.pre = pre
def set_next(self, next):
self.next = next
def classify_zs(self): def classify_zs(self):
""" """
根据中枢内笔的高低点变化趋势,对中枢进行分类 根据中枢内笔的高低点变化趋势,对中枢进行分类
+9 -25
View File
@@ -167,6 +167,10 @@ class Chan_FX_TYPE(Enum):
BB = auto() BB = auto()
PTOP = auto() PTOP = auto()
PBOTTOM = auto() PBOTTOM = auto()
class Chan_FX(Enum):
CONTINUATION = auto()
REVERSAL = auto()
UNKNOWN = auto()
class Chan_PRICE_TREND(Enum): class Chan_PRICE_TREND(Enum):
UP = auto() UP = auto()
DOWN = auto() DOWN = auto()
@@ -357,28 +361,8 @@ class Chan_DATA_FIELD:
class Chan_KLC_STATE: class Chan_KLC_STATE:
"""笔当下状态(缠论笔定理)。任意时刻必属其一。""" """笔当下状态(缠论笔定理)。任意时刻必属其一。"""
FX = auto() # 分型构造中(未确认顶/底) S10 = "(1, 0)" # 分型构造中 (1,0)
BI = auto() # 笔延伸中(分型已确认,笔在延伸) S_10 = "(-1, 0)" # 底分型构造中 (-1,0)
UP = auto() # 顶分型构造中 (1,0):向上笔末端 S11 = "(1,1)" # 向上笔延续中
DOWN = auto() # 底分型构造中 (-1,0):向下笔末端 S_11 = "(-1,1)" # 向下笔延续中
UNKNOWN = "Unknown" # 初始状态
# 笔定理四状态:(Chan_BI_DIR, Chan_KLC_STATE)。笔方向用 Chan_BI_DIR,阶段用 Chan_KLC_STATE。
# (UP, BI) 向上笔延伸;(DOWN, BI) 向下笔延伸;(UP, UP) 向上笔顶分型构造;(DOWN, DOWN) 向下笔底分型构造
def bi_theorem_state(direction: Chan_BI_DIR, phase: Literal[0, 1]) -> tuple[Chan_BI_DIR, int]:
"""(direction, phase) -> (Chan_BI_DIR, Chan_KLC_STATE)。phase 0=分型构造中,1=笔延伸中。"""
if phase == 1:
return (direction, Chan_KLC_STATE.BI)
return (direction, Chan_KLC_STATE.UP if direction == Chan_BI_DIR.UP else Chan_KLC_STATE.DOWN)
# 笔定理状态转移:当前 (Chan_BI_DIR, Chan_KLC_STATE) 允许的下一状态列表
# (UP,BI) 只能 -> (UP,UP)(DOWN,BI) 只能 -> (DOWN,DOWN)(UP,UP) 可 -> (UP,BI)|(DOWN,BI)(DOWN,DOWN) 可 -> (DOWN,BI)|(UP,BI)
Chan_BI_STATE_TRANSITIONS: dict[tuple[Chan_BI_DIR, int], list[tuple[Chan_BI_DIR, int]]] = {
(Chan_BI_DIR.UP, Chan_KLC_STATE.BI): [(Chan_BI_DIR.UP, Chan_KLC_STATE.UP)],
(Chan_BI_DIR.DOWN, Chan_KLC_STATE.BI): [(Chan_BI_DIR.DOWN, Chan_KLC_STATE.DOWN)],
(Chan_BI_DIR.UP, Chan_KLC_STATE.UP): [(Chan_BI_DIR.UP, Chan_KLC_STATE.BI), (Chan_BI_DIR.DOWN, Chan_KLC_STATE.BI)],
(Chan_BI_DIR.DOWN, Chan_KLC_STATE.DOWN): [(Chan_BI_DIR.DOWN, Chan_KLC_STATE.BI), (Chan_BI_DIR.UP, Chan_KLC_STATE.BI)],
}
Chan_TRADE_INFO_LST = [Chan_DATA_FIELD.FIELD_VOLUME, Chan_DATA_FIELD.FIELD_TURNOVER, Chan_DATA_FIELD.FIELD_TURNRATE]
+52 -7
View File
@@ -1,7 +1,9 @@
import copy import copy
from typing import Dict, Optional from typing import Dict, Optional
from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR, Chan_KLC_FX, Chan_K_DIR, Chan_MACD_STATE, Chan_PRICE_TREND, Chan_EMA_POS, Chan_EMA_SEMANTIC, Chan_BSP_TYPE from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR, Chan_KLC_FX
from ChanEnum import Chan_K_DIR, Chan_MACD_STATE, Chan_PRICE_TREND, Chan_EMA_POS
from ChanEnum import Chan_EMA_SEMANTIC, Chan_BSP_TYPE, Chan_KLC_STATE, Chan_FX
import ChanKLU import ChanKLU
import ChanCTime import ChanCTime
import Chan_FX_Box import Chan_FX_Box
@@ -22,6 +24,7 @@ class ChanKLC():
self.start_klu = klu self.start_klu = klu
self.end_klu = None self.end_klu = None
self.state = "00" self.state = "00"
self.klc_state = Chan_KLC_STATE.UNKNOWN
self.open = klu.open self.open = klu.open
self.close = klu.close self.close = klu.close
self.volume = klu.volume self.volume = klu.volume
@@ -40,8 +43,8 @@ class ChanKLC():
self.candle_dir = klu.candle_dir self.candle_dir = klu.candle_dir
self.range = klu.range self.range = klu.range
self.bb_out = True self.bb_out = True
self.macd = 0 self.macd = klu.macd
self.signal = 0 self.signal = klu.signal
self.state = Chan_MACD_STATE.UNKNOWN self.state = Chan_MACD_STATE.UNKNOWN
self.continue_div = False self.continue_div = False
self.separate_div = False self.separate_div = False
@@ -72,6 +75,14 @@ class ChanKLC():
self.fx_box = None self.fx_box = None
self.in_fx = False self.in_fx = False
self.fx_confirmed = False self.fx_confirmed = False
self.ema52_dis = klu.high - klu.ema52 if klu.close > klu.ema52 else klu.ema52 - klu.low
self.ema26_dis = klu.high - klu.ema26 if klu.close > klu.ema26 else klu.ema26 - klu.low
self.macd_signal_dis = abs(klu.macd - klu.signal)
self.ema52_ema26_dis = abs(klu.ema52 - klu.ema26)
self.fx_type = Chan_FX.UNKNOWN
self.bi_zs = None
self.seg_zs = None
self.last_bi_zs = None
# ==================== EMA 通用计算方法 ==================== # ==================== EMA 通用计算方法 ====================
@staticmethod @staticmethod
@@ -314,6 +325,9 @@ class ChanKLC():
price_diff = getattr(self, 'price_diff', None) price_diff = getattr(self, 'price_diff', None)
out += str(start) + " " + str(end) + " " + str(self.close) + " " + str(self.ema24) + " " + str(self.ema52) + " " + str(self.trend) + " " + str(self.close - self.ema52) out += str(start) + " " + str(end) + " " + str(self.close) + " " + str(self.ema24) + " " + str(self.ema52) + " " + str(self.trend) + " " + str(self.close - self.ema52)
return out return out
def set_bi_zs(self, bi_zs):
if bi_zs:
self.bi_zs = bi_zs
def set_klc_fx_type(self, klc_fx_type): def set_klc_fx_type(self, klc_fx_type):
#print(self.start_time, klc_fx_type, self.get_feature_data()['klu_macd'], self.get_feature_data()['klu_macdhist'], self.get_feature_data()['klu_rsi']) #print(self.start_time, klc_fx_type, self.get_feature_data()['klu_macd'], self.get_feature_data()['klu_macdhist'], self.get_feature_data()['klu_rsi'])
self.klc_fx_type = klc_fx_type self.klc_fx_type = klc_fx_type
@@ -327,6 +341,22 @@ class ChanKLC():
#print(self.pre.start_time, self.next.end_time, self.klc_fx_type) #print(self.pre.start_time, self.next.end_time, self.klc_fx_type)
if klc_fx_type == Chan_KLC_FX.TOP1 or klc_fx_type == Chan_KLC_FX.TOP2 or klc_fx_type == Chan_KLC_FX.BOTTOM1 or klc_fx_type == Chan_KLC_FX.BOTTOM2: if klc_fx_type == Chan_KLC_FX.TOP1 or klc_fx_type == Chan_KLC_FX.TOP2 or klc_fx_type == Chan_KLC_FX.BOTTOM1 or klc_fx_type == Chan_KLC_FX.BOTTOM2:
self.cal_fx_box() self.cal_fx_box()
self.cal_fx_type()
def cal_fx_type(self):
if self.fx == Chan_FX_TYPE.TOP and self.next:
if self.ema52_dis > self.ema26_dis:
if self.pre.macd < self.macd and self.macd < self.next.macd:
self.fx_type = Chan_FX.CONTINUATION
else:
self.fx_type = Chan_FX.REVERSAL
elif self.fx == Chan_FX_TYPE.BOTTOM and self.next:
if self.ema52_dis < self.ema26_dis:
if self.pre.macd > self.macd and self.macd > self.next.macd:
self.fx_type = Chan_FX.CONTINUATION
else:
self.fx_type = Chan_FX.REVERSAL
#if self.fx_type != Chan_FX.UNKNOWN and self.fx_type != Chan_FX.CONTINUATION:
#print(self.end_time, self.fx_type)
def cal_fx_box(self): def cal_fx_box(self):
# 每次重算前先清空,避免旧box残留 # 每次重算前先清空,避免旧box残留
self.fx_box = None self.fx_box = None
@@ -357,7 +387,7 @@ class ChanKLC():
self.fx_confirmed = True self.fx_confirmed = True
self.fx_box = Chan_FX_Box.Chan_FX_Box(start_time, end_time, high, low) self.fx_box = Chan_FX_Box.Chan_FX_Box(start_time, end_time, high, low)
def check_fx_confirmed(self, last_top, last_bottom): def check_fx_confirmed(self, last_top, last_bottom):
if last_top and last_bottom: if last_top and last_bottom and False:
if last_top.index > last_bottom.index: if last_top.index > last_bottom.index:
if self.in_fx == False and last_top.fx_confirmed == False: if self.in_fx == False and last_top.fx_confirmed == False:
pre = last_top.pre pre = last_top.pre
@@ -369,7 +399,7 @@ class ChanKLC():
else: else:
high = last_top.high high = last_top.high
low = self.low low = self.low
last_top.fx_box = Chan_FX_Box.Chan_FX_Box(last_top.pre.end_time, self.end_time, high, low) last_top.fx_box = Chan_FX_Box.Chan_FX_Box(last_top.pre.start_time, self.end_time, high, low)
#print(self.end_time, "fx_confirmed new box top") #print(self.end_time, "fx_confirmed new box top")
elif self.in_fx == False and last_bottom.fx_confirmed == False: elif self.in_fx == False and last_bottom.fx_confirmed == False:
pre = last_bottom.pre pre = last_bottom.pre
@@ -381,15 +411,30 @@ class ChanKLC():
else: else:
high = self.high high = self.high
low = last_bottom.low low = last_bottom.low
last_bottom.fx_box = Chan_FX_Box.Chan_FX_Box(last_bottom.pre.end_time, self.end_time, high, low) last_bottom.fx_box = Chan_FX_Box.Chan_FX_Box(last_bottom.pre.start_time, self.end_time, high, low)
#print(self.end_time, "fx_confirmed new box bottom") #print(self.end_time, "fx_confirmed new box bottom")
def add_klu(self, klu): def add_klu(self, klu):
self.klu_list.append(klu) self.klu_list.append(klu)
def check_klc_state(self, last_fx_klc):
if last_fx_klc and last_fx_klc.fx == Chan_FX_TYPE.TOP:
if self.high > last_fx_klc.high:
self.klc_state = Chan_KLC_STATE.S11
else:
self.klc_state = Chan_KLC_STATE.S_11
elif last_fx_klc and last_fx_klc.fx == Chan_FX_TYPE.BOTTOM:
if self.low < last_fx_klc.low:
self.klc_state = Chan_KLC_STATE.S_11
else:
self.klc_state = Chan_KLC_STATE.S11
if self.pre and self.pre.fx == Chan_FX_TYPE.TOP:
self.klc_state = Chan_KLC_STATE.S10
elif self.pre and self.pre.fx == Chan_FX_TYPE.BOTTOM:
self.klc_state = Chan_KLC_STATE.S_10
#print(self.end_time, self.klc_state)
def set_end_klu(self, klu): def set_end_klu(self, klu):
self.end_klu = klu self.end_klu = klu
self.end_time = klu.time self.end_time = klu.time
self.close = klu.close self.close = klu.close
for klu in self.klu_list: for klu in self.klu_list:
if klu.exception: if klu.exception:
self.exception = True self.exception = True
+6 -2
View File
@@ -233,14 +233,18 @@ class ChanKLU:
self.near0_return = 0 self.near0_return = 0
elif self.close > self.ema52 and self.high > self.ema52 and self.low < self.ema52: elif self.close > self.ema52 and self.high > self.ema52 and self.low < self.ema52:
self.near0_return = 0 self.near0_return = 0
# 向上穿越EMA52 7
if self.close > self.ema52 and self.open < self.ema52: if self.close > self.ema52 and self.open < self.ema52:
self.near0_return = 7 self.near0_return = 0
# 向下穿越EMA52 8
elif self.close < self.ema52 and self.open > self.ema52: elif self.close < self.ema52 and self.open > self.ema52:
self.near0_return = 8 self.near0_return = 0
if self.pre.near0_return == 7: if self.pre.near0_return == 7:
# 向上穿越后的一根价格再EMA52上方 9
if self.low > self.ema52 and self.close > self.open: if self.low > self.ema52 and self.close > self.open:
self.near0_return = 9 self.near0_return = 9
if self.pre.near0_return == 8: if self.pre.near0_return == 8:
# 向下穿越后的一根价格再EMA52下方 10
if self.high < self.ema52 and self.close < self.open: if self.high < self.ema52 and self.close < self.open:
self.near0_return = 10 self.near0_return = 10
# CROSS0 仅以 Signal 穿越零轴判定 # CROSS0 仅以 Signal 穿越零轴判定
+5 -2
View File
@@ -141,8 +141,8 @@ class ChanLun():
return self.tf_df.cal_kl_data(dataframe) return self.tf_df.cal_kl_data(dataframe)
def cal_volume_ratio(self, dataframe, window=10): def cal_volume_ratio(self, dataframe, window=10):
return self.tf_df.cal_volume_ratio(dataframe, window) return self.tf_df.cal_volume_ratio(dataframe, window)
def calculate_zs(self, bi_list, seg_list): def calculate_seg_zs(self, bi_list, seg_list):
return self.get_zs_list(bi_list, seg_list) return self.get_seg_zs_list(bi_list, seg_list)
def get_seg_list(self, bi_list): def get_seg_list(self, bi_list):
return self.tf_df.get_seg_list(bi_list) return self.tf_df.get_seg_list(bi_list)
def cal_trend(self, klc_list): def cal_trend(self, klc_list):
@@ -163,6 +163,9 @@ class ChanLun():
return self.tf_df.get_zs_list(bi_list, seg_list) return self.tf_df.get_zs_list(bi_list, seg_list)
def cal_bi_zs(self, seg_list): def cal_bi_zs(self, seg_list):
return self.tf_df.cal_bi_zs(seg_list) return self.tf_df.cal_bi_zs(seg_list)
def cal_bi_zs_list(self, bi_list):
#return self.tf_df.cal_bi_zs(bi_list)
return self.tf_df.cal_bi_zs_list(bi_list)
def get_decimal(self, value): def get_decimal(self, value):
return Decimal("{:.2f}".format(value)) return Decimal("{:.2f}".format(value))
def get_klc_list(self, klu_list): def get_klc_list(self, klu_list):
+40 -33
View File
@@ -91,41 +91,44 @@ class ChanSEG():
zs_list = [] zs_list = []
if len(self.bi_list) > 3: if len(self.bi_list) > 3:
last_zs = None last_zs = None
zs_count = 0
if self.dir == Chan_SEG_DIR.UP: if self.dir == Chan_SEG_DIR.UP:
for index in range(1, len(self.bi_list)): for index in range(1, len(self.bi_list)):
bi = self.bi_list[index] bi = self.bi_list[index]
if bi.next == None or bi.next.next == None:
continue
bi2 = bi.next
bi3 = bi.next.next
if len(zs_list) == 0 or (last_zs and last_zs.is_sure): if len(zs_list) == 0 or (last_zs and last_zs.is_sure):
if bi.next and bi.next.next and bi.next.next.is_sure and bi.next.next.index <= self.bi_list[-1].index and bi.check_bi_zs_overlap() and bi.dir == Chan_BI_DIR.DOWN: if bi3.is_sure and bi3.index <= self.bi_list[-1].index and bi.check_bi_zs_overlap() and bi.dir == Chan_BI_DIR.DOWN:
zg = min(bi.high, bi.next.high, bi.next.next.high) zg = min(bi.high, bi2.high, bi3.high)
zd = max(bi.low, bi.next.low, bi.next.next.low) zd = max(bi.low, bi2.low, bi3.low)
gg = max(bi.high, bi.next.high, bi.next.next.high) gg = max(bi.high, bi2.high, bi3.high)
dd = min(bi.low, bi.next.low, bi.next.next.low) dd = min(bi.low, bi2.low, bi3.low)
zs = ChanBIZS(bi, len(zs_list), Chan_ZS_DIR.UP) zs = ChanBIZS(bi, len(zs_list), Chan_ZS_DIR.UP)
zs.set_zg(zg) zs.set_zg(zg)
zs.set_zd(zd) zs.set_zd(zd)
zs.set_gg(gg) zs.set_gg(gg)
zs.set_dd(dd) zs.set_dd(dd)
zs.add_bi(bi.next) zs.add_bi(bi2)
zs.add_bi(bi.next.next) zs.add_bi(bi3)
zs_list.append(zs) zs_list.append(zs)
last_zs = zs last_zs = zs
else: else:
if bi.index > last_zs.bi_list[-1].index and bi.dir == Chan_BI_DIR.DOWN and bi.is_sure: if bi.index > last_zs.bi_list[-1].index and bi.dir == Chan_BI_DIR.DOWN and bi.is_sure:
if bi.low > last_zs.zg or bi.high < last_zs.zd: if bi.low > last_zs.zg or bi.high < last_zs.zd:
last_zs.set_end_bi(last_zs.bi_list[-1], bi) last_zs.set_end_bi(last_zs.bi_list[-1], last_zs.bi_list[-1].sure_time)
if bi.next and bi.next.next and bi.next.next.is_sure and bi.next.next.index <= self.bi_list[-1].index and bi.check_bi_zs_overlap() and bi.dir == Chan_BI_DIR.DOWN: if bi3.is_sure and bi3.index <= self.bi_list[-1].index and bi.check_bi_zs_overlap() and bi.dir == Chan_BI_DIR.DOWN:
zg = min(bi.high, bi.next.high, bi.next.next.high) zg = min(bi.high, bi2.high, bi3.high)
zd = max(bi.low, bi.next.low, bi.next.next.low) zd = max(bi.low, bi2.low, bi3.low)
gg = max(bi.high, bi.next.high, bi.next.next.high) gg = max(bi.high, bi2.high, bi3.high)
dd = min(bi.low, bi.next.low, bi.next.next.low) dd = min(bi.low, bi2.low, bi3.low)
zs = ChanBIZS(bi, len(zs_list), Chan_ZS_DIR.UP) zs = ChanBIZS(bi, len(zs_list), Chan_ZS_DIR.UP)
zs.set_zg(zg) zs.set_zg(zg)
zs.set_zd(zd) zs.set_zd(zd)
zs.set_gg(gg) zs.set_gg(gg)
zs.set_dd(dd) zs.set_dd(dd)
zs.add_bi(bi.next) zs.add_bi(bi2)
zs.add_bi(bi.next.next) zs.add_bi(bi3)
zs_list.append(zs) zs_list.append(zs)
last_zs = zs last_zs = zs
else: else:
@@ -133,41 +136,45 @@ class ChanSEG():
last_zs.add_bi(bi) last_zs.add_bi(bi)
if index == len(self.bi_list) - 1 and last_zs and not last_zs.is_sure: if index == len(self.bi_list) - 1 and last_zs and not last_zs.is_sure:
#print(bi.start_time, "BI", last_zs.is_sure) #print(bi.start_time, "BI", last_zs.is_sure)
last_zs.set_end_bi(last_zs.bi_list[-1], last_zs.bi_list[-1]) last_zs.set_end_bi(last_zs.bi_list[-1], last_zs.bi_list[-1].sure_time)
else: else:
for index in range(1, len(self.bi_list)): for index in range(1, len(self.bi_list)):
bi = self.bi_list[index] bi = self.bi_list[index]
if bi.next == None or bi.next.next == None:
continue
bi2 = bi.next
bi3 = bi.next.next
if len(zs_list) == 0 or (last_zs and last_zs.is_sure): if len(zs_list) == 0 or (last_zs and last_zs.is_sure):
if bi.next and bi.next.next and bi.next.next.is_sure and bi.next.next.index <= self.bi_list[-1].index and bi.check_bi_zs_overlap() and bi.dir == Chan_BI_DIR.UP: if bi3.is_sure and bi3.index <= self.bi_list[-1].index and bi.check_bi_zs_overlap() and bi.dir == Chan_BI_DIR.UP:
zg = min(bi.high, bi.next.high, bi.next.next.high) zg = min(bi.high, bi2.high, bi3.high)
zd = max(bi.low, bi.next.low, bi.next.next.low) zd = max(bi.low, bi2.low, bi3.low)
gg = max(bi.high, bi.next.high, bi.next.next.high) gg = max(bi.high, bi2.high, bi3.high)
dd = min(bi.low, bi.next.low, bi.next.next.low) dd = min(bi.low, bi2.low, bi3.low)
zs = ChanBIZS(bi, len(zs_list), Chan_ZS_DIR.DOWN) zs = ChanBIZS(bi, len(zs_list), Chan_ZS_DIR.DOWN)
zs.set_zg(zg) zs.set_zg(zg)
zs.set_zd(zd) zs.set_zd(zd)
zs.set_gg(gg) zs.set_gg(gg)
zs.set_dd(dd) zs.set_dd(dd)
zs.add_bi(bi.next) zs.add_bi(bi2)
zs.add_bi(bi.next.next) zs.add_bi(bi3)
zs_list.append(zs) zs_list.append(zs)
last_zs = zs last_zs = zs
else: else:
if bi.index > last_zs.bi_list[-1].index and bi.dir == Chan_BI_DIR.UP and bi.is_sure: if bi.index > last_zs.bi_list[-1].index and bi.dir == Chan_BI_DIR.UP and bi.is_sure:
if bi.low > last_zs.zg or bi.high < last_zs.zd: if bi.low > last_zs.zg or bi.high < last_zs.zd:
last_zs.set_end_bi(last_zs.bi_list[-1], bi) last_zs.set_end_bi(last_zs.bi_list[-1], last_zs.bi_list[-1].sure_time)
if bi.next and bi.next.next and bi.next.next.is_sure and bi.next.next.index <= self.bi_list[-1].index and bi.check_bi_zs_overlap() and bi.dir == Chan_BI_DIR.UP: if bi3.is_sure and bi3.index <= self.bi_list[-1].index and bi.check_bi_zs_overlap() and bi.dir == Chan_BI_DIR.UP:
zg = min(bi.high, bi.next.high, bi.next.next.high) zg = min(bi.high, bi2.high, bi3.high)
zd = max(bi.low, bi.next.low, bi.next.next.low) zd = max(bi.low, bi2.low, bi3.low)
gg = max(bi.high, bi.next.high, bi.next.next.high) gg = max(bi.high, bi2.high, bi3.high)
dd = min(bi.low, bi.next.low, bi.next.next.low) dd = min(bi.low, bi2.low, bi3.low)
zs = ChanBIZS(bi, len(zs_list), Chan_ZS_DIR.DOWN) zs = ChanBIZS(bi, len(zs_list), Chan_ZS_DIR.DOWN)
zs.set_zg(zg) zs.set_zg(zg)
zs.set_zd(zd) zs.set_zd(zd)
zs.set_gg(gg) zs.set_gg(gg)
zs.set_dd(dd) zs.set_dd(dd)
zs.add_bi(bi.next) zs.add_bi(bi2)
zs.add_bi(bi.next.next) zs.add_bi(bi3)
zs_list.append(zs) zs_list.append(zs)
last_zs = zs last_zs = zs
else: else:
@@ -175,7 +182,7 @@ class ChanSEG():
last_zs.add_bi(bi) last_zs.add_bi(bi)
if index == len(self.bi_list) - 1 and last_zs and not last_zs.is_sure: if index == len(self.bi_list) - 1 and last_zs and not last_zs.is_sure:
#print(bi.start_time, "BI", last_zs.is_sure) #print(bi.start_time, "BI", last_zs.is_sure)
last_zs.set_end_bi(last_zs.bi_list[-1], last_zs.bi_list[-1]) last_zs.set_end_bi(last_zs.bi_list[-1], last_zs.bi_list[-1].sure_time)
#print(self.start_time, len(zs_list)) #print(self.start_time, len(zs_list))
#print(self.bi_list[-1].end_time, "end_bi") #print(self.bi_list[-1].end_time, "end_bi")
+14 -1
View File
@@ -30,6 +30,7 @@ class ChanZS():
self.bi_out_list = [] self.bi_out_list = []
self.bi_out_seg_list = [] self.bi_out_seg_list = []
self.bi_out_seg = None self.bi_out_seg = None
self.is_extended = False
def set_last_bi_in(self, last_bi_in): def set_last_bi_in(self, last_bi_in):
self.last_bi_in = last_bi_in self.last_bi_in = last_bi_in
def set_bi_out(self, bi_out, bi_out_seg): def set_bi_out(self, bi_out, bi_out_seg):
@@ -70,7 +71,19 @@ class ChanZS():
self.gg = gg self.gg = gg
def set_dd(self, dd): def set_dd(self, dd):
self.dd = dd self.dd = dd
def extend_zs(self, seg_list):
self.is_sure = False
self.end_seg = None
self.end_klc = None
self.sure_time = None
for seg in seg_list:
if seg.end_bi.high > self.gg:
self.set_gg(seg.end_bi.high)
if seg.end_bi.low < self.dd:
self.set_dd(seg.end_bi.low)
self.seg_list.append(seg)
self.is_extended = True
#print(self.start_time, "extend zs", seg_list[-1].end_time)
# 大级别中枢:由多个区间重叠(扩张)的笔/线段中枢合并而成,用于显示更大级别的震荡区间 # 大级别中枢:由多个区间重叠(扩张)的笔/线段中枢合并而成,用于显示更大级别的震荡区间
class ChanZS_Big(): class ChanZS_Big():
+227 -45
View File
@@ -1,12 +1,13 @@
from datetime import timedelta from datetime import timedelta
from pandas import DataFrame 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 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 ChanKLU import ChanKLU
from ChanKLC import ChanKLC from ChanKLC import ChanKLC
from ChanBI import ChanBI from ChanBI import ChanBI
from ChanSBI import ChanSBI from ChanSBI import ChanSBI
from ChanSEG import ChanSEG from ChanSEG import ChanSEG
from ChanZS import ChanZS, ChanZS_Big from ChanZS import ChanZS, ChanZS_Big
from ChanBIZS import ChanBIZS
from ChanBSP import ChanBSP from ChanBSP import ChanBSP
import talib.abstract as ta import talib.abstract as ta
import pandas as pd import pandas as pd
@@ -72,8 +73,8 @@ class TF_DF():
return self.klc_list[-2] return self.klc_list[-2]
return None return None
def add_indicators(self, df): def add_indicators(self, df):
fast = 12 fast = 26
slow = 26 slow = 52
period = 9 period = 9
macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period) macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period)
bb365 = ta.BBANDS(df, timeperiod=365, nbdevup=3.0, nbdevdn=3.0, matype=0) bb365 = ta.BBANDS(df, timeperiod=365, nbdevup=3.0, nbdevdn=3.0, matype=0)
@@ -137,32 +138,24 @@ class TF_DF():
klc_index = len(klc_list) - 1 klc_index = len(klc_list) - 1
klc = klc_list[klc_index] klc = klc_list[klc_index]
if klc.end_klu and klc.end_klu.idx == index: if klc.end_klu and klc.end_klu.idx == index:
if klc.klc_fx_type == Chan_KLC_FX.TOP1 or klc.klc_fx_type == Chan_KLC_FX.TOP2: if klc.klc_state == Chan_KLC_STATE.S10:
klu_state_list.append("10") klu_state_list.append("10")
#print(klc.end_time, klc.klc_fx_type) #print(klc.end_time, klc.klc_fx_type)
elif klc.klc_fx_type == Chan_KLC_FX.BOTTOM1 or klc.klc_fx_type == Chan_KLC_FX.BOTTOM2: elif klc.klc_state == Chan_KLC_STATE.S_10:
klu_state_list.append("-10") klu_state_list.append("-10")
#print(klc.end_time, klc.klc_fx_type) #print(klc.end_time, klc.klc_fx_type)
else: elif klc.klc_state == Chan_KLC_STATE.S11:
if klc.continue_div: klu_state_list.append("11")
if klc.macdhist > 0: #print(klc.end_time, klc.klc_fx_type)
klu_state_list.append("20") elif klc.klc_state == Chan_KLC_STATE.S_11:
#print(klc.end_time, "CD", 20) klu_state_list.append("-11")
else: #print(klc.end_time, klc.klc_fx_type)
klu_state_list.append("-20")
#print(klc.end_time, "CD", -20)
elif klc.separate_div:
if klc.macdhist > 0:
klu_state_list.append("30")
#print(klc.end_time, "SD", 30)
else:
klu_state_list.append("-30")
#print(klc.end_time, "SD", -30)
else: else:
klu_state_list.append("00") klu_state_list.append("00")
klc_index += 1 klc_index += 1
else: else:
klu_state_list.append("00") klu_state_list.append("00")
print(klu_state_list[:20])
return klu_state_list return klu_state_list
def get_ema_state(self, dataframe): def get_ema_state(self, dataframe):
@@ -183,6 +176,23 @@ class TF_DF():
klu_state_list.append("0") klu_state_list.append("0")
return klu_state_list return klu_state_list
def check_fx(self, klc): 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.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_fx1(self, klc):
if klc.pre and klc.next: 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.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: #if (klc.close > klc.ema52 or klc.next.close > klc.next.ema52) and klc.macd > 0:
@@ -911,22 +921,31 @@ class TF_DF():
last_seg_bi = bi_list[i] last_seg_bi = bi_list[i]
break break
""" """
self.cal_bi_zs(seg_list) #self.cal_bi_zs(seg_list)
return 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): def cal_bi_list(self, klc_list):
bi_list = [] bi_list = []
last_top = None last_top = None
last_bottom = None last_bottom = None
bi_klc_min = 4 bi_klc_min = 4
last_fx_klc = None
for klc in klc_list: for klc in klc_list:
if last_fx_klc:
klc.check_klc_state(last_fx_klc)
klc.check_fx_confirmed(last_top, last_bottom) klc.check_fx_confirmed(last_top, last_bottom)
fx = self.check_fx(klc) fx = self.check_fx(klc)
if fx == Chan_FX_TYPE.TOP and False: if fx == Chan_FX_TYPE.TOP:
if last_bottom: if last_bottom:
if self.check_top_fx(last_bottom, klc) == False: if self.check_top_fx(last_bottom, klc) == False:
fx = Chan_FX_TYPE.UNKNOWN fx = Chan_FX_TYPE.UNKNOWN
if fx == Chan_FX_TYPE.BOTTOM and False: if fx == Chan_FX_TYPE.BOTTOM:
if last_top: if last_top:
if self.check_bottom_fx(last_top, klc) == False: if self.check_bottom_fx(last_top, klc) == False:
#print(klc.end_time, last_top.end_time, "---") #print(klc.end_time, last_top.end_time, "---")
@@ -979,6 +998,7 @@ class TF_DF():
#print(klc.start_time, bi.start_time, bi.end_time, bi.dir, bi.high, bi.low, bi.is_sure) #print(klc.start_time, bi.start_time, bi.end_time, bi.dir, bi.high, bi.low, bi.is_sure)
""" """
else: else:
last_fx_klc = klc
if fx == Chan_FX_TYPE.TOP: if fx == Chan_FX_TYPE.TOP:
#print(klc.end_time, fx, klc.pre.high, klc.high, klc.pre.start_time, klc.pre.end_time) #print(klc.end_time, fx, klc.pre.high, klc.high, klc.pre.start_time, klc.pre.end_time)
if last_top: if last_top:
@@ -1069,7 +1089,7 @@ class TF_DF():
klc.set_bi(bi_list[-1]) klc.set_bi(bi_list[-1])
# 初始化的时候用,其他时间不用 # 初始化的时候用,其他时间不用
else: else:
klc.set_fx(Chan_FX_TYPE.TT) #klc.set_fx(Chan_FX_TYPE.TT)
#print(klc.start_time, klc.fx, "二类卖点Sell 2") #print(klc.start_time, klc.fx, "二类卖点Sell 2")
bi_list[-1].add_klc(klc) bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1]) klc.set_bi(bi_list[-1])
@@ -1184,7 +1204,7 @@ class TF_DF():
klc.set_bi(bi_list[-1]) klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, "笔买点Buy 3") #print(klc.start_time, klc.fx, "笔买点Buy 3")
else: else:
klc.set_fx(Chan_FX_TYPE.BB) #klc.set_fx(Chan_FX_TYPE.BB)
#klc.set_state('-20') #klc.set_state('-20')
#print(klc.start_time, klc.fx, "二类买点Buy 2") #print(klc.start_time, klc.fx, "二类买点Buy 2")
bi_list[-1].add_klc(klc) bi_list[-1].add_klc(klc)
@@ -1248,13 +1268,144 @@ class TF_DF():
if (last_top.low < klc.pre.high or last_top.low < klc.next.high) and (klc.index - last_top.index < 10): if (last_top.low < klc.pre.high or last_top.low < klc.next.high) and (klc.index - last_top.index < 10):
return False return False
return True return True
# 建议用这种方式生成笔中枢
def cal_bi_zs(self, seg_list): def cal_bi_zs(self, seg_list):
bi_zs_list = [] bi_zs_list = []
for seg in seg_list: for seg in seg_list:
zs_list = seg.cal_bi_zs() zs_list = seg.cal_bi_zs()
if len(zs_list) > 0: if len(zs_list) > 0:
bi_zs_list.append(zs_list) 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.end_klc:
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.end_klc:
last_zs.set_end_bi(last_bi_of_zs, last_bi_of_zs.sure_time)
return bi_zs_list return bi_zs_list
def find_all_bsp(self, bi_list, bi_zs_list): def find_all_bsp(self, bi_list, bi_zs_list):
""" """
@@ -1570,9 +1721,9 @@ class TF_DF():
bsp_list.append(bsp) bsp_list.append(bsp)
return bsp_list return bsp_list
def calculate_zs(self, bi_list, seg_list): def calculate_seg_zs(self, seg_list):
return self.get_zs_list(bi_list, seg_list) return self.get_seg_zs_list(seg_list)
def get_zs_list(self, bi_list, seg_list): def get_seg_zs_list(self, seg_list):
""" """
根据缠论线段中枢定义计算中枢 根据缠论线段中枢定义计算中枢
从第4根线段开始(索引3),每3根线段为一组检查 从第4根线段开始(索引3),每3根线段为一组检查
@@ -1639,12 +1790,29 @@ class TF_DF():
# 不重叠下移 # 不重叠下移
zs_dir = Chan_ZS_DIR.DOWN 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) 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: 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 start_idx += 1
continue 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) gg = max(seg1.high, seg2.high, seg3.high)
dd = min(seg1.low, seg2.low, seg3.low) dd = min(seg1.low, seg2.low, seg3.low)
@@ -1659,6 +1827,7 @@ class TF_DF():
# 若第二线段与 [zd,zg] 重叠(如离开后回抽回到前中枢)则并入扩展 # 若第二线段与 [zd,zg] 重叠(如离开后回抽回到前中枢)则并入扩展
added_after_leave = [] added_after_leave = []
leave_index = start_idx + 4 leave_index = start_idx + 4
is_break = False
while leave_index < len(seg_list): while leave_index < len(seg_list):
s = seg_list[leave_index] s = seg_list[leave_index]
if not s.is_sure: if not s.is_sure:
@@ -1668,9 +1837,24 @@ class TF_DF():
if sh >= zs.zd and sl <= zs.zg: if sh >= zs.zd and sl <= zs.zg:
added_after_leave.append(s.pre) added_after_leave.append(s.pre)
added_after_leave.append(s) added_after_leave.append(s)
else:
break
leave_index += 2 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
if is_break:
break
if added_after_leave: if added_after_leave:
#print(len(added_after_leave)) #print(len(added_after_leave))
segs_for_zs = list(zs.seg_list) + list(added_after_leave) segs_for_zs = list(zs.seg_list) + list(added_after_leave)
@@ -1680,16 +1864,12 @@ class TF_DF():
zs.set_dd(min(seg_lows)) zs.set_dd(min(seg_lows))
zs.seg_list = segs_for_zs zs.seg_list = segs_for_zs
seg = segs_for_zs[-1] seg = segs_for_zs[-1]
if seg.end_bi: #if seg.end_bi:
zs.set_end_klc(seg.end_bi.end_klc, seg.sure_time, 0, seg) #zs.set_end_klc(seg.end_bi.end_klc, seg.sure_time, 0, seg)
zs.set_end_seg(seg) #zs.set_end_seg(seg)
zs.is_sure = True #zs.is_sure = True
start_idx = start_idx + len(added_after_leave) start_idx = start_idx + len(added_after_leave)
else: if last_zs and last_zs.index != zs.index:
zs.set_end_klc(seg3.end_bi.end_klc, seg3.sure_time, 0, seg3)
zs.set_end_seg(seg3)
zs.is_sure = True
if last_zs:
last_zs.set_next(zs) last_zs.set_next(zs)
zs.set_pre(last_zs) zs.set_pre(last_zs)
@@ -1700,6 +1880,7 @@ class TF_DF():
start_idx += 4 start_idx += 4
if last_zs: if last_zs:
last_zs.is_sure = seg_list[-1].is_sure last_zs.is_sure = seg_list[-1].is_sure
"""
# 处理最后一个未确认的中枢 - 不自动扩展,保持未完成状态 # 处理最后一个未确认的中枢 - 不自动扩展,保持未完成状态
if last_zs and not last_zs.is_sure: if last_zs and not last_zs.is_sure:
# 获取中枢最后一个线段的索引 # 获取中枢最后一个线段的索引
@@ -1731,10 +1912,11 @@ class TF_DF():
else: else:
# 有离开,确认中枢 # 有离开,确认中枢
if last_seg_of_zs.end_bi: 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_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.set_end_seg(last_seg_of_zs)
last_zs.is_sure = True last_zs.is_sure = True
"""
return zs_list return zs_list
def get_big_zs_list(self, zs_list): def get_big_zs_list(self, zs_list):
+70
View File
@@ -0,0 +1,70 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 2,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"dry_run": true,
"db_url": "sqlite:///tradesv3.elliottwave_btc.sqlite",
"dry_run_wallet": 10000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"unfilledtimeout": {
"entry": 5,
"exit": 5,
"exit_timeout_count": 3,
"unit": "minutes"
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "",
"secret": "",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USDT:USDT"
]
},
"pairlists": [
{
"method": "StaticPairList",
"number_assets": 1,
"sort_key": "quoteVolume",
"min_value": 0
}
],
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "freqtrade_secret",
"ws_token": "freqtrade_ws",
"username": "freqtrade",
"password": "freqtrade"
},
"bot_name": "ElliottWaveBTC"
}
+14 -15
View File
@@ -121,6 +121,7 @@ class ChanLun_BTC_30(IStrategy):
#dataframe_5['chanpy_state'] = chanpy_state #dataframe_5['chanpy_state'] = chanpy_state
#state_list = self.chan.get_klu_state(dataframe_3) #state_list = self.chan.get_klu_state(dataframe_3)
#dataframe_3['state'] = state_list #dataframe_3['state'] = state_list
dataframe['state'] = self.chan.get_klu_state(dataframe)
state_list = self.chan.get_klu_state(dataframe_5) state_list = self.chan.get_klu_state(dataframe_5)
dataframe_5['state'] = state_list dataframe_5['state'] = state_list
#state_list = self.chan.get_klu_state(dataframe_15) #state_list = self.chan.get_klu_state(dataframe_15)
@@ -329,36 +330,34 @@ class ChanLun_BTC_30(IStrategy):
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
shift30 = self.time30 shift30 = self.time30
state30 = 'resample_{}_state'.format(self.get_ticker_indicator()*shift30) state30 = 'resample_{}_state'.format(self.get_ticker_indicator()*shift30)
shift5 = self.time5
state5 = 'resample_{}_state'.format(self.get_ticker_indicator()*shift5)
dataframe.loc[ dataframe.loc[
( (
(dataframe[state30].shift(shift30) == "-10") | (dataframe[state30].shift(shift30) == "11") &
(dataframe[state30].shift(shift30) == "-20") | (dataframe[state5].shift(shift5) == "-10") &
(dataframe[state30].shift(shift30) == "-30") (dataframe['state'].shift(1) == "11")
), ),
['enter_long', 'enter_tag']] = (1, 'long_10') ['enter_long', 'enter_tag']] = (1, 'long')
dataframe.loc[ dataframe.loc[
( (
(dataframe[state30].shift(shift30) == "10") | (dataframe[state30].shift(shift30) == "-11") &
(dataframe[state30].shift(shift30) == "20") | (dataframe[state5].shift(shift5) == "10") &
(dataframe[state30].shift(shift30) == "30") (dataframe['state'].shift(1) == "-11")
), ),
['enter_short', 'enter_tag']] = (1, 'short_10') ['enter_short', 'enter_tag']] = (1, 'short')
return dataframe return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
shift30 = self.time30 shift30 = self.time5
state30 = 'resample_{}_state'.format(self.get_ticker_indicator()*shift30) state30 = 'resample_{}_state'.format(self.get_ticker_indicator()*shift30)
dataframe.loc[ dataframe.loc[
( (
(dataframe[state30].shift(shift30) == "10") | (dataframe[state30].shift(shift30) == "10")
(dataframe[state30].shift(shift30) == "20") |
(dataframe[state30].shift(shift30) == "30")
), ),
['exit_long', 'exit_tag']] = (1, 'long_close_30') ['exit_long', 'exit_tag']] = (1, 'long_close_30')
dataframe.loc[ dataframe.loc[
( (
(dataframe[state30].shift(shift30) == "-10") | (dataframe[state30].shift(shift30) == "-10")
(dataframe[state30].shift(shift30) == "-20") |
(dataframe[state30].shift(shift30) == "-30")
), ),
['exit_short', 'exit_tag']] = (1, 'short_close_30') ['exit_short', 'exit_tag']] = (1, 'short_close_30')
return dataframe return dataframe
+240
View File
@@ -0,0 +1,240 @@
"""
ChanLun Wave Strategy for BTC Perpetual Futures
基于缠论波浪策略 V8
核心逻辑:
- 使用Chan库计算KLC-based缠论分型
- 只做空头在下跌趋势中做空反弹
- 顶分型确认 + RSI > 55 + 趋势确认 做空
- 空头出场底分型 + RSI < 40
策略设计:
- 短周期(5m)为主长周期(1h/1d)确认趋势
- 使用Chan库KLC分型确认入场
- RSI > 55 做空条件RSI < 40 出场条件
- 不做多头下跌趋势中做多风险太大
作者: AI Assistant
"""
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from ChanLun import ChanLun
from freqtrade.strategy import IStrategy
from pandas import DataFrame
import pandas as pd
import numpy as np
import talib.abstract as ta
import logging
from datetime import datetime
from typing import Optional
logger = logging.getLogger(__name__)
class ElliottWaveBTCStrategy(IStrategy):
INTERFACE_VERSION = 3
can_short = True
stoploss = -0.02
minimal_roi = {
"0": 0.06,
"120": 0.03,
"360": 0.01
}
trailing_stop = True
trailing_stop_positive = 0.02
trailing_stop_positive_offset = 0.08
trailing_only_offset_is_reached = True
startup_candle_count = 500
position_adjustment_enable = False
pair = 'BTC/USDT:USDT'
timeframe = '5m'
chan = ChanLun()
def informative_pairs(self):
return [
(self.pair, '5m'),
(self.pair, '1h'),
(self.pair, '1d'),
]
def _add_indicators(self, df: DataFrame) -> DataFrame:
df['ema20'] = ta.EMA(df, timeperiod=20)
df['ema50'] = ta.EMA(df, timeperiod=50)
df['ema200'] = ta.EMA(df, timeperiod=200)
df['rsi'] = ta.RSI(df, timeperiod=14)
df['atr'] = ta.ATR(df, timeperiod=14)
macd = ta.MACD(df, fastperiod=12, slowperiod=26, signalperiod=9)
df['macd'] = macd['macd']
df['macdsignal'] = macd['macdsignal']
df['macdhist'] = macd['macdhist']
# Chan库指标
df['ema52'] = ta.EMA(df, timeperiod=52)
df['ema104'] = ta.EMA(df, timeperiod=104)
df['ema24'] = ta.EMA(df, timeperiod=24)
df['ema26'] = ta.EMA(df, timeperiod=26)
df['volume_sma'] = ta.SMA(df, timeperiod=20)
df['volume_ratio'] = df['volume'] / df['volume_sma']
bb = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0)
df['bb2633upper'] = bb['upperband']
df['bb2633lower'] = bb['lowerband']
df['bb2633middle'] = bb['middleband']
return df
def _get_dataframe(self, timeframe: str) -> DataFrame:
return self.dp.get_pair_dataframe(pair=self.pair, timeframe=timeframe)
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe = self._add_indicators(dataframe)
df_1h = self._get_dataframe('1h')
df_1d = self._get_dataframe('1d')
if len(df_1h) > 0:
df_1h = self._add_indicators(df_1h)
df_1h['chan_state'] = self.chan.get_klu_state(df_1h)
dataframe['1h_ema200'] = df_1h['ema200'].reindex(dataframe.index, method='ffill')
dataframe['1h_trend_up'] = (df_1h['close'] > df_1h['ema200']).reindex(dataframe.index, method='ffill')
dataframe['1h_trend_down'] = (df_1h['close'] < df_1h['ema200']).reindex(dataframe.index, method='ffill')
dataframe['1h_chan_state'] = df_1h['chan_state'].reindex(dataframe.index, method='ffill')
else:
dataframe['1h_ema200'] = dataframe['ema200']
dataframe['1h_trend_up'] = True
dataframe['1h_trend_down'] = True
dataframe['1h_chan_state'] = '00'
if len(df_1d) > 0:
df_1d = self._add_indicators(df_1d)
df_1d['chan_state'] = self.chan.get_klu_state(df_1d)
dataframe['1d_ema200'] = df_1d['ema200'].reindex(dataframe.index, method='ffill')
dataframe['1d_trend_up'] = (df_1d['close'] > df_1d['ema200']).reindex(dataframe.index, method='ffill')
dataframe['1d_trend_down'] = (df_1d['close'] < df_1d['ema200']).reindex(dataframe.index, method='ffill')
dataframe['1d_rsi'] = df_1d['rsi'].reindex(dataframe.index, method='ffill')
dataframe['1d_chan_state'] = df_1d['chan_state'].reindex(dataframe.index, method='ffill')
else:
dataframe['1d_ema200'] = dataframe['ema200']
dataframe['1d_trend_up'] = True
dataframe['1d_trend_down'] = True
dataframe['1d_rsi'] = 50
dataframe['1d_chan_state'] = '00'
# 缠论分型(使用Chan库)
dataframe['chan_state'] = self.chan.get_klu_state(dataframe)
dataframe = self._generate_signals(dataframe)
return dataframe
def _generate_signals(self, df: DataFrame) -> DataFrame:
"""缠论分型 + 趋势确认 - 做空为主"""
n = len(df)
if n < 10:
return df
# 延迟分型状态(避免未来数据)
df['_fx'] = df['chan_state'].shift(1).fillna('00')
# 1h趋势
hourly_down = df['1h_trend_down'].fillna(False)
hourly_up = df['1h_trend_up'].fillna(False)
# MACD方向
macd_cross_down = (df['macd'] < df['macdsignal']) & (df['macd'].shift(1) >= df['macdsignal'].shift(1))
# === 空头信号(下跌趋势中做空)===
# 条件1: 顶分型 + RSI > 55 + 1h下跌趋势
short_cond1 = (
(df['_fx'] == '10') &
(df['rsi'] > 55) &
hourly_down
)
# 条件2: 1h共振顶分型 + RSI > 55
short_cond2 = (
(df['_fx'] == '10') &
(df['1h_chan_state'].fillna('00') == '10') &
(df['rsi'] > 55)
)
# 条件3: 顶分型 + MACD死叉 + RSI > 60
short_cond3 = (
(df['_fx'] == '10') &
macd_cross_down &
(df['rsi'] > 60)
)
df['chan_short'] = (short_cond1 | short_cond2 | short_cond3).astype(bool)
# === 多头信号(仅在1h上涨趋势中做多,且很少)===
# 只在1d和1h同时上涨时才做多,且需要强确认
daily_up = df['1d_trend_up'].fillna(False)
long_cond = (
(df['_fx'] == '-10') &
(df['rsi'] < 30) & # 极低RSI才做多
hourly_up &
daily_up
)
# 1h和1d共振底分型
long_cond2 = (
(df['_fx'] == '-10') &
(df['rsi'] < 30) &
(df['1h_chan_state'].fillna('00') == '-10') &
(df['1d_chan_state'].fillna('00') == '-10')
)
df['chan_long'] = (long_cond | long_cond2).astype(bool)
return df
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['enter_long'] = 0
dataframe['enter_short'] = 0
dataframe['enter_tag'] = ''
if 'chan_long' not in dataframe.columns:
return dataframe
dataframe.loc[dataframe['chan_long'], 'enter_long'] = 1
dataframe.loc[dataframe['chan_long'], 'enter_tag'] = 'chan_long'
dataframe.loc[dataframe['chan_short'], 'enter_short'] = 1
dataframe.loc[dataframe['chan_short'], 'enter_tag'] = 'chan_short'
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['exit_long'] = 0
dataframe['exit_short'] = 0
if len(dataframe) < 2:
return dataframe
if 'chan_state' not in dataframe.columns:
return dataframe
df = dataframe.copy()
df['_fx'] = df['chan_state'].shift(1).fillna('00')
# 空头出场:底分型 + RSI < 40(仅在明显反弹时出场)
dataframe['exit_short'] = ((df['_fx'] == '-10') & (df['rsi'] < 40)).astype(int)
# 多头出场:顶分型 + RSI > 60
dataframe['exit_long'] = ((df['_fx'] == '10') & (df['rsi'] > 60)).astype(int)
return dataframe
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
**kwargs) -> float:
return 2.0
+692
View File
@@ -0,0 +1,692 @@
"""
Elliott Wave Strategy for BTC Perpetual Futures V2
基于真正的艾略特波浪理论 + 缠论分型确认
核心逻辑:
- 艾略特波浪识别: 自动识别1-5浪上涨和A-C浪下跌
- 多时间框架确认: 5m入场1h确认趋势方向1d确认大周期浪型
- 双向交易: 根据波浪位置决定做多或做空
- 动态风险管理: 根据波动率调整仓位和止损
改进点:
1. 实现真正的波浪计数器 (Wave Counter)
2. 斐波那契回撤/扩展用于止盈止损
3. 波浪完成度评估
4. 多周期共振确认
5. 市场情绪过滤
作者: AI Assistant (Optimized)
"""
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from ChanLun import ChanLun
from freqtrade.strategy import IStrategy
from pandas import DataFrame
import pandas as pd
import numpy as np
import talib.abstract as ta
import logging
from datetime import datetime
from typing import Optional, Tuple, List
from dataclasses import dataclass
from enum import Enum
logger = logging.getLogger(__name__)
class WaveType(Enum):
"""波浪类型"""
IMPULSE = "impulse" # 推动浪 (1,2,3,4,5)
CORRECTIVE = "corrective" # 调整浪 (A,B,C)
UNKNOWN = "unknown"
class WavePosition(Enum):
"""当前在波浪中的位置"""
WAVE_1 = 1
WAVE_2 = 2
WAVE_3 = 3
WAVE_4 = 4
WAVE_5 = 5
WAVE_A = 6
WAVE_B = 7
WAVE_C = 8
UNKNOWN = 0
@dataclass
class Wave:
"""波浪数据结构"""
start_idx: int
end_idx: int
start_price: float
end_price: float
wave_num: int # 1-5 or 6-8 (A-C)
wave_type: WaveType
is_complete: bool = False
class ElliottWaveBTCStrategyV2(IStrategy):
"""
艾略特波浪理论策略 V2
结合缠论分型进行波浪确认
"""
INTERFACE_VERSION = 3
can_short = True
# 基础止损止盈 (会根据波动率动态调整)
stoploss = -0.015
minimal_roi = {
"0": 0.08,
"60": 0.05,
"120": 0.03,
"240": 0.015
}
# 追踪止损
trailing_stop = True
trailing_stop_positive = 0.015
trailing_stop_positive_offset = 0.06
trailing_only_offset_is_reached = True
startup_candle_count = 1000
position_adjustment_enable = True
pair = 'BTC/USDT:USDT'
timeframe = '5m'
chan = ChanLun()
# ========== 策略参数 (可优化) ==========
# 波浪检测参数
wave_pivot_lookback = 5 # 波浪枢轴点回看周期
min_wave_bars = 8 # 最小波浪K线数
max_wave_bars = 200 # 最大波浪K线数
# 斐波那契参数
fib_entry_threshold = 0.618 # 入场回撤位
fib_target_1 = 1.272 # 第一目标位
fib_target_2 = 1.618 # 第二目标位
fib_stop_loss = 0.5 # 止损位 (低于/高于0.5)
# RSI参数
rsi_oversold = 35
rsi_overbought = 65
rsi_period = 14
# 波动率参数
atr_period = 14
atr_multiplier_entry = 1.5 # 入场ATR倍数
atr_multiplier_stop = 2.0 # 止损ATR倍数
# 趋势过滤参数
ema_trend_period = 200
trend_filter_strict = True # 严格趋势过滤
# 波浪完成度阈值
wave_completion_threshold = 0.8
def informative_pairs(self):
return [
(self.pair, '5m'),
(self.pair, '1h'),
(self.pair, '4h'),
(self.pair, '1d'),
]
def _add_indicators(self, df: DataFrame) -> DataFrame:
"""添加技术指标"""
# 基础EMA
df['ema20'] = ta.EMA(df, timeperiod=20)
df['ema50'] = ta.EMA(df, timeperiod=50)
df['ema200'] = ta.EMA(df, timeperiod=self.ema_trend_period)
# RSI
df['rsi'] = ta.RSI(df, timeperiod=self.rsi_period)
df['rsi_ma'] = df['rsi'].rolling(window=9).mean()
# ATR
df['atr'] = ta.ATR(df, timeperiod=self.atr_period)
df['atr_percent'] = df['atr'] / df['close'] * 100
# MACD
macd = ta.MACD(df, fastperiod=12, slowperiod=26, signalperiod=9)
df['macd'] = macd['macd']
df['macdsignal'] = macd['macdsignal']
df['macdhist'] = macd['macdhist']
# 布林带
bb = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
df['bb_upper'] = bb['upper']
df['bb_lower'] = bb['lower']
df['bb_middle'] = bb['middle']
df['bb_percent'] = (df['close'] - df['bb_lower']) / (df['bb_upper'] - df['bb_lower'])
# 成交量
df['volume_sma'] = ta.SMA(df, timeperiod=20)
df['volume_ratio'] = df['volume'] / df['volume_sma']
# 波动率
df['volatility'] = df['close'].pct_change().rolling(20).std() * np.sqrt(365 * 24 * 12)
return df
def _detect_pivots(self, df: DataFrame, left_bars: int = 5) -> Tuple[List[int], List[int]]:
"""
检测价格枢轴点 (用于波浪识别) - 无未来数据版本
只使用左侧已确认的数据避免lookahead bias
返回: (高点索引列表, 低点索引列表)
"""
highs = []
lows = []
# 只使用左侧数据确认枢轴点,不使用right_bars避免未来数据
for i in range(left_bars, len(df)):
# 检测高点: 当前点比之前left_bars个bar都高
is_high = True
for j in range(1, left_bars + 1):
if df['high'].iloc[i] <= df['high'].iloc[i - j]:
is_high = False
break
if is_high:
highs.append(i)
# 检测低点: 当前点比之前left_bars个bar都低
is_low = True
for j in range(1, left_bars + 1):
if df['low'].iloc[i] >= df['low'].iloc[i - j]:
is_low = False
break
if is_low:
lows.append(i)
return highs, lows
def _calculate_wave(self, pivots: List[int], df: DataFrame, is_up: bool) -> Optional[Wave]:
"""
计算单个波浪的属性
"""
if len(pivots) < 2:
return None
start_idx = pivots[0]
end_idx = pivots[-1]
start_price = df['low'].iloc[start_idx] if is_up else df['high'].iloc[start_idx]
end_price = df['high'].iloc[end_idx] if is_up else df['low'].iloc[end_idx]
wave_height = abs(end_price - start_price)
wave_bars = end_idx - start_idx
if wave_bars < self.min_wave_bars or wave_bars > self.max_wave_bars:
return None
return Wave(
start_idx=start_idx,
end_idx=end_idx,
start_price=start_price,
end_price=end_price,
wave_num=0, # 稍后分配
wave_type=WaveType.UNKNOWN
)
def _identify_elliott_waves(self, df: DataFrame) -> List[Wave]:
"""
识别艾略特波浪结构
简化版基于枢轴点识别5浪上涨或3浪下跌
"""
highs, lows = self._detect_pivots(df, self.wave_pivot_lookback)
waves = []
all_pivots = sorted(highs + lows)
if len(all_pivots) < 4:
return waves
# 简化波浪识别:基于价格走势判断当前处于哪个浪
recent_pivots = all_pivots[-8:] # 取最近8个枢轴点
for i in range(0, len(recent_pivots) - 1, 2):
if i + 1 >= len(recent_pivots):
break
start_idx = recent_pivots[i]
end_idx = recent_pivots[i + 1]
# 确定是上涨还是下跌浪
price_change = df['close'].iloc[end_idx] - df['close'].iloc[start_idx]
is_up = price_change > 0
wave = Wave(
start_idx=start_idx,
end_idx=end_idx,
start_price=df['close'].iloc[start_idx],
end_price=df['close'].iloc[end_idx],
wave_num=(i // 2) + 1,
wave_type=WaveType.IMPULSE if is_up else WaveType.CORRECTIVE,
is_complete=True
)
waves.append(wave)
return waves
def _get_current_wave_position(self, df: DataFrame, waves: List[Wave]) -> WavePosition:
"""
判断当前处于波浪的哪个位置
"""
if not waves:
return WavePosition.UNKNOWN
last_wave = waves[-1]
current_price = df['close'].iloc[-1]
# 基于最后一浪的特征判断位置
if last_wave.wave_num == 1:
return WavePosition.WAVE_2 if current_price < last_wave.end_price else WavePosition.WAVE_1
elif last_wave.wave_num == 2:
return WavePosition.WAVE_3 if current_price > last_wave.end_price else WavePosition.WAVE_2
elif last_wave.wave_num == 3:
return WavePosition.WAVE_4 if current_price < last_wave.end_price else WavePosition.WAVE_3
elif last_wave.wave_num == 4:
return WavePosition.WAVE_5 if current_price > last_wave.end_price else WavePosition.WAVE_4
elif last_wave.wave_num >= 5:
return WavePosition.WAVE_A
return WavePosition.UNKNOWN
def _calculate_fibonacci_levels(self, wave: Wave) -> dict:
"""
计算斐波那契回撤和扩展位
"""
if wave is None:
return {}
price_range = abs(wave.end_price - wave.start_price)
is_up = wave.end_price > wave.start_price
if is_up:
levels = {
'0.0': wave.end_price,
'0.236': wave.end_price - price_range * 0.236,
'0.382': wave.end_price - price_range * 0.382,
'0.5': wave.end_price - price_range * 0.5,
'0.618': wave.end_price - price_range * 0.618,
'0.786': wave.end_price - price_range * 0.786,
'1.0': wave.start_price,
'1.272': wave.end_price + price_range * 0.272,
'1.618': wave.end_price + price_range * 0.618,
}
else:
levels = {
'0.0': wave.end_price,
'0.236': wave.end_price + price_range * 0.236,
'0.382': wave.end_price + price_range * 0.382,
'0.5': wave.end_price + price_range * 0.5,
'0.618': wave.end_price + price_range * 0.618,
'0.786': wave.end_price + price_range * 0.786,
'1.0': wave.start_price,
'1.272': wave.end_price - price_range * 0.272,
'1.618': wave.end_price - price_range * 0.618,
}
return levels
def _get_dataframe(self, timeframe: str) -> DataFrame:
return self.dp.get_pair_dataframe(pair=self.pair, timeframe=timeframe)
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""计算所有指标"""
dataframe = self._add_indicators(dataframe)
# 获取多时间框架数据
df_1h = self._get_dataframe('1h')
df_4h = self._get_dataframe('4h')
df_1d = self._get_dataframe('1d')
# 1小时指标
if len(df_1h) > 50:
df_1h = self._add_indicators(df_1h)
dataframe['1h_ema200'] = df_1h['ema200'].reindex(dataframe.index, method='ffill')
dataframe['1h_trend'] = np.where(dataframe['close'] > dataframe['1h_ema200'], 1, -1)
dataframe['1h_rsi'] = df_1h['rsi'].reindex(dataframe.index, method='ffill')
dataframe['1h_atr'] = df_1h['atr'].reindex(dataframe.index, method='ffill')
# 1h波浪识别
waves_1h = self._identify_elliott_waves(df_1h)
dataframe['1h_wave_position'] = self._get_current_wave_position(df_1h, waves_1h).value
else:
dataframe['1h_trend'] = 0
dataframe['1h_rsi'] = 50
dataframe['1h_wave_position'] = 0
# 4小时指标
if len(df_4h) > 50:
df_4h = self._add_indicators(df_4h)
dataframe['4h_ema200'] = df_4h['ema200'].reindex(dataframe.index, method='ffill')
dataframe['4h_trend'] = np.where(dataframe['close'] > dataframe['4h_ema200'], 1, -1)
else:
dataframe['4h_trend'] = 0
# 日线指标
if len(df_1d) > 50:
df_1d = self._add_indicators(df_1d)
dataframe['1d_ema200'] = df_1d['ema200'].reindex(dataframe.index, method='ffill')
dataframe['1d_trend'] = np.where(dataframe['close'] > dataframe['1d_ema200'], 1, -1)
dataframe['1d_rsi'] = df_1d['rsi'].reindex(dataframe.index, method='ffill')
# 日线波浪 (大趋势)
waves_1d = self._identify_elliott_waves(df_1d)
dataframe['1d_wave_position'] = self._get_current_wave_position(df_1d, waves_1d).value
else:
dataframe['1d_trend'] = 0
dataframe['1d_rsi'] = 50
dataframe['1d_wave_position'] = 0
# 当前时间框架波浪识别
waves = self._identify_elliott_waves(dataframe)
dataframe['wave_position'] = self._get_current_wave_position(dataframe, waves).value
# 缠论分型
dataframe['chan_state'] = self.chan.get_klu_state(dataframe)
# 生成交易信号
dataframe = self._generate_signals(dataframe, waves)
return dataframe
def _generate_signals(self, df: DataFrame, waves: List[Wave]) -> DataFrame:
"""
基于艾略特波浪理论生成交易信号
"""
n = len(df)
if n < 50:
return df
# 获取当前波浪位置
current_wave = self._get_current_wave_position(df, waves)
# 延迟分型 (避免未来数据)
df['_fx'] = df['chan_state'].shift(1).fillna('00')
# 趋势方向
trend_up = df['1h_trend'] > 0
trend_down = df['1h_trend'] < 0
trend_aligned_daily = df['1d_trend'] == df['1h_trend']
# RSI条件
rsi_oversold = df['rsi'] < self.rsi_oversold
rsi_overbought = df['rsi'] > self.rsi_overbought
rsi_divergence_long = (df['rsi'] > df['rsi'].shift(5)) & (df['close'] < df['close'].shift(5))
rsi_divergence_short = (df['rsi'] < df['rsi'].shift(5)) & (df['close'] > df['close'].shift(5))
# 波动率过滤
low_volatility = df['atr_percent'] < df['atr_percent'].rolling(50).mean() * 0.8
high_volatility = df['atr_percent'] > df['atr_percent'].rolling(50).mean() * 1.5
# ========== 多头信号 ==========
long_conditions = []
# 浪2回调做多 (最佳入场点)
# 条件: 浪2位置 + 底分型 + RSI超卖 + 趋势向上
long_cond_wave2 = (
(df['wave_position'] == WavePosition.WAVE_2.value) |
(df['1h_wave_position'] == WavePosition.WAVE_2.value)
) & (
(df['_fx'] == '-10') |
((df['close'] > df['ema20']) & (df['ema20'] > df['ema50']))
) & rsi_oversold & trend_up
long_conditions.append(('wave2', long_cond_wave2))
# 浪4回调做多 (谨慎入场)
long_cond_wave4 = (
(df['wave_position'] == WavePosition.WAVE_4.value) |
(df['1h_wave_position'] == WavePosition.WAVE_4.value)
) & (df['_fx'] == '-10') & rsi_oversold & trend_up & (
df['rsi_divergence_long'] if 'rsi_divergence_long' in df.columns else True
)
long_conditions.append(('wave4', long_cond_wave4))
# C浪结束做多 (趋势反转)
long_cond_wave_c = (
(df['wave_position'] == WavePosition.WAVE_C.value) |
(df['1h_wave_position'] == WavePosition.WAVE_C.value)
) & (df['_fx'] == '-10') & rsi_oversold & (
df['volume_ratio'] > 1.5 # 放量确认
)
long_conditions.append(('wave_c', long_cond_wave_c))
# 强势突破做多
long_cond_breakout = (
(df['close'] > df['bb_upper']) &
(df['volume_ratio'] > 2.0) &
trend_up &
(df['macdhist'] > 0) &
(df['1h_wave_position'].isin([WavePosition.WAVE_3.value, WavePosition.WAVE_5.value]))
)
long_conditions.append(('breakout', long_cond_breakout))
# 合并多头信号
df['elliott_long'] = False
for name, cond in long_conditions:
df[f'long_{name}'] = cond & ~low_volatility # 避免低波动时入场
df['elliott_long'] |= df[f'long_{name}']
# ========== 空头信号 ==========
short_conditions = []
# 浪2回调做空 (下跌趋势)
short_cond_wave2 = (
(df['wave_position'] == WavePosition.WAVE_2.value) |
(df['1h_wave_position'] == WavePosition.WAVE_2.value)
) & (
(df['_fx'] == '10') |
((df['close'] < df['ema20']) & (df['ema20'] < df['ema50']))
) & rsi_overbought & trend_down
short_conditions.append(('wave2', short_cond_wave2))
# 浪4回调做空 (谨慎)
short_cond_wave4 = (
(df['wave_position'] == WavePosition.WAVE_4.value) |
(df['1h_wave_position'] == WavePosition.WAVE_4.value)
) & (df['_fx'] == '10') & rsi_overbought & trend_down
short_conditions.append(('wave4', short_cond_wave4))
# 浪5结束做空 (趋势反转)
short_cond_wave5 = (
(df['wave_position'] == WavePosition.WAVE_5.value) |
(df['1h_wave_position'] == WavePosition.WAVE_5.value)
) & (df['_fx'] == '10') & rsi_overbought & (
df['volume_ratio'] > 1.5
)
short_conditions.append(('wave5', short_cond_wave5))
# B浪反弹做空 (继续下跌)
short_cond_wave_b = (
(df['wave_position'] == WavePosition.WAVE_B.value) |
(df['1h_wave_position'] == WavePosition.WAVE_B.value)
) & (df['_fx'] == '10') & rsi_overbought & trend_down
short_conditions.append(('wave_b', short_cond_wave_b))
# 强势跌破做空
short_cond_breakdown = (
(df['close'] < df['bb_lower']) &
(df['volume_ratio'] > 2.0) &
trend_down &
(df['macdhist'] < 0) &
(df['1h_wave_position'].isin([WavePosition.WAVE_3.value, WavePosition.WAVE_C.value]))
)
short_conditions.append(('breakdown', short_cond_breakdown))
# 合并空头信号
df['elliott_short'] = False
for name, cond in short_conditions:
df[f'short_{name}'] = cond & ~low_volatility
df['elliott_short'] |= df[f'short_{name}']
# 强趋势过滤
if self.trend_filter_strict:
df['elliott_long'] &= trend_up | (df['1d_trend'] > 0)
df['elliott_short'] &= trend_down | (df['1d_trend'] < 0)
# 避免高波动时期入场
df['elliott_long'] &= ~high_volatility
df['elliott_short'] &= ~high_volatility
return df
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""入场信号"""
dataframe['enter_long'] = 0
dataframe['enter_short'] = 0
dataframe['enter_tag'] = ''
if 'elliott_long' not in dataframe.columns:
return dataframe
# 多头入场
long_mask = dataframe['elliott_long'].fillna(False)
dataframe.loc[long_mask, 'enter_long'] = 1
# 标记入场类型
for col in dataframe.columns:
if col.startswith('long_') and col != 'elliott_long':
mask = dataframe[col].fillna(False) & (dataframe['enter_long'] == 1)
dataframe.loc[mask, 'enter_tag'] = col.replace('long_', 'elliott_')
# 空头入场
short_mask = dataframe['elliott_short'].fillna(False)
dataframe.loc[short_mask, 'enter_short'] = 1
for col in dataframe.columns:
if col.startswith('short_') and col != 'elliott_short':
mask = dataframe[col].fillna(False) & (dataframe['enter_short'] == 1)
dataframe.loc[mask, 'enter_tag'] = col.replace('short_', 'elliott_')
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""出场信号"""
dataframe['exit_long'] = 0
dataframe['exit_short'] = 0
if len(dataframe) < 2:
return dataframe
df = dataframe.copy()
df['_fx'] = df['chan_state'].shift(1).fillna('00')
# 多头出场条件
exit_long_cond = (
# 顶分型出场
(df['_fx'] == '10') |
# RSI超买
(df['rsi'] > 75) |
# 跌破EMA20
(df['close'] < df['ema20']) & (df['close'].shift(1) > df['ema20'].shift(1)) |
# MACD死叉
(df['macd'] < df['macdsignal']) & (df['macd'].shift(1) > df['macdsignal'].shift(1))
)
# 波浪位置出场
exit_long_wave = df['wave_position'].isin([
WavePosition.WAVE_5.value,
WavePosition.WAVE_C.value
])
dataframe['exit_long'] = (exit_long_cond | exit_long_wave).astype(int)
# 空头出场条件
exit_short_cond = (
# 底分型出场
(df['_fx'] == '-10') |
# RSI超卖
(df['rsi'] < 25) |
# 突破EMA20
(df['close'] > df['ema20']) & (df['close'].shift(1) < df['ema20'].shift(1)) |
# MACD金叉
(df['macd'] > df['macdsignal']) & (df['macd'].shift(1) < df['macdsignal'].shift(1))
)
# 波浪位置出场
exit_short_wave = df['wave_position'].isin([
WavePosition.WAVE_C.value,
WavePosition.WAVE_5.value
])
dataframe['exit_short'] = (exit_short_cond | exit_short_wave).astype(int)
return dataframe
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
"""
动态止损基于ATR和波浪位置调整
"""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if len(dataframe) < 2:
return self.stoploss
current_candle = dataframe.iloc[-1]
atr = current_candle['atr_percent']
# 基于ATR的动态止损
dynamic_stop = -atr * self.atr_multiplier_stop / 100
# 根据盈利情况收紧止损
if current_profit > 0.03: # 盈利3%后收紧止损
return max(dynamic_stop, -0.01)
elif current_profit > 0.05: # 盈利5%后更紧
return max(dynamic_stop, -0.005)
return max(dynamic_stop, self.stoploss)
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
**kwargs) -> float:
"""
动态杠杆根据波动率调整
"""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if len(dataframe) < 2:
return 2.0
current_candle = dataframe.iloc[-1]
volatility = current_candle['atr_percent']
# 低波动时提高杠杆,高波动时降低杠杆
if volatility < 0.5:
return 3.0
elif volatility < 1.0:
return 2.0
elif volatility < 2.0:
return 1.5
else:
return 1.0
def adjust_trade_position(self, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float,
min_stake: Optional[float], max_stake: float,
current_entry_rate: float, current_exit_rate: float,
current_entry_profit: float, current_exit_profit: float,
**kwargs) -> Optional[float]:
"""
仓位调整金字塔加仓
"""
if current_profit < -0.01: # 亏损时不加仓
return None
if current_profit > 0.02 and current_profit < 0.03: # 盈利2-3%时加仓
return min_stake * 0.5 if min_stake else None
return None
+12 -13
View File
@@ -33,7 +33,8 @@ class TRADE_POINT_TYPE:
SELL3 = -3 # 三类卖点 SELL3 = -3 # 三类卖点
app = Flask(__name__) app = Flask(__name__)
macd_factor = 2 *6
smooth_factor = 1
# 初始化交易所 # 初始化交易所
exchange = ccxt.binance({ exchange = ccxt.binance({
'enableRateLimit': True, 'enableRateLimit': True,
@@ -421,9 +422,9 @@ def get_a_stock_kl_data(symbol, timeframe, limit=100000, start_time=None, end_ti
return None return None
def add_indicators(df): def add_indicators(df):
fast = 12*1 fast = 12 * macd_factor
slow = 26*1 slow = 26 * macd_factor
period = 9*1 period = 9 * smooth_factor
macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period) macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period)
df['macd'] = macd['macd'] df['macd'] = macd['macd']
@@ -509,10 +510,10 @@ def add_indicators(df):
def calculate_macd(df): def calculate_macd(df):
"""计算MACD指标""" """计算MACD指标"""
exp1 = df['close'].ewm(span=12, adjust=False).mean() exp1 = df['close'].ewm(span=12*macd_factor, adjust=False).mean()
exp2 = df['close'].ewm(span=26, adjust=False).mean() exp2 = df['close'].ewm(span=26*macd_factor, adjust=False).mean()
macd = exp1 - exp2 macd = exp1 - exp2
signal = macd.ewm(span=9, adjust=False).mean() signal = macd.ewm(span=9 * smooth_factor, adjust=False).mean()
histogram = macd - signal histogram = macd - signal
return { return {
@@ -534,13 +535,11 @@ def analyze_chan(df, symbol=None, timeframe=None):
#for index in range(0, 10): #for index in range(0, 10):
#print(bi_list[index].start_time, bi_list[index].start_klc.end_time, bi_list[index].dir) #print(bi_list[index].start_time, bi_list[index].start_klc.end_time, bi_list[index].dir)
seg_list = chan.get_seg_list(bi_list) seg_list = chan.get_seg_list(bi_list)
zs_list = chan.calculate_zs(bi_list, seg_list) zs_list = chan.calculate_seg_zs(seg_list)
# 计算笔中枢(BI中枢)并拍平成列表 # 计算笔中枢(BI中枢)并拍平成列表
try:
bi_zs_nested = chan.cal_bi_zs(seg_list) #bi_zs_list = chan.cal_bi_zs_list(bi_list)
bi_zs_list = [zs for group in bi_zs_nested for zs in (group or [])] if bi_zs_nested else [] bi_zs_list = chan.cal_bi_zs(seg_list)
except Exception:
bi_zs_list = []
bsp_list = [] bsp_list = []
if len(bi_zs_list) > 0: if len(bi_zs_list) > 0:
bsp_list = chan.find_all_bsp(bi_list, bi_zs_list) bsp_list = chan.find_all_bsp(bi_list, bi_zs_list)
-4
View File
@@ -768,10 +768,6 @@
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<div class="header">
<h5 class="my-1">缠论分析系统</h5>
</div>
<div class="controls"> <div class="controls">
<div class="row g-1 d-flex align-items-end"> <div class="row g-1 d-flex align-items-end">
<div class="col-md-2"> <div class="col-md-2">
+1
View File
@@ -81,3 +81,4 @@
注意,这里必须提醒一句,就是这在以前也曾说过,就是,如果线段中,最高或最低点不是线段的端点,那么,在任何以线段为基础的分析中,例如把线段为基础构成最小级别的中枢等,都可以把该线段标准化为最高低点都在端点。因为, 在以线段为基础的分析中,都把线段当成一个没有内部 结构的基本部件,所以,只需要关心这线段的实际区间就可以,这样就可以只看其高低点。 注意,这里必须提醒一句,就是这在以前也曾说过,就是,如果线段中,最高或最低点不是线段的端点,那么,在任何以线段为基础的分析中,例如把线段为基础构成最小级别的中枢等,都可以把该线段标准化为最高低点都在端点。因为, 在以线段为基础的分析中,都把线段当成一个没有内部 结构的基本部件,所以,只需要关心这线段的实际区间就可以,这样就可以只看其高低点。
经过标准化处理后,所有向上线段都是以最低点开始最高点结束,向下线段都是以最高点开始最低点结束,这样,所以线段的连接,就形成一条延续不断、首尾相连的折线,这样,复杂的图形,就会十分地标准化,也为后面的中枢、走势类型等分析提供了最标准且基础的部件。 经过标准化处理后,所有向上线段都是以最低点开始最高点结束,向下线段都是以最高点开始最低点结束,这样,所以线段的连接,就形成一条延续不断、首尾相连的折线,这样,复杂的图形,就会十分地标准化,也为后面的中枢、走势类型等分析提供了最标准且基础的部件。
本级别没有背驰的,次级别背驰下跌后形成第三类卖点进而形成本级别的V型反转,小转大