修改了很多,明天继续

This commit is contained in:
jackyu66git
2025-09-25 02:18:21 +08:00
parent daa8c25c91
commit 68e9c38922
6 changed files with 682 additions and 3612 deletions
+5 -1
View File
@@ -61,7 +61,11 @@ class Chan_FX_TYPE(Enum):
BB = auto()
PTOP = auto()
PBOTTOM = auto()
class Chan_PRICE_TREND(Enum):
UP = auto()
DOWN = auto()
FLAT = auto()
UNKNOWN = auto()
class Chan_KLC_FX(Enum):
TOP1 = auto()
TOP2 = auto()
+7 -1554
View File
File diff suppressed because it is too large Load Diff
+5 -547
View File
@@ -1,4 +1,4 @@
from ChanEnum import Chan_FX_TYPE, Chan_KLU_TYPE, Chan_K_DIR, Chan_MACD_STATE, Chan_MACDHIST_STATE
from ChanEnum import Chan_FX_TYPE, Chan_KLU_TYPE, Chan_K_DIR, Chan_MACD_STATE, Chan_MACDHIST_STATE, Chan_PRICE_TREND
class ChanKLU:
def __init__(self, time, open, high, low, close, volume):
# _time, _close, _open, _high, _low, _extra_info={}
@@ -42,7 +42,6 @@ class ChanKLU:
self.fx_strength = 0 # 分型强度:0-100
self.fx_confirmed = False # 分型是否确认
self.klu_type = None
self.cal_klu_min_max()
self.range = self.high - self.low
self.body = abs(self.close - self.open)
self.upper_shadow = self.high - max(self.close, self.open)
@@ -51,7 +50,6 @@ class ChanKLU:
self.upper_shadow_ratio = self.upper_shadow / self.body
self.lower_shadow_ratio = self.lower_shadow / self.body
self.candle_dir = Chan_K_DIR.CROSS if self.close == self.open else Chan_K_DIR.BULL if self.close > self.open else Chan_K_DIR.BEAR
self.strength = 0 if self.candle_dir == Chan_K_DIR.CROSS else self.cal_klu_strength()
self.continue_div = 0
self.separate_div = 0
@@ -64,6 +62,7 @@ class ChanKLU:
self.hist_state = Chan_MACDHIST_STATE.UNKNOWN
self.macd_state = Chan_MACD_STATE.UNKNOWN
self.macd_hist_gap = 0
self.trend = Chan_PRICE_TREND.UNKNOWN
# === 归零轴细化与模式/背离 ===
self.zero_axis = False # 是否归零轴(穿越或接近)
self.zero_axis_state = "none" # {none,crossing,near}
@@ -79,49 +78,10 @@ class ChanKLU:
#print(self.open, self.close, self.high, self.low, self.candle_dir, self.strength)
def set_macd_state(self, state):
self.macd_state = state
def cal_klu_strength(self):
strength = 0
if range != 0:
if self.candle_dir == Chan_K_DIR.BULL and (self.upper_shadow + self.lower_shadow) != 0:
strength += self.body / self.range
strength += self.body / (self.upper_shadow + self.lower_shadow)
elif self.candle_dir == Chan_K_DIR.BEAR and (self.upper_shadow + self.lower_shadow) != 0:
strength -=self.body / self.range
strength -= self.body / (self.upper_shadow + self.lower_shadow)
#print(self.time, self.body, self.range, self.upper_shadow, self.lower_shadow, self.candle_dir, strength)
return strength
return strength
def cal_klu_min_max(self):
"""
计算K线类型大阳线大阴线小阳线小阴线
"""
if self.open <= 0: # 避免除零错误
self.kline_type = None
return
# 计算涨跌幅
price_change_ratio = (self.close - self.open) / self.open
strength = 0
# 判断K线类型
if price_change_ratio > 0.005: # 涨幅超过2%
self.kline_type = Chan_KLU_TYPE.BigBull
strength += abs(price_change_ratio)
elif price_change_ratio > 0: # 涨幅0-2%
self.kline_type = Chan_KLU_TYPE.SmallBull
strength += abs(price_change_ratio)
elif price_change_ratio < -0.005: # 跌幅超过2%
self.kline_type = Chan_KLU_TYPE.BigBear
strength += abs(price_change_ratio)
elif price_change_ratio < 0: # 跌幅0-2%
self.kline_type = Chan_KLU_TYPE.SmallBear
strength += abs(price_change_ratio)
else: # 开盘价等于收盘价
self.kline_type = Chan_KLU_TYPE.Cross
strength += abs(price_change_ratio)
return strength
def set_trend(self, trend):
self.trend = trend
def set_next(self, next):
self.next = next
self.update_realtime_analysis()
#if self.fx_type != Chan_FX_TYPE.UNKNOWN and self.fx_strength > 1:
#print(self.index, self.time, self.fx_type, self.fx_confirmed, self.fx_strength)
def set_pre(self, pre):
@@ -139,471 +99,6 @@ class ChanKLU:
def set_unittf(self, unittf):
"""设置UnitTF关联"""
self.unittf = unittf
def detect_realtime_fx(self):
"""
实时检测K线分型不等待KLC确认
基于原始K线的即时分型识别
"""
if not self.pre or not self.next:
self.fx_type = Chan_FX_TYPE.UNKNOWN
return False
# 顶分型检测
if (self.high > self.pre.high and
self.high > self.next.high):
self.fx_type = Chan_FX_TYPE.TOP
self.fx_confirmed = True
return True
# 底分型检测
elif (self.low < self.pre.low and
self.low < self.next.low):
self.fx_type = Chan_FX_TYPE.BOTTOM
self.fx_confirmed = True
return True
self.fx_type = Chan_FX_TYPE.UNKNOWN
self.fx_confirmed = False
return False
def cal_fx(self):
"""
根据缠论经典规则计算分型强弱
返回分型强度3=极强2=1=中等0=-1=极弱
"""
if self.fx_type == Chan_FX_TYPE.UNKNOWN or not self.pre or not self.next:
return 0
if self.fx_type == Chan_FX_TYPE.TOP:
return self._cal_top_fx_strength()
else: # BOTTOM
return self._cal_bottom_fx_strength()
def _check_contain_relation(self, k1, k2):
"""检查两根K线是否存在包含关系"""
return (k1.high >= k2.high and k1.low <= k2.low) or (k2.high >= k1.high and k2.low <= k1.low)
def _is_big_yang_line(self, klu):
"""判断是否为大阳线"""
return klu.close > klu.open and (klu.close - klu.open) / klu.open > 0.02
def _is_big_yin_line(self, klu):
"""判断是否为大阴线"""
return klu.close < klu.open and (klu.open - klu.close) / klu.open > 0.02
def _is_small_line(self, klu):
"""判断是否为小K线"""
return abs(klu.close - klu.open) / klu.open < 0.01
def _has_long_upper_shadow(self, klu):
"""判断是否有长上影线"""
body_size = abs(klu.close - klu.open)
upper_shadow = klu.high - max(klu.close, klu.open)
return upper_shadow > body_size * 1.5
def _cal_top_fx_strength(self):
"""计算顶分型强度"""
strength = 0
k1, k2, k3 = self.pre, self, self.next
# (1) 检查包含关系 - 没有包含关系加分
has_contain_12 = self._check_contain_relation(k1, k2)
has_contain_23 = self._check_contain_relation(k2, k3)
if not has_contain_12 and not has_contain_23:
strength += 1 # 完全没有包含关系,加1分
# (2) 检查第1条K线是大阳线,第2、3条是小K线的情况
if self._is_big_yang_line(k1) and self._is_small_line(k2) and self._is_small_line(k3):
strength -= 2 # 中继顶分型特征,减2分
# (3) 检查第2条K线有长上影线或大阴线,且第3条K线条件
k2_mid = (k2.high + k2.low) / 2
k3_is_yang = k3.close > k3.open
k3_close_above_mid = k3.close > k2_mid
if (self._has_long_upper_shadow(k2) or self._is_big_yin_line(k2)) and not (k3_is_yang and k3_close_above_mid):
strength += 2 # 力度大的顶分型,加2分
# (4) 检查第2、3条K线包含关系,第3条为大阴线"吃掉"第2条
if has_contain_23 and self._is_big_yin_line(k3) and k3.low < k2.low and k3.high < k2.high:
strength += 1 # 最坏包含关系,但对顶分型有利,加1分
# (5) 第3条K线跌破第1条K线底部且不能高于第1条K线区间一半之上
k1_mid = (k1.high + k1.low) / 2
if k3.low < k1.low and k3.high < k1_mid:
strength -= 1 # 较弱的顶分型,减1分
# 额外检查:第3条K线收盘价相对第1条K线的位置
if k3.close < k1.low:
strength += 1 # 强烈下跌确认,加1分
return max(-1, min(3, strength)) # 限制在-1到3范围内
def _cal_bottom_fx_strength(self):
"""计算底分型强度"""
strength = 0
k1, k2, k3 = self.pre, self, self.next
# 底分型上边沿
fx_top = max(k1.high, k2.high)
# (1) 第3条K线高点远高于第1条K线高点
if k3.high > k1.high * 1.02: # 高出2%以上认为是"远高于"
strength += 2 # 较强走势,加2分
# (2) 第3条K线高点正好是第1根K线高点,或略微高于底分型上边沿
elif k1.high * 0.99 <= k3.high <= fx_top * 1.01: # 在合理范围内
strength += 0 # 一般走势,不加分也不减分
# (3) 第3条K线高点低于第1条K线高点
elif k3.high < k1.high:
strength -= 1 # 较弱走势,减1分
# 检查包含关系
has_contain_12 = self._check_contain_relation(k1, k2)
has_contain_23 = self._check_contain_relation(k2, k3)
if not has_contain_12 and not has_contain_23:
strength += 1 # 完全没有包含关系,加1分
# 检查第3条K线是否为强阳线
if self._is_big_yang_line(k3):
strength += 1 # 强阳线确认,加1分
# (4) 检查后续第1条K线(如果存在)
if hasattr(k3, 'next') and k3.next:
next_k = k3.next
if next_k.low > fx_top:
strength += 2 # 后续K线低点高于底分型上边沿,强烈确认,加2分
elif next_k.low <= k2.low:
strength -= 1 # 后续K线跌破分型低点,减1分
return max(-1, min(3, strength)) # 限制在-1到3范围内
def calculate_realtime_fx_strength(self):
"""
用self.pre和self.next实现分型强弱判断与KLC中cal_fx_strength一致
核心缠论原理
- 强分型出现在笔的末端能够终结当前笔标志着趋势转折
- 弱分型出现在笔的中间是中继性质笔还会继续延伸
返回值
3: 极强分型笔终结+强确认
2: 强分型笔终结
1: 偏强分型可能终结笔
0: 中性分型
-1: 偏弱分型中继特征明显
-2: 弱分型明显中继
-3: 极弱分型无效分型
"""
# 检查是否为分型,且有前后K线数据
if self.fx_type == Chan_FX_TYPE.UNKNOWN:
return 0
if not self.pre or not self.next:
return 100
# === 核心判断:分型在笔中的位置 ===
# 1. 检查这个分型是否能够终结当前笔
is_bi_end = self._check_if_bi_ending_fx()
# 2. 检查分型的后续走势确认
post_fx_confirmation = self._check_post_fx_confirmation()
# 3. 检查分型的标准性和强度
fx_quality = self._check_fx_quality()
# === 综合评分 ===
base_score = 0
# 笔位置是最重要的判断标准
if is_bi_end == 2: # 强烈确认笔终结
base_score = 2
elif is_bi_end == 1: # 可能笔终结
base_score = 1
elif is_bi_end == -1: # 明显中继
base_score = -2
elif is_bi_end == -2: # 强烈中继特征
base_score = -3
else: # 不确定
base_score = 0
# 后续确认调整
base_score += post_fx_confirmation
# 分型质量调整
base_score += fx_quality
# 限制在-3到3范围内
final_score = max(-3, min(3, base_score))
self.fx_strength = final_score
# 转换为0-100分制以保持接口一致性
#self.fx_strength = int((final_score + 3) * 100 / 6) # -3到3映射到0-100
#if final_score > 1.8:
#print(self.time, final_score, is_bi_end, post_fx_confirmation, fx_quality)
#print(self.time, final_score, is_bi_end, post_fx_confirmation, fx_quality)
#self.fx_strength = self.cal_fx()
return self.fx_strength
def _check_if_bi_ending_fx(self):
"""
检查分型是否为笔终结分型
返回值
2: 强烈确认笔终结
1: 可能笔终结
0: 不确定
-1: 明显中继
-2: 强烈中继特征
"""
# 检查是否有足够的后续数据来判断
if not self.next or not hasattr(self.next, 'next'):
return 0
# 获取分型后的几根K线数据
subsequent_klus = []
temp = self.next
for i in range(2): # 检查后续2根K线
if temp:
subsequent_klus.append(temp)
temp = temp.next if hasattr(temp, 'next') else None
else:
break
if len(subsequent_klus) < 2:
return 0
if self.fx_type == Chan_FX_TYPE.TOP:
return self._check_top_bi_ending(subsequent_klus)
else: # BOTTOM
return self._check_bottom_bi_ending(subsequent_klus)
def _check_top_bi_ending(self, subsequent_klus):
"""检查顶分型是否为笔终结"""
# 强烈笔终结特征:
# 1. 后续K线持续下跌,且跌破关键位置
# 2. 没有新的更高的高点出现
broken_key_levels = 0
new_highs = 0
downward_trend = 0
# 检查关键价位突破
first_low = self.pre.low
middle_low = self.low
key_support = min(first_low, middle_low)
for i, klu in enumerate(subsequent_klus):
# 检查是否跌破关键支撑
if klu.low < key_support:
broken_key_levels += 1
# 检查是否出现新高
if klu.high > self.high:
new_highs += 1
# 检查下跌趋势
if i > 0 and klu.close < subsequent_klus[i-1].close:
downward_trend += 1
# 强烈笔终结:跌破关键位且无新高
if broken_key_levels >= 1 and new_highs == 0 and downward_trend >= 2:
return 2
# 可能笔终结:部分条件满足
if (broken_key_levels >= 1 and new_highs <= 1) or (new_highs == 0 and downward_trend >= 3):
return 1
# 明显中继:出现新高且未跌破关键位
if new_highs >= 2 and broken_key_levels == 0:
return -2
# 中继倾向:出现新高
if new_highs >= 1:
return -1
return 0
def _check_bottom_bi_ending(self, subsequent_klus):
"""检查底分型是否为笔终结"""
# 强烈笔终结特征:
# 1. 后续K线持续上涨,且突破关键位置
# 2. 没有新的更低的低点出现
broken_key_levels = 0
new_lows = 0
upward_trend = 0
# 检查关键价位突破
first_high = self.pre.high
middle_high = self.high
key_resistance = max(first_high, middle_high)
for i, klu in enumerate(subsequent_klus):
# 检查是否突破关键阻力
if klu.high > key_resistance:
broken_key_levels += 1
# 检查是否出现新低
if klu.low < self.low:
new_lows += 1
# 检查上涨趋势
if i > 0 and klu.close > subsequent_klus[i-1].close:
upward_trend += 1
# 强烈笔终结:突破关键位且无新低
if broken_key_levels >= 1 and new_lows == 0 and upward_trend >= 2:
return 2
# 可能笔终结:部分条件满足
if (broken_key_levels >= 1 and new_lows <= 1) or (new_lows == 0 and upward_trend >= 3):
return 1
# 明显中继:出现新低且未突破关键位
if new_lows >= 2 and broken_key_levels == 0:
return -2
# 中继倾向:出现新低
if new_lows >= 1:
return -1
return 0
def _check_post_fx_confirmation(self):
"""
检查分型后的走势确认
返回值-1到1的调整分数
"""
if not self.next:
return 0
score = 0
# 检查第三根K线的确认
third_klu = self.next
if self.fx_type == Chan_FX_TYPE.TOP:
# 顶分型:第三根K线应该走弱
middle_price = (self.high + self.low) / 2
if third_klu.close < middle_price:
score += 0.5
if third_klu.low < self.pre.low: # 跌破第一根K线低点
score += 0.5
if third_klu.close < third_klu.open and abs(third_klu.close - third_klu.open) > abs(self.close - self.open) * 0.5:
score += 0.3 # 明显阴线
else: # BOTTOM
# 底分型:第三根K线应该走强
middle_price = (self.high + self.low) / 2
if third_klu.close > middle_price:
score += 0.5
if third_klu.high > self.pre.high: # 突破第一根K线高点
score += 0.5
if third_klu.close > third_klu.open and abs(third_klu.close - third_klu.open) > abs(self.close - self.open) * 0.5:
score += 0.3 # 明显阳线
return min(1, max(-1, score))
def _check_fx_quality(self):
"""
检查分型本身的质量
返回值-1到1的调整分数
"""
score = 0
# 检查分型的标准性
if self.fx_type == Chan_FX_TYPE.TOP:
# 高点突出程度
high_diff1 = (self.high - self.pre.high) / self.high if self.high > 0 else 0
high_diff2 = (self.high - self.next.high) / self.high if self.high > 0 else 0
min_diff = min(high_diff1, high_diff2)
if min_diff > 0.03: # 非常突出
score += 0.5
elif min_diff > 0.01: # 比较突出
score += 0.2
elif min_diff < 0.003: # 不够突出
score -= 0.5
else: # BOTTOM
# 低点突出程度
low_diff1 = (self.pre.low - self.low) / self.pre.low if self.pre.low > 0 else 0
low_diff2 = (self.next.low - self.low) / self.next.low if self.next.low > 0 else 0
min_diff = min(low_diff1, low_diff2)
if min_diff > 0.03: # 非常突出
score += 0.5
elif min_diff > 0.01: # 比较突出
score += 0.2
elif min_diff < 0.003: # 不够突出
score -= 0.5
# 检查量价配合
avg_volume = self._get_avg_volume(lookback=5)
if avg_volume > 0:
volume_ratio = self.volume / avg_volume
if volume_ratio > 1.5:
score += 0.3
elif volume_ratio < 0.7:
score -= 0.2
return min(1, max(-1, score))
def _get_avg_volume(self, lookback=5):
"""获取前N根K线平均成交量"""
volumes = []
temp = self.pre
for i in range(lookback):
if temp:
volumes.append(temp.volume)
temp = temp.pre if hasattr(temp, 'pre') else None
else:
break
return sum(volumes) / len(volumes) if volumes else self.volume
def get_fx_signal(self):
"""
获取分型交易信号
返回: (信号类型, 强度, 建议)
"""
if not self.fx_confirmed:
return ("无信号", 0, "等待分型确认")
strength_level = ""
if self.fx_strength >= 80:
strength_level = "极强"
elif self.fx_strength >= 65:
strength_level = ""
elif self.fx_strength >= 50:
strength_level = "中等"
if self.fx_type == Chan_FX_TYPE.TOP:
signal_type = f"{strength_level}顶分型"
if self.fx_strength >= 65:
suggestion = "考虑减仓或止盈"
else:
suggestion = "谨慎观望"
else:
signal_type = f"{strength_level}底分型"
if self.fx_strength >= 65:
suggestion = "考虑建仓或加仓"
else:
suggestion = "谨慎观望"
return (signal_type, self.fx_strength, suggestion)
def update_realtime_analysis(self):
"""
更新实时分析在每根K线完成时调用
"""
self.detect_realtime_fx()
if self.fx_confirmed:
self.calculate_realtime_fx_strength()
def set_idx(self, idx):
self.idx = idx
self.index = idx
@@ -639,9 +134,6 @@ class ChanKLU:
self.bblow120 = float(item['bblow120']) if 'bblow120' in item and item['bblow120'] else 0
self.bbup365 = float(item['bbup365']) if 'bbup365' in item and item['bbup365'] else 0
self.bblow365 = float(item['bblow365']) if 'bblow365' in item and item['bblow365'] else 0
# 设置指标后更新实时分析
self.update_realtime_analysis()
#self.cal_macd_state()
def cal_macd_state(self):
# 按定义精简实现:优先级 CROSS0 > 位置(HIGH/HE/RETURN_ZERO) > NEAR0 > UNKNOWN
# 首条或缺前一根
@@ -828,38 +320,4 @@ class ChanKLU:
self.macd_state = Chan_MACD_STATE.UNKNOWN
return self.macd_state
return self.macd_state
def get_feature_data(self):
features = dict()
features['klu_close'] = self.close
features['klu_open'] = self.open
features['klu_high'] = self.high
features['klu_low'] = self.low
features['klu_volume'] = self.volume
features['klu_index'] = self.index
features['klu_macd'] = self.macd
features['klu_signal'] = self.signal
features['klu_macdhist'] = self.macdhist
features['klu_ma5'] = self.ma5
features['klu_ma10'] = self.ma10
features['klu_ma30'] = self.ma30
features['klu_ma50'] = self.ma50
features['klu_ma200'] = self.ma200
features['klu_ma250'] = self.ma250
features['klu_rsi'] = self.rsi
features['klu_volume_ratio'] = self.volume_ratio
# === 新增:实时分型特征 ===
# 将枚举转换为数值:UNKNOWN=0, TOP=1, BOTTOM=-1
if self.fx_type == Chan_FX_TYPE.TOP:
fx_type_value = 1
elif self.fx_type == Chan_FX_TYPE.BOTTOM:
fx_type_value = -1
else:
fx_type_value = 0
features['klu_fx_type'] = fx_type_value
features['klu_fx_strength'] = self.fx_strength
features['klu_fx_confirmed'] = 1 if self.fx_confirmed else 0
return features
+131 -1478
View File
File diff suppressed because it is too large Load Diff
+206 -32
View File
@@ -1,6 +1,6 @@
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
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
from ChanKLU import ChanKLU
from ChanKLC import ChanKLC
from ChanBI import ChanBI
@@ -20,40 +20,213 @@ import numpy as np
from ChanMACD import ChanMACD
class TF_DF():
def __init__(self, timeframe, df, ticker_indicator):
self.timeframe = timeframe
self.dataframe = resample_to_interval(df, ticker_indicator*timeframe)
self.ticker_indicator = ticker_indicator
self.klu_list = []
self.klc_list = []
self.bi_list = []
self.zs_list = []
self.bsp_list = []
self.seg_list = []
self.init_TF_DF()
def init_TF_DF(self):
self.klu_list = self.cal_kl_data(self.dataframe)
self.klc_list = self.cal_klc_list(self.klu_list)
self.bi_list = self.cal_bi_list(self.klc_list)
self.seg_list = self.cal_seg_list(self.bi_list)
self.zs_list = self.cal_zs_list(self.bi_list, self.seg_list)
self.chanmacd = ChanMACD(self.klu_list)
self.klu_list = self.chanmacd.cal_macd_state()
def check_fx(self, klc):
def __init__(self, timeframe, df, ticker_indicator):
self.timeframe = timeframe
self.dataframe = resample_to_interval(df, ticker_indicator*timeframe)
self.dataframe = self.add_indicators(self.dataframe)
self.ticker_indicator = ticker_indicator
self.klu_list = []
self.klc_list = []
self.bi_list = []
self.zs_list = []
self.bsp_list = []
self.seg_list = []
self.init_TF_DF()
def init_TF_DF(self):
self.klu_list = self.cal_kl_data(self.dataframe)
self.klc_list = self.cal_klc_list(self.klu_list)
self.bi_list = self.cal_bi_list(self.klc_list)
self.seg_list = self.cal_seg_list(self.bi_list)
self.zs_list = self.cal_zs_list(self.bi_list, self.seg_list)
self.chanmacd = ChanMACD(self.klu_list)
self.klu_list = self.chanmacd.cal_macd_state()
def add_indicators(self, df):
fast = 12
slow = 26
period = 9
macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period)
bb365 = ta.BBANDS(df, timeperiod=365, nbdevup=3.0, nbdevdn=3.0, matype=0)
bb120 = ta.BBANDS(df, timeperiod=120, nbdevup=3.0, nbdevdn=3.0, matype=0)
bb30 = ta.BBANDS(df, timeperiod=41, nbdevup=2.3, nbdevdn=2.3, matype=0)
bb302 = ta.BBANDS(df, timeperiod=41, nbdevup=2.0, nbdevdn=2.0, matype=0)
bb30 = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0)
bb302 = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0)
# 计算布林带中轨(移动平均线)
bb30_middle = ta.SMA(df, timeperiod=90)
# 手动计算布林带 %B 指标 (BBP)
# %B = (Price - Lower Band) / (Upper Band - Lower Band)
bbp365 = (df['close'] - bb365['lowerband']) / (bb365['upperband'] - bb365['lowerband'])
bbp120 = (df['close'] - bb120['lowerband']) / (bb120['upperband'] - bb120['lowerband'])
bbp30 = (df['close'] - bb30['lowerband']) / (bb30['upperband'] - bb30['lowerband'])
bbp302 = (df['close'] - bb302['lowerband']) / (bb302['upperband'] - bb302['lowerband'])
df['atr'] = ta.ATR(df, timeperiod=14)
df['bbup365'] = bb365['upperband']
df['bblow365'] = bb365['lowerband']
df['bbp365'] = bbp365
df['bbup120'] = bb120['upperband']
df['bblow120'] = bb120['lowerband']
df['bbp120'] = bbp120
df['bbup30'] = bb30['upperband']
df['bblow30'] = bb30['lowerband']
df['bbmiddle30'] = bb30_middle # 添加bb30中轨
df['bbp30'] = bbp30
df['bbup302'] = bb302['upperband']
df['bblow302'] = bb302['lowerband']
df['bbp302'] = bbp302
df['macd'] = macd['macd']
df['macdsignal'] = macd['macdsignal']
df['macdhist'] = macd['macdhist']
df['ema5'] = ta.EMA(df, timeperiod=5)
df['ema10'] = ta.EMA(df, timeperiod=10)
df['ema24'] = ta.EMA(df, timeperiod=24)
df['ema26'] = ta.EMA(df, timeperiod=26)
df['ema52'] = ta.EMA(df, timeperiod=52)
df['rsi'] = ta.RSI(df, timeperiod=14)
df['volume_ratio'] = self.cal_volume_ratio(df)
return df
def check_fx(self, klc):
if klc.pre and klc.next:
if klc.high > klc.pre.high and klc.high > klc.next.high:
if klc.macd > 0 and klc.macd > klc.signal and klc.signal > klc.macdhist:
klc.set_fx(Chan_FX_TYPE.TOP)
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time,klc.fx, "TOP")
# print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time,klc.fx, "TOP")
return Chan_FX_TYPE.TOP
elif klc.low < klc.pre.low and klc.low < klc.next.low:
if klc.macd < 0 and klc.macd < klc.signal and klc.signal < klc.macdhist:
klc.set_fx(Chan_FX_TYPE.BOTTOM)
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time,klc.fx, "BOTTOM")
# print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time,klc.fx, "BOTTOM")
return Chan_FX_TYPE.BOTTOM
return Chan_FX_TYPE.UNKNOWN
def cal_kl_data(self, dataframe:DataFrame):
def cal_volume_ratio(self, dataframe, window=10):
df = dataframe.copy()
# 计算过去N根K线的平均成交量
df['avg_volume'] = df['volume'].rolling(window=window).mean()
# 计算量比
df['volume_ratio'] = df['volume'] / df['avg_volume']
# 填充缺失值(前N根K线)
df['volume_ratio'] = df['volume_ratio'].fillna(1.0)
return df['volume_ratio']
def cal_trend(self, klc_list):
"""
基于价格与EMA24/EMA52的位置关系以及MACD/Signal/Hist的方向
为每个KLC打上趋势标签'UP' / 'DOWN' / 'FLAT'
仅设置 klc.trend不影响其它字段
"""
if not klc_list:
return klc_list
last_trend = Chan_PRICE_TREND.UNKNOWN
for klc in klc_list:
price = getattr(klc, 'close', None)
ema24 = getattr(klc, 'ema24', None)
ema52 = getattr(klc, 'ema52', None)
macd = getattr(klc, 'macd', 0) if getattr(klc, 'macd', None) is not None else 0
signal = getattr(klc, 'signal', 0) if getattr(klc, 'signal', None) is not None else 0
hist = getattr(klc, 'macdhist', 0) if getattr(klc, 'macdhist', None) is not None else 0
rsi = getattr(klc, 'rsi', None)
trend = Chan_PRICE_TREND.UNKNOWN
try:
# 有效性
price_valid = price is not None and price != 0
ema24_valid = ema24 is not None and ema24 != 0
ema52_valid = ema52 is not None and ema52 != 0
# 多因子投票
score = 0
# 1) 均线结构 + 价位
if ema24_valid and ema52_valid:
score += 1 if ema24 > ema52 else -1
if price_valid and ema24_valid:
score += 1 if price > ema24 else -1
if price_valid and ema52_valid:
score += 1 if price > ema52 else -1
# 2) MACD结构
score += 1 if macd >= signal else -1
if hist != 0:
score += 1 if hist > 0 else -1
# 3) 动量与均线差分斜率
pre = getattr(klc, 'pre', None)
if pre:
pre_close = getattr(pre, 'close', None)
if price_valid and pre_close is not None:
score += 1 if price >= pre_close else -1
pre_ema24 = getattr(pre, 'ema24', None)
pre_ema52 = getattr(pre, 'ema52', None)
if ema24_valid and ema52_valid and pre_ema24 not in (None, 0) and pre_ema52 not in (None, 0):
spread_now = ema24 - ema52
spread_pre = pre_ema24 - pre_ema52
score += 1 if spread_now >= spread_pre else -1
# 4) RSI 辅助
if rsi is not None:
if rsi >= 55:
score += 1
elif rsi <= 45:
score -= 1
# 5) 指标未就绪回退(EMA/MACD缺失时,用动量与RSI辅助,延续趋势)
has_full_ind = ema24_valid and ema52_valid and not (macd == 0 and signal == 0 and hist == 0)
if not has_full_ind:
# 仅根据价动量/RSI做轻量判断,默认延续 last_trend,除非出现强反向
strong_up = False
strong_down = False
pre = getattr(klc, 'pre', None)
if pre:
pre_close = getattr(pre, 'close', None)
if price_valid and pre_close is not None:
strong_up = (price >= pre_close)
strong_down = (price < pre_close)
if rsi is not None:
if rsi >= 60:
strong_up = True
elif rsi <= 40:
strong_down = True
if last_trend == Chan_PRICE_TREND.UP and not strong_down:
trend = Chan_PRICE_TREND.UP
elif last_trend == Chan_PRICE_TREND.DOWN and not strong_up:
trend = Chan_PRICE_TREND.DOWN
else:
trend = Chan_PRICE_TREND.UP if strong_up and not strong_down else (Chan_PRICE_TREND.DOWN if strong_down and not strong_up else Chan_PRICE_TREND.FLAT)
else:
# 6) 震荡过滤(仅当极近EMA52且MACD贴合时判作震荡)
near_flat = False
if price_valid and ema52_valid:
near_ema52 = abs(price - ema52) / abs(ema52) <= 0.0005 # 0.05%
near_macd = abs(macd - signal) <= (abs(price) * 0.00005 if price_valid else 0)
near_flat = near_ema52 and near_macd
# 7) 动态阈值 + 趋势记忆(更强粘滞:趋势中容忍小幅反分)
if near_flat:
trend = Chan_PRICE_TREND.FLAT
else:
if last_trend == Chan_PRICE_TREND.UP:
# 仅当出现明显反向才翻转,否则维持UP
if score <= -2:
trend = Chan_PRICE_TREND.DOWN
else:
trend = Chan_PRICE_TREND.UP
elif last_trend == Chan_PRICE_TREND.DOWN:
if score >= 2:
trend = Chan_PRICE_TREND.UP
else:
trend = Chan_PRICE_TREND.DOWN
else:
# 初始无记忆时,降低进入门槛
if score >= 1:
trend = Chan_PRICE_TREND.UP
elif score <= -1:
trend = Chan_PRICE_TREND.DOWN
else:
trend = Chan_PRICE_TREND.FLAT
except Exception:
trend = Chan_PRICE_TREND.UNKNOWN
# 写回趋势
if hasattr(klc, 'set_trend'):
klc.set_trend(trend)
else:
setattr(klc, 'trend', trend)
last_trend = trend
price_diff = klc.close - klc.pre.close if klc.pre else 0
print(klc.start_time, klc.end_time, klc.close, klc.ema24, klc.ema52, klc.macd, klc.signal, klc.macdhist, klc.trend, price_diff)
#print(klc.start_time, klc.end_time, klc.trend, price_diff)
return klc_list
def cal_kl_data(self, dataframe:DataFrame):
fields = "time,open,high,low,close,volume"
klu_list = []
last_klu = None
@@ -65,8 +238,8 @@ class TF_DF():
l = item['low']
c = item['close']
v = item['volume']
#time_obj = date.fromtimestamp(date)
#date = date + timedelta(hours=8)
# time_obj = date.fromtimestamp(date)
# date = date + timedelta(hours=8)
time_str = date.strftime('%Y-%m-%d %H:%M:%S')
item_data = [
time_str,
@@ -76,9 +249,9 @@ class TF_DF():
c,
v
]
#klu = KLU(self.create_item_dict(item_data, GetColumnNameFromFieldList(fields)))
# klu = KLU(self.create_item_dict(item_data, GetColumnNameFromFieldList(fields)))
klu = ChanKLU(time_str, o, h, l, c, v)
#print(klu.time, klu.open, klu.high, klu.low, klu.close, klu.volume)
# print(klu.time, klu.open, klu.high, klu.low, klu.close, klu.volume)
klu.set_idx(i)
klu_list.append(klu)
if last_klu:
@@ -89,7 +262,7 @@ class TF_DF():
klu.set_indicators(item)
return klu_list
def cal_klc_list(self, klu_list):
def cal_klc_list(self, klu_list):
klc_list = []
last_klu = None
macd = ChanMACD(klu_list)
@@ -117,9 +290,10 @@ class TF_DF():
klc = ChanKLC(klu, 0, ddir)
klc_list.append(klc)
last_klu = klu
klc_list = self.cal_trend(klc_list)
return klc_list
def cal_seg_list(self, bi_list):
def cal_seg_list(self, bi_list):
seg_list = []
up_bi_list = []
down_bi_list = []
@@ -707,7 +881,7 @@ class TF_DF():
#print(bi_list[index].start_time, bi_list[index].start_klc.start_time, bi_list[index].dir)
return bi_list
def get_decimal(self, value):
def get_decimal(self, value):
return Decimal("{:.2f}".format(value))
def cal_zs_list(self, bi_list, seg_list):
+328
View File
@@ -0,0 +1,328 @@
# --- Do not remove these libs ---
from statistics import median
from freqtrade.strategy import IStrategy, stoploss_from_absolute
import sys
import os
# 添加父目录到系统路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from ChanLun import ChanLun
from ChanEnum import Chan_FX_TYPE, Chan_KLC_FX, Chan_BI_DIR, Chan_KLC_FX
# --------------------------------
from technical.util import resample_to_interval, resampled_merge
import talib.abstract as ta
from pandas import DataFrame
import pandas as pd
from datetime import datetime, timedelta
from freqtrade.persistence import Trade, Order
from typing import Optional
import logging
logger = logging.getLogger(__name__)
### Now you can use logger.info('asfd') to log
# freqtrade plot-dataframe --strategy ChanLun_BTC --datadir user_data/data/binance -c ./user_data/ChanLun_SOL_30.json --timerange=20250309-
# freqtrade trade -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies
# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies --timerange=20250901-
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_30.json -t 1m --pairs BTC/USDT:USDT --timerange=20250405-
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_30.json -e 200 --timerange=20250201-20250901
# freqtrade edge -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
# freqtrade plot-dataframe -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
# sudo docker compose run --rm chanlun_btc backtesting -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies --timerange=20250721-
# sudo docker compose run --rm chanlun_btc download-data -c ./user_data/Chan/config/ChanLun_BTC_30.json --pairs BTC/USDT:USDT -t 1m --timerange 20240101-
# sudo docker compose run --rm chanlun_btc trade -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies
class ChanLun_BTC(IStrategy):
INTERFACE_VERSION: int = 3
# Minimal ROI designed for the strategy.
# This attribute will be overridden if the config file contains "minimal_roi"
# 30m and 1h
minimal_roi = {
"0": 0.15,
"360": 0.2,
"640": 0.1,
"1200": 0
}
# 5m and 15m
minimal_roi_1 = {
"0": 0.1,
"60": 0.05,
"120": 0.02,
"240": 0
}
# 15m and 30m
minimal_roi_1 = {
"0": 0.1,
"240": 0.05,
"480": 0.03,
"600": 0
}
minimal_roi_1 = {
"0": 1.50,
"120": 0.05,
"240": 0.025,
"360": 0
}
can_short = True
lev = 1.0
stoploss = -0.3 # 设置为很大的负值,让custom_stoploss来控制
use_custom_stoploss = True # 启用自定义止损
trailing_stop = False
trailing_stop_positive = 0.03
trailing_stop_positive_offset = 0.06
trailing_only_offset_is_reached = False
# 关闭分批止盈/仓位调整
position_adjustment_enable = False
startup_candle_count = 2880
time3 = 3
time5 = 5
time15 = 15
time30 = 30
time60 = 60
time2h = 120
time4h = 240
time1d = 1440
last_time = datetime.now()
chan = ChanLun()
last_order = None
last_trade = None
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe = self.add_indicators(dataframe)
# 仅保留15m(用于BSP)与60m(用于ATR过滤/止损)两个重采样
dataframe_15 = resample_to_interval(dataframe, self.get_ticker_indicator() * 15)
dataframe_60 = resample_to_interval(dataframe, self.get_ticker_indicator() * 60)
# 计算多周期BSP(以15m为基准),并合并到15m数据上
# 先给重采样帧补指标
dataframe_15 = self.add_indicators(dataframe_15)
dataframe_60 = self.add_indicators(dataframe_60)
# 计算15m BSP
bsp_15 = self.chan.cal_bsp(dataframe, self.get_ticker_indicator())
# 合并15m与60m到主DF,生成 resample_*_* 列
dataframe = resampled_merge(dataframe, dataframe_15)
dataframe = resampled_merge(dataframe, dataframe_60)
return dataframe
def add_indicators(self, df):
fast = 12
slow = 26
period = 9
macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period)
bb365 = ta.BBANDS(df, timeperiod=365, nbdevup=3.0, nbdevdn=3.0, matype=0)
bb120 = ta.BBANDS(df, timeperiod=120, nbdevup=3.0, nbdevdn=3.0, matype=0)
bb30 = ta.BBANDS(df, timeperiod=41, nbdevup=2.3, nbdevdn=2.3, matype=0)
bb302 = ta.BBANDS(df, timeperiod=41, nbdevup=2.0, nbdevdn=2.0, matype=0)
bb30 = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0)
bb302 = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0)
# 计算布林带中轨(移动平均线)
bb30_middle = ta.SMA(df, timeperiod=90)
# 手动计算布林带 %B 指标 (BBP)
# %B = (Price - Lower Band) / (Upper Band - Lower Band)
bbp365 = (df['close'] - bb365['lowerband']) / (bb365['upperband'] - bb365['lowerband'])
bbp120 = (df['close'] - bb120['lowerband']) / (bb120['upperband'] - bb120['lowerband'])
bbp30 = (df['close'] - bb30['lowerband']) / (bb30['upperband'] - bb30['lowerband'])
bbp302 = (df['close'] - bb302['lowerband']) / (bb302['upperband'] - bb302['lowerband'])
df['atr'] = ta.ATR(df, timeperiod=14)
df['bbup365'] = bb365['upperband']
df['bblow365'] = bb365['lowerband']
df['bbp365'] = bbp365
df['bbup120'] = bb120['upperband']
df['bblow120'] = bb120['lowerband']
df['bbp120'] = bbp120
df['bbup30'] = bb30['upperband']
df['bblow30'] = bb30['lowerband']
df['bbmiddle30'] = bb30_middle # 添加bb30中轨
df['bbp30'] = bbp30
df['bbup302'] = bb302['upperband']
df['bblow302'] = bb302['lowerband']
df['bbp302'] = bbp302
df['macd'] = macd['macd']
df['macdsignal'] = macd['macdsignal']
df['macdhist'] = macd['macdhist']
df['ema5'] = ta.EMA(df, timeperiod=5)
df['ema10'] = ta.EMA(df, timeperiod=10)
df['ema24'] = ta.EMA(df, timeperiod=24)
df['ema26'] = ta.EMA(df, timeperiod=26)
df['ema52'] = ta.EMA(df, timeperiod=52)
df['rsi'] = ta.RSI(df, timeperiod=14)
df['volume_ratio'] = self.cal_volume_ratio(df)
return df
def cal_volume_ratio(self, dataframe, window=10):
df = dataframe.copy()
# 计算过去N根K线的平均成交量
df['avg_volume'] = df['volume'].rolling(window=window).mean()
# 计算量比
df['volume_ratio'] = df['volume'] / df['avg_volume']
# 填充缺失值(前N根K线)
df['volume_ratio'] = df['volume_ratio'].fillna(1.0)
return df['volume_ratio']
def custom_entry_price(self, pair: str, trade: Trade | None, current_time: datetime, proposed_rate: float,
entry_tag: str | None, side: str, **kwargs) -> float:
new_entryprice = proposed_rate
if trade:
if trade.is_short:
new_entryprice = proposed_rate - 50
else:
new_entryprice = proposed_rate + 50
return new_entryprice
def custom_exit_price(self, pair: str, trade: Trade,
current_time: datetime, proposed_rate: float,
current_profit: float, exit_tag: str | None, **kwargs) -> float:
new_exitprice = proposed_rate
if trade:
if trade.is_short:
new_exitprice = proposed_rate + 50
else:
new_exitprice = proposed_rate - 50
return new_exitprice
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]:
# 关闭分批止盈,始终不调整仓位
return None
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> float | None:
"""
止损 = 开仓价 ± 1 * ATR开仓时的ATR
多单: 开仓价 - ATR空单: 开仓价 + ATR
"""
# 保本止损:当浮盈达到或超过 1% 时,将止损提至开仓价
#if current_profit is not None and current_profit >= 0.14:
#return stoploss_from_absolute(trade.open_rate, current_rate, is_short=trade.is_short)
entry_atr = trade.get_custom_data(key="entry_atr")
if entry_atr is None:
# 回退:取当前数据的 ATR 估算
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
if dataframe is not None and len(dataframe) > 0 and 'atr' in dataframe.columns:
entry_atr = float(dataframe.iloc[-1]['atr'])
else:
# 最保守的回退:5%
return -0.05
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
ema52_str = 'resample_{}_ema52'.format(self.get_ticker_indicator()*self.time15)
ema52_val = float(last_candle.get(ema52_str, 0) or 0)
close_str = 'resample_{}_close'.format(self.get_ticker_indicator()*self.time15)
close_val = float(last_candle.get(close_str, 0) or 0)
if close_val < ema52_val:
return -0.01
if trade.is_short:
stop_price = trade.open_rate + float(entry_atr)
else:
stop_price = trade.open_rate - float(entry_atr)
return stoploss_from_absolute(stop_price, current_rate, is_short=trade.is_short)
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, **kwargs):
# 不做分批止盈/最终止盈处理,退出由策略信号/ROI/止损决定
return None
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: str | None,
side: str, **kwargs) -> bool:
"""
ATR 过滤atr < 100 不开单
"""
try:
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if dataframe is None or len(dataframe) == 0:
return False
last = dataframe.iloc[-1]
atr_str = 'resample_{}_atr'.format(self.get_ticker_indicator()*self.time60)
atr_val = float(last.get(atr_str, 0) or 0)
if atr_val < 0.001:
#logger.info(f"ATR过滤:atr={atr_val:.2f} < 100, 拒绝进场 {pair}")
return False
return True
except Exception as e:
logger.warning(f"confirm_trade_entry 异常: {e}")
return True
def order_filled(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> None:
"""
Called right after an order fills.
Will be called for all order types (entry, exit, stoploss, position adjustment).
:param pair: Pair for trade
:param trade: trade object.
:param order: Order object.
:param current_time: datetime object, containing the current datetime
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
"""
# Obtain pair dataframe (just to show how to access it)
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
atr_str = 'resample_{}_atr'.format(self.get_ticker_indicator()*self.time15)
# 保存开仓时的ATR值用于止损计算
if (trade.nr_of_successful_entries == 1) and (order.ft_order_side == trade.entry_side):
entry_atr = last_candle[atr_str] * 4
trade.set_custom_data(key="entry_atr", value=entry_atr)
#logger.info(f"保存开仓时ATR值: {entry_atr}")
return None
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
shift15 = self.time15
shift60 = self.time60
bsp_col = 'resample_{}_bsp_mtf'.format(self.get_ticker_indicator()*shift15)
score_col = 'resample_{}_mtf_score'.format(self.get_ticker_indicator()*shift15)
macdh_col = 'resample_{}_macdhist'.format(self.get_ticker_indicator()*shift15)
c60_col = 'resample_{}_close'.format(self.get_ticker_indicator()*shift60)
e60_col = 'resample_{}_ema52'.format(self.get_ticker_indicator()*shift60)
# 强化过滤:15m BSP + 分数阈值 + 60m 趋势同向 + 15m MACD柱同向
if all(col in dataframe.columns for col in [bsp_col, score_col, macdh_col, c60_col, e60_col]):
dataframe.loc[
(
(dataframe[bsp_col].shift(shift15) == 1) &
(dataframe[score_col].shift(shift15) >= 1.2) &
(dataframe[c60_col].shift(shift60) >= dataframe[e60_col].shift(shift60)) &
(dataframe[macdh_col].shift(shift15) > 0)
),
['enter_long', 'enter_tag']] = (1, 'long_bsp15_v2')
dataframe.loc[
(
(dataframe[bsp_col].shift(shift15) == -1) &
(dataframe[score_col].shift(shift15) <= -1.2) &
(dataframe[c60_col].shift(shift60) <= dataframe[e60_col].shift(shift60)) &
(dataframe[macdh_col].shift(shift15) < 0)
),
['enter_short', 'enter_tag']] = (1, 'short_bsp15_v2')
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
shift15 = self.time15
shift60 = self.time60
bsp_col = 'resample_{}_bsp_mtf'.format(self.get_ticker_indicator()*shift15)
score_col = 'resample_{}_mtf_score'.format(self.get_ticker_indicator()*shift15)
c60_col = 'resample_{}_close'.format(self.get_ticker_indicator()*shift60)
e60_col = 'resample_{}_ema52'.format(self.get_ticker_indicator()*shift60)
# 反向强信号或60m趋势反向时平仓
if all(col in dataframe.columns for col in [bsp_col, score_col, c60_col, e60_col]):
dataframe.loc[
(
((dataframe[bsp_col].shift(shift15) == -1) & (dataframe[score_col].shift(shift15) <= -0.8)) |
(dataframe[c60_col].shift(shift60) < dataframe[e60_col].shift(shift60))
),
['exit_long', 'exit_tag']] = (1, 'long_close_bsp15')
dataframe.loc[
(
((dataframe[bsp_col].shift(shift15) == 1) & (dataframe[score_col].shift(shift15) >= 0.8)) |
(dataframe[c60_col].shift(shift60) > dataframe[e60_col].shift(shift60))
),
['exit_short', 'exit_tag']] = (1, 'short_close_bsp15')
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 self.lev
def get_ticker_indicator(self):
return int(self.timeframe[:-1])