+112
-2
@@ -1,4 +1,4 @@
|
|||||||
from ChanEnum import Chan_ZS_DIR
|
from ChanEnum import Chan_ZS_DIR, Chan_ZS_TYPE, Chan_BI_DIR
|
||||||
import ChanBI
|
import ChanBI
|
||||||
# 中枢
|
# 中枢
|
||||||
class ChanBIZS():
|
class ChanBIZS():
|
||||||
@@ -20,12 +20,13 @@ class ChanBIZS():
|
|||||||
self.dir = ddir
|
self.dir = ddir
|
||||||
self.sure_time = None
|
self.sure_time = None
|
||||||
self.end_klc = None
|
self.end_klc = None
|
||||||
|
self.zs_type = Chan_ZS_TYPE.NORMAL
|
||||||
def set_end_bi(self, end_bi, sure_bi):
|
def set_end_bi(self, end_bi, sure_bi):
|
||||||
self.end_bi = end_bi
|
self.end_bi = end_bi
|
||||||
self.set_end_time(end_bi.end_klc.end_time)
|
self.set_end_time(end_bi.end_klc.end_time)
|
||||||
self.is_sure = True
|
self.is_sure = True
|
||||||
self.sure_time = sure_bi.sure_time
|
self.sure_time = sure_bi.sure_time
|
||||||
# print(self.start_time, self.is_sure, len(self.bi_list), self.dir)
|
print(self.start_time, self.is_sure, len(self.bi_list), self.dir, self.zs_type)
|
||||||
def set_end_time(self, end_time):
|
def set_end_time(self, end_time):
|
||||||
self.end_time = end_time
|
self.end_time = end_time
|
||||||
def set_zg(self, zg):
|
def set_zg(self, zg):
|
||||||
@@ -39,3 +40,112 @@ class ChanBIZS():
|
|||||||
def add_bi(self, bi: ChanBI):
|
def add_bi(self, bi: ChanBI):
|
||||||
if bi:
|
if bi:
|
||||||
self.bi_list.append(bi)
|
self.bi_list.append(bi)
|
||||||
|
self.classify_zs()
|
||||||
|
|
||||||
|
def classify_zs(self):
|
||||||
|
"""
|
||||||
|
根据中枢内笔的高低点变化趋势,对中枢进行分类
|
||||||
|
|
||||||
|
分类逻辑:
|
||||||
|
- 取中枢内向上笔的高点(peaks)和向下笔的低点(valleys)
|
||||||
|
- 比较前半段和后半段的均值,判断高点和低点的整体趋势
|
||||||
|
|
||||||
|
分类结果:
|
||||||
|
- RISING 上升中枢:高点抬高 + 低点抬高 → 多方占优,可能向上突破
|
||||||
|
- FALLING 下行中枢:高点降低 + 低点降低 → 空方占优,可能向下突破
|
||||||
|
- CONVERGING 收敛中枢:高点降低 + 低点抬高 → 区间收窄,即将选择方向
|
||||||
|
- DIVERGING 扩散中枢:高点抬高 + 低点降低 → 波动加剧,市场不稳定
|
||||||
|
- NORMAL 常规中枢:无明显趋势 → 多空均衡,区间震荡
|
||||||
|
"""
|
||||||
|
if len(self.bi_list) < 3:
|
||||||
|
self.zs_type = Chan_ZS_TYPE.NORMAL
|
||||||
|
return
|
||||||
|
|
||||||
|
# 提取向上笔的高点(peaks)和向下笔的低点(valleys)
|
||||||
|
peaks = [bi.high for bi in self.bi_list if bi.dir == Chan_BI_DIR.UP]
|
||||||
|
valleys = [bi.low for bi in self.bi_list if bi.dir == Chan_BI_DIR.DOWN]
|
||||||
|
|
||||||
|
high_trend = self._calc_trend(peaks)
|
||||||
|
low_trend = self._calc_trend(valleys)
|
||||||
|
|
||||||
|
if high_trend > 0 and low_trend > 0:
|
||||||
|
self.zs_type = Chan_ZS_TYPE.RISING
|
||||||
|
elif high_trend < 0 and low_trend < 0:
|
||||||
|
self.zs_type = Chan_ZS_TYPE.FALLING
|
||||||
|
elif high_trend < 0 and low_trend > 0:
|
||||||
|
self.zs_type = Chan_ZS_TYPE.CONVERGING
|
||||||
|
elif high_trend > 0 and low_trend < 0:
|
||||||
|
self.zs_type = Chan_ZS_TYPE.DIVERGING
|
||||||
|
else:
|
||||||
|
self.zs_type = Chan_ZS_TYPE.NORMAL
|
||||||
|
|
||||||
|
def _calc_trend(self, values):
|
||||||
|
"""
|
||||||
|
计算序列的趋势方向
|
||||||
|
将序列分为前后两半,比较均值:
|
||||||
|
- 后半均值 > 前半均值 → 返回 1(上升趋势)
|
||||||
|
- 后半均值 < 前半均值 → 返回 -1(下降趋势)
|
||||||
|
- 相等或数据不足 → 返回 0(无趋势)
|
||||||
|
|
||||||
|
使用均值比较而非首尾比较,可以过滤单笔异常波动带来的误判
|
||||||
|
"""
|
||||||
|
if len(values) < 2:
|
||||||
|
return 0
|
||||||
|
mid = len(values) // 2
|
||||||
|
first_half = values[:mid] if mid > 0 else values[:1]
|
||||||
|
second_half = values[mid:]
|
||||||
|
avg_first = sum(first_half) / len(first_half)
|
||||||
|
avg_second = sum(second_half) / len(second_half)
|
||||||
|
# 使用中枢区间的一定比例作为阈值,避免微小波动误判
|
||||||
|
threshold = abs(avg_first) * 0.005 if avg_first != 0 else 0
|
||||||
|
if avg_second - avg_first > threshold:
|
||||||
|
return 1
|
||||||
|
elif avg_first - avg_second > threshold:
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def is_weakening(self):
|
||||||
|
"""
|
||||||
|
判断中枢是否在衰弱(即将反向突破的信号)
|
||||||
|
|
||||||
|
衰弱条件:
|
||||||
|
1. 中枢内笔数 >= 5(有足够的数据判断)
|
||||||
|
2. 最后一笔的MACD面积相比同方向前一笔出现背驰(macd_div < 1)
|
||||||
|
3. 中枢类型为收敛型或常规型
|
||||||
|
|
||||||
|
返回: True表示中枢力量衰弱,可能反向
|
||||||
|
"""
|
||||||
|
if len(self.bi_list) < 5:
|
||||||
|
return False
|
||||||
|
last_bi = self.bi_list[-1]
|
||||||
|
# 最后一笔与同方向前一笔比较MACD面积是否背驰
|
||||||
|
if last_bi.macd_div > 0 and last_bi.macd_div < 1.0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_zs_strength(self):
|
||||||
|
"""
|
||||||
|
计算中枢强度,用于辅助判断中枢延续还是反向
|
||||||
|
|
||||||
|
返回字典包含:
|
||||||
|
- type: 中枢类型 (Chan_ZS_TYPE)
|
||||||
|
- bi_count: 中枢内笔数
|
||||||
|
- range_ratio: 中枢区间占比 = (zg - zd) / (gg - dd),越小说明中枢越紧密
|
||||||
|
- last_bi_div: 最后一笔的MACD背驰比率
|
||||||
|
- is_weakening: 是否衰弱
|
||||||
|
- is_extending: 是否在延伸(笔数 >= 9 可能升级)
|
||||||
|
"""
|
||||||
|
total_range = self.gg - self.dd if self.gg != self.dd else 1
|
||||||
|
zs_range = self.zg - self.zd if self.zg != self.zd else 0
|
||||||
|
range_ratio = zs_range / total_range if total_range > 0 else 0
|
||||||
|
last_bi_div = self.bi_list[-1].macd_div if len(self.bi_list) > 0 else 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
'type': self.zs_type,
|
||||||
|
'bi_count': len(self.bi_list),
|
||||||
|
'range_ratio': round(range_ratio, 4),
|
||||||
|
'last_bi_div': round(last_bi_div, 4),
|
||||||
|
'is_weakening': self.is_weakening(),
|
||||||
|
'is_extending': len(self.bi_list) >= 9, # 9段可能升级
|
||||||
|
}
|
||||||
+32
@@ -10,10 +10,42 @@ class Chan_DATA_SRC(Enum):
|
|||||||
class Chan_ZS_DIR(Enum):
|
class Chan_ZS_DIR(Enum):
|
||||||
UP = auto()
|
UP = auto()
|
||||||
DOWN = auto()
|
DOWN = auto()
|
||||||
|
|
||||||
|
class Chan_ZS_TYPE(Enum):
|
||||||
|
"""中枢类型分类"""
|
||||||
|
NORMAL = auto() # 常规中枢:高低点无明显趋势,区间震荡
|
||||||
|
RISING = auto() # 上升中枢:高点抬高,低点也抬高,重心上移
|
||||||
|
FALLING = auto() # 下行中枢:高点降低,低点也降低,重心下移
|
||||||
|
CONVERGING = auto() # 收敛中枢:高点降低,低点抬高,区间收窄(三角收敛)
|
||||||
|
DIVERGING = auto() # 扩散中枢:高点抬高,低点降低,区间扩大(喇叭口)
|
||||||
class Chan_K_DIR(Enum):
|
class Chan_K_DIR(Enum):
|
||||||
BULL = auto()
|
BULL = auto()
|
||||||
BEAR = auto()
|
BEAR = auto()
|
||||||
CROSS = auto()
|
CROSS = auto()
|
||||||
|
|
||||||
|
class Chan_EMA_POS(Enum):
|
||||||
|
"""K线与任意EMA的位置关系(与趋势方向无关的客观分类,支持threshold容差)"""
|
||||||
|
ABOVE = auto() # 完全在EMA上方(远离):low > ema + threshold
|
||||||
|
NEAR_ABOVE = auto() # 在EMA上方但接近:ema < low <= ema + threshold
|
||||||
|
CROSS_CLOSE_ABOVE = auto() # 跨越EMA,收盘在上方:close > ema, low <= ema(含threshold范围内触碰)
|
||||||
|
ON_EMA = auto() # 收盘价在EMA附近:abs(close - ema) <= threshold
|
||||||
|
CROSS_CLOSE_BELOW = auto() # 跨越EMA,收盘在下方:close < ema, high >= ema(含threshold范围内触碰)
|
||||||
|
NEAR_BELOW = auto() # 在EMA下方但接近:ema - threshold <= high < ema
|
||||||
|
BELOW = auto() # 完全在EMA下方(远离):high < ema - threshold
|
||||||
|
UNKNOWN = auto() # 未知(EMA值无效)
|
||||||
|
|
||||||
|
class Chan_EMA_SEMANTIC(Enum):
|
||||||
|
"""K线与EMA结合趋势方向的语义状态(用于交易判断)"""
|
||||||
|
STRONG_TREND = auto() # 7: 顺势K线完全在EMA趋势侧(强势,远未及EMA)
|
||||||
|
TREND_SIDE = auto() # 6: 完全在EMA趋势侧(正常趋势运行)
|
||||||
|
RECOVER = auto() # 5: 逆势后穿越EMA回到趋势侧(收复EMA,趋势恢复)
|
||||||
|
TOUCH_FAIL = auto() # 4: 逆势触碰EMA但未穿越(反弹/反抽力度不足)
|
||||||
|
DEEP_COUNTER = auto() # 3: 完全在EMA逆势侧(深度回调/反抽)
|
||||||
|
BREAK = auto() # 2: 穿越EMA,收盘在逆势侧(支撑/压力失败)
|
||||||
|
TOUCH_HOLD = auto() # 1: 触碰EMA,收盘守住趋势侧(支撑/压力有效)
|
||||||
|
WEAK_COUNTER = auto() # 8: 逆势K线完全在EMA逆势侧(弱势,远未到EMA)
|
||||||
|
APPROACHING = auto() # 9: K线接近EMA但未触碰(即将测试支撑/压力)
|
||||||
|
NEUTRAL = auto() # 0: 盘整/无法判断
|
||||||
class Chan_KL_TYPE(Enum):
|
class Chan_KL_TYPE(Enum):
|
||||||
K_1S = auto()
|
K_1S = auto()
|
||||||
K_1M = auto()
|
K_1M = auto()
|
||||||
|
|||||||
+254
-9
@@ -1,7 +1,7 @@
|
|||||||
import copy
|
import copy
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR, Chan_KLC_FX, Chan_K_DIR, Chan_MACD_STATE, Chan_PRICE_TREND
|
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
|
||||||
import ChanKLU
|
import ChanKLU
|
||||||
import ChanCTime
|
import ChanCTime
|
||||||
|
|
||||||
@@ -45,11 +45,247 @@ class ChanKLC():
|
|||||||
self.state = Chan_MACD_STATE.UNKNOWN
|
self.state = Chan_MACD_STATE.UNKNOWN
|
||||||
self.continue_div = False
|
self.continue_div = False
|
||||||
self.separate_div = False
|
self.separate_div = False
|
||||||
self.ema52 = klu.ema52
|
|
||||||
self.ema24 = klu.ema24
|
self.ema24 = klu.ema24
|
||||||
|
self.ema52 = klu.ema52
|
||||||
|
self.ema104 = klu.ema104
|
||||||
|
self.ema156 = klu.ema156
|
||||||
|
self.ema208 = klu.ema208
|
||||||
self.trend = Chan_PRICE_TREND.UNKNOWN
|
self.trend = Chan_PRICE_TREND.UNKNOWN
|
||||||
self.exception = klu.exception
|
self.exception = klu.exception
|
||||||
self.klc_dir = Chan_KLINE_DIR.UP if klu.close > klu.open else Chan_KLINE_DIR.DOWN
|
self.klc_dir = Chan_KLINE_DIR.UP if klu.close > klu.open else Chan_KLINE_DIR.DOWN
|
||||||
|
self.ema_dir = klu.ema_dir
|
||||||
|
self.bsp = False
|
||||||
|
# EMA状态字典:key为EMA名称,value为 {'pos': Chan_EMA_POS, 'semantic': Chan_EMA_SEMANTIC}
|
||||||
|
self.ema_status = {}
|
||||||
|
# 向后兼容:保留 ema52_status 和 ema52_pos
|
||||||
|
self.ema52_status = 0
|
||||||
|
self.ema52_pos = Chan_EMA_POS.UNKNOWN
|
||||||
|
self.cal_all_ema_status()
|
||||||
|
|
||||||
|
# ==================== EMA 通用计算方法 ====================
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def cal_ema_pos(high, low, close, ema_value, threshold=0):
|
||||||
|
"""
|
||||||
|
计算K线与任意EMA的客观位置关系(与趋势方向无关,支持threshold容差)
|
||||||
|
|
||||||
|
参数:
|
||||||
|
high, low, close: K线的高低收盘价
|
||||||
|
ema_value: EMA的值
|
||||||
|
threshold: 容差值(绝对值),在此范围内视为"接近/触碰"
|
||||||
|
例如 BTC 价格 $100,000 时 threshold=100 表示差100点视为触碰
|
||||||
|
返回:
|
||||||
|
Chan_EMA_POS 枚举值
|
||||||
|
|
||||||
|
判断逻辑(以threshold=100, ema=97000为例):
|
||||||
|
ema_zone = [96900, 97100] (EMA上下各扩展threshold)
|
||||||
|
|
||||||
|
ABOVE: low > 97100 K线完全在zone上方(远离EMA)
|
||||||
|
NEAR_ABOVE: 97000 < low <= 97100 K线在上方但下影线进入zone(接近EMA)
|
||||||
|
CROSS_CLOSE_ABOVE: close > 97000, low <= 97000 K线穿越EMA,收盘在上方
|
||||||
|
ON_EMA: abs(close - 97000) <= 100 收盘价在zone内
|
||||||
|
CROSS_CLOSE_BELOW: close < 97000, high >= 97000 K线穿越EMA,收盘在下方
|
||||||
|
NEAR_BELOW: 96900 <= high < 97000 K线在下方但上影线进入zone(接近EMA)
|
||||||
|
BELOW: high < 96900 K线完全在zone下方(远离EMA)
|
||||||
|
"""
|
||||||
|
if ema_value is None or ema_value == 0:
|
||||||
|
return Chan_EMA_POS.UNKNOWN
|
||||||
|
|
||||||
|
ema_upper = ema_value + threshold # EMA zone 上界
|
||||||
|
ema_lower = ema_value - threshold # EMA zone 下界
|
||||||
|
|
||||||
|
# 1. 收盘价在EMA附近(zone内)
|
||||||
|
if threshold > 0 and abs(close - ema_value) <= threshold:
|
||||||
|
# 收盘价在zone内,但还需要看是否有实际穿越
|
||||||
|
if low <= ema_value and close >= ema_value:
|
||||||
|
return Chan_EMA_POS.CROSS_CLOSE_ABOVE # 实际穿越了精确EMA线
|
||||||
|
elif high >= ema_value and close <= ema_value:
|
||||||
|
return Chan_EMA_POS.CROSS_CLOSE_BELOW
|
||||||
|
return Chan_EMA_POS.ON_EMA
|
||||||
|
|
||||||
|
# 2. K线实际穿越了精确的EMA线
|
||||||
|
if close > ema_value and low <= ema_value:
|
||||||
|
return Chan_EMA_POS.CROSS_CLOSE_ABOVE
|
||||||
|
if close < ema_value and high >= ema_value:
|
||||||
|
return Chan_EMA_POS.CROSS_CLOSE_BELOW
|
||||||
|
if close == ema_value:
|
||||||
|
return Chan_EMA_POS.ON_EMA
|
||||||
|
|
||||||
|
# 3. 没有实际穿越,检查是否"接近"(在threshold zone内)
|
||||||
|
if close > ema_value:
|
||||||
|
# K线在EMA上方
|
||||||
|
if threshold > 0 and low <= ema_upper:
|
||||||
|
return Chan_EMA_POS.NEAR_ABOVE # 下影线进入zone,接近但未触碰
|
||||||
|
return Chan_EMA_POS.ABOVE # 远离EMA
|
||||||
|
else:
|
||||||
|
# K线在EMA下方
|
||||||
|
if threshold > 0 and high >= ema_lower:
|
||||||
|
return Chan_EMA_POS.NEAR_BELOW # 上影线进入zone,接近但未触碰
|
||||||
|
return Chan_EMA_POS.BELOW # 远离EMA
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def cal_ema_semantic(ema_pos, kline_dir, ema_dir):
|
||||||
|
"""
|
||||||
|
根据客观位置 + K线方向 + 趋势方向,计算语义状态
|
||||||
|
|
||||||
|
参数:
|
||||||
|
ema_pos: Chan_EMA_POS 客观位置
|
||||||
|
kline_dir: Chan_KLINE_DIR K线方向 (UP/DOWN/COMBINE/INCLUDED)
|
||||||
|
ema_dir: int 趋势方向 (1=多头, -1=空头, 0=盘整)
|
||||||
|
返回:
|
||||||
|
Chan_EMA_SEMANTIC 枚举值
|
||||||
|
|
||||||
|
语义含义(以多头为例,空头完全对称):
|
||||||
|
TOUCH_HOLD: 触碰EMA,收盘守住趋势侧(支撑/压力有效)
|
||||||
|
BREAK: 穿越EMA,收盘在逆势侧(支撑/压力失败)
|
||||||
|
DEEP_COUNTER: 完全在EMA逆势侧(深度回调/反抽)
|
||||||
|
TOUCH_FAIL: 逆势触碰EMA但未穿越(反弹/反抽力度不足)
|
||||||
|
RECOVER: 逆势后穿越EMA回到趋势侧(收复EMA)
|
||||||
|
TREND_SIDE: 完全在EMA趋势侧(正常运行)
|
||||||
|
STRONG_TREND: 顺势K线完全在EMA趋势侧(强势,远未及EMA)
|
||||||
|
WEAK_COUNTER: 逆势K线完全在EMA逆势侧(弱势,远未到EMA)
|
||||||
|
"""
|
||||||
|
if ema_pos == Chan_EMA_POS.UNKNOWN:
|
||||||
|
return Chan_EMA_SEMANTIC.NEUTRAL
|
||||||
|
|
||||||
|
# 统一处理:将多头/盘整和空头映射到同一套逻辑
|
||||||
|
# is_bull=True 时,"趋势侧"=上方,"逆势侧"=下方
|
||||||
|
# is_bull=False时,"趋势侧"=下方,"逆势侧"=上方
|
||||||
|
is_bull = ema_dir >= 0 # 多头和盘整都按多头逻辑处理
|
||||||
|
|
||||||
|
# K线是否是顺势方向(多头下UP为顺势,空头下DOWN为顺势)
|
||||||
|
is_trend_kline = (kline_dir == Chan_KLINE_DIR.UP) if is_bull else (kline_dir == Chan_KLINE_DIR.DOWN)
|
||||||
|
is_counter_kline = (kline_dir == Chan_KLINE_DIR.DOWN) if is_bull else (kline_dir == Chan_KLINE_DIR.UP)
|
||||||
|
|
||||||
|
# 位置映射:多头下 ABOVE=趋势侧, BELOW=逆势侧; 空头反过来
|
||||||
|
trend_side = Chan_EMA_POS.ABOVE if is_bull else Chan_EMA_POS.BELOW
|
||||||
|
counter_side = Chan_EMA_POS.BELOW if is_bull else Chan_EMA_POS.ABOVE
|
||||||
|
near_trend = Chan_EMA_POS.NEAR_ABOVE if is_bull else Chan_EMA_POS.NEAR_BELOW
|
||||||
|
near_counter = Chan_EMA_POS.NEAR_BELOW if is_bull else Chan_EMA_POS.NEAR_ABOVE
|
||||||
|
cross_to_trend = Chan_EMA_POS.CROSS_CLOSE_ABOVE if is_bull else Chan_EMA_POS.CROSS_CLOSE_BELOW
|
||||||
|
cross_to_counter = Chan_EMA_POS.CROSS_CLOSE_BELOW if is_bull else Chan_EMA_POS.CROSS_CLOSE_ABOVE
|
||||||
|
|
||||||
|
# COMBINE / INCLUDED 方向:只看位置,不区分强弱
|
||||||
|
if not is_trend_kline and not is_counter_kline:
|
||||||
|
if ema_pos == trend_side:
|
||||||
|
return Chan_EMA_SEMANTIC.TREND_SIDE
|
||||||
|
elif ema_pos in (near_trend, cross_to_trend, Chan_EMA_POS.ON_EMA):
|
||||||
|
return Chan_EMA_SEMANTIC.APPROACHING
|
||||||
|
elif ema_pos in (near_counter, cross_to_counter):
|
||||||
|
return Chan_EMA_SEMANTIC.APPROACHING
|
||||||
|
elif ema_pos == counter_side:
|
||||||
|
return Chan_EMA_SEMANTIC.DEEP_COUNTER
|
||||||
|
return Chan_EMA_SEMANTIC.NEUTRAL
|
||||||
|
|
||||||
|
# 逆势K线(多头下的下跌K线 / 空头下的上涨K线)
|
||||||
|
if is_counter_kline:
|
||||||
|
if ema_pos == trend_side:
|
||||||
|
return Chan_EMA_SEMANTIC.STRONG_TREND # 逆势K线仍在趋势侧(回调很浅)
|
||||||
|
elif ema_pos == near_trend:
|
||||||
|
return Chan_EMA_SEMANTIC.APPROACHING # 接近EMA,即将测试支撑/压力
|
||||||
|
elif ema_pos == cross_to_trend:
|
||||||
|
return Chan_EMA_SEMANTIC.TOUCH_HOLD # 触碰EMA后守住趋势侧
|
||||||
|
elif ema_pos == Chan_EMA_POS.ON_EMA:
|
||||||
|
return Chan_EMA_SEMANTIC.TOUCH_HOLD # 收盘在EMA附近,视为守住
|
||||||
|
elif ema_pos == cross_to_counter:
|
||||||
|
return Chan_EMA_SEMANTIC.BREAK # 穿越EMA到逆势侧
|
||||||
|
elif ema_pos == near_counter:
|
||||||
|
return Chan_EMA_SEMANTIC.BREAK # 接近EMA但收盘在逆势侧,也视为击穿
|
||||||
|
elif ema_pos == counter_side:
|
||||||
|
return Chan_EMA_SEMANTIC.DEEP_COUNTER # 完全在逆势侧
|
||||||
|
|
||||||
|
# 顺势K线(多头下的上涨K线 / 空头下的下跌K线)
|
||||||
|
if is_trend_kline:
|
||||||
|
if ema_pos == counter_side:
|
||||||
|
return Chan_EMA_SEMANTIC.WEAK_COUNTER # 顺势K线却在逆势侧(弱势)
|
||||||
|
elif ema_pos == near_counter:
|
||||||
|
return Chan_EMA_SEMANTIC.APPROACHING # 从逆势侧接近EMA
|
||||||
|
elif ema_pos == cross_to_counter:
|
||||||
|
return Chan_EMA_SEMANTIC.TOUCH_FAIL # 触碰EMA但未穿越回趋势侧
|
||||||
|
elif ema_pos == Chan_EMA_POS.ON_EMA:
|
||||||
|
return Chan_EMA_SEMANTIC.TOUCH_FAIL # 收盘在EMA附近,未确认突破
|
||||||
|
elif ema_pos == cross_to_trend:
|
||||||
|
return Chan_EMA_SEMANTIC.RECOVER # 从逆势侧穿越回趋势侧
|
||||||
|
elif ema_pos == near_trend:
|
||||||
|
return Chan_EMA_SEMANTIC.RECOVER # 接近趋势侧(刚收复EMA附近)
|
||||||
|
elif ema_pos == trend_side:
|
||||||
|
return Chan_EMA_SEMANTIC.TREND_SIDE # 完全在趋势侧(正常)
|
||||||
|
|
||||||
|
return Chan_EMA_SEMANTIC.NEUTRAL
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def semantic_to_int(semantic):
|
||||||
|
"""将 Chan_EMA_SEMANTIC 枚举转换为整数,兼容旧的 ema52_status 数值"""
|
||||||
|
mapping = {
|
||||||
|
Chan_EMA_SEMANTIC.TOUCH_HOLD: 1,
|
||||||
|
Chan_EMA_SEMANTIC.BREAK: 2,
|
||||||
|
Chan_EMA_SEMANTIC.DEEP_COUNTER: 3,
|
||||||
|
Chan_EMA_SEMANTIC.TOUCH_FAIL: 4,
|
||||||
|
Chan_EMA_SEMANTIC.RECOVER: 5,
|
||||||
|
Chan_EMA_SEMANTIC.TREND_SIDE: 6,
|
||||||
|
Chan_EMA_SEMANTIC.STRONG_TREND: 7,
|
||||||
|
Chan_EMA_SEMANTIC.WEAK_COUNTER: 8,
|
||||||
|
Chan_EMA_SEMANTIC.APPROACHING: 9,
|
||||||
|
Chan_EMA_SEMANTIC.NEUTRAL: 0,
|
||||||
|
}
|
||||||
|
return mapping.get(semantic, 0)
|
||||||
|
|
||||||
|
# threshold_pct: 阈值百分比,用于自动计算绝对阈值
|
||||||
|
# 例如 0.001 表示 EMA 值的 0.1%,BTC $100,000 时 threshold = $100
|
||||||
|
threshold_pct = 0.001
|
||||||
|
|
||||||
|
def cal_all_ema_status(self):
|
||||||
|
"""
|
||||||
|
统一计算所有EMA与K线的位置关系和语义状态
|
||||||
|
|
||||||
|
threshold 自动按 EMA 值的百分比计算(cls.threshold_pct,默认0.1%)
|
||||||
|
- BTC $100,000 时:threshold ≈ $100
|
||||||
|
- ETH $3,000 时:threshold ≈ $3
|
||||||
|
- SOL $200 时:threshold ≈ $0.2
|
||||||
|
|
||||||
|
结果存储在 self.ema_status 字典中,格式:
|
||||||
|
{
|
||||||
|
'ema24': {'pos': Chan_EMA_POS, 'semantic': Chan_EMA_SEMANTIC, 'value': float, 'threshold': float},
|
||||||
|
'ema52': {...},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
同时保持向后兼容:self.ema52_pos 和 self.ema52_status
|
||||||
|
"""
|
||||||
|
ema_configs = {
|
||||||
|
'ema24': self.ema24,
|
||||||
|
'ema52': self.ema52,
|
||||||
|
'ema104': self.ema104,
|
||||||
|
'ema156': self.ema156,
|
||||||
|
'ema208': self.ema208,
|
||||||
|
}
|
||||||
|
self.ema_status = {}
|
||||||
|
for name, value in ema_configs.items():
|
||||||
|
# 按 EMA 值的百分比自动计算阈值
|
||||||
|
threshold = abs(value) * self.threshold_pct if value and self.threshold_pct > 0 else 0
|
||||||
|
pos = ChanKLC.cal_ema_pos(self.high, self.low, self.close, value, threshold)
|
||||||
|
semantic = ChanKLC.cal_ema_semantic(pos, self.dir, self.ema_dir)
|
||||||
|
self.ema_status[name] = {
|
||||||
|
'pos': pos,
|
||||||
|
'semantic': semantic,
|
||||||
|
'value': value,
|
||||||
|
'threshold': threshold,
|
||||||
|
}
|
||||||
|
# 向后兼容
|
||||||
|
self.ema52_pos = self.ema_status['ema52']['pos']
|
||||||
|
self.ema52_status = ChanKLC.semantic_to_int(self.ema_status['ema52']['semantic'])
|
||||||
|
|
||||||
|
def get_ema_pos(self, ema_name):
|
||||||
|
"""获取指定EMA的客观位置,如 klc.get_ema_pos('ema24')"""
|
||||||
|
if ema_name in self.ema_status:
|
||||||
|
return self.ema_status[ema_name]['pos']
|
||||||
|
return Chan_EMA_POS.UNKNOWN
|
||||||
|
|
||||||
|
def get_ema_semantic(self, ema_name):
|
||||||
|
"""获取指定EMA的语义状态,如 klc.get_ema_semantic('ema52')"""
|
||||||
|
if ema_name in self.ema_status:
|
||||||
|
return self.ema_status[ema_name]['semantic']
|
||||||
|
return Chan_EMA_SEMANTIC.NEUTRAL
|
||||||
def set_trend(self, trend):
|
def set_trend(self, trend):
|
||||||
self.trend = trend
|
self.trend = trend
|
||||||
def to_string(self):
|
def to_string(self):
|
||||||
@@ -123,14 +359,23 @@ class ChanKLC():
|
|||||||
self.rsi += self.klu_list[index].rsi
|
self.rsi += self.klu_list[index].rsi
|
||||||
self.volume_ratio += self.klu_list[index].volume_ratio
|
self.volume_ratio += self.klu_list[index].volume_ratio
|
||||||
self.macdhist += self.klu_list[index].macdhist
|
self.macdhist += self.klu_list[index].macdhist
|
||||||
self.ema52 += self.klu_list[index].ema52
|
|
||||||
self.ema24 += self.klu_list[index].ema24
|
self.ema24 += self.klu_list[index].ema24
|
||||||
self.rsi = self.rsi / len(self.klu_list)
|
self.ema52 += self.klu_list[index].ema52
|
||||||
self.volume_ratio = self.volume_ratio / len(self.klu_list)
|
self.ema104 += self.klu_list[index].ema104
|
||||||
self.volume = self.volume / len(self.klu_list)
|
self.ema156 += self.klu_list[index].ema156
|
||||||
self.macdhist = self.macdhist / len(self.klu_list)
|
self.ema208 += self.klu_list[index].ema208
|
||||||
self.ema52 = self.ema52 / len(self.klu_list)
|
if self.ema_dir != self.klu_list[index].ema_dir:
|
||||||
self.ema24 = self.ema24 / len(self.klu_list)
|
self.ema_dir = 0
|
||||||
|
n = len(self.klu_list)
|
||||||
|
self.rsi = self.rsi / n
|
||||||
|
self.volume_ratio = self.volume_ratio / n
|
||||||
|
self.volume = self.volume / n
|
||||||
|
self.macdhist = self.macdhist / n
|
||||||
|
self.ema24 = self.ema24 / n
|
||||||
|
self.ema52 = self.ema52 / n
|
||||||
|
self.ema104 = self.ema104 / n
|
||||||
|
self.ema156 = self.ema156 / n
|
||||||
|
self.ema208 = self.ema208 / n
|
||||||
if len(self.klu_list) > 0:
|
if len(self.klu_list) > 0:
|
||||||
self.macd = self.klu_list[-1].macd
|
self.macd = self.klu_list[-1].macd
|
||||||
self.signal = self.klu_list[-1].signal
|
self.signal = self.klu_list[-1].signal
|
||||||
|
|||||||
+6
-4
@@ -69,6 +69,8 @@ class ChanKLU:
|
|||||||
self.mode4_touch52_no_zero = False # 先触碰EMA52但黄白线未归零
|
self.mode4_touch52_no_zero = False # 先触碰EMA52但黄白线未归零
|
||||||
self.div_type = "none" # {bearish, bullish, hidden_bearish, hidden_bullish, none}
|
self.div_type = "none" # {bearish, bullish, hidden_bearish, hidden_bullish, none}
|
||||||
self.div_score = 0.0 # 背离强度(0-100)
|
self.div_score = 0.0 # 背离强度(0-100)
|
||||||
|
self.ema_dir = 0
|
||||||
|
self.get_ema_dir()
|
||||||
#print(self.open, self.close, self.high, self.low, self.candle_dir, self.strength)
|
#print(self.open, self.close, self.high, self.low, self.candle_dir, self.strength)
|
||||||
def set_macd_state(self, state):
|
def set_macd_state(self, state):
|
||||||
self.macd_state = state
|
self.macd_state = state
|
||||||
@@ -123,14 +125,14 @@ class ChanKLU:
|
|||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
def ema_pattern(self):
|
def get_ema_dir(self):
|
||||||
if self.check_indicators():
|
if self.check_indicators():
|
||||||
if self.ema24 > self.ema52 and self.ema52 > self.ema104 and self.ema104 > self.ema156:
|
if self.ema24 > self.ema52 and self.ema52 > self.ema104 and self.ema104 > self.ema156:
|
||||||
return 1
|
self.ema_dir = 1
|
||||||
elif self.ema24 < self.ema52 and self.ema52 < self.ema104 and self.ema104 < self.ema156:
|
elif self.ema24 < self.ema52 and self.ema52 < self.ema104 and self.ema104 < self.ema156:
|
||||||
return -1
|
self.ema_dir = -1
|
||||||
else:
|
else:
|
||||||
return 0
|
self.ema_dir = 0
|
||||||
def check_indicators(self):
|
def check_indicators(self):
|
||||||
if self.ema156 == 0:
|
if self.ema156 == 0:
|
||||||
return False
|
return False
|
||||||
|
|||||||
+10
-5
@@ -51,9 +51,9 @@ class ChanLun():
|
|||||||
self.time1y = 12*30*24*60
|
self.time1y = 12*30*24*60
|
||||||
self.time_M_intervals = [2*30*24*60, 3*30*24*60, 6*30*24*60, 12*30*24*60]
|
self.time_M_intervals = [2*30*24*60, 3*30*24*60, 6*30*24*60, 12*30*24*60]
|
||||||
self.time_M_symbols = ['2M', '3M', '6M', '1y']
|
self.time_M_symbols = ['2M', '3M', '6M', '1y']
|
||||||
self.time_symbols = ['1m', '2m', '3m', '5m', '10m', '15m', '20m', '30m', '45m','1h', '2h', '4h', '6h', '8h', '12h', '16h', '1d', '2d', '3d', '1w', '2w', '1M', '3M', '6M', '1y']
|
self.time_symbols = ['1m', '2m', '3m', '5m', '10m', '15m', '20m', '30m', '45m','1h', '2h', '4h', '6h', '8h', '12h', '16h', '1d', '2d', '3d']
|
||||||
self.tf_df_dict = {}
|
self.tf_df_dict = {}
|
||||||
self.ema_symbols = ['5m', '10m', '15m', '20m', '30m', '45m', '1h', '2h', '4h', '6h', '8h', '12h', '16h', '1d', '2d', '3d']
|
self.ema_symbols = ['5m', '15m', '30m', '45m', '1h', '2h', '4h', '8h', '12h', '1d', '2d', '3d']
|
||||||
self.tf_df = TF_DF()
|
self.tf_df = TF_DF()
|
||||||
def init_data(self, dataframe, intervals, timeframes):
|
def init_data(self, dataframe, intervals, timeframes):
|
||||||
for index in range(0, len(intervals)):
|
for index in range(0, len(intervals)):
|
||||||
@@ -74,10 +74,10 @@ class ChanLun():
|
|||||||
if dataframe_d is not None:
|
if dataframe_d is not None:
|
||||||
self.tf_df_dict['1d'] = TF_DF(dataframe_d, 1, '1d')
|
self.tf_df_dict['1d'] = TF_DF(dataframe_d, 1, '1d')
|
||||||
self.init_data(dataframe_d, self.time_d_intervals, self.time_d_symbols)
|
self.init_data(dataframe_d, self.time_d_intervals, self.time_d_symbols)
|
||||||
if dataframe_w is not None:
|
if dataframe_w is not None and False:
|
||||||
self.tf_df_dict['1w'] = TF_DF(dataframe_w, 1, '1w')
|
self.tf_df_dict['1w'] = TF_DF(dataframe_w, 1, '1w')
|
||||||
self.init_data(dataframe_w, self.time_w_intervals, self.time_w_symbols)
|
self.init_data(dataframe_w, self.time_w_intervals, self.time_w_symbols)
|
||||||
if dataframe_M is not None:
|
if dataframe_M is not None and False:
|
||||||
self.tf_df_dict['1M'] = TF_DF(dataframe_M, 1, '1M')
|
self.tf_df_dict['1M'] = TF_DF(dataframe_M, 1, '1M')
|
||||||
self.init_data(dataframe_M, self.time_M_intervals, self.time_M_symbols)
|
self.init_data(dataframe_M, self.time_M_intervals, self.time_M_symbols)
|
||||||
def get_ema52_dict(self):
|
def get_ema52_dict(self):
|
||||||
@@ -105,7 +105,12 @@ class ChanLun():
|
|||||||
if abs(price - ema52_dict[key]) < 100:
|
if abs(price - ema52_dict[key]) < 100:
|
||||||
key_list.append(key)
|
key_list.append(key)
|
||||||
return key_list
|
return key_list
|
||||||
|
def get_ema_bsp(self, long_tf='1h', short_tf='15m'):
|
||||||
|
if long_tf in self.tf_df_dict and short_tf in self.tf_df_dict:
|
||||||
|
long_df = self.tf_df_dict[long_tf]
|
||||||
|
short_df = self.tf_df_dict[short_tf]
|
||||||
|
return long_df.get_ema_bsp(short_df)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+9
-2
@@ -12,8 +12,15 @@
|
|||||||
大周期看多,小周期跌完做多,跌完:顶分型和EMA52归零轴反弹
|
大周期看多,小周期跌完做多,跌完:顶分型和EMA52归零轴反弹
|
||||||
大周期看空,小周期涨完做空,涨完:顶分型和EMA52归零轴反抽
|
大周期看空,小周期涨完做空,涨完:顶分型和EMA52归零轴反抽
|
||||||
|
|
||||||
多周期EMA52
|
中枢分类
|
||||||
|
常规中枢
|
||||||
|
上升中枢
|
||||||
|
收敛中枢
|
||||||
|
扩散中枢
|
||||||
|
下行中枢
|
||||||
|
止损放到顶底分型的高低点
|
||||||
|
1. 从大周期开始找到价格接近ema52,MACD也接近零轴的周期,需要看这个周期的长级别是否高位空,大趋势方向
|
||||||
|
2. 然后去小于这个时间周期的周期找买卖点,小级趋势方向和大趋势相反并且开始反向,小级别需要检查MACD是否归零轴反转,同时价格是否接近EMA52
|
||||||
|
|
||||||
|
|
||||||
EMA52线的反弹比零轴的反弹弱
|
EMA52线的反弹比零轴的反弹弱
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ class TF_DF():
|
|||||||
self.zs_list = []
|
self.zs_list = []
|
||||||
self.bsp_list = []
|
self.bsp_list = []
|
||||||
self.seg_list = []
|
self.seg_list = []
|
||||||
|
self.klc_fx_list = []
|
||||||
self.klu_list = self.cal_kl_data(self.dataframe)
|
self.klu_list = self.cal_kl_data(self.dataframe)
|
||||||
self.klc_list = self.get_klc_list(self.klu_list)
|
self.klc_list = self.get_klc_list(self.klu_list)
|
||||||
self.bi_list = self.cal_bi_list(self.klc_list)
|
self.bi_list = self.cal_bi_list(self.klc_list)
|
||||||
@@ -47,6 +48,8 @@ class TF_DF():
|
|||||||
self.zs_list = self.get_zs_list(self.bi_list, self.seg_list)
|
self.zs_list = self.get_zs_list(self.bi_list, self.seg_list)
|
||||||
self.chanmacd = ChanMACD(self.klu_list)
|
self.chanmacd = ChanMACD(self.klu_list)
|
||||||
self.klu_list = self.chanmacd.cal_macd_state()
|
self.klu_list = self.chanmacd.cal_macd_state()
|
||||||
|
|
||||||
|
|
||||||
def get_ema52(self, index=-1):
|
def get_ema52(self, index=-1):
|
||||||
if self.klu_list:
|
if self.klu_list:
|
||||||
ema52_value = self.klu_list[index].ema52
|
ema52_value = self.klu_list[index].ema52
|
||||||
@@ -136,14 +139,14 @@ class TF_DF():
|
|||||||
return klu_state_list
|
return klu_state_list
|
||||||
def check_fx(self, klc):
|
def check_fx(self, klc):
|
||||||
if klc.pre and klc.next:
|
if klc.pre and klc.next:
|
||||||
if klc.high > klc.pre.high and klc.high > klc.next.high and klc.low > klc.pre.low and klc.low > klc.next.low:
|
if klc.high > klc.pre.high and klc.high > klc.next.high and klc.low > klc.pre.low and klc.low > klc.next.low and klc.macd > 0:
|
||||||
if klc.pre.pre and klc.next.next:
|
if klc.pre.pre and klc.next.next:
|
||||||
if klc.high > klc.pre.pre.high and klc.high > klc.next.next.high:
|
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:
|
#if (klc.close > klc.ema52 or klc.next.close > klc.next.ema52) and klc.macd > 0:
|
||||||
klc.set_fx(Chan_FX_TYPE.TOP)
|
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")
|
#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
|
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:
|
elif klc.low < klc.pre.low and klc.low < klc.next.low and klc.high < klc.pre.high and klc.high < klc.next.high and klc.macd < 0:
|
||||||
if klc.pre.pre and klc.next.next:
|
if klc.pre.pre and klc.next.next:
|
||||||
if klc.low < klc.pre.pre.low and klc.low < klc.next.next.low:
|
if klc.low < klc.pre.pre.low and klc.low < klc.next.next.low:
|
||||||
#if (klc.close < klc.ema52 or klc.next.close < klc.next.ema52) and klc.macd < 0:
|
#if (klc.close < klc.ema52 or klc.next.close < klc.next.ema52) and klc.macd < 0:
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://schema.freqtrade.io/schema.json",
|
||||||
|
"max_open_trades": 1,
|
||||||
|
"stake_currency": "USDT",
|
||||||
|
"stake_amount": "unlimited",
|
||||||
|
"tradable_balance_ratio": 0.99,
|
||||||
|
"fiat_display_currency": "USD",
|
||||||
|
"dry_run": true,
|
||||||
|
"db_url": "sqlite:///tradesv3.chanlun_btc_15.sqlite",
|
||||||
|
"dry_run_wallet": 1000,
|
||||||
|
"cancel_open_orders_on_exit": true,
|
||||||
|
"trading_mode": "futures",
|
||||||
|
"margin_mode": "isolated",
|
||||||
|
"can_short" : true,
|
||||||
|
"timeframe" : "1m",
|
||||||
|
"process_only_new_candles" : false,
|
||||||
|
"unfilledtimeout": {
|
||||||
|
"entry": 1,
|
||||||
|
"exit": 1,
|
||||||
|
"exit_timeout_count": 5,
|
||||||
|
"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": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
|
||||||
|
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
|
||||||
|
"ccxt_config": {},
|
||||||
|
"ccxt_async_config": {},
|
||||||
|
"pair_whitelist": [
|
||||||
|
"BTC/USDT:USDT"
|
||||||
|
],
|
||||||
|
"pair_blacklist": [
|
||||||
|
"BNB/.*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pairlists": [
|
||||||
|
{
|
||||||
|
"method": "StaticPairList",
|
||||||
|
"number_assets": 1,
|
||||||
|
"sort_key": "quoteVolume",
|
||||||
|
"min_value": 0,
|
||||||
|
"refresh_period": 1800
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"telegram": {
|
||||||
|
"enabled": true,
|
||||||
|
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
|
||||||
|
"chat_id": "580807463"
|
||||||
|
},
|
||||||
|
"api_server": {
|
||||||
|
"enabled": true,
|
||||||
|
"listen_ip_address": "0.0.0.0",
|
||||||
|
"listen_port": 8820,
|
||||||
|
"verbosity": "error",
|
||||||
|
"enable_openapi": false,
|
||||||
|
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
|
||||||
|
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
|
||||||
|
"CORS_origins": [],
|
||||||
|
"username": "freqtrader",
|
||||||
|
"password": "FreqTrade007"
|
||||||
|
},
|
||||||
|
"bot_name": "freqtrade",
|
||||||
|
"initial_state": "running",
|
||||||
|
"force_entry_enable": false,
|
||||||
|
"internals": {
|
||||||
|
"process_throttle_secs": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
+96
-23
@@ -18,12 +18,23 @@ from typing import Optional
|
|||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
"""
|
"""
|
||||||
使用EMA周期52
|
大周期:1h
|
||||||
1. 检查当前price是否穿越,如果穿越时,MACD也是归零轴反转,则开仓
|
小周期:15m,30m
|
||||||
2. 接近某个EMA周期后反转,此时MACD归零轴反转,则开仓
|
大周期EMA156以下找做空机会
|
||||||
止损放到顶底分型的高低点
|
找到最近的中枢,中枢下跌以后穿过EMA156,EMA52均线,形成死叉,macd黄白线穿越0轴
|
||||||
1. 从大周期开始找到价格接近ema52,MACD也接近零轴的周期,需要看这个周期的长级别是否高位空,大趋势方向
|
EMA24,EMA52,EMA104,EMA156成下跌趋势依次排列(EMA156 > EMA104 > EMA52 > EMA24)
|
||||||
2. 然后去小于这个时间周期的周期找买卖点,小级趋势方向和大趋势相反并且开始反向,小级别需要检查MACD是否归零轴反转,同时价格是否接近EMA52
|
做空
|
||||||
|
1. 做空开始点位条件:
|
||||||
|
确定下跌周期,价格在大于大周期的时间周期找到MACD归零轴+EMA52阻力线,按照K线动能理论,小周期确认是否背驰,背驰则开仓并且MACD穿零轴
|
||||||
|
止损放到最近的顶分型高点或者价格突破EMA156
|
||||||
|
2. 开始点位止盈策略
|
||||||
|
计算盈亏比方式:至少1:2,到达1:2后平仓一半,移动止损到开仓价,1:3再平仓剩下的一半仓位,依次类推
|
||||||
|
如果大周期遇到底背离可以平完所有仓位
|
||||||
|
3. 加仓点位
|
||||||
|
小周期顶分型+价格接近或突破大周期EMA24但是不突破EMA52后下跌可以加仓到最大仓位+大周期黄白线归零轴/小周期顶分型+小周期EMA52归零轴
|
||||||
|
大周期顶分型+大周期macd归零轴可以加仓到最大仓位
|
||||||
|
大周期顶分型或顶分型后,macd穿零轴后价格和macd红绿柱背驰可以加仓到最大仓位
|
||||||
|
小周期顶分型+大周期macd归零轴
|
||||||
"""
|
"""
|
||||||
|
|
||||||
### Now you can use logger.info('asfd') to log
|
### Now you can use logger.info('asfd') to log
|
||||||
@@ -31,7 +42,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# freqtrade trade -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies
|
# freqtrade trade -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies
|
||||||
# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies --timerange=20260101-
|
# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies --timerange=20260101-
|
||||||
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_EMA52.json -t 1m 1m 1h 1d 1M --pairs BTC/USDT:USDT --timerange=20250405-
|
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_EMA52.json -t 1m 1m 1h 1d 1w 1M --pairs BTC/USDT:USDT --timerange=20240101-
|
||||||
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_EMA52.json -t 1m 1h 1d 1M --pairs BTC/USDT --timerange=20170101-
|
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_EMA52.json -t 1m 1h 1d 1M --pairs BTC/USDT --timerange=20170101-
|
||||||
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_EMA52.json -e 200 --timerange=20250201-20250901
|
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_EMA52.json -e 200 --timerange=20250201-20250901
|
||||||
# freqtrade edge -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
|
# freqtrade edge -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
|
||||||
@@ -101,34 +112,99 @@ class ChanLun_EMA52(IStrategy):
|
|||||||
def informative_pairs(self):
|
def informative_pairs(self):
|
||||||
return [(self.pair, "1h"),
|
return [(self.pair, "1h"),
|
||||||
(self.pair, "1d"),
|
(self.pair, "1d"),
|
||||||
(self.pair, "1M"),
|
#(self.pair, "1M"),
|
||||||
(self.pair, "15m"),
|
(self.pair, "15m"),
|
||||||
(self.pair, "1w"),
|
#(self.pair, "1w"),
|
||||||
]
|
]
|
||||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
dataframe = self.add_indicators(dataframe)
|
dataframe = self.add_indicators(dataframe)
|
||||||
long_df = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1h')
|
long_df = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1h')
|
||||||
long_df = self.add_indicators(long_df)
|
long_df = self.add_indicators(long_df)
|
||||||
long_df['entry_long'] = self.long_entry_condition(long_df)
|
|
||||||
dataframe['rsi'] = ta.RSI(long_df, timeperiod=14)
|
dataframe['rsi'] = ta.RSI(long_df, timeperiod=14)
|
||||||
if self.last_time is None or self.last_time + timedelta(minutes=1) < datetime.now():
|
if self.last_time is None or self.last_time + timedelta(seconds=10) < datetime.now():
|
||||||
self.last_time = datetime.now()
|
self.last_time = datetime.now()
|
||||||
logger.info("init_dataframes----------------------------")
|
logger.info("init_dataframes----------------------------")
|
||||||
last_price = dataframe.iloc[-1]['close']
|
last_price = dataframe.iloc[-1]['close']
|
||||||
macdstr = str(long_df.iloc[-1]['macd']) + " " + str(long_df.iloc[-1]['macdsignal']) + " " + str(long_df.iloc[-1]['macdhist'])
|
|
||||||
date = dataframe.iloc[-1]['date']
|
date = dataframe.iloc[-1]['date']
|
||||||
tf_ema52_list = self.chan.check_price_ema52(last_price)
|
tf_ema52_list = self.chan.check_price_ema52(last_price)
|
||||||
self.init_dataframes(dataframe)
|
self.init_dataframes(dataframe)
|
||||||
logger.info("Date: " + date.strftime('%Y-%m-%d %H:%M:%S') + " Price: " + str(last_price) + " EMA52_list: " + str(tf_ema52_list) + " MACD: " + macdstr)
|
logger.info("Date: " + date.strftime('%Y-%m-%d %H:%M:%S') + " Price: " + str(last_price) + " EMA52_list: " + str(tf_ema52_list))
|
||||||
|
#print(long_df.iloc[-1])
|
||||||
dataframe = resampled_merge(dataframe, long_df)
|
dataframe = resampled_merge(dataframe, long_df)
|
||||||
#print(dataframe.iloc[-1])
|
|
||||||
return dataframe
|
return dataframe
|
||||||
def long_entry_condition(self, long_df):
|
def ema_dir(self, dataframe):
|
||||||
long_entry_condition = (long_df['dir52'] > 0) & (long_df['dir156'] > 0) & (long_df['macdhist'] > 0)
|
"""
|
||||||
return long_entry_condition
|
趋势方向综合判断,分为三个维度:
|
||||||
|
|
||||||
|
1. ema_dir: 主趋势方向 (基于MACD零轴 + 价格与EMA52/EMA156关系)
|
||||||
|
- 3: 强多(价格在EMA156上方 + MACD在零轴上方 + 价格在EMA24上方)
|
||||||
|
- 2: 中多(价格在EMA156上方 + MACD在零轴上方)
|
||||||
|
- 1: 弱多(价格在EMA52上方 或 MACD在零轴上方,满足其一)
|
||||||
|
- -1: 弱空(价格在EMA52下方 或 MACD在零轴下方,满足其一)
|
||||||
|
- -2: 中空(价格在EMA156下方 + MACD在零轴下方)
|
||||||
|
- -3: 强空(价格在EMA156下方 + MACD在零轴下方 + 价格在EMA24下方)
|
||||||
|
- 0: 盘整(无明确方向)
|
||||||
|
|
||||||
|
2. ema_align: EMA排列状态(辅助确认趋势强度)
|
||||||
|
- 1: 多头排列 (EMA24 > EMA52 > EMA104 > EMA156)
|
||||||
|
- -1: 空头排列 (EMA24 < EMA52 < EMA104 < EMA156)
|
||||||
|
- 0: 交叉/纠缠
|
||||||
|
|
||||||
|
3. ema_slope: EMA52斜率方向(趋势加速/减速判断)
|
||||||
|
- 正值: EMA52向上倾斜,趋势加速
|
||||||
|
- 负值: EMA52向下倾斜,趋势减速
|
||||||
|
"""
|
||||||
|
close = dataframe['close']
|
||||||
|
ema24 = dataframe['ema24']
|
||||||
|
ema52 = dataframe['ema52']
|
||||||
|
ema104 = dataframe['ema104']
|
||||||
|
ema156 = dataframe['ema156']
|
||||||
|
macd_signal = dataframe['macdsignal'] # 黄线(慢线),用于判断零轴
|
||||||
|
|
||||||
|
# === 1. 主趋势方向 ===
|
||||||
|
# 核心条件:价格与EMA156的关系(大趋势)+ MACD黄线与零轴的关系
|
||||||
|
above_ema156 = close > ema156
|
||||||
|
below_ema156 = close < ema156
|
||||||
|
above_ema52 = close > ema52
|
||||||
|
below_ema52 = close < ema52
|
||||||
|
above_ema24 = close > ema24
|
||||||
|
below_ema24 = close < ema24
|
||||||
|
macd_above_zero = macd_signal > 0
|
||||||
|
macd_below_zero = macd_signal < 0
|
||||||
|
|
||||||
|
dataframe['ema_dir'] = 0
|
||||||
|
# 强多:价格在EMA156上方 + MACD零轴上方 + 价格在EMA24上方(超强势结构)
|
||||||
|
dataframe.loc[above_ema156 & macd_above_zero & above_ema24, 'ema_dir'] = 3
|
||||||
|
# 中多:价格在EMA156上方 + MACD零轴上方
|
||||||
|
dataframe.loc[above_ema156 & macd_above_zero & ~above_ema24, 'ema_dir'] = 2
|
||||||
|
# 弱多:满足其一(价格在EMA52上方 或 MACD零轴上方)
|
||||||
|
dataframe.loc[(above_ema52 & ~macd_above_zero) | (macd_above_zero & ~above_ema156), 'ema_dir'] = 1
|
||||||
|
# 弱空:满足其一(价格在EMA52下方 或 MACD零轴下方)
|
||||||
|
dataframe.loc[(below_ema52 & ~macd_below_zero) | (macd_below_zero & ~below_ema156), 'ema_dir'] = -1
|
||||||
|
# 中空:价格在EMA156下方 + MACD零轴下方
|
||||||
|
dataframe.loc[below_ema156 & macd_below_zero & ~below_ema24, 'ema_dir'] = -2
|
||||||
|
# 强空:价格在EMA156下方 + MACD零轴下方 + 价格在EMA24下方(超强空势结构)
|
||||||
|
dataframe.loc[below_ema156 & macd_below_zero & below_ema24, 'ema_dir'] = -3
|
||||||
|
|
||||||
|
# === 2. EMA排列状态(辅助参考)===
|
||||||
|
bull_align = (ema24 > ema52) & (ema52 > ema104) & (ema104 > ema156)
|
||||||
|
bear_align = (ema24 < ema52) & (ema52 < ema104) & (ema104 < ema156)
|
||||||
|
dataframe['ema_align'] = 0
|
||||||
|
dataframe.loc[bull_align, 'ema_align'] = 1
|
||||||
|
dataframe.loc[bear_align, 'ema_align'] = -1
|
||||||
|
|
||||||
|
# === 3. EMA52斜率(趋势加速/减速)===
|
||||||
|
# 用EMA52的变化率判断趋势是否在加速
|
||||||
|
dataframe['ema_slope'] = (ema52 - ema52.shift(3)) / ema52.shift(3) * 100
|
||||||
|
|
||||||
|
return dataframe
|
||||||
def add_indicators(self, dataframe):
|
def add_indicators(self, dataframe):
|
||||||
|
dataframe['ema24'] = ta.EMA(dataframe, timeperiod=24)
|
||||||
|
dataframe['dir24'] = dataframe['close'] - dataframe['ema24']
|
||||||
dataframe['ema52'] = ta.EMA(dataframe, timeperiod=52)
|
dataframe['ema52'] = ta.EMA(dataframe, timeperiod=52)
|
||||||
dataframe['dir52'] = dataframe['close'] - dataframe['ema52']
|
dataframe['dir52'] = dataframe['close'] - dataframe['ema52']
|
||||||
|
dataframe['ema104'] = ta.EMA(dataframe, timeperiod=104)
|
||||||
|
dataframe['dir104'] = dataframe['close'] - dataframe['ema104']
|
||||||
dataframe['ema156'] = ta.EMA(dataframe, timeperiod=156)
|
dataframe['ema156'] = ta.EMA(dataframe, timeperiod=156)
|
||||||
dataframe['dir156'] = dataframe['close'] - dataframe['ema156']
|
dataframe['dir156'] = dataframe['close'] - dataframe['ema156']
|
||||||
dataframe['dir52_156'] = dataframe['dir52'] - dataframe['dir156']
|
dataframe['dir52_156'] = dataframe['dir52'] - dataframe['dir156']
|
||||||
@@ -141,10 +217,9 @@ class ChanLun_EMA52(IStrategy):
|
|||||||
dataframe_15m = self.dp.get_pair_dataframe(pair=self.pair, timeframe='15m')
|
dataframe_15m = self.dp.get_pair_dataframe(pair=self.pair, timeframe='15m')
|
||||||
dataframe_1h = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1h')
|
dataframe_1h = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1h')
|
||||||
dataframe_1d = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1d')
|
dataframe_1d = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1d')
|
||||||
dataframe_1w = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1w')
|
#dataframe_1w = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1w')
|
||||||
dataframe_1M = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1M')
|
#dataframe_1M = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1M')
|
||||||
self.chan = ChanLun()
|
self.chan.init_dataframes(dataframe_1m, dataframe_15m,dataframe_1h, dataframe_1d)
|
||||||
self.chan.init_dataframes(dataframe_1m, dataframe_15m,dataframe_1h, dataframe_1d, dataframe_1w, dataframe_1M)
|
|
||||||
def custom_entry_price(self, pair: str, trade: Trade | None, current_time: datetime, proposed_rate: float,
|
def custom_entry_price(self, pair: str, trade: Trade | None, current_time: datetime, proposed_rate: float,
|
||||||
entry_tag: str | None, side: str, **kwargs) -> float:
|
entry_tag: str | None, side: str, **kwargs) -> float:
|
||||||
new_entryprice = proposed_rate
|
new_entryprice = proposed_rate
|
||||||
@@ -182,7 +257,6 @@ class ChanLun_EMA52(IStrategy):
|
|||||||
|
|
||||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(dataframe['rsi'] < 30) &
|
|
||||||
(dataframe['dir156'] > 0) &
|
(dataframe['dir156'] > 0) &
|
||||||
(dataframe['dir52_156'] > 0) &
|
(dataframe['dir52_156'] > 0) &
|
||||||
(dataframe['macdhist'] > 0),
|
(dataframe['macdhist'] > 0),
|
||||||
@@ -190,7 +264,6 @@ class ChanLun_EMA52(IStrategy):
|
|||||||
return dataframe
|
return dataframe
|
||||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(dataframe['rsi'] > 70) &
|
|
||||||
(dataframe['dir156'] < 0) &
|
(dataframe['dir156'] < 0) &
|
||||||
(dataframe['dir52_156'] < 0) &
|
(dataframe['dir52_156'] < 0) &
|
||||||
(dataframe['macdhist'] < 0),
|
(dataframe['macdhist'] < 0),
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
# --- 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__)
|
||||||
|
"""
|
||||||
|
大周期:1h
|
||||||
|
小周期:15m,30m
|
||||||
|
大周期EMA156以下找做空机会
|
||||||
|
找到最近的中枢,中枢下跌以后穿过EMA156,EMA52均线,形成死叉,macd黄白线穿越0轴
|
||||||
|
EMA24,EMA52,EMA104,EMA156成下跌趋势依次排列(EMA156 > EMA104 > EMA52 > EMA24)
|
||||||
|
做空
|
||||||
|
1. 做空开始点位条件:
|
||||||
|
确定下跌周期,价格在大于大周期的时间周期找到MACD归零轴+EMA52阻力线,按照K线动能理论,小周期确认是否背驰,背驰则开仓并且MACD穿零轴
|
||||||
|
止损放到最近的顶分型高点或者价格突破EMA156
|
||||||
|
2. 开始点位止盈策略
|
||||||
|
计算盈亏比方式:至少1:2,到达1:2后平仓一半,移动止损到开仓价,1:3再平仓剩下的一半仓位,依次类推
|
||||||
|
如果大周期遇到底背离可以平完所有仓位
|
||||||
|
3. 加仓点位
|
||||||
|
小周期顶分型+价格接近或突破大周期EMA24但是不突破EMA52后下跌可以加仓到最大仓位+大周期黄白线归零轴/小周期顶分型+小周期EMA52归零轴
|
||||||
|
大周期顶分型+大周期macd归零轴可以加仓到最大仓位
|
||||||
|
大周期顶分型或顶分型后,macd穿零轴后价格和macd红绿柱背驰可以加仓到最大仓位
|
||||||
|
小周期顶分型+大周期macd归零轴
|
||||||
|
"""
|
||||||
|
|
||||||
|
### 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_EMA_Align.json --strategy ChanLun_EMA_Align --strategy-path ./user_data/Chan/strategies
|
||||||
|
# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_EMA_Align.json --strategy ChanLun_EMA_Align --strategy-path ./user_data/Chan/strategies --timerange=20260101-
|
||||||
|
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_EMA_Align.json -t 1m 1m 1h 1d 1w 1M --pairs BTC/USDT:USDT --timerange=20240101-
|
||||||
|
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_EMA_Align.json -t 1m 1h 1d 1M --pairs BTC/USDT --timerange=20170101-
|
||||||
|
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy ChanLun_EMA_Align --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_EMA_Align.json -e 200 --timerange=20250201-20250901
|
||||||
|
# freqtrade edge -c ./user_data/Chan/config/ChanLun_EMA_Align.json --strategy ChanLun_EMA_Align --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
|
||||||
|
# freqtrade plot-dataframe -c ./user_data/Chan/config/ChanLun_EMA_Align.json --strategy ChanLun_EMA_Align --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
|
||||||
|
|
||||||
|
# sudo docker compose run --rm chanlun_btc backtesting -c ./user_data/Chan/config/ChanLun_EMA_Align.json --strategy ChanLun_EMA_Align --strategy-path ./user_data/Chan/strategies --timerange=20250721-
|
||||||
|
# sudo docker compose run --rm chanlun_btc download-data -c ./user_data/Chan/config/ChanLun_EMA_Align.json --pairs BTC/USDT:USDT -t 1m --timerange 20240101-
|
||||||
|
# sudo docker compose run --rm chanlun_btc trade -c ./user_data/Chan/config/ChanLun_EMA_Align.json --strategy ChanLun_EMA_Align --strategy-path ./user_data/Chan/strategies
|
||||||
|
|
||||||
|
class ChanLun_EMA_Align(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
|
||||||
|
}
|
||||||
|
startup_candle_count = 1600
|
||||||
|
can_short = True
|
||||||
|
lev = 1.0
|
||||||
|
stoploss = -0.3 # 设置为很大的负值,让custom_stoploss来控制
|
||||||
|
use_custom_stoploss = False # 启用自定义止损
|
||||||
|
|
||||||
|
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 = 1600
|
||||||
|
time5 = 5
|
||||||
|
time15 = 15
|
||||||
|
time30 = 30
|
||||||
|
time60 = 60
|
||||||
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
dataframe = self.add_indicators(dataframe)
|
||||||
|
dataframe_5m = resample_to_interval(dataframe, self.get_ticker_indicator() * self.time5)
|
||||||
|
dataframe_5m = self.add_indicators(dataframe_5m)
|
||||||
|
#print(dataframe_5m.iloc[-1])
|
||||||
|
dataframe = resampled_merge(dataframe, dataframe_5m)
|
||||||
|
return dataframe
|
||||||
|
def add_indicators(self, dataframe):
|
||||||
|
dataframe['ema24'] = ta.EMA(dataframe, timeperiod=24)
|
||||||
|
dataframe['dir24'] = dataframe['close'] - dataframe['ema24']
|
||||||
|
dataframe['ema52'] = ta.EMA(dataframe, timeperiod=52)
|
||||||
|
dataframe['dir52'] = dataframe['close'] - dataframe['ema52']
|
||||||
|
dataframe['ema104'] = ta.EMA(dataframe, timeperiod=104)
|
||||||
|
dataframe['dir104'] = dataframe['close'] - dataframe['ema104']
|
||||||
|
dataframe['ema156'] = ta.EMA(dataframe, timeperiod=156)
|
||||||
|
dataframe['dir156'] = dataframe['close'] - dataframe['ema156']
|
||||||
|
dataframe['dir52_156'] = dataframe['ema52'] - dataframe['ema156']
|
||||||
|
dataframe['dir52_104'] = dataframe['ema52'] - dataframe['ema104']
|
||||||
|
dataframe_macd = ta.MACD(dataframe, fast=12, slow=26, signal=9)
|
||||||
|
dataframe['macdsignal'] = dataframe_macd['macdsignal']
|
||||||
|
dataframe['macd'] = dataframe_macd['macd']
|
||||||
|
dataframe['macdhist'] = dataframe_macd['macdhist']
|
||||||
|
dataframe['ema_align'] = (
|
||||||
|
((dataframe['ema24'] > dataframe['ema52']) & (dataframe['ema52'] > dataframe['ema104'])) |
|
||||||
|
((dataframe['ema24'] < dataframe['ema52']) & (dataframe['ema52'] < dataframe['ema104']))
|
||||||
|
)
|
||||||
|
return dataframe
|
||||||
|
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_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
|
||||||
|
current_profit: float, **kwargs):
|
||||||
|
# 不做分批止盈/最终止盈处理,退出由策略信号/ROI/止损决定
|
||||||
|
return None
|
||||||
|
|
||||||
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
resample_5m_align = 'resample_{}_ema_align'.format(self.get_ticker_indicator() * self.time5)
|
||||||
|
# 使用高周期的 dir52_156 方向作为多空判定依据
|
||||||
|
resample_5m_dir = 'resample_{}_dir52_104'.format(self.get_ticker_indicator() * self.time5)
|
||||||
|
resample_5m_signal = 'resample_{}_macdsignal'.format(self.get_ticker_indicator() * self.time5)
|
||||||
|
dataframe.loc[
|
||||||
|
(dataframe[resample_5m_align]) &
|
||||||
|
(dataframe[resample_5m_dir] > 0) &
|
||||||
|
(dataframe[resample_5m_signal] > 0),
|
||||||
|
['enter_long', 'enter_tag']] = (1, 'long_signal_chan')
|
||||||
|
dataframe.loc[
|
||||||
|
(dataframe[resample_5m_align]) &
|
||||||
|
(dataframe[resample_5m_dir] < 0) &
|
||||||
|
(dataframe[resample_5m_signal] < 0),
|
||||||
|
['enter_short', 'enter_tag']] = (1, 'short_signal_chan')
|
||||||
|
return dataframe
|
||||||
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
dataframe.loc[
|
||||||
|
(dataframe['dir156'] < 0) &
|
||||||
|
(dataframe['dir52_156'] < 0) &
|
||||||
|
(dataframe['macdhist'] < 0),
|
||||||
|
['exit_long', 'exit_tag']] = (1, 'long_exit_signal_chan')
|
||||||
|
dataframe.loc[
|
||||||
|
(dataframe['macd'] > 0) &
|
||||||
|
(dataframe['dir156'] > 0) &
|
||||||
|
(dataframe['dir52_156'] > 0) &
|
||||||
|
(dataframe['macdhist'] > 0),
|
||||||
|
['exit_short', 'exit_tag']] = (1, 'short_exit_signal_chan')
|
||||||
|
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])
|
||||||
Reference in New Issue
Block a user