refactor chanlun, tf_df

This commit is contained in:
jackyu66git
2025-10-31 20:34:27 +08:00
parent fe4193b153
commit 8bb3798a7c
7 changed files with 618 additions and 1650 deletions
+24 -1315
View File
File diff suppressed because it is too large Load Diff
+388 -65
View File
@@ -1,6 +1,6 @@
from datetime import timedelta from datetime import timedelta
from pandas import DataFrame from pandas import DataFrame
from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR, Chan_SEG_DIR, Chan_ZS_DIR, Chan_BSP_DIR, Chan_BSP_TYPE, Chan_KLC_FX, Chan_PRICE_TREND from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR, Chan_SEG_DIR, Chan_ZS_DIR, Chan_BSP_DIR, Chan_BSP_TYPE, Chan_KLC_FX, Chan_PRICE_TREND, Chan_KLU_PATTERN
from ChanKLU import ChanKLU from ChanKLU import ChanKLU
from ChanKLC import ChanKLC from ChanKLC import ChanKLC
from ChanBI import ChanBI from ChanBI import ChanBI
@@ -10,17 +10,16 @@ from ChanZS import ChanZS
from ChanBSP import ChanBSP from ChanBSP import ChanBSP
import talib.abstract as ta import talib.abstract as ta
import pandas as pd import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.dates import DateFormatter, date2num
import matplotlib.patches as patches
from technical.util import resample_to_interval from technical.util import resample_to_interval
from decimal import Decimal from decimal import Decimal
import xgboost as xgb
import numpy as np import numpy as np
from ChanMACD import ChanMACD from ChanMACD import ChanMACD
class TF_DF(): class TF_DF():
def __init__(self, df, interval, timeframe): def __init__(self, df=None, interval=0, timeframe=None):
if df is not None:
self.init_TF_DF(df, interval, timeframe)
def init_TF_DF(self, df, interval, timeframe):
self.timeframe = timeframe self.timeframe = timeframe
self.interval = interval self.interval = interval
self.dataframe = resample_to_interval(df, interval) self.dataframe = resample_to_interval(df, interval)
@@ -31,12 +30,10 @@ class TF_DF():
self.zs_list = [] self.zs_list = []
self.bsp_list = [] self.bsp_list = []
self.seg_list = [] self.seg_list = []
self.init_TF_DF()
def init_TF_DF(self):
self.klu_list = self.cal_kl_data(self.dataframe) self.klu_list = self.cal_kl_data(self.dataframe)
self.klc_list = self.cal_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)
self.seg_list = self.cal_seg_list(self.bi_list) self.seg_list = self.get_seg_list(self.bi_list)
self.zs_list = self.cal_zs_list(self.bi_list, self.seg_list) self.zs_list = self.cal_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()
@@ -97,44 +94,44 @@ class TF_DF():
df['macd'] = macd['macd'] df['macd'] = macd['macd']
df['macdsignal'] = macd['macdsignal'] df['macdsignal'] = macd['macdsignal']
df['macdhist'] = macd['macdhist'] df['macdhist'] = macd['macdhist']
df['ema5'] = self.cal_ema(df, 5) df['ema5'] = ta.EMA(df, timeperiod=5)
df['ema10'] = self.cal_ema(df, 10) df['ema10'] = ta.EMA(df, timeperiod=10)
df['ema24'] = self.cal_ema(df, 24) df['ema24'] = ta.EMA(df, timeperiod=24)
df['ema26'] = self.cal_ema(df, 26) df['ema52'] = ta.EMA(df, timeperiod=52)
df['ema52'] = self.cal_ema(df, 52)
df['rsi'] = ta.RSI(df, timeperiod=14) df['rsi'] = ta.RSI(df, timeperiod=14)
df['volume_ratio'] = self.cal_volume_ratio(df) df['volume_ratio'] = self.cal_volume_ratio(df)
return df return df
@staticmethod def get_klu_state(self, dataframe):
def cal_ema(df, timeperiod): klc_list = self.get_klc_list(dataframe)
""" bi_list = self.cal_bi_list(klc_list)
计算 EMA,优先使用 pandas ewm(adjust=False) 以贴近前端/TradingView 显示; klu_state_list = []
必要时回退到 TA-Libabstract)。 klc_index = 0
""" for index in range(0, len(dataframe)):
try: if klc_index == len(klc_list):
series = df['close'].astype(float) if isinstance(df, pd.DataFrame) else pd.Series(df).astype(float) klc_index = len(klc_list) - 1
return series.ewm(span=int(timeperiod), adjust=False).mean() klc = klc_list[klc_index]
except Exception: if klc.end_klu and klc.end_klu.idx == index:
try: if klc.klc_fx_type == Chan_KLC_FX.TOP4:
if isinstance(df, pd.DataFrame): klu_state_list.append("10")
return ta.EMA(df, timeperiod=int(timeperiod)) elif klc.klc_fx_type == Chan_KLC_FX.BOTTOM4:
except Exception: klu_state_list.append("-10")
pass else:
# 最后回退:返回同索引的 NaN 序列 klu_state_list.append("00")
if isinstance(df, pd.DataFrame) and 'close' in df: klc_index += 1
return pd.Series(np.nan, index=df.index) else:
return pd.Series(dtype=float) klu_state_list.append("00")
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: 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.macd > 0 and klc.signal > klc.macdhist: #if (klc.close > klc.ema52 or klc.next.close > klc.next.ema52) and klc.macd > 0 and klc.macd > klc.macdhist:
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.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: elif klc.low < klc.pre.low and klc.low < klc.next.low and klc.high < klc.pre.high and klc.high < klc.next.high:
if klc.macd < 0 and klc.signal < klc.macdhist: #if (klc.close < klc.ema52 or klc.next.close < klc.next.ema52) and klc.macd < 0 and klc.macd < klc.macdhist:
klc.set_fx(Chan_FX_TYPE.BOTTOM) 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.macd, klc.state, klc.fx, "BOTTOM")
return Chan_FX_TYPE.BOTTOM return Chan_FX_TYPE.BOTTOM
return Chan_FX_TYPE.UNKNOWN return Chan_FX_TYPE.UNKNOWN
def cal_volume_ratio(self, dataframe, window=10): def cal_volume_ratio(self, dataframe, window=10):
@@ -155,6 +152,9 @@ class TF_DF():
if not klc_list: if not klc_list:
return klc_list return klc_list
last_trend = Chan_PRICE_TREND.UNKNOWN last_trend = Chan_PRICE_TREND.UNKNOWN
# 趋势延续性:参考近 N 根已完成的KLC
lookback_n = 5
prev_klcs = []
for klc in klc_list: for klc in klc_list:
price = getattr(klc, 'close', None) price = getattr(klc, 'close', None)
ema24 = getattr(klc, 'ema24', None) ema24 = getattr(klc, 'ema24', None)
@@ -164,13 +164,14 @@ class TF_DF():
hist = getattr(klc, 'macdhist', 0) if getattr(klc, 'macdhist', 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) rsi = getattr(klc, 'rsi', None)
trend = Chan_PRICE_TREND.UNKNOWN trend = Chan_PRICE_TREND.UNKNOWN
score = 0
try: try:
# 有效性 # 有效性
price_valid = price is not None and price != 0 price_valid = price is not None and price != 0
ema24_valid = ema24 is not None and ema24 != 0 ema24_valid = ema24 is not None and ema24 != 0
ema52_valid = ema52 is not None and ema52 != 0 ema52_valid = ema52 is not None and ema52 != 0
# 多因子投票 # 多因子投票
score = 0
# 1) 均线结构 + 价位 # 1) 均线结构 + 价位
if ema24_valid and ema52_valid: if ema24_valid and ema52_valid:
score += 1 if ema24 > ema52 else -1 score += 1 if ema24 > ema52 else -1
@@ -194,6 +195,76 @@ class TF_DF():
spread_now = ema24 - ema52 spread_now = ema24 - ema52
spread_pre = pre_ema24 - pre_ema52 spread_pre = pre_ema24 - pre_ema52
score += 1 if spread_now >= spread_pre else -1 score += 1 if spread_now >= spread_pre else -1
# 3.1) MACD柱体动量趋势:考虑 macdhist 的斜率与过零
pre_hist = getattr(pre, 'macdhist', None)
if pre_hist is not None and hist is not None:
# 柱体斜率:上升加分,下降减分
if hist > pre_hist:
score += 1
elif hist < pre_hist:
score -= 1
# 过零加权:负转正更偏多,正转负更偏空
if pre_hist < 0 and hist > 0:
score += 1
elif pre_hist > 0 and hist < 0:
score -= 1
# 3.2) EMA52 突破/跌破加权
if ema52_valid and price_valid and pre_close is not None and pre_ema52 not in (None, 0):
# 看多突破:从均线下方上破且动量配合
if pre_close <= pre_ema52 and price > ema52 and (hist is None or pre_hist is None or hist >= pre_hist):
score += 1
# 看空跌破:从均线上方下破且动量配合
if pre_close >= pre_ema52 and price < ema52 and (hist is None or pre_hist is None or hist <= pre_hist):
score -= 1
# 3.3) EMA52 支撑/阻力触碰(非强穿越)
if ema52_valid and price_valid:
low_v = getattr(klc, 'low', None)
high_v = getattr(klc, 'high', None)
if low_v is not None and high_v is not None and ema52 not in (None, 0):
# 触碰容差(相对EMA52的0.15%
touch_tol = 0.0015
# 作为支撑:收盘在上,最低靠近EMA52
near_support_touch = (price > ema52) and (abs(low_v - ema52) / abs(ema52) <= touch_tol)
# 作为阻力:收盘在下,最高靠近EMA52
near_resistance_touch = (price < ema52) and (abs(high_v - ema52) / abs(ema52) <= touch_tol)
if near_support_touch:
# 若动量不弱,则更偏多
score += 1 if (hist is None or pre_hist is None or hist >= pre_hist) else 0
if near_resistance_touch:
# 若动量不强,则更偏空
score -= 1 if (hist is None or pre_hist is None or hist <= pre_hist) else 0
# 3.4) 多次对 EMA52 的"拒绝"配合 MACD 逆向:易形成压/支并反向
# 统计近窗口内的上/下拒绝次数:
# - 上拒绝:价格位于 EMA52 下方,最高触及/越过 EMA52 但收盘仍在下方
# - 下拒绝:价格位于 EMA52 上方,最低触及/跌破 EMA52 但收盘仍在上方
recent_up_rejects = 0
recent_down_rejects = 0
if ema52_valid:
window_rej = prev_klcs[-lookback_n:] if len(prev_klcs) > 0 else []
rej_tol = 0.0015
for wk in window_rej:
wk_close = getattr(wk, 'close', None)
wk_ema52 = getattr(wk, 'ema52', None)
wk_high = getattr(wk, 'high', None)
wk_low = getattr(wk, 'low', None)
if wk_close is None or wk_ema52 in (None, 0):
continue
# 上拒绝(阻力):下方多次试图上破但未站上
if wk_close < wk_ema52 and wk_high is not None:
if wk_high >= wk_ema52 or abs(wk_high - wk_ema52) / abs(wk_ema52) <= rej_tol:
recent_up_rejects += 1
# 下拒绝(支撑):上方多次试图下破但未跌破
if wk_close > wk_ema52 and wk_low is not None:
if wk_low <= wk_ema52 or abs(wk_low - wk_ema52) / abs(wk_ema52) <= rej_tol:
recent_down_rejects += 1
# 定义 MACD 的方向偏好
macd_bias_up = (macd >= signal) and (hist is None or pre_hist is None or hist >= pre_hist)
macd_bias_down = (macd <= signal) and (hist is None or pre_hist is None or hist <= pre_hist)
# 若多次上拒绝且 MACD 偏空,则更偏向下行;若多次下拒绝且 MACD 偏多,则更偏向上行
if recent_up_rejects >= 2 and macd_bias_down:
score -= 2
if recent_down_rejects >= 2 and macd_bias_up:
score += 2
# 4) RSI 辅助 # 4) RSI 辅助
if rsi is not None: if rsi is not None:
if rsi >= 55: if rsi >= 55:
@@ -231,17 +302,121 @@ class TF_DF():
near_macd = abs(macd - signal) <= (abs(price) * 0.00005 if price_valid else 0) near_macd = abs(macd - signal) <= (abs(price) * 0.00005 if price_valid else 0)
near_flat = near_ema52 and near_macd near_flat = near_ema52 and near_macd
# 7) 动态阈值 + 趋势记忆(更强粘滞:趋势中容忍小幅反分) # 7) 动态阈值 + 趋势记忆(更强粘滞:趋势中容忍小幅反分)
# 引入过去 N 根KLC 的趋势延续性来动态调整翻转阈值,并结合 EMA52 支撑/阻力触碰强化门槛
force_flip_down = False
force_flip_up = False
if near_flat: if near_flat:
trend = Chan_PRICE_TREND.FLAT trend = Chan_PRICE_TREND.FLAT
else: else:
if last_trend == Chan_PRICE_TREND.UP: # 计算过去窗口的趋势一致性
# 仅当出现明显反向才翻转,否则维持UP window = prev_klcs[-lookback_n:] if len(prev_klcs) > 0 else []
if score <= -2: persist_up = 0
persist_down = 0
for wk in window:
if getattr(wk, 'trend', None) == Chan_PRICE_TREND.UP:
persist_up += 1
elif getattr(wk, 'trend', None) == Chan_PRICE_TREND.DOWN:
persist_down += 1
persist_ratio_up = (persist_up / len(window)) if len(window) > 0 else 0
persist_ratio_down = (persist_down / len(window)) if len(window) > 0 else 0
# 基准阈值
down_flip_threshold = -2
up_flip_threshold = 2
# 若最近多为UP,则从UP翻转需更强反向信号;同理对DOWN
if last_trend == Chan_PRICE_TREND.UP and persist_ratio_up >= 0.6:
down_flip_threshold = -3
elif last_trend == Chan_PRICE_TREND.DOWN and persist_ratio_down >= 0.6:
up_flip_threshold = 3
# EMA52 触碰强化门槛:UP时若出现支撑触碰,下翻更难;DOWN时若出现阻力触碰,上翻更难
if ema52_valid and price_valid:
low_v = getattr(klc, 'low', None)
high_v = getattr(klc, 'high', None)
if low_v is not None and high_v is not None and ema52 not in (None, 0):
touch_tol = 0.0015
near_support_touch = (price > ema52) and (abs(low_v - ema52) / abs(ema52) <= touch_tol)
near_resistance_touch = (price < ema52) and (abs(high_v - ema52) / abs(ema52) <= touch_tol)
if last_trend == Chan_PRICE_TREND.UP and near_support_touch:
# 强化维持UP:进一步降低向下翻转阈值
down_flip_threshold = min(down_flip_threshold - 1, -3)
if last_trend == Chan_PRICE_TREND.DOWN and near_resistance_touch:
# 强化维持DOWN:进一步提高向上翻转阈值
up_flip_threshold = max(up_flip_threshold + 1, 3)
# 7.1) 复合拐头信号:MACD/Signal 同向拐头 + hist 连续减弱 + 多次未能越过 EMA52
pre_macd = getattr(pre, 'macd', None) if pre else None
pre_signal = getattr(pre, 'signal', None) if pre else None
macd_slope = (macd - pre_macd) if (pre_macd is not None and macd is not None) else 0
signal_slope = (signal - pre_signal) if (pre_signal is not None and signal is not None) else 0
# hist 连续减弱(绝对值缩小)
hist_seq = []
for wk in prev_klcs[-2:]:
val = getattr(wk, 'macdhist', None)
if val is not None:
hist_seq.append(val)
if hist is not None:
hist_seq.append(hist)
weaken_steps = 0
for i in range(1, len(hist_seq)):
if abs(hist_seq[i]) < abs(hist_seq[i-1]):
weaken_steps += 1
# 近窗口对 EMA52 的"未能站上/跌破"统计(放宽窗口与条件)
window_ema = prev_klcs[-4:] if len(prev_klcs) > 0 else []
no_up_break = False
no_down_break = False
if ema52_valid:
# 未能有效上破:最近若干根收盘大多数不在 EMA52 上方,且高点多次触及/接近
cnt_touch_up = 0
cnt_close_above = 0
for wk in window_ema:
wk_close = getattr(wk, 'close', None)
wk_high = getattr(wk, 'high', None)
wk_ema = getattr(wk, 'ema52', None)
if wk_close is not None and wk_ema not in (None, 0):
if wk_close > wk_ema:
cnt_close_above += 1
if wk_high is not None and (wk_high >= wk_ema or abs(wk_high - wk_ema) / abs(wk_ema) <= 0.0015):
cnt_touch_up += 1
no_up_break = (cnt_close_above <= 1 and cnt_touch_up >= 1 and price <= ema52)
# 未能有效下破:最近若干根收盘大多数不在 EMA52 下方,且低点多次触及/接近
cnt_touch_down = 0
cnt_close_below = 0
for wk in window_ema:
wk_close = getattr(wk, 'close', None)
wk_low = getattr(wk, 'low', None)
wk_ema = getattr(wk, 'ema52', None)
if wk_close is not None and wk_ema not in (None, 0):
if wk_close < wk_ema:
cnt_close_below += 1
if wk_low is not None and (wk_low <= wk_ema or abs(wk_low - wk_ema) / abs(wk_ema) <= 0.0015):
cnt_touch_down += 1
no_down_break = (cnt_close_below <= 1 and cnt_touch_down >= 1 and price >= ema52)
# 若当前为UP趋势,出现明显拐头+hist减弱+未能上破EMA52,则加速看空
if last_trend == Chan_PRICE_TREND.UP and macd_slope < 0 and signal_slope < 0 and weaken_steps >= 1 and no_up_break and macd_bias_down:
score -= 3
down_flip_threshold = max(down_flip_threshold, 0)
force_flip_down = True
# 若当前为DOWN趋势,出现明显拐头+hist减弱+未能下破EMA52,则加速看多
if last_trend == Chan_PRICE_TREND.DOWN and macd_slope > 0 and signal_slope > 0 and weaken_steps >= 1 and no_down_break and macd_bias_up:
score += 3
up_flip_threshold = min(up_flip_threshold, 0)
force_flip_up = True
# 多次对 EMA52 的拒绝配合 MACD 逆向:加速反向翻转(降低相反方向阈值)
if recent_up_rejects >= 2 and macd_bias_down:
# 从 UP 向 DOWN 的翻转更容易
down_flip_threshold = max(down_flip_threshold, -1)
if recent_down_rejects >= 2 and macd_bias_up:
# 从 DOWN 向 UP 的翻转更容易
up_flip_threshold = min(up_flip_threshold, 1)
if force_flip_down:
trend = Chan_PRICE_TREND.DOWN
elif force_flip_up:
trend = Chan_PRICE_TREND.UP
elif last_trend == Chan_PRICE_TREND.UP:
if score <= down_flip_threshold:
trend = Chan_PRICE_TREND.DOWN trend = Chan_PRICE_TREND.DOWN
else: else:
trend = Chan_PRICE_TREND.UP trend = Chan_PRICE_TREND.UP
elif last_trend == Chan_PRICE_TREND.DOWN: elif last_trend == Chan_PRICE_TREND.DOWN:
if score >= 2: if score >= up_flip_threshold:
trend = Chan_PRICE_TREND.UP trend = Chan_PRICE_TREND.UP
else: else:
trend = Chan_PRICE_TREND.DOWN trend = Chan_PRICE_TREND.DOWN
@@ -256,14 +431,19 @@ class TF_DF():
except Exception: except Exception:
trend = Chan_PRICE_TREND.UNKNOWN trend = Chan_PRICE_TREND.UNKNOWN
# 写回趋势 # 写回趋势
if klc.end_time is None:
trend = Chan_PRICE_TREND.FLAT
if hasattr(klc, 'set_trend'): if hasattr(klc, 'set_trend'):
klc.set_trend(trend) klc.set_trend(trend)
else: else:
setattr(klc, 'trend', trend) setattr(klc, 'trend', trend)
last_trend = trend last_trend = trend
# 更新滑窗:仅向后看
prev_klcs.append(klc)
price_diff = klc.close - klc.pre.close if klc.pre else 0 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) #if klc.index > len(klc_list) - 10:
#print(klc.start_time, klc.end_time, klc.trend, price_diff) #print(klc.start_time, klc.end_time, klc.close, klc.ema24, klc.ema52, klc.macd, klc.signal, klc.macdhist, klc.trend, price_diff, score)
#print(klc.start_time, klc.end_time, klc.trend, price_diff, score)
return klc_list return klc_list
def cal_kl_data(self, dataframe:DataFrame): def cal_kl_data(self, dataframe:DataFrame):
fields = "time,open,high,low,close,volume" fields = "time,open,high,low,close,volume"
@@ -300,8 +480,46 @@ class TF_DF():
if 'macd' in item: if 'macd' in item:
klu.set_indicators(item) klu.set_indicators(item)
return klu_list return klu_list
def get_bi_list(self, dataframe):
def cal_klc_list(self, klu_list): bi_list = self.cal_bi_list(self.get_klc_list(dataframe))
return bi_list
def get_kl_data(self, dataframe:DataFrame):
fields = "time,open,high,low,close,volume"
klu_list = []
last_klu = None
for i in range(0, len(dataframe)):
item = dataframe.iloc[i]
date = item['date']
o = item['open']
h = item['high']
l = item['low']
c = item['close']
v = item['volume']
#time_obj = date.fromtimestamp(date)
#date = date + timedelta(hours=8)
time_str = date.strftime('%Y-%m-%d %H:%M:%S')
item_data = [
time_str,
o,
h,
l,
c,
v
]
#klu = KLU(self.create_item_dict(item_data, GetColumnNameFromFieldList(fields)))
klu = ChanKLU(time_str, o, h, l, c, v)
#print(klu.time, klu.open, klu.high, klu.low, klu.close, klu.volume)
klu.set_idx(i)
klu_list.append(klu)
if last_klu:
last_klu.set_next(klu)
klu.set_pre(last_klu)
last_klu = klu
if 'macd' in item:
klu.set_indicators(item)
return klu_list
def get_klc_list(self, dataframe):
klu_list = self.get_klu_list(dataframe)
klc_list = [] klc_list = []
last_klu = None last_klu = None
macd = ChanMACD(klu_list) macd = ChanMACD(klu_list)
@@ -309,6 +527,20 @@ class TF_DF():
for klu in klu_list: for klu in klu_list:
if len(klc_list) > 0: if len(klc_list) > 0:
last_klc = klc_list[-1] last_klc = klc_list[-1]
if klu.exception:
ddir = Chan_KLINE_DIR.DOWN
if last_klc.high < klu.high:
ddir = Chan_KLINE_DIR.UP
klc = ChanKLC(klu, index=len(klc_list), ddir=ddir)
klc.high = klu.close if klu.close > klu.open else klu.open
klc.low = klu.open if klu.close > klu.open else klu.close
klc_list.append(klc)
last_klc.set_next(klc)
klc.set_pre(last_klc)
last_klc.set_end_klu(last_klu)
klc.set_pre_fx()
#print(klu.time, klu.high, klu.low, klu.close, klu.open, klu.exception)
else:
included = last_klc.check_klu_included(klu) included = last_klc.check_klu_included(klu)
if not included: if not included:
ddir = Chan_KLINE_DIR.DOWN ddir = Chan_KLINE_DIR.DOWN
@@ -332,7 +564,7 @@ class TF_DF():
klc_list = self.cal_trend(klc_list) klc_list = self.cal_trend(klc_list)
return klc_list return klc_list
def cal_seg_list(self, bi_list): def get_seg_list(self, bi_list):
seg_list = [] seg_list = []
up_bi_list = [] up_bi_list = []
down_bi_list = [] down_bi_list = []
@@ -371,7 +603,7 @@ class TF_DF():
if last_down_sbi.has_fx_gap: if last_down_sbi.has_fx_gap:
look_for_bottom = True look_for_bottom = True
last_seg.pre_set_end_bi(bi_list[last_down_sbi.start_bi.index - 1]) last_seg.pre_set_end_bi(bi_list[last_down_sbi.start_bi.index - 1])
seg = ChanSEG(last_down_sbi.start_bi, len(seg_list), Chan_SEG_DIR.DOWN) seg = ChanSEG(last_down_sbi.start_bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
seg_list.append(seg) seg_list.append(seg)
last_seg.set_next(seg) last_seg.set_next(seg)
seg.set_pre(last_seg) seg.set_pre(last_seg)
@@ -397,7 +629,7 @@ class TF_DF():
#print(bi.start_time, look_for_top, "UP 3") #print(bi.start_time, look_for_top, "UP 3")
else: else:
last_seg.set_end_bi(bi_list[last_down_sbi.start_bi.index - 1], bi) last_seg.set_end_bi(bi_list[last_down_sbi.start_bi.index - 1], bi)
seg = ChanSEG(last_down_sbi.start_bi, len(seg_list), Chan_SEG_DIR.DOWN) seg = ChanSEG(last_down_sbi.start_bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
seg_list.append(seg) seg_list.append(seg)
last_seg.set_next(seg) last_seg.set_next(seg)
seg.set_pre(last_seg) seg.set_pre(last_seg)
@@ -465,7 +697,7 @@ class TF_DF():
if last_up_sbi.has_fx_gap: if last_up_sbi.has_fx_gap:
look_for_top = True look_for_top = True
last_seg.pre_set_end_bi(bi_list[last_up_sbi.start_bi.index - 1]) last_seg.pre_set_end_bi(bi_list[last_up_sbi.start_bi.index - 1])
seg = ChanSEG(last_up_sbi.start_bi, len(seg_list), Chan_SEG_DIR.UP) seg = ChanSEG(last_up_sbi.start_bi, len(seg_list), Chan_SEG_DIR.UP, bi)
seg_list.append(seg) seg_list.append(seg)
last_seg.set_next(seg) last_seg.set_next(seg)
seg.set_pre(last_seg) seg.set_pre(last_seg)
@@ -491,7 +723,7 @@ class TF_DF():
#print(bi.start_time, look_for_top, "DOWN 3") #print(bi.start_time, look_for_top, "DOWN 3")
else: else:
last_seg.set_end_bi(bi_list[last_up_sbi.start_bi.index - 1], bi) last_seg.set_end_bi(bi_list[last_up_sbi.start_bi.index - 1], bi)
seg = ChanSEG(last_up_sbi.start_bi, len(seg_list), Chan_SEG_DIR.UP) seg = ChanSEG(last_up_sbi.start_bi, len(seg_list), Chan_SEG_DIR.UP, bi)
#print(last_up_sbi.start_bi.start_time) #print(last_up_sbi.start_bi.start_time)
last_seg.set_next(seg) last_seg.set_next(seg)
seg.set_pre(last_seg) seg.set_pre(last_seg)
@@ -539,14 +771,14 @@ class TF_DF():
else: else:
if bi.check_overlap(): if bi.check_overlap():
if bi.dir == Chan_BI_DIR.UP: if bi.dir == Chan_BI_DIR.UP:
seg = ChanSEG(bi, len(seg_list), Chan_SEG_DIR.UP) seg = ChanSEG(bi, len(seg_list), Chan_SEG_DIR.UP, bi)
last_up_bi = bi last_up_bi = bi
last_up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir) last_up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
seg_list.append(seg) seg_list.append(seg)
last_seg = seg last_seg = seg
#print(bi.start_time, 'Create first UP SEG') #print(bi.start_time, 'Create first UP SEG')
else: else:
seg = ChanSEG(bi, len(seg_list), Chan_SEG_DIR.DOWN) seg = ChanSEG(bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
last_down_bi = bi last_down_bi = bi
last_down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir) last_down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
seg_list.append(seg) seg_list.append(seg)
@@ -573,7 +805,7 @@ class TF_DF():
# The confirmed # The confirmed
print("Last UP seg is broken, create a new seg. 1") print("Last UP seg is broken, create a new seg. 1")
seg.pre_set_end_bi(bi_list[i]) seg.pre_set_end_bi(bi_list[i])
seg = ChanSEG(bi_list[i+1], len(seg_list), Chan_SEG_DIR.DOWN) seg = ChanSEG(bi_list[i+1], len(seg_list), Chan_SEG_DIR.DOWN, bi)
seg_list.append(seg) seg_list.append(seg)
last_seg = seg_list[-2] last_seg = seg_list[-2]
if len(last_seg.bi_list) > 3: if len(last_seg.bi_list) > 3:
@@ -585,7 +817,7 @@ class TF_DF():
if bi_list[i].low < last_seg_peak: if bi_list[i].low < last_seg_peak:
print("Last DOWN seg is broken, create a new seg. 1") print("Last DOWN seg is broken, create a new seg. 1")
seg.pre_set_end_bi(bi_list[i]) seg.pre_set_end_bi(bi_list[i])
seg = ChanSEG(bi_list[i+1], len(seg_list), Chan_SEG_DIR.UP) seg = ChanSEG(bi_list[i+1], len(seg_list), Chan_SEG_DIR.UP, bi)
seg_list.append(seg) seg_list.append(seg)
last_seg = seg_list[-2] last_seg = seg_list[-2]
if len(last_seg.bi_list) > 3: if len(last_seg.bi_list) > 3:
@@ -603,12 +835,13 @@ class TF_DF():
if bi_list[i].high > last_seg_peak: if bi_list[i].high > last_seg_peak:
print("Last seg is broken, create a new seg. 2") print("Last seg is broken, create a new seg. 2")
last_seg.pre_set_end_bi(bi_list[i-1]) last_seg.pre_set_end_bi(bi_list[i-1])
seg = ChanSEG(bi_list[i], len(seg_list), Chan_SEG_DIR.UP) seg = ChanSEG(bi_list[i], len(seg_list), Chan_SEG_DIR.UP, bi)
seg_list.append(seg) seg_list.append(seg)
last_seg = seg last_seg = seg
last_seg_bi = bi_list[i] last_seg_bi = bi_list[i]
break break
""" """
self.cal_bi_zs(seg_list)
return seg_list return seg_list
def cal_bi_list(self, klc_list): def cal_bi_list(self, klc_list):
@@ -617,7 +850,14 @@ class TF_DF():
last_bottom = None last_bottom = None
for klc in klc_list: for klc in klc_list:
fx = self.check_fx(klc) fx = self.check_fx(klc)
if fx == Chan_FX_TYPE.TOP:
if last_bottom:
if self.check_top_fx(last_bottom, klc) == False:
fx = Chan_FX_TYPE.UNKNOWN
if fx == Chan_FX_TYPE.BOTTOM:
if last_top:
if self.check_bottom_fx(last_top, klc) == False:
fx = Chan_FX_TYPE.UNKNOWN
# Do nothing # Do nothing
if fx == Chan_FX_TYPE.UNKNOWN: if fx == Chan_FX_TYPE.UNKNOWN:
continue continue
@@ -665,6 +905,7 @@ class TF_DF():
#klc.set_state("20") #klc.set_state("20")
bi_list[-1].add_klc(klc) bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1]) klc.set_bi(bi_list[-1])
#klc.cal_invisible()
#klc.set_klc_fx_type(Chan_KLC_FX.TOP3) #klc.set_klc_fx_type(Chan_KLC_FX.TOP3)
#print(klc.start_time, klc.fx, "二类卖点Sell 1") #print(klc.start_time, klc.fx, "二类卖点Sell 1")
else: else:
@@ -789,6 +1030,7 @@ class TF_DF():
#klc.set_state("-20") #klc.set_state("-20")
bi_list[-1].add_klc(klc) bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1]) klc.set_bi(bi_list[-1])
#klc.cal_invisible()
#klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM3) #klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM3)
#print(last_bottom.start_time, last_bottom.end_time, "--------------------------------1") #print(last_bottom.start_time, last_bottom.end_time, "--------------------------------1")
#print(klc.start_time, klc.fx, "二类买点Buy 1") #print(klc.start_time, klc.fx, "二类买点Buy 1")
@@ -917,11 +1159,25 @@ class TF_DF():
#for index in range(0, 10): #for index in range(0, 10):
#print(bi_list[index].start_time, bi_list[index].start_klc.start_time, bi_list[index].dir) #print(bi_list[index].start_time, bi_list[index].start_klc.start_time, bi_list[index].dir)
return bi_list return bi_list
def check_top_fx(self, last_bottom, klc):
if last_bottom.high > klc.pre.low or last_bottom.high > klc.next.low:
return False
return True
def get_decimal(self, value): def check_bottom_fx(self, last_top, klc):
return Decimal("{:.2f}".format(value)) if last_top.low < klc.pre.high or last_top.low < klc.next.high:
return False
return True
def cal_zs_list(self, bi_list, seg_list): def cal_bi_zs(self, seg_list):
bi_zs_list = []
for seg in seg_list:
zs_list = seg.cal_bi_zs()
if len(zs_list) > 0:
bi_zs_list.append(zs_list)
return bi_zs_list
def get_zs_list(self, bi_list, seg_list):
zs_list = [] zs_list = []
bsp_list = [] bsp_list = []
if len(seg_list) > 3: if len(seg_list) > 3:
@@ -940,7 +1196,6 @@ class TF_DF():
zd = max(seg.low, seg.next.low, seg.next.next.low) zd = max(seg.low, seg.next.low, seg.next.next.low)
gg = max(seg.high, seg.next.high, seg.next.next.high) gg = max(seg.high, seg.next.high, seg.next.next.high)
dd = min(seg.low, seg.next.low, seg.next.next.low) dd = min(seg.low, seg.next.low, seg.next.next.low)
ddir = Chan_ZS_DIR.UP
ddir = None ddir = None
if last_zs: if last_zs:
if zg < last_zs.zd: if zg < last_zs.zd:
@@ -981,7 +1236,7 @@ class TF_DF():
# SEG is not in ZS # SEG is not in ZS
if seg.is_sure: if seg.is_sure:
if ((seg.low > last_zs.zg and seg.high > last_zs.zg) or (seg.high < last_zs.zd and seg.low < last_zs.zd)): if ((seg.low > last_zs.zg and seg.high > last_zs.zg) or (seg.high < last_zs.zd and seg.low < last_zs.zd)):
last_zs.set_end_klc(last_zs.last_bi_in.end_klc, seg.sure_time, bi_out_count, seg) last_zs.set_end_klc(seg.pre.end_bi.end_klc, seg.sure_time, bi_out_count, seg.pre)
bi_out_count = 0 bi_out_count = 0
#print(seg.start_bi.start_klc.start_time) #print(seg.start_bi.start_klc.start_time)
first_bi_out = None first_bi_out = None
@@ -1096,3 +1351,71 @@ class TF_DF():
bsp_list.append(bsp) bsp_list.append(bsp)
#self.print_zs(zs_list) #self.print_zs(zs_list)
return zs_list return zs_list
def get_klu_list(self, dataframe):
klu_list = self.get_kl_data(dataframe)
return self.cal_klu_pattern(klu_list)
def cal_klu_pattern(self, klu_list):
"""
计算裸K的pattern - 只识别反转形态
"""
if not klu_list or len(klu_list) < 3:
return klu_list
for i, klu in enumerate(klu_list):
# 单根K线反转模式识别
self._detect_single_reversal_pattern(klu)
if klu.pattern != Chan_KLU_PATTERN.UNKNOWN:
print(klu.time, klu.pattern)
return klu_list
def _detect_single_reversal_pattern(self, klu):
"""检测单根K线反转模式"""
body = abs(klu.close - klu.open)
upper_shadow = klu.high - max(klu.close, klu.open)
lower_shadow = min(klu.close, klu.open) - klu.low
total_range = klu.high - klu.low
# 避免除零
if total_range == 0:
return
body_ratio = body / total_range
upper_ratio = upper_shadow / total_range
lower_ratio = lower_shadow / total_range
# 锤子线/上吊线 - 反转信号
if lower_ratio / body_ratio >= 2:
# 锤子线:底部反转,需要前面一段
if klu.close > klu.open and klu.pre and klu.pre.close < klu.pre.open:
klu.set_pattern(Chan_KLU_PATTERN.HAMMER) # 底部反转
# 上吊线:顶部反转,需要前一根是上涨趋势
elif klu.close < klu.open and klu.pre and klu.pre.close > klu.pre.open:
klu.set_pattern(Chan_KLU_PATTERN.HANGING_MAN) # 顶部反转
# 倒锤子线/射击之星 - 反转信号
elif upper_ratio / body_ratio >= 2:
# 倒锤子线:底部反转,需要前一根是下跌趋势
if klu.close > klu.open and klu.pre and klu.pre.close < klu.pre.open:
klu.set_pattern(Chan_KLU_PATTERN.INVERTED_HAMMER) # 底部反转
# 射击之星:顶部反转,需要前一根是上涨趋势
elif klu.close < klu.open and klu.pre and klu.pre.close > klu.pre.open:
klu.set_pattern(Chan_KLU_PATTERN.SHOOTING_STAR) # 顶部反转
# 十字星 - 反转信号
elif body_ratio <= 0.1:
if upper_ratio > 0.4 and lower_ratio > 0.4:
klu.set_pattern(Chan_KLU_PATTERN.LONG_LEGGED_DOJI) # 强烈反转信号
elif upper_ratio > 0.4 and lower_ratio <= 0.1:
# 墓碑十字星:顶部反转,需要前一根是上涨趋势
if klu.pre and klu.pre.close > klu.pre.open:
klu.set_pattern(Chan_KLU_PATTERN.GRAVESTONE_DOJI) # 顶部反转
elif lower_ratio > 0.4 and upper_ratio <= 0.1:
# 蜻蜓十字星:底部反转,需要前一根是下跌趋势
if klu.pre and klu.pre.close < klu.pre.open:
klu.set_pattern(Chan_KLU_PATTERN.DRAGONFLY_DOJI) # 底部反转
else:
klu.set_pattern(Chan_KLU_PATTERN.DOJI) # 一般反转信号
def get_decimal(self, value):
return Decimal("{:.2f}".format(value))
-223
View File
@@ -1,223 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
KLU与KLC分型强度算法一致性测试
验证两种算法在相同数据下是否产生一致的结果
"""
from ChanKLU import ChanKLU
from ChanKLC import ChanKLC
from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR
import pandas as pd
from datetime import datetime, timedelta
def create_test_data():
"""创建测试用的K线数据"""
test_cases = [
# 测试用例1:标准顶分型
{
'name': '标准顶分型',
'data': [
{'open': 100, 'high': 102, 'low': 99, 'close': 101, 'volume': 1000}, # K1
{'open': 101, 'high': 105, 'low': 100, 'close': 103, 'volume': 1500}, # K2 (顶分型中心)
{'open': 103, 'high': 104, 'low': 98, 'close': 99, 'volume': 1200}, # K3
]
},
# 测试用例2:标准底分型
{
'name': '标准底分型',
'data': [
{'open': 100, 'high': 102, 'low': 99, 'close': 101, 'volume': 1000}, # K1
{'open': 101, 'high': 103, 'low': 95, 'close': 97, 'volume': 1500}, # K2 (底分型中心)
{'open': 97, 'high': 104, 'low': 96, 'close': 102, 'volume': 1200}, # K3
]
},
# 测试用例3:强势顶分型(放量+下影线)
{
'name': '强势顶分型',
'data': [
{'open': 100, 'high': 102, 'low': 99, 'close': 101, 'volume': 1000}, # K1
{'open': 101, 'high': 108, 'low': 100, 'close': 102, 'volume': 2500}, # K2 (强顶分型)
{'open': 102, 'high': 103, 'low': 95, 'close': 96, 'volume': 1800}, # K3 (大阴线确认)
]
}
]
return test_cases
def setup_klu_chain(data_list):
"""设置KLU链"""
klus = []
base_time = datetime.now()
for i, data in enumerate(data_list):
time_str = (base_time + timedelta(minutes=i)).strftime("%Y-%m-%d %H:%M:%S")
klu = ChanKLU(time_str, data['open'], data['high'], data['low'], data['close'], data['volume'])
klu.set_idx(i)
# 设置基础技术指标
indicators = {
'ma5': data['close'] + (i-1) * 0.1,
'ma10': data['close'] + (i-1) * 0.05,
'rsi': 50 + (i % 3 - 1) * 15,
'macd': (i % 3 - 1) * 0.01,
'macdhist': (i % 2) * 0.005,
'volume_ratio': 1.0 + (i % 2) * 0.3
}
klu.set_indicators(indicators)
klus.append(klu)
# 建立前后关系
for i in range(len(klus)):
if i > 0:
klus[i].set_pre(klus[i-1])
if i < len(klus) - 1:
klus[i].set_next(klus[i+1])
return klus
def setup_klc_chain(data_list):
"""设置KLC链(基于KLU"""
klus = setup_klu_chain(data_list)
klcs = []
# 为简化测试,假设每个KLU对应一个KLC(无包含关系处理)
for i, klu in enumerate(klus):
klc = ChanKLC(klu, i, Chan_KLINE_DIR.UP)
klc.set_end_klu(klu)
klcs.append(klc)
# 建立前后关系
for i in range(len(klcs)):
if i > 0:
klcs[i].set_pre(klcs[i-1])
if i < len(klcs) - 1:
klcs[i].set_next(klcs[i+1])
# 设置分型类型
if len(klcs) >= 3:
middle_klc = klcs[1]
if (middle_klc.high > klcs[0].high and middle_klc.high > klcs[2].high):
middle_klc.set_fx(Chan_FX_TYPE.TOP)
elif (middle_klc.low < klcs[0].low and middle_klc.low < klcs[2].low):
middle_klc.set_fx(Chan_FX_TYPE.BOTTOM)
return klcs
def compare_algorithms(test_cases):
"""对比KLU和KLC算法"""
print("=" * 80)
print("KLU与KLC分型强度算法一致性测试")
print("=" * 80)
for case in test_cases:
print(f"\n🔍 测试用例: {case['name']}")
print("-" * 50)
# 准备数据
klus = setup_klu_chain(case['data'])
klcs = setup_klc_chain(case['data'])
if len(klus) >= 3 and len(klcs) >= 3:
middle_klu = klus[1]
middle_klc = klcs[1]
# KLU分析
middle_klu.update_realtime_analysis()
klu_fx_type = middle_klu.fx_type
klu_strength = middle_klu.fx_strength
klu_confirmed = middle_klu.fx_confirmed
# KLC分析
klc_fx_type = middle_klc.fx
klc_strength_raw = middle_klc.cal_fx_strength() # -3到3
klc_strength_converted = int((klc_strength_raw + 3) * 100 / 6) # 转换为0-100
# 输出对比结果
print(f"K线数据: {case['data'][1]}")
print(f"\nKLU算法结果:")
print(f" 分型类型: {klu_fx_type}")
print(f" 分型强度: {klu_strength}")
print(f" 是否确认: {klu_confirmed}")
print(f"\nKLC算法结果:")
print(f" 分型类型: {klc_fx_type}")
print(f" 分型强度(原始): {klc_strength_raw}")
print(f" 分型强度(转换): {klc_strength_converted}")
# 一致性检查
type_consistent = (klu_fx_type == klc_fx_type)
strength_diff = abs(klu_strength - klc_strength_converted)
strength_consistent = strength_diff <= 10 # 允许10分以内的差异
print(f"\n一致性检查:")
print(f" 分型类型一致: {'' if type_consistent else ''}")
print(f" 强度差异: {strength_diff}{'' if strength_consistent else ''}")
if not type_consistent or not strength_consistent:
print(f" ⚠️ 算法结果不一致!")
else:
print(f" ✅ 算法结果一致")
else:
print("❌ 数据不足,无法进行对比")
def detailed_strength_analysis():
"""详细的强度分析对比"""
print("\n" + "=" * 80)
print("详细强度分析对比")
print("=" * 80)
# 创建一个明确的强分型案例
strong_top_data = [
{'open': 100, 'high': 101, 'low': 99, 'close': 100, 'volume': 1000},
{'open': 100, 'high': 110, 'low': 99, 'close': 102, 'volume': 3000}, # 强顶分型
{'open': 102, 'high': 103, 'low': 92, 'close': 93, 'volume': 2000}, # 强确认
{'open': 93, 'high': 94, 'low': 90, 'close': 91, 'volume': 1500}, # 继续下跌
{'open': 91, 'high': 92, 'low': 88, 'close': 89, 'volume': 1200}, # 进一步确认
]
klus = setup_klu_chain(strong_top_data)
if len(klus) >= 5:
target_klu = klus[1] # 目标分型K线
print(f"分析目标: 第2根K线 (索引1)")
print(f"K线数据: {strong_top_data[1]}")
# 更新分析
target_klu.update_realtime_analysis()
print(f"\n分型检测结果:")
print(f" 分型类型: {target_klu.fx_type}")
print(f" 分型确认: {target_klu.fx_confirmed}")
print(f" 最终强度: {target_klu.fx_strength}")
# 显示中间计算过程(需要重新调用以获取详细信息)
if target_klu.fx_confirmed:
print(f"\n强度计算过程:")
is_bi_end = target_klu._check_if_bi_ending_fx()
post_confirmation = target_klu._check_post_fx_confirmation()
fx_quality = target_klu._check_fx_quality()
print(f" 笔终结判断: {is_bi_end}")
print(f" 后续确认: {post_confirmation}")
print(f" 分型质量: {fx_quality}")
raw_score = is_bi_end + post_confirmation + fx_quality
final_raw = max(-3, min(3, raw_score))
converted_score = int((final_raw + 3) * 100 / 6)
print(f" 原始总分: {raw_score} -> {final_raw}")
print(f" 转换分数: {converted_score}")
if __name__ == "__main__":
# 运行测试
test_cases = create_test_data()
compare_algorithms(test_cases)
# 详细分析
detailed_strength_analysis()
print("\n" + "=" * 80)
print("测试完成!")
print("=" * 80)
-39
View File
@@ -1,39 +0,0 @@
2025-04-18 20:25:03,767 - INFO - Fetched 500 K-lines for BTC/USDT (5m)
2025-04-18 20:25:06,074 - INFO - Fetched 200 K-lines for BTC/USDT (30m)
2025-04-18 20:25:06,104 - INFO - Merged K-lines: 500 -> 404
2025-04-18 20:25:06,105 - INFO - Detected 49 top fractals and 48 bottom fractals
2025-04-18 20:25:06,111 - INFO - Detected 80 strokes
2025-04-18 20:25:06,111 - INFO - Detected 22 segments
2025-04-18 20:25:06,111 - INFO - Detected 0 pivots
2025-04-18 20:25:06,112 - INFO - Detected 27 top fractals and 29 bottom fractals
2025-04-18 20:25:06,115 - INFO - Detected 45 strokes
2025-04-18 20:25:06,115 - INFO - 30m trend: down
2025-04-18 20:25:06,119 - INFO - Detected 4 buy signals and 0 sell signals
2025-04-18 20:25:06,233 - ERROR - Chart plotting failed: x and y must have same first dimension, but have shapes (404,) and (2,)
2025-04-18 20:25:06,233 - ERROR - Main function failed: x and y must have same first dimension, but have shapes (404,) and (2,)
2025-04-18 20:27:05,455 - INFO - Fetched 500 K-lines for BTC/USDT (5m)
2025-04-18 20:27:08,491 - INFO - Fetched 200 K-lines for BTC/USDT (30m)
2025-04-18 20:27:08,521 - INFO - Merged K-lines: 500 -> 404
2025-04-18 20:27:08,522 - INFO - Detected 49 top fractals and 48 bottom fractals
2025-04-18 20:27:08,528 - INFO - Detected 80 strokes
2025-04-18 20:27:08,528 - INFO - Detected 22 segments
2025-04-18 20:27:08,528 - INFO - Detected 0 pivots
2025-04-18 20:27:08,529 - INFO - Detected 27 top fractals and 29 bottom fractals
2025-04-18 20:27:08,532 - INFO - Detected 45 strokes
2025-04-18 20:27:08,532 - INFO - 30m trend: down
2025-04-18 20:27:08,537 - INFO - Detected 4 buy signals and 0 sell signals
2025-04-18 20:27:08,647 - ERROR - Chart plotting failed: x and y must have same first dimension, but have shapes (404,) and (2,)
2025-04-18 20:27:08,647 - ERROR - Main function failed: x and y must have same first dimension, but have shapes (404,) and (2,)
2025-04-18 20:28:51,970 - INFO - Fetched 500 K-lines for BTC/USDT (5m)
2025-04-18 20:28:53,861 - INFO - Fetched 200 K-lines for BTC/USDT (30m)
2025-04-18 20:28:53,897 - INFO - Merged K-lines: 500 -> 404
2025-04-18 20:28:53,898 - INFO - Detected 49 top fractals and 48 bottom fractals
2025-04-18 20:28:53,904 - INFO - Detected 80 strokes
2025-04-18 20:28:53,904 - INFO - Detected 22 segments
2025-04-18 20:28:53,904 - INFO - Detected 0 pivots
2025-04-18 20:28:53,905 - INFO - Detected 27 top fractals and 29 bottom fractals
2025-04-18 20:28:53,908 - INFO - Detected 45 strokes
2025-04-18 20:28:53,908 - INFO - 30m trend: down
2025-04-18 20:28:53,913 - INFO - Detected 4 buy signals and 0 sell signals
2025-04-18 20:28:53,913 - ERROR - Chart plotting failed: Wrong type for data, in make_addplot()
2025-04-18 20:28:53,913 - ERROR - Main function failed: Wrong type for data, in make_addplot()
+83
View File
@@ -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.ema_pattern.sqlite",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"trading_mode": "futures",
"margin_mode": "isolated",
"can_short" : true,
"timeframe" : "15m",
"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": false,
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
"chat_id": "580807463"
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8888,
"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
}
}
+1 -1
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
# freqtrade plot-dataframe --strategy BB9033 --datadir user_data/data/binance -c ./user_data/ChanLun_SOL_30.json --timerange=20250309- # freqtrade plot-dataframe --strategy BB9033 --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 BB9033 --strategy-path ./user_data/Chan/strategies # freqtrade trade -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy BB9033 --strategy-path ./user_data/Chan/strategies
# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy BB9033 --strategy-path ./user_data/Chan/strategies --timerange=20250623- # freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy BB9033 --strategy-path ./user_data/Chan/strategies --timerange=20251023-
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_30.json -t 1m --pairs BTC/USDT:USDT --timerange=20250501- # freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_30.json -t 1m --pairs BTC/USDT:USDT --timerange=20250501-
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss --strategy BB9033 --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_30.json -e 200 --timerange=20250201-20250401 # freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss --strategy BB9033 --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_30.json -e 200 --timerange=20250201-20250401
+115
View File
@@ -0,0 +1,115 @@
# --- Do not remove these libs ---
from statistics import median
from freqtrade.strategy import IStrategy
from technical.util import resample_to_interval, resampled_merge
from pandas import DataFrame
import talib.abstract as ta
from technical import qtpylib
### Now you can use logger.info('asfd') to log
# freqtrade plot-dataframe --strategy EMA_Pattern --datadir user_data/data/binance -c ./user_data/Chan/EMA_Pattern.json --timerange=20250309-
# freqtrade backtesting -c ./user_data/Chan/config/EMA_Pattern.json --strategy EMA_Pattern --strategy-path ./user_data/Chan/strategies --timerange=20251030-
# freqtrade download-data -c ./user_data/Chan/config/EMA_Pattern.json -t 1m 3m 5m 15m 30m 1h --pairs BTC/USDT:USDT --timerange=20250405-
# freqtrade download-data -c ./user_data/Chan/config/EMA_Pattern.json -t 1m 1h 1d 1M --pairs BTC/USDT --timerange=20170101-
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy EMA_Pattern --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/EMA_Pattern.json -e 200 --timerange=20250201-20250901
# freqtrade edge -c ./user_data/Chan/config/EMA_Pattern.json --strategy EMA_Pattern --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
# freqtrade plot-dataframe -c ./user_data/Chan/config/EMA_Pattern.json --strategy EMA_Pattern --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
class EMA_Pattern(IStrategy):
time1h = 1440
can_short: bool = True
timeframe: str = "1m"
process_only_new_candles: bool = False
# ROI 与止损可根据需要在配置中覆盖
minimal_roi = {
"60": 0.005,
"30": 0.01,
"0": 0.02,
}
stoploss: float = -0.30
# 需要的历史K线数量(包含EMA等指标预热)
startup_candle_count: int = 200
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
if dataframe is None or dataframe.empty:
return dataframe
dataframe = self.add_indicators(dataframe)
return dataframe
def add_indicators(self, dataframe: DataFrame) -> DataFrame:
macd = ta.MACD(dataframe, timeperiod=12, fastperiod=12, slowperiod=26, signalperiod=9)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
dataframe['ema6'] = ta.EMA(dataframe, timeperiod=6)
dataframe['ema12'] = ta.EMA(dataframe, timeperiod=12)
dataframe['ema24'] = ta.EMA(dataframe, timeperiod=24)
dataframe['ema52'] = ta.EMA(dataframe, timeperiod=52)
dataframe['adx'] = ta.ADX(dataframe, timeperiod=14)
dataframe['strong_trend'] = dataframe['adx'] > 25
dataframe['UP_Pattern'] = (dataframe['ema6'] > dataframe['ema12']) & (dataframe['ema12'] > dataframe['ema24']) & (dataframe['ema24'] > dataframe['ema52'])
dataframe['DOWN_Pattern'] = (dataframe['ema6'] < dataframe['ema12']) & (dataframe['ema12'] < dataframe['ema24']) & (dataframe['ema24'] < dataframe['ema52'])
dataframe['UP_Confirm'] = (dataframe['ema6'] > dataframe['ema6'].shift(1)) & (dataframe['ema12'] > dataframe['ema12'].shift(1)) & (dataframe['ema24'] > dataframe['ema24'].shift(1)) & (dataframe['ema52'] > dataframe['ema52'].shift(1))
dataframe['DOWN_Confirm'] = (dataframe['ema6'] < dataframe['ema6'].shift(1)) & (dataframe['ema12'] < dataframe['ema12'].shift(1)) & (dataframe['ema24'] < dataframe['ema24'].shift(1)) & (dataframe['ema52'] < dataframe['ema52'].shift(1))
dataframe['EMA52_Cross_EMA24_UP'] = (dataframe['ema52'] < dataframe['ema24']) & (dataframe['ema52'].shift(1) > dataframe['ema24'].shift(1))
dataframe['EMA52_Cross_EMA24_DOWN'] = (dataframe['ema52'] > dataframe['ema24']) & (dataframe['ema52'].shift(1) < dataframe['ema24'].shift(1))
dataframe['Price_Above_EMA52'] = (dataframe['close'] > dataframe['ema52'])
dataframe['Price_Below_EMA52'] = (dataframe['close'] < dataframe['ema52'])
dataframe['MACD_Above_Zero'] = (dataframe['macd'] > 0) & (dataframe['macdsignal'] > 0)
dataframe['MACD_Below_Zero'] = (dataframe['macd'] < 0) & (dataframe['macdsignal'] < 0)
dataframe['BUY_END'] = dataframe['close'] < dataframe['ema52']
dataframe['SELL_END'] = dataframe['close'] > dataframe['ema52']
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
if dataframe is None or dataframe.empty:
return dataframe
dataframe.loc[
(
(dataframe['UP_Pattern']) &
(dataframe['UP_Confirm']) &
(dataframe['Price_Above_EMA52']) &
(dataframe['MACD_Above_Zero']) &
(dataframe['EMA52_Cross_EMA24_UP']) &
(dataframe['strong_trend'])
),
["enter_long", "enter_tag"],
] = (1, "ema_up_trend")
dataframe.loc[
(
(dataframe['DOWN_Pattern']) &
(dataframe['DOWN_Confirm']) &
(dataframe['Price_Below_EMA52']) &
(dataframe['MACD_Below_Zero']) &
(dataframe['EMA52_Cross_EMA24_DOWN']) &
(dataframe['strong_trend'])
),
["enter_short", "enter_tag"],
] = (1, "ema_down_trend")
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
if dataframe is None or dataframe.empty:
return dataframe
dataframe.loc[
(
(dataframe['BUY_END']) |
(dataframe['DOWN_Pattern']) |
(dataframe['EMA52_Cross_EMA24_DOWN'])
),
["exit_long", "exit_tag"],
] = (1, "ema_long_exit")
dataframe.loc[
(
(dataframe['SELL_END']) |
(dataframe['UP_Pattern']) |
(dataframe['EMA52_Cross_EMA24_UP'])
),
["exit_short", "exit_tag"],
] = (1, "ema_short_exit")
return dataframe
def get_ticker_indicator(self):
return int(self.timeframe[:-1])