添加笔中枢,删除了很多没有用的方法

This commit is contained in:
jackyu66git
2025-10-24 23:31:43 +08:00
parent cb621c3d87
commit dce2099ba1
9 changed files with 519 additions and 1266 deletions
+68 -697
View File
@@ -125,7 +125,7 @@ def get_crypto_kl_data(symbol, timeframe, limit=1000, start_time=None, end_time=
# 添加请求计数和最大限制
request_count = 0
max_requests = 50 # 最大请求次数,防止无限循环
max_requests = 300 # 最大请求次数,防止无限循环
# 分页加载数据
while request_count < max_requests:
@@ -188,8 +188,8 @@ def get_crypto_kl_data(symbol, timeframe, limit=1000, start_time=None, end_time=
# 限制数据条数的逻辑 - 优先考虑时间范围
if start_time and end_time:
# 如果指定了明确的时间范围,返回该时间范围内的所有数据
if len(df) > 10000: # 防止数据量过大,设置一个合理的上限
df = df.tail(10000).reset_index(drop=True)
if len(df) > 100000: # 防止数据量过大,设置一个合理的上限
df = df.tail(100000).reset_index(drop=True)
elif limit and len(df) > limit:
# 如果没有指定明确时间范围,使用默认的limit限制
df = df.tail(limit).reset_index(drop=True)
@@ -391,8 +391,13 @@ def analyze_chan(df, symbol=None, timeframe=None):
#print(bi_list[index].start_time, bi_list[index].start_klc.end_time, bi_list[index].dir)
seg_list = chan.get_seg_list(bi_list)
zs_list = chan.calculate_zs(bi_list, seg_list)
# 计算笔中枢(BI中枢)并拍平成列表
try:
bi_zs_nested = chan.cal_bi_zs(seg_list)
bi_zs_list = [zs for group in bi_zs_nested for zs in (group or [])] if bi_zs_nested else []
except Exception:
bi_zs_list = []
# 添加买卖点识别
buy_sell_points = identify_trade_points(bi_list, seg_list, zs_list)
for bi in bi_list:
bi.cal_macdhist()
for bi in bi_list:
@@ -513,63 +518,6 @@ def analyze_chan(df, symbol=None, timeframe=None):
'is_strong_fx': False
})
# 提取KLU分型信息
klu_fx_info = []
for klu in klu_list:
if hasattr(klu, 'fx_type') and klu.fx_type != Chan_FX_TYPE.UNKNOWN:
try:
# 计算分型强度
fx_strength = 0
fx_strength_level = ""
is_strong_fx = False
# 尝试调用分型强度计算方法
if hasattr(klu, 'calculate_realtime_fx_strength'):
fx_strength = klu.calculate_realtime_fx_strength()
elif hasattr(klu, 'fx_strength'):
fx_strength = klu.fx_strength
# 尝试获取分型强度等级 - 基于强度值生成等级
if fx_strength >= 2:
fx_strength_level = ""
is_strong_fx = True
elif fx_strength >= 1:
fx_strength_level = ""
is_strong_fx = False
elif fx_strength >= 0:
fx_strength_level = ""
is_strong_fx = False
else:
fx_strength_level = "极弱"
is_strong_fx = False
# 确保分型确认状态
is_confirmed = getattr(klu, 'fx_confirmed', True)
klu_fx_info.append({
'time': klu.time,
'price': klu.low if klu.fx_type == Chan_FX_TYPE.BOTTOM else klu.high,
'fx_type': str(klu.fx_type).replace("Chan_FX_TYPE.", ""),
'is_bottom': klu.fx_type == Chan_FX_TYPE.BOTTOM,
'fx_strength': fx_strength, # 分型强度分数
'fx_strength_level': fx_strength_level, # 分型强度等级
'is_strong_fx': is_strong_fx, # 是否为强分型
'fx_confirmed': is_confirmed # 分型是否确认
})
except Exception as e:
# 如果出错,仍然添加基本信息,但分型强度为0
klu_fx_info.append({
'time': klu.time,
'price': klu.low if klu.fx_type == Chan_FX_TYPE.BOTTOM else klu.high,
'fx_type': str(klu.fx_type).replace("Chan_FX_TYPE.", ""),
'is_bottom': klu.fx_type == Chan_FX_TYPE.BOTTOM,
'fx_strength': 0,
'fx_strength_level': "",
'is_strong_fx': False,
'fx_confirmed': False
})
return {
'klc_list': klc_list,
@@ -577,474 +525,12 @@ def analyze_chan(df, symbol=None, timeframe=None):
'bi_list': bi_list,
'seg_list': seg_list,
'zs_list': zs_list,
'trade_points': buy_sell_points,
'bi_zs_list': bi_zs_list, # 添加BI中枢列表
'klc_fx_info': klc_fx_info, # KLC分型信息
'klu_fx_info': klu_fx_info, # 添加KLU分型信息
'chan_macd': chan_macd_data, # 添加ChanMACD分析数据
'ema52_dict': ema52_dict # 添加多时间周期EMA52数据
}
def generate_replay_data(df, client_tz, symbol=None, element_timeframe=None, start_time=None, end_time=None):
"""生成逐步计算的回放数据"""
replay_data = {}
# 预先获取完整的次周期数据(避免重复数据获取)
element_full_data = None
if element_timeframe and symbol:
# 一次性获取完整的次周期数据
element_full_data = get_kl_data(symbol, element_timeframe, start_time=start_time, end_time=end_time)
if element_full_data is not None and len(element_full_data) > 0:
# 一次性添加技术指标
element_full_data = add_indicators(element_full_data)
# 为每个K线索引计算分析结果
for i in range(1, len(df) + 1): # 从1开始,至少需要1根K线
try:
# 截取到当前索引的数据
current_df = df.iloc[:i].copy()
# 添加技术指标
current_df = add_indicators(current_df)
# 进行缠论分析
analysis_result = analyze_chan(current_df, symbol, timeframe)
# 计算MACD
macd_data = calculate_macd(current_df)
# 如果有次周期数据,筛选对应时间范围的数据
element_step_data = {}
if element_full_data is not None:
# 获取当前主周期时间范围
current_end_time = current_df['timestamp'].iloc[-1] if len(current_df) > 0 else None
if current_end_time:
# 筛选次周期数据:只取时间戳小于等于当前主周期结束时间的数据
element_current_df = element_full_data[element_full_data['timestamp'] <= current_end_time].copy()
if len(element_current_df) > 0:
# 重新对当前时间范围的次周期数据进行缠论分析
# 这样可以确保数据的准确性,避免时间筛选的复杂性
element_current_analysis = analyze_chan(element_current_df, symbol, element_timeframe)
# 直接使用分析结果,无需复杂的时间筛选
filtered_bi_list = element_current_analysis['bi_list']
filtered_seg_list = element_current_analysis['seg_list']
filtered_zs_list = element_current_analysis['zs_list']
filtered_trade_points = element_current_analysis['trade_points']
filtered_klc_fx = element_current_analysis['klc_fx_info']
filtered_klu_fx = element_current_analysis['klu_fx_info']
# 计算当前时间范围的MACD
element_macd_data = calculate_macd(element_current_df)
element_step_data = {
'element_kline_data': clean_dataframe_for_json(element_current_df).to_dict('records'),
'element_bi_list': [{
'start_time': bi.start_klc.end_time if isinstance(bi.start_klc.end_time, str) else bi.start_klc.end_time.astimezone(client_tz).isoformat(),
'end_time': (bi.end_klc.end_time if isinstance(bi.end_klc.end_time, str) else bi.end_klc.end_time.astimezone(client_tz).isoformat()) if bi.end_klc else None,
'sure_time': format_time_safely(bi.sure_time, client_tz) if bi.sure_time else None,
'start_price': bi.start_klc.low if convert_direction(bi.dir) == 1 else bi.start_klc.high,
'end_price': bi.end_klc.high if convert_direction(bi.dir) == 1 else bi.end_klc.low if bi.end_klc else None,
'direction': convert_direction(bi.dir),
'macd_div': float(bi.macd_div) if hasattr(bi, 'macd_div') else 0
} for bi in filtered_bi_list if bi.end_klc],
'element_uncompleted_bi_list': [{
'start_time': bi.start_klc.end_time if isinstance(bi.start_klc.end_time, str) else bi.start_klc.end_time.astimezone(client_tz).isoformat(),
'end_time': None, # 未完成笔没有结束时间
'sure_time': format_time_safely(bi.sure_time, client_tz) if bi.sure_time else None,
'start_price': bi.start_klc.low if convert_direction(bi.dir) == 1 else bi.start_klc.high,
'end_price': None, # 未完成笔没有结束价格
'direction': convert_direction(bi.dir),
'macd_div': float(bi.macd_div) if hasattr(bi, 'macd_div') else 0
} for bi in filtered_bi_list if not bi.end_klc],
'element_seg_list': [{
'start_time': seg.start_bi.start_klc.end_time if isinstance(seg.start_bi.start_klc.end_time, str) else seg.start_bi.start_klc.end_time.astimezone(client_tz).isoformat(),
'end_time': (seg.end_bi.end_klc.end_time if isinstance(seg.end_bi.end_klc.end_time, str) else seg.end_bi.end_klc.end_time.astimezone(client_tz).isoformat()) if seg.end_bi else None,
'sure_time': format_time_safely(seg.sure_time, client_tz) if seg.sure_time else None,
'start_price': seg.start_bi.start_klc.low if convert_direction(seg.dir) == 1 else seg.start_bi.start_klc.high,
'end_price': seg.end_bi.end_klc.high if convert_direction(seg.dir) == 1 else seg.end_bi.end_klc.low if seg.end_bi else None,
'direction': convert_direction(seg.dir)
} for seg in filtered_seg_list if seg.is_sure],
'element_uncompleted_seg_list': get_uncompleted_seg_list(filtered_seg_list, client_tz),
'element_zs_list': [{
'start_time': zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat(),
'end_time': (zs.end_klc.end_time if isinstance(zs.end_klc.end_time, str) else zs.end_klc.end_time.astimezone(client_tz).isoformat()) if zs.end_klc else None,
'zg': zs.zg,
'zd': zs.zd,
'gg': zs.gg,
'dd': zs.dd,
'is_sure': zs.is_sure
} for zs in filtered_zs_list if zs.end_klc],
'element_uncompleted_zs_list': [{
'start_time': zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat(),
'end_time': None,
'zg': zs.zg,
'zd': zs.zd,
'gg': zs.gg,
'dd': zs.dd,
'is_sure': zs.is_sure
} for zs in filtered_zs_list if not zs.is_sure],
'element_trade_points': [{
'type': point['type'],
'time': format_time_safely(point['time'], client_tz),
'price': point['price'],
'desc': point['desc']
} for point in filtered_trade_points],
'element_macd': element_macd_data,
'element_bollinger': {
'upper': element_current_df['bb_upper'].tolist(),
'middle': element_current_df['bb_middle'].tolist(),
'lower': element_current_df['bb_lower'].tolist()
},
'element_element_bollinger': {
'upper': element_current_df['element_bb_upper'].tolist(),
'middle': element_current_df['element_bb_middle'].tolist(),
'lower': element_current_df['element_bb_lower'].tolist()
},
# 添加次周期ATR数据
'element_atr': element_current_df['atr'].tolist(),
'element_klc_fx_info': [{
'time': format_time_safely(point['time'], client_tz),
'price': float(point['price']),
'fx_type': point['fx_type'],
'is_bottom': bool(point['is_bottom']),
'fx_strength': float(point['fx_strength']),
'fx_strength_level': str(point['fx_strength_level']),
'is_strong_fx': bool(point['is_strong_fx'])
} for point in filtered_klc_fx],
'element_klu_fx_info': [{
'time': format_time_safely(point['time'], client_tz),
'price': float(point['price']),
'fx_type': point['fx_type'],
'is_bottom': bool(point['is_bottom']),
'fx_strength': float(point['fx_strength']),
'fx_strength_level': str(point['fx_strength_level']),
'is_strong_fx': bool(point['is_strong_fx']),
'fx_confirmed': bool(point['fx_confirmed'])
} for point in filtered_klu_fx]
}
# 构建该索引对应的分析结果
step_data = {
'step_index': i-1, # 当前步骤索引
'total_steps': len(df), # 总步骤数
'has_element_data': element_timeframe is not None and len(element_step_data) > 0, # 是否包含次周期数据
'element_timeframe': element_timeframe, # 次周期时间框架
'kline_data': clean_dataframe_for_json(current_df).to_dict('records'),
'bi_list': [{
'start_time': bi.start_klc.end_time if isinstance(bi.start_klc.end_time, str) else bi.start_klc.end_time.astimezone(client_tz).isoformat(),
'end_time': (bi.end_klc.end_time if isinstance(bi.end_klc.end_time, str) else bi.end_klc.end_time.astimezone(client_tz).isoformat()) if bi.end_klc else None,
'sure_time': format_time_safely(bi.sure_time, client_tz) if bi.sure_time else None,
'start_price': bi.start_klc.low if convert_direction(bi.dir) == 1 else bi.start_klc.high,
'end_price': bi.end_klc.high if convert_direction(bi.dir) == 1 else bi.end_klc.low if bi.end_klc else None,
'direction': convert_direction(bi.dir),
'macd_div': float(bi.macd_div) if hasattr(bi, 'macd_div') else 0
} for bi in analysis_result['bi_list'] if bi.end_klc],
'uncompleted_bi_list': [{
'start_time': bi.start_klc.end_time if isinstance(bi.start_klc.end_time, str) else bi.start_klc.end_time.astimezone(client_tz).isoformat(),
'end_time': None, # 未完成笔没有结束时间
'sure_time': format_time_safely(bi.sure_time, client_tz) if bi.sure_time else None,
'start_price': bi.start_klc.low if convert_direction(bi.dir) == 1 else bi.start_klc.high,
'end_price': None, # 未完成笔没有结束价格
'direction': convert_direction(bi.dir),
'macd_div': float(bi.macd_div) if hasattr(bi, 'macd_div') else 0
} for bi in analysis_result['bi_list'] if not bi.end_klc],
'seg_list': [{
'start_time': seg.start_bi.start_klc.end_time if isinstance(seg.start_bi.start_klc.end_time, str) else seg.start_bi.start_klc.end_time.astimezone(client_tz).isoformat(),
'end_time': (seg.end_bi.end_klc.end_time if isinstance(seg.end_bi.end_klc.end_time, str) else seg.end_bi.end_klc.end_time.astimezone(client_tz).isoformat()) if seg.end_bi else None,
'sure_time': format_time_safely(seg.sure_time, client_tz) if seg.sure_time else None,
'start_price': seg.start_bi.start_klc.low if convert_direction(seg.dir) == 1 else seg.start_bi.start_klc.high,
'end_price': seg.end_bi.end_klc.high if convert_direction(seg.dir) == 1 else seg.end_bi.end_klc.low if seg.end_bi else None,
'direction': convert_direction(seg.dir)
} for seg in analysis_result['seg_list'] if seg.is_sure],
'uncompleted_seg_list': get_uncompleted_seg_list(analysis_result['seg_list'], client_tz),
'zs_list': [{
'start_time': zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat(),
'end_time': (zs.end_klc.end_time if isinstance(zs.end_klc.end_time, str) else zs.end_klc.end_time.astimezone(client_tz).isoformat()) if zs.end_klc else None,
'zg': zs.zg,
'zd': zs.zd,
'gg': zs.gg,
'dd': zs.dd,
'is_sure': zs.is_sure
} for zs in analysis_result['zs_list'] if zs.end_klc],
'uncompleted_zs_list': [{
'start_time': zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat(),
'end_time': None,
'zg': zs.zg,
'zd': zs.zd,
'gg': zs.gg,
'dd': zs.dd,
'is_sure': zs.is_sure
} for zs in analysis_result['zs_list'] if not zs.is_sure],
'trade_points': [{
'type': point['type'],
'time': format_time_safely(point['time'], client_tz),
'price': point['price'],
'desc': point['desc']
} for point in analysis_result['trade_points']],
'macd': macd_data,
'bollinger': {
'upper': current_df['bb_upper'].tolist(),
'middle': current_df['bb_middle'].tolist(),
'lower': current_df['bb_lower'].tolist()
},
'element_bollinger': {
'upper': current_df['element_bb_upper'].tolist(),
'middle': current_df['element_bb_middle'].tolist(),
'lower': current_df['element_bb_lower'].tolist()
},
# 添加ATR数据
'atr': current_df['atr'].tolist(),
'klc_fx_info': [{
'time': format_time_safely(point['time'], client_tz),
'price': float(point['price']),
'fx_type': point['fx_type'],
'is_bottom': bool(point['is_bottom']),
'fx_strength': float(point['fx_strength']), # 分型强度分数
'fx_strength_level': str(point['fx_strength_level']), # 分型强度等级
'is_strong_fx': bool(point['is_strong_fx']) # 是否为强分型
} for point in analysis_result['klc_fx_info']],
'klu_fx_info': [{
'time': format_time_safely(point['time'], client_tz),
'price': float(point['price']),
'fx_type': point['fx_type'],
'is_bottom': bool(point['is_bottom']),
'fx_strength': float(point['fx_strength']), # 分型强度分数
'fx_strength_level': str(point['fx_strength_level']), # 分型强度等级
'is_strong_fx': bool(point['is_strong_fx']), # 是否为强分型
'fx_confirmed': bool(point['fx_confirmed']) # 分型是否确认
} for point in analysis_result['klu_fx_info']]
}
# 合并次周期数据到step_data中,如果没有次周期数据则提供空的占位符
if element_step_data:
step_data.update(element_step_data)
else:
# 提供空的次周期数据结构,确保前端可以统一处理
step_data.update({
'element_kline_data': [],
'element_bi_list': [],
'element_uncompleted_bi_list': [],
'element_seg_list': [],
'element_uncompleted_seg_list': [],
'element_zs_list': [],
'element_uncompleted_zs_list': [],
'element_trade_points': [],
'element_macd': {'macd': [], 'signal': [], 'histogram': []},
'element_bollinger': {'upper': [], 'middle': [], 'lower': []},
'element_element_bollinger': {'upper': [], 'middle': [], 'lower': []},
'element_atr': [],
'element_klc_fx_info': [],
'element_klu_fx_info': []
})
replay_data[i-1] = step_data # 使用0-based索引
except Exception as e:
continue
return replay_data
def identify_trade_points(bi_list, seg_list, zs_list):
"""识别缠论买卖点 - 多级别识别,减少滞后性"""
trade_points = []
# 1. 基于笔的二三类买卖点识别(更及时)
trade_points.extend(identify_bi_trade_points(bi_list, zs_list))
# 2. 基于线段的一类买卖点识别(传统方法)
trade_points.extend(identify_seg_trade_points(seg_list))
# 3. 基于分型强度的预警点识别(最及时)
trade_points.extend(identify_fx_warning_points(bi_list))
# 4. 基于MACD背驰的买卖点识别
trade_points.extend(identify_macd_divergence_points(bi_list))
# 按时间排序
trade_points.sort(key=lambda x: x['time'])
return trade_points
def identify_bi_trade_points(bi_list, zs_list):
"""基于笔识别二三类买卖点 - 更及时的信号"""
trade_points = []
if len(bi_list) < 3:
return trade_points
# 构建中枢映射,便于快速查找
zs_map = {}
for zs in zs_list:
if zs.is_sure: # 只考虑已确认的中枢
zs_map[zs.start_klc.end_time] = zs
for i in range(2, len(bi_list)):
current_bi = bi_list[i]
prev_bi = bi_list[i-1]
prev_prev_bi = bi_list[i-2]
# 确保笔已完成
if not current_bi.end_klc or not prev_bi.end_klc or not prev_prev_bi.end_klc:
continue
# 二类买点:向下笔后的向上笔,且不创新低
if (convert_direction(prev_bi.dir) == -1 and
convert_direction(current_bi.dir) == 1):
prev_low = prev_bi.end_klc.low
current_end_price = current_bi.end_klc.high
# 检查是否不创新低(相对于前面的低点)
if i >= 4: # 至少需要5个笔来判断
earlier_lows = [bi.end_klc.low for bi in bi_list[max(0, i-4):i-1]
if convert_direction(bi.dir) == -1 and bi.end_klc]
if earlier_lows and prev_low > min(earlier_lows):
trade_points.append({
'type': TRADE_POINT_TYPE.BUY2,
'time': current_bi.end_klc.end_time,
'price': current_end_price,
'desc': '二类买点(笔)'
})
# 二类卖点:向上笔后的向下笔,且不创新高
if (convert_direction(prev_bi.dir) == 1 and
convert_direction(current_bi.dir) == -1):
prev_high = prev_bi.end_klc.high
current_end_price = current_bi.end_klc.low
# 检查是否不创新高(相对于前面的高点)
if i >= 4: # 至少需要5个笔来判断
earlier_highs = [bi.end_klc.high for bi in bi_list[max(0, i-4):i-1]
if convert_direction(bi.dir) == 1 and bi.end_klc]
if earlier_highs and prev_high < max(earlier_highs):
trade_points.append({
'type': TRADE_POINT_TYPE.SELL2,
'time': current_bi.end_klc.end_time,
'price': current_end_price,
'desc': '二类卖点(笔)'
})
return trade_points
def identify_seg_trade_points(seg_list):
"""基于线段识别一类买卖点 - 传统方法"""
trade_points = []
if len(seg_list) >= 3:
for i in range(2, len(seg_list)):
# 确保线段已完成
if seg_list[i].end_bi and seg_list[i-1].end_bi and seg_list[i-2].end_bi:
# 一类买点:向下-向上-向下的底分型
if (convert_direction(seg_list[i-2].dir) == -1 and
convert_direction(seg_list[i-1].dir) == 1 and
convert_direction(seg_list[i].dir) == -1):
trade_points.append({
'type': TRADE_POINT_TYPE.BUY1,
'time': seg_list[i].end_bi.end_klc.end_time,
'price': seg_list[i].end_bi.end_klc.low,
'desc': '一类买点(线段)'
})
# 一类卖点:向上-向下-向上的顶分型
if (convert_direction(seg_list[i-2].dir) == 1 and
convert_direction(seg_list[i-1].dir) == -1 and
convert_direction(seg_list[i].dir) == 1):
trade_points.append({
'type': TRADE_POINT_TYPE.SELL1,
'time': seg_list[i].end_bi.end_klc.end_time,
'price': seg_list[i].end_bi.end_klc.high,
'desc': '一类卖点(线段)'
})
return trade_points
def identify_fx_warning_points(bi_list):
"""基于分型强度识别预警点 - 最及时的信号"""
trade_points = []
if len(bi_list) < 2:
return trade_points
# 检查最近的几个笔
recent_bis = bi_list[-3:] if len(bi_list) >= 3 else bi_list
for bi in recent_bis:
if not bi.end_klc:
continue
# 获取分型强度(如果有的话)
fx_strength = 0
if hasattr(bi.end_klc, 'cal_fx_strength'):
try:
fx_strength = bi.end_klc.cal_fx_strength(5)
except:
fx_strength = 0
# 强分型预警(分型强度>=2
if fx_strength >= 2:
if convert_direction(bi.dir) == -1: # 向下笔结束,可能的底部
trade_points.append({
'type': TRADE_POINT_TYPE.BUY3,
'time': bi.end_klc.end_time,
'price': bi.end_klc.low,
'desc': f'强分型预警-买点(强度:{fx_strength})'
})
elif convert_direction(bi.dir) == 1: # 向上笔结束,可能的顶部
trade_points.append({
'type': TRADE_POINT_TYPE.SELL3,
'time': bi.end_klc.end_time,
'price': bi.end_klc.high,
'desc': f'强分型预警-卖点(强度:{fx_strength})'
})
return trade_points
def identify_macd_divergence_points(bi_list):
"""基于MACD背驰识别买卖点"""
trade_points = []
if len(bi_list) < 4:
return trade_points
# 检查最近的笔是否有背驰
for i in range(2, len(bi_list)):
current_bi = bi_list[i]
if not current_bi.end_klc or not hasattr(current_bi, 'macd_div'):
continue
# MACD背驰阈值
divergence_threshold = 0.3
# 向下笔的底背驰 -> 买点
if (convert_direction(current_bi.dir) == -1 and
hasattr(current_bi, 'macd_div') and
current_bi.macd_div > divergence_threshold):
trade_points.append({
'type': TRADE_POINT_TYPE.BUY2,
'time': current_bi.end_klc.end_time,
'price': current_bi.end_klc.low,
'desc': f'MACD底背驰买点(背驰度:{current_bi.macd_div:.2f})'
})
# 向上笔的顶背驰 -> 卖点
elif (convert_direction(current_bi.dir) == 1 and
hasattr(current_bi, 'macd_div') and
current_bi.macd_div > divergence_threshold):
trade_points.append({
'type': TRADE_POINT_TYPE.SELL2,
'time': current_bi.end_klc.end_time,
'price': current_bi.end_klc.high,
'desc': f'MACD顶背驰卖点(背驰度:{current_bi.macd_div:.2f})'
})
return trade_points
# 辅助函数,转换缠论方向枚举为整数
def convert_direction(direction):
"""转换方向枚举为数字"""
@@ -1719,6 +1205,20 @@ def analyze():
'dd': zs.dd,
'is_sure': zs.is_sure # 添加中枢是否完成的标志
} for zs in analysis_result['zs_list'] if zs.end_klc],
# 添加主周期BI中枢列表(已完成)
'bi_zs_list': [{
'start_time': (
(zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat())
if getattr(zs.start_klc, 'end_time', None) else
(zs.start_klc.start_time if isinstance(zs.start_klc.start_time, str) else zs.start_klc.start_time.astimezone(client_tz).isoformat())
),
'end_time': (zs.end_time if isinstance(zs.end_time, str) else zs.end_time.astimezone(client_tz).isoformat()) if getattr(zs, 'end_time', None) else None,
'zg': zs.zg,
'zd': zs.zd,
'gg': zs.gg,
'dd': zs.dd,
'is_sure': bool(getattr(zs, 'is_sure', False))
} for zs in analysis_result.get('bi_zs_list', []) if getattr(zs, 'is_sure', False)],
# 添加未完成中枢列表
'uncompleted_zs_list': [{
'start_time': zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat(),
@@ -1729,12 +1229,21 @@ def analyze():
'dd': zs.dd,
'is_sure': zs.is_sure # 未完成中枢的is_sure为False
} for zs in analysis_result['zs_list'] if not zs.is_sure],
'trade_points': [{
'type': point['type'],
'time': format_time_safely(point['time'], client_tz),
'price': point['price'],
'desc': point['desc']
} for point in analysis_result['trade_points']],
# 添加未完成BI中枢列表
'uncompleted_bi_zs_list': [{
'start_time': (
(zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat())
if getattr(zs.start_klc, 'end_time', None) else
(zs.start_klc.start_time if isinstance(zs.start_klc.start_time, str) else zs.start_klc.start_time.astimezone(client_tz).isoformat())
),
'end_time': None,
'zg': zs.zg,
'zd': zs.zd,
'gg': zs.gg,
'dd': zs.dd,
'is_sure': bool(getattr(zs, 'is_sure', False))
} for zs in analysis_result.get('bi_zs_list', []) if not getattr(zs, 'is_sure', False)],
'macd': macd_data,
# 添加布林带数据
'bollinger': {
@@ -1759,16 +1268,6 @@ def analyze():
'fx_strength_level': str(point['fx_strength_level']), # 分型强度等级
'is_strong_fx': bool(point['is_strong_fx']) # 是否为强分型
} for point in analysis_result['klc_fx_info']],
'klu_fx_info': [{
'time': format_time_safely(point['time'], client_tz),
'price': float(point['price']),
'fx_type': point['fx_type'],
'is_bottom': bool(point['is_bottom']),
'fx_strength': float(point['fx_strength']), # 分型强度分数
'fx_strength_level': str(point['fx_strength_level']), # 分型强度等级
'is_strong_fx': bool(point['is_strong_fx']), # 是否为强分型
'fx_confirmed': bool(point['fx_confirmed']) # 分型是否确认
} for point in analysis_result['klu_fx_info']],
# 添加ChanMACD分析数据
'chan_macd': serialize_chan_macd_data(analysis_result.get('chan_macd', {}), client_tz),
# 添加多时间周期EMA52数据
@@ -1885,13 +1384,35 @@ def analyze():
'dd': zs.dd,
'is_sure': zs.is_sure # 未完成中枢的is_sure为False
} for zs in element_analysis['zs_list'] if not zs.is_sure]
# 添加次周期 BI 中枢(已完成/未完成)
result['element_bi_zs_list'] = [{
'start_time': (
(zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat())
if getattr(zs.start_klc, 'end_time', None) else
(zs.start_klc.start_time if isinstance(zs.start_klc.start_time, str) else zs.start_klc.start_time.astimezone(client_tz).isoformat())
),
'end_time': (zs.end_time if isinstance(zs.end_time, str) else zs.end_time.astimezone(client_tz).isoformat()) if getattr(zs, 'end_time', None) else None,
'zg': zs.zg,
'zd': zs.zd,
'gg': zs.gg,
'dd': zs.dd,
'is_sure': bool(getattr(zs, 'is_sure', False))
} for zs in element_analysis.get('bi_zs_list', []) if getattr(zs, 'is_sure', False)]
result['element_uncompleted_bi_zs_list'] = [{
'start_time': (
(zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat())
if getattr(zs.start_klc, 'end_time', None) else
(zs.start_klc.start_time if isinstance(zs.start_klc.start_time, str) else zs.start_klc.start_time.astimezone(client_tz).isoformat())
),
'end_time': None,
'zg': zs.zg,
'zd': zs.zd,
'gg': zs.gg,
'dd': zs.dd,
'is_sure': bool(getattr(zs, 'is_sure', False))
} for zs in element_analysis.get('bi_zs_list', []) if not getattr(zs, 'is_sure', False)]
result['element_trade_points'] = [{
'type': point['type'],
'time': format_time_safely(point['time'], client_tz),
'price': point['price'],
'desc': point['desc']
} for point in element_analysis['trade_points']]
# 添加小周期分型信息
result['element_klc_fx_info'] = [{
@@ -1903,18 +1424,7 @@ def analyze():
'fx_strength_level': str(point['fx_strength_level']), # 分型强度等级
'is_strong_fx': bool(point['is_strong_fx']) # 是否为强分型
} for point in element_analysis['klc_fx_info']]
result['element_klu_fx_info'] = [{
'time': format_time_safely(point['time'], client_tz),
'price': float(point['price']),
'fx_type': point['fx_type'],
'is_bottom': bool(point['is_bottom']),
'fx_strength': float(point['fx_strength']), # 分型强度分数
'fx_strength_level': str(point['fx_strength_level']), # 分型强度等级
'is_strong_fx': bool(point['is_strong_fx']), # 是否为强分型
'fx_confirmed': bool(point['fx_confirmed']) # 分型是否确认
} for point in element_analysis['klu_fx_info']]
# 添加次周期ChanMACD分析数据
result['element_chan_macd'] = serialize_chan_macd_data(element_analysis.get('chan_macd', {}), client_tz)
@@ -1990,145 +1500,6 @@ def search_stock():
except Exception as e:
return jsonify({'error': str(e)})
@app.route('/api/test_element_data')
def test_element_data():
"""测试次周期数据是否正确生成"""
try:
symbol = request.args.get('symbol', 'SOL/USDT:USDT')
timeframe = request.args.get('timeframe', '1h')
element_timeframe = request.args.get('element_timeframe', '15m')
# 获取主周期数据
main_df = get_kl_data(symbol, timeframe, limit=3)
if main_df is None or len(main_df) == 0:
return jsonify({'error': '无法获取主周期数据'})
# 获取次周期数据
element_df = get_kl_data(symbol, element_timeframe,
start_time=main_df['timestamp'].iloc[0],
end_time=main_df['timestamp'].iloc[-1])
if element_df is None or len(element_df) == 0:
return jsonify({'error': '无法获取次周期数据'})
# 分析次周期数据
element_df = add_indicators(element_df)
element_analysis = analyze_chan(element_df, symbol, element_timeframe)
return jsonify({
'main_data_count': len(main_df),
'element_data_count': len(element_df),
'element_analysis': {
'bi_count': len(element_analysis['bi_list']),
'seg_count': len(element_analysis['seg_list']),
'zs_count': len(element_analysis['zs_list']),
'klc_fx_count': len(element_analysis['klc_fx_info']),
'klu_fx_count': len(element_analysis['klu_fx_info']),
'trade_points_count': len(element_analysis['trade_points'])
},
'sample_bi': [{'has_end_klc': bi.end_klc is not None,
'direction': convert_direction(bi.dir)}
for bi in element_analysis['bi_list'][:2]] if len(element_analysis['bi_list']) > 0 else [],
'sample_klc_fx': element_analysis['klc_fx_info'][:3] if len(element_analysis['klc_fx_info']) > 0 else [],
'sample_klu_fx': element_analysis['klu_fx_info'][:3] if len(element_analysis['klu_fx_info']) > 0 else []
})
except Exception as e:
import traceback
return jsonify({'error': str(e), 'traceback': traceback.format_exc()})
@app.route('/api/debug_replay_sample')
def debug_replay_sample():
"""调试接口:返回回放数据样本,方便前端调试"""
try:
symbol = request.args.get('symbol', 'SOL/USDT:USDT')
timeframe = request.args.get('timeframe', '1h')
element_timeframe = request.args.get('element_timeframe', '15m')
step = int(request.args.get('step', 2)) # 返回第几步的数据
# 获取少量数据进行测试
df = get_kl_data(symbol, timeframe, limit=5)
if df is None or len(df) == 0:
return jsonify({'error': '无法获取测试数据'})
# 生成回放数据
client_tz = timezone('Asia/Shanghai')
replay_data = generate_replay_data(
df, client_tz, symbol, element_timeframe,
start_time=None, end_time=None
)
if step not in replay_data:
return jsonify({'error': f'步骤 {step} 不存在,可用步骤:{list(replay_data.keys())}'})
# 返回指定步骤的完整数据
step_data = replay_data[step]
return jsonify({
'step': step,
'data': step_data,
'summary': {
'has_element_data': step_data.get('has_element_data', False),
'element_timeframe': step_data.get('element_timeframe'),
'main_bi_count': len(step_data.get('bi_list', [])),
'main_klc_fx_count': len(step_data.get('klc_fx_info', [])),
'main_klu_fx_count': len(step_data.get('klu_fx_info', [])),
'element_bi_count': len(step_data.get('element_bi_list', [])),
'element_klc_fx_count': len(step_data.get('element_klc_fx_info', [])),
'element_klu_fx_count': len(step_data.get('element_klu_fx_info', [])),
'element_kline_count': len(step_data.get('element_kline_data', []))
}
})
except Exception as e:
return jsonify({'error': str(e)})
@app.route('/api/debug_replay_structure')
def debug_replay_structure():
"""调试接口:检查回放数据结构"""
try:
# 获取一个简单的测试案例
symbol = request.args.get('symbol', 'SOL/USDT:USDT')
timeframe = request.args.get('timeframe', '1h')
element_timeframe = request.args.get('element_timeframe', '15m')
# 获取少量数据进行测试
df = get_kl_data(symbol, timeframe, limit=5) # 只取5根K线
if df is None or len(df) == 0:
return jsonify({'error': '无法获取测试数据'})
# 生成回放数据
client_tz = timezone('Asia/Shanghai')
replay_data = generate_replay_data(
df, client_tz, symbol, element_timeframe,
start_time=None, end_time=None
)
# 返回结构信息
result = {
'total_steps': len(replay_data),
'sample_step_keys': list(replay_data[0].keys()) if len(replay_data) > 0 else [],
'has_element_data_in_steps': [],
'element_data_counts': {}
}
# 检查每个步骤的次周期数据
for step_idx, step_data in replay_data.items():
has_element = step_data.get('has_element_data', False)
result['has_element_data_in_steps'].append({
'step': step_idx,
'has_element_data': has_element,
'element_bi_count': len(step_data.get('element_bi_list', [])),
'element_klc_fx_count': len(step_data.get('element_klc_fx_info', [])),
'element_klu_fx_count': len(step_data.get('element_klu_fx_info', []))
})
return jsonify(result)
except Exception as e:
return jsonify({'error': str(e)})
# 已移除:/api/filter_stocks 路由
def get_uncompleted_seg_list(seg_list, client_tz):
"""获取未完成线段列表,正确处理倒数第二个和最后一个未完成线段"""