@@ -20,8 +20,19 @@ class ChanBI():
|
||||
self.macd_hist = 0
|
||||
self.macd_div = 0
|
||||
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):
|
||||
self.seg = seg
|
||||
self.seg_index = len(seg.bi_list)-1
|
||||
def set_macdhist(self, macd_hist):
|
||||
self.macd_hist = macd_hist
|
||||
def set_macd_div(self, macd_div):
|
||||
@@ -66,7 +77,17 @@ class ChanBI():
|
||||
self.end_klc = klc
|
||||
self.set_is_sure(True, sure_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)
|
||||
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):
|
||||
self.is_sure = is_sure
|
||||
self.sure_time = time
|
||||
|
||||
+9
-3
@@ -21,11 +21,13 @@ class ChanBIZS():
|
||||
self.sure_time = None
|
||||
self.end_klc = None
|
||||
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.set_end_time(end_bi.end_klc.end_time)
|
||||
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)
|
||||
def set_end_time(self, end_time):
|
||||
self.end_time = end_time
|
||||
@@ -40,8 +42,12 @@ class ChanBIZS():
|
||||
def add_bi(self, bi: ChanBI):
|
||||
if bi:
|
||||
self.bi_list.append(bi)
|
||||
bi.set_bi_zs(self)
|
||||
self.classify_zs()
|
||||
|
||||
def set_pre(self, pre):
|
||||
self.pre = pre
|
||||
def set_next(self, next):
|
||||
self.next = next
|
||||
def classify_zs(self):
|
||||
"""
|
||||
根据中枢内笔的高低点变化趋势,对中枢进行分类
|
||||
|
||||
+9
-25
@@ -167,6 +167,10 @@ class Chan_FX_TYPE(Enum):
|
||||
BB = auto()
|
||||
PTOP = auto()
|
||||
PBOTTOM = auto()
|
||||
class Chan_FX(Enum):
|
||||
CONTINUATION = auto()
|
||||
REVERSAL = auto()
|
||||
UNKNOWN = auto()
|
||||
class Chan_PRICE_TREND(Enum):
|
||||
UP = auto()
|
||||
DOWN = auto()
|
||||
@@ -357,28 +361,8 @@ class Chan_DATA_FIELD:
|
||||
|
||||
class Chan_KLC_STATE:
|
||||
"""笔当下状态(缠论笔定理)。任意时刻必属其一。"""
|
||||
FX = auto() # 分型构造中(未确认顶/底)
|
||||
BI = auto() # 笔延伸中(分型已确认,笔在延伸)
|
||||
UP = auto() # 顶分型构造中 (1,0):向上笔末端
|
||||
DOWN = auto() # 底分型构造中 (-1,0):向下笔末端
|
||||
|
||||
|
||||
# 笔定理四状态:(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]
|
||||
S10 = "(1, 0)" # 顶分型构造中 (1,0)
|
||||
S_10 = "(-1, 0)" # 底分型构造中 (-1,0)
|
||||
S11 = "(1,1)" # 向上笔延续中
|
||||
S_11 = "(-1,1)" # 向下笔延续中
|
||||
UNKNOWN = "Unknown" # 初始状态
|
||||
|
||||
+52
-7
@@ -1,7 +1,9 @@
|
||||
import copy
|
||||
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 ChanCTime
|
||||
import Chan_FX_Box
|
||||
@@ -22,6 +24,7 @@ class ChanKLC():
|
||||
self.start_klu = klu
|
||||
self.end_klu = None
|
||||
self.state = "00"
|
||||
self.klc_state = Chan_KLC_STATE.UNKNOWN
|
||||
self.open = klu.open
|
||||
self.close = klu.close
|
||||
self.volume = klu.volume
|
||||
@@ -40,8 +43,8 @@ class ChanKLC():
|
||||
self.candle_dir = klu.candle_dir
|
||||
self.range = klu.range
|
||||
self.bb_out = True
|
||||
self.macd = 0
|
||||
self.signal = 0
|
||||
self.macd = klu.macd
|
||||
self.signal = klu.signal
|
||||
self.state = Chan_MACD_STATE.UNKNOWN
|
||||
self.continue_div = False
|
||||
self.separate_div = False
|
||||
@@ -72,6 +75,14 @@ class ChanKLC():
|
||||
self.fx_box = None
|
||||
self.in_fx = 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 通用计算方法 ====================
|
||||
|
||||
@staticmethod
|
||||
@@ -314,6 +325,9 @@ class ChanKLC():
|
||||
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)
|
||||
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):
|
||||
#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
|
||||
@@ -327,6 +341,22 @@ class ChanKLC():
|
||||
#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:
|
||||
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):
|
||||
# 每次重算前先清空,避免旧box残留
|
||||
self.fx_box = None
|
||||
@@ -357,7 +387,7 @@ class ChanKLC():
|
||||
self.fx_confirmed = True
|
||||
self.fx_box = Chan_FX_Box.Chan_FX_Box(start_time, end_time, high, low)
|
||||
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 self.in_fx == False and last_top.fx_confirmed == False:
|
||||
pre = last_top.pre
|
||||
@@ -369,7 +399,7 @@ class ChanKLC():
|
||||
else:
|
||||
high = last_top.high
|
||||
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")
|
||||
elif self.in_fx == False and last_bottom.fx_confirmed == False:
|
||||
pre = last_bottom.pre
|
||||
@@ -381,15 +411,30 @@ class ChanKLC():
|
||||
else:
|
||||
high = self.high
|
||||
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")
|
||||
def add_klu(self, 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):
|
||||
self.end_klu = klu
|
||||
self.end_time = klu.time
|
||||
self.close = klu.close
|
||||
|
||||
for klu in self.klu_list:
|
||||
if klu.exception:
|
||||
self.exception = True
|
||||
|
||||
+6
-2
@@ -233,14 +233,18 @@ class ChanKLU:
|
||||
self.near0_return = 0
|
||||
elif self.close > self.ema52 and self.high > self.ema52 and self.low < self.ema52:
|
||||
self.near0_return = 0
|
||||
# 向上穿越EMA52 7
|
||||
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:
|
||||
self.near0_return = 8
|
||||
self.near0_return = 0
|
||||
if self.pre.near0_return == 7:
|
||||
# 向上穿越后的一根价格再EMA52上方 9
|
||||
if self.low > self.ema52 and self.close > self.open:
|
||||
self.near0_return = 9
|
||||
if self.pre.near0_return == 8:
|
||||
# 向下穿越后的一根价格再EMA52下方 10
|
||||
if self.high < self.ema52 and self.close < self.open:
|
||||
self.near0_return = 10
|
||||
# CROSS0 仅以 Signal 穿越零轴判定
|
||||
|
||||
+5
-2
@@ -141,8 +141,8 @@ class ChanLun():
|
||||
return self.tf_df.cal_kl_data(dataframe)
|
||||
def cal_volume_ratio(self, dataframe, window=10):
|
||||
return self.tf_df.cal_volume_ratio(dataframe, window)
|
||||
def calculate_zs(self, bi_list, seg_list):
|
||||
return self.get_zs_list(bi_list, seg_list)
|
||||
def calculate_seg_zs(self, bi_list, seg_list):
|
||||
return self.get_seg_zs_list(bi_list, seg_list)
|
||||
def get_seg_list(self, bi_list):
|
||||
return self.tf_df.get_seg_list(bi_list)
|
||||
def cal_trend(self, klc_list):
|
||||
@@ -163,6 +163,9 @@ class ChanLun():
|
||||
return self.tf_df.get_zs_list(bi_list, seg_list)
|
||||
def cal_bi_zs(self, 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):
|
||||
return Decimal("{:.2f}".format(value))
|
||||
def get_klc_list(self, klu_list):
|
||||
|
||||
+40
-33
@@ -91,41 +91,44 @@ class ChanSEG():
|
||||
zs_list = []
|
||||
if len(self.bi_list) > 3:
|
||||
last_zs = None
|
||||
zs_count = 0
|
||||
if self.dir == Chan_SEG_DIR.UP:
|
||||
for index in range(1, len(self.bi_list)):
|
||||
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 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:
|
||||
zg = min(bi.high, bi.next.high, bi.next.next.high)
|
||||
zd = max(bi.low, bi.next.low, bi.next.next.low)
|
||||
gg = max(bi.high, bi.next.high, bi.next.next.high)
|
||||
dd = min(bi.low, bi.next.low, bi.next.next.low)
|
||||
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, bi2.high, bi3.high)
|
||||
zd = max(bi.low, bi2.low, bi3.low)
|
||||
gg = max(bi.high, bi2.high, bi3.high)
|
||||
dd = min(bi.low, bi2.low, bi3.low)
|
||||
zs = ChanBIZS(bi, len(zs_list), Chan_ZS_DIR.UP)
|
||||
zs.set_zg(zg)
|
||||
zs.set_zd(zd)
|
||||
zs.set_gg(gg)
|
||||
zs.set_dd(dd)
|
||||
zs.add_bi(bi.next)
|
||||
zs.add_bi(bi.next.next)
|
||||
zs.add_bi(bi2)
|
||||
zs.add_bi(bi3)
|
||||
zs_list.append(zs)
|
||||
last_zs = zs
|
||||
else:
|
||||
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:
|
||||
last_zs.set_end_bi(last_zs.bi_list[-1], bi)
|
||||
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:
|
||||
zg = min(bi.high, bi.next.high, bi.next.next.high)
|
||||
zd = max(bi.low, bi.next.low, bi.next.next.low)
|
||||
gg = max(bi.high, bi.next.high, bi.next.next.high)
|
||||
dd = min(bi.low, bi.next.low, bi.next.next.low)
|
||||
last_zs.set_end_bi(last_zs.bi_list[-1], last_zs.bi_list[-1].sure_time)
|
||||
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, bi2.high, bi3.high)
|
||||
zd = max(bi.low, bi2.low, bi3.low)
|
||||
gg = max(bi.high, bi2.high, bi3.high)
|
||||
dd = min(bi.low, bi2.low, bi3.low)
|
||||
zs = ChanBIZS(bi, len(zs_list), Chan_ZS_DIR.UP)
|
||||
zs.set_zg(zg)
|
||||
zs.set_zd(zd)
|
||||
zs.set_gg(gg)
|
||||
zs.set_dd(dd)
|
||||
zs.add_bi(bi.next)
|
||||
zs.add_bi(bi.next.next)
|
||||
zs.add_bi(bi2)
|
||||
zs.add_bi(bi3)
|
||||
zs_list.append(zs)
|
||||
last_zs = zs
|
||||
else:
|
||||
@@ -133,41 +136,45 @@ class ChanSEG():
|
||||
last_zs.add_bi(bi)
|
||||
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)
|
||||
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:
|
||||
for index in range(1, len(self.bi_list)):
|
||||
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 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:
|
||||
zg = min(bi.high, bi.next.high, bi.next.next.high)
|
||||
zd = max(bi.low, bi.next.low, bi.next.next.low)
|
||||
gg = max(bi.high, bi.next.high, bi.next.next.high)
|
||||
dd = min(bi.low, bi.next.low, bi.next.next.low)
|
||||
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, bi2.high, bi3.high)
|
||||
zd = max(bi.low, bi2.low, bi3.low)
|
||||
gg = max(bi.high, bi2.high, bi3.high)
|
||||
dd = min(bi.low, bi2.low, bi3.low)
|
||||
zs = ChanBIZS(bi, len(zs_list), Chan_ZS_DIR.DOWN)
|
||||
zs.set_zg(zg)
|
||||
zs.set_zd(zd)
|
||||
zs.set_gg(gg)
|
||||
zs.set_dd(dd)
|
||||
zs.add_bi(bi.next)
|
||||
zs.add_bi(bi.next.next)
|
||||
zs.add_bi(bi2)
|
||||
zs.add_bi(bi3)
|
||||
zs_list.append(zs)
|
||||
last_zs = zs
|
||||
else:
|
||||
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:
|
||||
last_zs.set_end_bi(last_zs.bi_list[-1], bi)
|
||||
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:
|
||||
zg = min(bi.high, bi.next.high, bi.next.next.high)
|
||||
zd = max(bi.low, bi.next.low, bi.next.next.low)
|
||||
gg = max(bi.high, bi.next.high, bi.next.next.high)
|
||||
dd = min(bi.low, bi.next.low, bi.next.next.low)
|
||||
last_zs.set_end_bi(last_zs.bi_list[-1], last_zs.bi_list[-1].sure_time)
|
||||
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, bi2.high, bi3.high)
|
||||
zd = max(bi.low, bi2.low, bi3.low)
|
||||
gg = max(bi.high, bi2.high, bi3.high)
|
||||
dd = min(bi.low, bi2.low, bi3.low)
|
||||
zs = ChanBIZS(bi, len(zs_list), Chan_ZS_DIR.DOWN)
|
||||
zs.set_zg(zg)
|
||||
zs.set_zd(zd)
|
||||
zs.set_gg(gg)
|
||||
zs.set_dd(dd)
|
||||
zs.add_bi(bi.next)
|
||||
zs.add_bi(bi.next.next)
|
||||
zs.add_bi(bi2)
|
||||
zs.add_bi(bi3)
|
||||
zs_list.append(zs)
|
||||
last_zs = zs
|
||||
else:
|
||||
@@ -175,7 +182,7 @@ class ChanSEG():
|
||||
last_zs.add_bi(bi)
|
||||
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)
|
||||
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.bi_list[-1].end_time, "end_bi")
|
||||
|
||||
@@ -30,6 +30,7 @@ class ChanZS():
|
||||
self.bi_out_list = []
|
||||
self.bi_out_seg_list = []
|
||||
self.bi_out_seg = None
|
||||
self.is_extended = False
|
||||
def set_last_bi_in(self, last_bi_in):
|
||||
self.last_bi_in = last_bi_in
|
||||
def set_bi_out(self, bi_out, bi_out_seg):
|
||||
@@ -70,7 +71,19 @@ class ChanZS():
|
||||
self.gg = gg
|
||||
def set_dd(self, 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():
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
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
|
||||
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
|
||||
@@ -72,8 +73,8 @@ class TF_DF():
|
||||
return self.klc_list[-2]
|
||||
return None
|
||||
def add_indicators(self, df):
|
||||
fast = 12
|
||||
slow = 26
|
||||
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)
|
||||
@@ -137,32 +138,24 @@ class TF_DF():
|
||||
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.TOP1 or klc.klc_fx_type == Chan_KLC_FX.TOP2:
|
||||
if klc.klc_state == Chan_KLC_STATE.S10:
|
||||
klu_state_list.append("10")
|
||||
#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")
|
||||
#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:
|
||||
if klc.continue_div:
|
||||
if klc.macdhist > 0:
|
||||
klu_state_list.append("20")
|
||||
#print(klc.end_time, "CD", 20)
|
||||
else:
|
||||
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:
|
||||
klu_state_list.append("00")
|
||||
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_ema_state(self, dataframe):
|
||||
@@ -183,6 +176,23 @@ class TF_DF():
|
||||
klu_state_list.append("0")
|
||||
return klu_state_list
|
||||
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.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:
|
||||
@@ -911,22 +921,31 @@ class TF_DF():
|
||||
last_seg_bi = bi_list[i]
|
||||
break
|
||||
"""
|
||||
self.cal_bi_zs(seg_list)
|
||||
#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 and False:
|
||||
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 and False:
|
||||
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, "---")
|
||||
@@ -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)
|
||||
"""
|
||||
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:
|
||||
@@ -1069,7 +1089,7 @@ class TF_DF():
|
||||
klc.set_bi(bi_list[-1])
|
||||
# 初始化的时候用,其他时间不用
|
||||
else:
|
||||
klc.set_fx(Chan_FX_TYPE.TT)
|
||||
#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])
|
||||
@@ -1184,7 +1204,7 @@ class TF_DF():
|
||||
klc.set_bi(bi_list[-1])
|
||||
#print(klc.start_time, klc.fx, "笔买点Buy 3")
|
||||
else:
|
||||
klc.set_fx(Chan_FX_TYPE.BB)
|
||||
#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)
|
||||
@@ -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):
|
||||
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.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
|
||||
def find_all_bsp(self, bi_list, bi_zs_list):
|
||||
"""
|
||||
@@ -1570,9 +1721,9 @@ class TF_DF():
|
||||
bsp_list.append(bsp)
|
||||
|
||||
return bsp_list
|
||||
def calculate_zs(self, bi_list, seg_list):
|
||||
return self.get_zs_list(bi_list, seg_list)
|
||||
def get_zs_list(self, bi_list, seg_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根线段为一组检查
|
||||
@@ -1639,26 +1790,44 @@ class TF_DF():
|
||||
# 不重叠下移
|
||||
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:
|
||||
start_idx += 1
|
||||
continue
|
||||
# 如果新中枢和前一个中枢的中枢区间有重叠,不行成新中枢需要合并两个中枢
|
||||
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)
|
||||
|
||||
# 创建新中枢
|
||||
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]
|
||||
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:
|
||||
@@ -1668,9 +1837,24 @@ class TF_DF():
|
||||
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
|
||||
if is_break:
|
||||
break
|
||||
leave_index += 2
|
||||
if added_after_leave:
|
||||
#print(len(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.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
|
||||
#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)
|
||||
else:
|
||||
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:
|
||||
if last_zs and last_zs.index != zs.index:
|
||||
last_zs.set_next(zs)
|
||||
zs.set_pre(last_zs)
|
||||
|
||||
@@ -1700,6 +1880,7 @@ class TF_DF():
|
||||
start_idx += 4
|
||||
if last_zs:
|
||||
last_zs.is_sure = seg_list[-1].is_sure
|
||||
"""
|
||||
# 处理最后一个未确认的中枢 - 不自动扩展,保持未完成状态
|
||||
if last_zs and not last_zs.is_sure:
|
||||
# 获取中枢最后一个线段的索引
|
||||
@@ -1731,10 +1912,11 @@ class TF_DF():
|
||||
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):
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -121,6 +121,7 @@ class ChanLun_BTC_30(IStrategy):
|
||||
#dataframe_5['chanpy_state'] = chanpy_state
|
||||
#state_list = self.chan.get_klu_state(dataframe_3)
|
||||
#dataframe_3['state'] = state_list
|
||||
dataframe['state'] = self.chan.get_klu_state(dataframe)
|
||||
state_list = self.chan.get_klu_state(dataframe_5)
|
||||
dataframe_5['state'] = state_list
|
||||
#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:
|
||||
shift30 = self.time30
|
||||
state30 = 'resample_{}_state'.format(self.get_ticker_indicator()*shift30)
|
||||
shift5 = self.time5
|
||||
state5 = 'resample_{}_state'.format(self.get_ticker_indicator()*shift5)
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe[state30].shift(shift30) == "-10") |
|
||||
(dataframe[state30].shift(shift30) == "-20") |
|
||||
(dataframe[state30].shift(shift30) == "-30")
|
||||
(dataframe[state30].shift(shift30) == "11") &
|
||||
(dataframe[state5].shift(shift5) == "-10") &
|
||||
(dataframe['state'].shift(1) == "11")
|
||||
),
|
||||
['enter_long', 'enter_tag']] = (1, 'long_10')
|
||||
['enter_long', 'enter_tag']] = (1, 'long')
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe[state30].shift(shift30) == "10") |
|
||||
(dataframe[state30].shift(shift30) == "20") |
|
||||
(dataframe[state30].shift(shift30) == "30")
|
||||
(dataframe[state30].shift(shift30) == "-11") &
|
||||
(dataframe[state5].shift(shift5) == "10") &
|
||||
(dataframe['state'].shift(1) == "-11")
|
||||
),
|
||||
['enter_short', 'enter_tag']] = (1, 'short_10')
|
||||
['enter_short', 'enter_tag']] = (1, 'short')
|
||||
return 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)
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe[state30].shift(shift30) == "10") |
|
||||
(dataframe[state30].shift(shift30) == "20") |
|
||||
(dataframe[state30].shift(shift30) == "30")
|
||||
(dataframe[state30].shift(shift30) == "10")
|
||||
),
|
||||
['exit_long', 'exit_tag']] = (1, 'long_close_30')
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe[state30].shift(shift30) == "-10") |
|
||||
(dataframe[state30].shift(shift30) == "-20") |
|
||||
(dataframe[state30].shift(shift30) == "-30")
|
||||
(dataframe[state30].shift(shift30) == "-10")
|
||||
),
|
||||
['exit_short', 'exit_tag']] = (1, 'short_close_30')
|
||||
return dataframe
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -33,7 +33,8 @@ class TRADE_POINT_TYPE:
|
||||
SELL3 = -3 # 三类卖点
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
macd_factor = 2 *6
|
||||
smooth_factor = 1
|
||||
# 初始化交易所
|
||||
exchange = ccxt.binance({
|
||||
'enableRateLimit': True,
|
||||
@@ -421,9 +422,9 @@ def get_a_stock_kl_data(symbol, timeframe, limit=100000, start_time=None, end_ti
|
||||
return None
|
||||
|
||||
def add_indicators(df):
|
||||
fast = 12*1
|
||||
slow = 26*1
|
||||
period = 9*1
|
||||
fast = 12 * macd_factor
|
||||
slow = 26 * macd_factor
|
||||
period = 9 * smooth_factor
|
||||
macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period)
|
||||
|
||||
df['macd'] = macd['macd']
|
||||
@@ -509,10 +510,10 @@ def add_indicators(df):
|
||||
|
||||
def calculate_macd(df):
|
||||
"""计算MACD指标"""
|
||||
exp1 = df['close'].ewm(span=12, adjust=False).mean()
|
||||
exp2 = df['close'].ewm(span=26, adjust=False).mean()
|
||||
exp1 = df['close'].ewm(span=12*macd_factor, adjust=False).mean()
|
||||
exp2 = df['close'].ewm(span=26*macd_factor, adjust=False).mean()
|
||||
macd = exp1 - exp2
|
||||
signal = macd.ewm(span=9, adjust=False).mean()
|
||||
signal = macd.ewm(span=9 * smooth_factor, adjust=False).mean()
|
||||
histogram = macd - signal
|
||||
|
||||
return {
|
||||
@@ -534,13 +535,11 @@ def analyze_chan(df, symbol=None, timeframe=None):
|
||||
#for index in range(0, 10):
|
||||
#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)
|
||||
zs_list = chan.calculate_zs(bi_list, seg_list)
|
||||
zs_list = chan.calculate_seg_zs(seg_list)
|
||||
# 计算笔中枢(BI中枢)并拍平成列表
|
||||
try:
|
||||
bi_zs_nested = chan.cal_bi_zs(seg_list)
|
||||
bi_zs_list = [zs for group in bi_zs_nested for zs in (group or [])] if bi_zs_nested else []
|
||||
except Exception:
|
||||
bi_zs_list = []
|
||||
|
||||
#bi_zs_list = chan.cal_bi_zs_list(bi_list)
|
||||
bi_zs_list = chan.cal_bi_zs(seg_list)
|
||||
bsp_list = []
|
||||
if len(bi_zs_list) > 0:
|
||||
bsp_list = chan.find_all_bsp(bi_list, bi_zs_list)
|
||||
|
||||
@@ -768,10 +768,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h5 class="my-1">缠论分析系统</h5>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="row g-1 d-flex align-items-end">
|
||||
<div class="col-md-2">
|
||||
|
||||
@@ -81,3 +81,4 @@
|
||||
注意,这里必须提醒一句,就是这在以前也曾说过,就是,如果线段中,最高或最低点不是线段的端点,那么,在任何以线段为基础的分析中,例如把线段为基础构成最小级别的中枢等,都可以把该线段标准化为最高低点都在端点。因为, 在以线段为基础的分析中,都把线段当成一个没有内部 结构的基本部件,所以,只需要关心这线段的实际区间就可以,这样就可以只看其高低点。
|
||||
经过标准化处理后,所有向上线段都是以最低点开始最高点结束,向下线段都是以最高点开始最低点结束,这样,所以线段的连接,就形成一条延续不断、首尾相连的折线,这样,复杂的图形,就会十分地标准化,也为后面的中枢、走势类型等分析提供了最标准且基础的部件。
|
||||
|
||||
本级别没有背驰的,次级别背驰下跌后形成第三类卖点进而形成本级别的V型反转,小转大
|
||||
|
||||
Reference in New Issue
Block a user