Files
Chan/web/services/runtime/serialize.py
T
jackyu66gitandCursor 2cd50f1e01 主图增加笔/线段背驰与面积数字,并修正 SD99999 显示。
三周期分开关控制,只画数字不画图标;线段面积比沿用同向笔面积口径。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-10 04:33:40 +08:00

339 lines
12 KiB
Python

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, 'unittf_dir', None)
dir_name = getattr(dir_value, 'name', dir_value if isinstance(dir_value, str) else None)
if dir_name == 'ABOVE':
dir_num = 1
elif dir_name == 'UNDER':
dir_num = -1
else:
dir_num = 0
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_num,
'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),
'macd_div': float(getattr(seg, 'macd_div', 0) or 0),
'macd_hist': float(getattr(seg, 'macd_hist', 0) or 0)
}
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