添加笔中枢,删除了很多没有用的方法
This commit is contained in:
+68
-697
@@ -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):
|
||||
"""获取未完成线段列表,正确处理倒数第二个和最后一个未完成线段"""
|
||||
|
||||
Reference in New Issue
Block a user