from __future__ import annotations import pandas as pd from chanlun.core.ChanEnum import Chan_BI_DIR, Chan_SEG_DIR, Chan_MACDSEG_DIR, Chan_MACDHISTSET_DIR # 辅助函数,转换缠论方向枚举为整数 def convert_direction(direction): """转换方向枚举为数字""" if direction == Chan_BI_DIR.UP or direction == Chan_SEG_DIR.UP: return 1 elif direction == Chan_BI_DIR.DOWN or direction == Chan_SEG_DIR.DOWN: return -1 else: return 0 def format_time_safely(time_obj, client_tz): """安全地格式化时间对象,处理字符串和datetime两种情况""" if time_obj is None: return None if isinstance(time_obj, str): # 尝试将字符串解析为datetime try: from dateutil import parser time_obj = parser.parse(time_obj) return time_obj.astimezone(client_tz).isoformat() except: return time_obj else: # 已经是datetime对象 return time_obj.astimezone(client_tz).isoformat() def serialize_fast_bsp_list(fast_bsp_list, client_tz): """序列化第四类买卖点(ChanFastBSP)。 htf_agree(大级别分型同向)与 ladder_ok(中枢顺向推进)分别输出, 由前端决定要显示全部还是只显示两者都满足的。 """ out = [] for bsp in fast_bsp_list or []: try: out.append({ 'time': format_time_safely(bsp.time, client_tz), 'price': float(bsp.price), 'type': str(bsp.type).split('.')[-1].split('(')[0], 'dir': str(bsp.dir).split('.')[-1].split('(')[0], 'is_sure': True, 'lag': int(bsp.lag), 'depth': float(bsp.depth), 'zg': bsp.zg, 'zd': bsp.zd, 'occ': int(bsp.occ), 'htf_agree': bsp.htf_agree, 'ladder_ok': bsp.ladder_ok, 'bo_time': format_time_safely(bsp.bo_time, client_tz) if bsp.bo_time else None, 'pb_time': format_time_safely(bsp.pb_time, client_tz) if bsp.pb_time else None, }) except Exception as e: print(f"序列化fast_bsp出错: {e}") continue return out def serialize_chan_macd_data(chan_macd_data, client_tz): """序列化ChanMACD数据为JSON可序列化格式""" serialized_data = { 'seg_list': [], 'unittf_list': [], 'histset_list': [], # 状态标记数据 'high_position_list': [], 'high_empty_list': [], 'low_position_list': [], 'low_empty_list': [], 'return_zero_list': [], 'cross0_up_list': [], 'cross0_down_list': [], # 新增:输出KLU的继续背驰/分离背驰标志 'klu_list': [] } # 序列化seg_list for seg in chan_macd_data.get('seg_list', []): try: seg_data = { 'start_time': format_time_safely(seg.start_time, client_tz), 'end_time': format_time_safely(seg.end_time, client_tz) if seg.end_time else None, 'seg_dir': 'ABOVE' if seg.seg_dir == Chan_MACDSEG_DIR.ABOVE else 'UNDER', 'klu_count': len(seg.klu_list) if hasattr(seg, 'klu_list') else 0, 'unittf_count': len(seg.unittf_list) if hasattr(seg, 'unittf_list') else 0, 'histset_count': len(seg.hist_set) if hasattr(seg, 'hist_set') else 0 } serialized_data['seg_list'].append(seg_data) except Exception as e: print(f"序列化seg出错: {e}") continue # 序列化unittf_list(兼容新结构与枚举类型) for unittf in chan_macd_data.get('unittf_list', []): try: dir_value = getattr(unittf, 'uinttf_dir', None) dir_name = getattr(dir_value, 'name', dir_value if isinstance(dir_value, str) else None) start_t = getattr(unittf, 'start_type', None) start_type = getattr(start_t, 'name', start_t) end_t = getattr(unittf, 'end_type', None) end_type = getattr(end_t, 'name', end_t) peak_abs = getattr(unittf, 'peak_abs', None) if peak_abs is None: peak_abs = getattr(unittf, 'peak_hist', None) length = getattr(unittf, 'length', None) if length is None: length = len(unittf.klu_list) if hasattr(unittf, 'klu_list') else None unittf_data = { 'start_time': format_time_safely(getattr(unittf, 'start_time', None), client_tz), 'end_time': format_time_safely(getattr(unittf, 'end_time', None), client_tz) if getattr(unittf, 'end_time', None) else None, 'dir': dir_name, # 'ABOVE' | 'UNDER' | None 'start_type': start_type, # e.g. 'START' | 'CROSS0' | 'NEAR0_UP' | 'NEAR0_DOWN' 'end_type': end_type, 'invalid': getattr(unittf, 'invalid', False), 'peak_abs': peak_abs, 'length': length, 'klu_count': len(unittf.klu_list) if hasattr(unittf, 'klu_list') else 0, 'histset_count': len(unittf.histset_list) if hasattr(unittf, 'histset_list') else 0 } serialized_data['unittf_list'].append(unittf_data) except Exception as e: print(f"序列化unittf出错: {e}") continue # 序列化histset_list for histset in chan_macd_data.get('histset_list', []): try: histset_data = { 'start_time': format_time_safely(getattr(histset, 'start_time', None), client_tz), 'end_time': format_time_safely(getattr(histset, 'end_time', None), client_tz), 'histset_dir': 'ABOVE' if histset.histset_dir == Chan_MACDHISTSET_DIR.ABOVE else 'UNDER', 'klu_count': len(histset.klu_list) if hasattr(histset, 'klu_list') else 0 } serialized_data['histset_list'].append(histset_data) except Exception as e: print(f"序列化histset出错: {e}") continue # 序列化状态标记数据 # 序列化高位列表 for high_pos in chan_macd_data.get('high_position_list', []): try: high_pos_data = { 'time': format_time_safely(high_pos['time'], client_tz), 'end_time': format_time_safely(high_pos.get('end_time'), client_tz) if high_pos.get('end_time') else None, 'type': high_pos.get('type', 'start'), 'macd': high_pos.get('macd'), 'signal': high_pos.get('signal'), 'macdhist': high_pos.get('macdhist'), 'end_macd': high_pos.get('end_macd'), 'end_signal': high_pos.get('end_signal'), 'end_macdhist': high_pos.get('end_macdhist') } serialized_data['high_position_list'].append(high_pos_data) except Exception as e: print(f"序列化high_position出错: {e}") continue # 序列化高位空列表 for high_empty in chan_macd_data.get('high_empty_list', []): try: high_empty_data = { 'time': format_time_safely(high_empty['time'], client_tz), 'end_time': format_time_safely(high_empty.get('end_time'), client_tz) if high_empty.get('end_time') else None, 'type': high_empty.get('type', 'start'), 'macd': high_empty.get('macd'), 'signal': high_empty.get('signal'), 'macdhist': high_empty.get('macdhist'), 'end_macd': high_empty.get('end_macd'), 'end_signal': high_empty.get('end_signal'), 'end_macdhist': high_empty.get('end_macdhist') } serialized_data['high_empty_list'].append(high_empty_data) except Exception as e: print(f"序列化high_empty出错: {e}") continue # 序列化低位与低位空 for low_pos in chan_macd_data.get('low_position_list', []): try: low_pos_data = { 'time': format_time_safely(low_pos['time'], client_tz), 'end_time': format_time_safely(low_pos.get('end_time'), client_tz) if low_pos.get('end_time') else None, 'type': low_pos.get('type', 'start'), 'macd': low_pos.get('macd'), 'signal': low_pos.get('signal'), 'macdhist': low_pos.get('macdhist'), 'end_macd': low_pos.get('end_macd'), 'end_signal': low_pos.get('end_signal'), 'end_macdhist': low_pos.get('end_macdhist') } serialized_data['low_position_list'].append(low_pos_data) except Exception as e: print(f"序列化low_position出错: {e}") continue for low_empty in chan_macd_data.get('low_empty_list', []): try: low_empty_data = { 'time': format_time_safely(low_empty['time'], client_tz), 'end_time': format_time_safely(low_empty.get('end_time'), client_tz) if low_empty.get('end_time') else None, 'type': low_empty.get('type', 'start'), 'macd': low_empty.get('macd'), 'signal': low_empty.get('signal'), 'macdhist': low_empty.get('macdhist'), 'end_macd': low_empty.get('end_macd'), 'end_signal': low_empty.get('end_signal'), 'end_macdhist': low_empty.get('end_macdhist') } serialized_data['low_empty_list'].append(low_empty_data) except Exception as e: print(f"序列化low_empty出错: {e}") continue # 序列化归零轴列表 for return_zero in chan_macd_data.get('return_zero_list', []): try: return_zero_data = { 'time': format_time_safely(return_zero['time'], client_tz), 'end_time': format_time_safely(return_zero.get('end_time'), client_tz) if return_zero.get('end_time') else None, 'type': return_zero.get('type', 'start'), 'macd': return_zero.get('macd'), 'signal': return_zero.get('signal'), 'macdhist': return_zero.get('macdhist'), 'end_macd': return_zero.get('end_macd'), 'end_signal': return_zero.get('end_signal'), 'end_macdhist': return_zero.get('end_macdhist') } serialized_data['return_zero_list'].append(return_zero_data) except Exception as e: print(f"序列化return_zero出错: {e}") continue # 序列化穿越零轴列表 for cross0_up in chan_macd_data.get('cross0_up_list', []): try: cross0_up_data = { 'time': format_time_safely(cross0_up['time'], client_tz), 'type': cross0_up.get('type', 'start'), 'macd': cross0_up.get('macd'), 'signal': cross0_up.get('signal'), 'macdhist': cross0_up.get('macdhist') } serialized_data['cross0_up_list'].append(cross0_up_data) except Exception as e: print(f"序列化cross0_up出错: {e}") continue for cross0_down in chan_macd_data.get('cross0_down_list', []): try: cross0_down_data = { 'time': format_time_safely(cross0_down['time'], client_tz), 'type': cross0_down.get('type', 'start'), 'macd': cross0_down.get('macd'), 'signal': cross0_down.get('signal'), 'macdhist': cross0_down.get('macdhist') } serialized_data['cross0_down_list'].append(cross0_down_data) except Exception as e: print(f"序列化cross0_down出错: {e}") continue # 序列化 KLU 列表(仅导出需要的时间与背驰标志) for klu in chan_macd_data.get('klu_list', []): try: serialized_data['klu_list'].append({ 'time': format_time_safely(getattr(klu, 'time', None), client_tz), 'continue_div': bool(getattr(klu, 'continue_div', False)), 'separate_div': int(getattr(klu, 'separate_div', 0)) if getattr(klu, 'separate_div', 0) is not None else 0, 'near0_return': int(getattr(klu, 'near0_return', 0)) if getattr(klu, 'near0_return', 0) is not None else 0 }) except Exception as e: print(f"序列化klu出错: {e}") continue return serialized_data def clean_dataframe_for_json(df): """清理DataFrame数据用于JSON序列化""" # 创建副本避免修改原始数据 clean_df = df.copy() # 替换NaN值为None clean_df = clean_df.where(pd.notnull(clean_df), None) return clean_df def get_uncompleted_seg_list(seg_list, client_tz): """获取未完成线段列表,正确处理倒数第二个和最后一个未完成线段""" uncompleted_segs = [seg for seg in seg_list if not seg.is_sure] if len(uncompleted_segs) == 0: return [] result = [] for i, seg in enumerate(uncompleted_segs): is_last = (i == len(uncompleted_segs) - 1) # 是否为最后一个未完成线段 seg_data = { '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(), '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, 'direction': convert_direction(seg.dir) } if is_last: # 最后一个未完成线段:没有结束时间和价格 seg_data['end_time'] = None seg_data['end_price'] = None else: # 倒数第二个及之前的未完成线段:使用实际的结束时间和价格 if seg.end_bi and seg.end_bi.end_klc: seg_data['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() seg_data['end_price'] = seg.end_bi.end_klc.high if convert_direction(seg.dir) == 1 else seg.end_bi.end_klc.low else: # 如果没有结束笔,设为None seg_data['end_time'] = None seg_data['end_price'] = None result.append(seg_data) return result