添加三类买卖点识别和图上标注,还不错,感觉可以赚钱了,明天继续优化一下

This commit is contained in:
jackyu66git
2026-02-20 01:37:39 +08:00
parent e2726db9c1
commit 01e71b7098
9 changed files with 674 additions and 55 deletions
+45 -2
View File
@@ -532,7 +532,9 @@ def analyze_chan(df, symbol=None, timeframe=None):
bi_zs_list = []
bsp_list = []
if len(bi_zs_list) > 0:
bsp_list = chan.find_third_bsp(bi_list, bi_zs_list)
bsp_list = chan.find_all_bsp(bi_list, bi_zs_list)
for bsp in bsp_list:
print(bsp.end_time, bsp.type, bsp.dir)
# 添加买卖点识别
for bi in bi_list:
bi.cal_macdhist()
@@ -646,6 +648,7 @@ def analyze_chan(df, symbol=None, timeframe=None):
'seg_list': seg_list,
'zs_list': zs_list,
'bi_zs_list': bi_zs_list, # 添加BI中枢列表
'bsp_list': bsp_list, # 添加买卖点列表
'klc_fx_info': klc_fx_info, # KLC分型信息
'chan_macd': chan_macd_data, # 添加ChanMACD分析数据
'ema52_dict': ema52_dict # 添加多时间周期EMA52数据
@@ -1379,7 +1382,36 @@ def analyze():
# 添加多时间周期EMA52数据
'ema52_dict': analysis_result.get('ema52_dict', {}),
# 直接输出KLC趋势标记(使用已有trend字段)
'klc_trend': klc_trend
'klc_trend': klc_trend,
# 添加主周期买卖点列表
# 注意:部分枚举在转为字符串时可能形如 "Chan_BSP_TYPE.BSP1(1)"
# 这里进行健壮的解析,确保前端拿到的始终是 "BSP1" / "BUY" 这种简洁形式,
# 以便与前端的 BSP_STYLE 键(如 "BSP1_BUY")正确匹配。
'bsp_list': [{
'time': format_time_safely(bsp.end_time, client_tz),
'price': float(bsp.klc.low if 'BUY' in str(bsp.dir) else bsp.klc.high),
# -- 规范化 type 名称,例如:
# "Chan_BSP_TYPE.BSP1" -> "BSP1"
# "Chan_BSP_TYPE.BSP1(1)" -> "BSP1"
# "BSP1" -> "BSP1"
'type': (
lambda raw: (
(raw.split('.')[-1] if '.' in raw else raw).split('(')[0]
)
)(str(bsp.type)),
# -- 规范化 dir 名称,例如:
# "Chan_BSP_DIR.BUY" -> "BUY"
# "Chan_BSP_DIR.BUY(1)" -> "BUY"
# "BUY" -> "BUY"
'dir': (
lambda raw: (
(raw.split('.')[-1] if '.' in raw else raw).split('(')[0]
)
)(str(bsp.dir)),
'is_sure': bool(bsp.is_sure),
'sure_time': format_time_safely(bsp.sure_time, client_tz) if bsp.sure_time else None,
'zs_count': int(bsp.zs_count) if hasattr(bsp, 'zs_count') else 0
} for bsp in analysis_result.get('bsp_list', [])]
})
@@ -1547,6 +1579,17 @@ def analyze():
# 添加小周期 KLC 趋势标记
result['element_klc_trend'] = element_klc_trend
# 添加次周期买卖点列表
result['element_bsp_list'] = [{
'time': format_time_safely(bsp.end_time, client_tz),
'price': float(bsp.klc.low if str(bsp.dir) == 'Chan_BSP_DIR.BUY' else bsp.klc.high),
'type': str(bsp.type).replace('Chan_BSP_TYPE.', ''),
'dir': str(bsp.dir).replace('Chan_BSP_DIR.', ''),
'is_sure': bool(bsp.is_sure),
'sure_time': format_time_safely(bsp.sure_time, client_tz) if bsp.sure_time else None,
'zs_count': int(bsp.zs_count) if hasattr(bsp, 'zs_count') else 0
} for bsp in element_analysis.get('bsp_list', [])]
pass
+193 -34
View File
@@ -912,6 +912,10 @@
<input class="form-check-input" type="checkbox" id="toggleUOnMain">
<label class="form-check-label" for="toggleUOnMain">显示U</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showMainBsp">
<label class="form-check-label" for="showMainBsp">买卖点</label>
</div>
</div>
<div class="d-flex align-items-center mt-2">
<label class="form-label me-0 mb-0">次周期:</label>
@@ -950,6 +954,10 @@
<input class="form-check-input" type="checkbox" id="toggleUOnElement">
<label class="form-check-label" for="toggleUOnElement">显示U</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showElementBsp">
<label class="form-check-label" for="showElementBsp">买卖点</label>
</div>
</div>
</div>
</div>
@@ -4691,9 +4699,135 @@
} catch (e) { console.error('次周期未完成BI中枢处理出错:', e); }
});
}
// 添加买卖点标记
if ($('#showTradePoints').is(':checked')) {
console.log('绘制买卖点 - 已启用');
// 添加买卖点标记(新版:基于 bsp_list / element_bsp_list,按与 KLC 分型相同方式合并到主图标记)
if ($('#showMainBsp').is(':checked') || $('#showElementBsp').is(':checked')) {
console.log('绘制买卖点(BSP) - 已启用');
// BSP 样式定义
const BSP_STYLE = {
'BSP1_BUY': { color: '#FF1744', text: 'B1', position: 'belowBar', size: 0.5 },
'BSP2_BUY': { color: '#F50057', text: 'B2', position: 'belowBar', size: 0.5 },
'BSP3_BUY': { color: '#D500F9', text: 'B3', position: 'belowBar', size: 0.5 },
'BSP1_SELL': { color: '#00E676', text: 'S1', position: 'aboveBar', size: 0.5 },
'BSP2_SELL': { color: '#00B0FF', text: 'S2', position: 'aboveBar', size: 0.5 },
'BSP3_SELL': { color: '#8B4513', text: 'S3', position: 'aboveBar', size: 0.5 },
};
const getBspStyleKey = (bsp) => {
// 统一 BSP key
// - type 可能是 "BSP1"/"BSP2"/"BSP3"
// - 也可能是后端给的 "B1"/"B2"/"B3" 或 "S1"/"S2"/"S3"
// 最终都映射为 "BSP1_BUY" / "BSP1_SELL" 这类 key,方便复用现有样式定义
let type = (bsp.type || '').toUpperCase();
const dir = (bsp.dir || '').toUpperCase();
// 若是 "B1" / "B2" / "B3" 或 "S1" / "S2" / "S3" 形式,则提取数字并映射成 "BSP{n}"
const simpleMatch = type.match(/^([BS])(\d)$/);
if (simpleMatch) {
const n = simpleMatch[2]; // "1" / "2" / "3"
type = 'BSP' + n;
}
return type + '_' + dir;
};
// 收集所有 BSP 标记
const allBspMarkers = [];
// 主周期买卖点
// 兼容不同字段命名:优先使用 bsp_list,若不存在则尝试 bsp
const mainBspList = currentData.bsp_list || currentData.bsp || [];
// 调试:打印前几条主周期 BSP 的 key,方便排查样式不匹配问题
if (mainBspList.length > 0) {
console.log(
'主周期 BSP 示例 (前5条):',
mainBspList.slice(0, 5).map(b => ({
raw_type: b.type,
raw_dir: b.dir,
key: getBspStyleKey(b)
}))
);
}
if ($('#showMainBsp').is(':checked') && mainBspList.length > 0) {
console.log(`绘制主周期买卖点,共${mainBspList.length}条`);
mainBspList.forEach(function(bsp) {
try {
const ts = Math.floor(new Date(bsp.time).getTime() / 1000);
if (isNaN(ts)) return;
const key = getBspStyleKey(bsp);
const style = BSP_STYLE[key] || { color: '#999', shape: 'circle', text: '?', position: 'inBar' };
const sureText = bsp.is_sure ? '' : '?';
allBspMarkers.push({
time: ts,
position: style.position,
color: style.color,
shape: style.shape,
text: style.text + sureText,
size: 2
});
} catch (e) {
console.error('主周期BSP处理出错:', e);
}
});
}
// 次周期买卖点
// 兼容不同字段命名:优先使用 element_bsp_list,若不存在则尝试 element_bsp
const elementBspList = currentData.element_bsp_list || currentData.element_bsp || [];
// 调试:打印前几条次周期 BSP 的 key
if (elementBspList.length > 0) {
console.log(
'次周期 BSP 示例 (前5条):',
elementBspList.slice(0, 5).map(b => ({
raw_type: b.type,
raw_dir: b.dir,
key: getBspStyleKey(b)
}))
);
}
if ($('#showElementBsp').is(':checked') && elementBspList.length > 0) {
console.log(`绘制次周期买卖点,共${elementBspList.length}条`);
elementBspList.forEach(function(bsp) {
try {
const ts = Math.floor(new Date(bsp.time).getTime() / 1000);
if (isNaN(ts)) return;
const key = getBspStyleKey(bsp);
const style = BSP_STYLE[key] || { color: '#999', shape: 'circle', text: '?', position: 'inBar' };
const sureText = bsp.is_sure ? '' : '?';
// 次周期使用稍小的标记和不同前缀以区分
allBspMarkers.push({
time: ts,
position: style.position,
color: style.color,
shape: style.shape,
text: 'e' + style.text + sureText,
size: 1
});
} catch (e) {
console.error('次周期BSP处理出错:', e);
}
});
}
// 将 BSP 标记挂到全局,后面与 KLC 分型等标记一起合并到主系列上
if (allBspMarkers.length > 0) {
// 按时间排序(lightweight-charts 要求标记按时间升序)
allBspMarkers.sort((a, b) => a.time - b.time);
window.bspMarkers = allBspMarkers;
console.log(`准备合并 ${allBspMarkers.length} 个BSP标记到主图标记中`);
} else {
window.bspMarkers = [];
}
} else {
// 关闭 BSP 显示时,清空全局 BSP 标记
window.bspMarkers = [];
}
// 添加买卖点标记(旧版,保留兼容)
// 这里为了与主面板上的「买卖点」开关保持一致,
// 同时响应顶部的 `#showMainBsp` 复选框
if ($('#showTradePoints').is(':checked') || $('#showMainBsp').is(':checked')) {
console.log('绘制买卖点 - 已启用(来源: showTradePoints / showMainBsp');
// 优先使用小周期数据,如果不存在则使用主周期数据
const tradePointsData = currentData.element_trade_points || currentData.trade_points;
@@ -4837,29 +4971,31 @@
lastValueVisible: false,
priceLineVisible: false,
lineVisible: false,
color: 'transparent',
title: '买点'
});
// 设置临时的数据点
buyMarkersSeries.setData([{ time: buyMarkers[0].time, value: 0 }]);
// 使用主K线的收盘价作为基准数据,保证买点标记与价格在同一纵轴范围
if (Array.isArray(candles) && candles.length > 0) {
const baseData = candles.map(c => ({ time: c.time, value: c.close }));
buyMarkersSeries.setData(baseData);
} else {
// 兜底:至少一个数据点,避免报错
buyMarkersSeries.setData([{ time: buyMarkers[0].time, value: buyMarkers[0].price || 0 }]);
}
try {
// 设置买点标记,并添加偏移
// 设置买点标记:文字在价格上方,仅显示文字不显示形状
buyMarkersSeries.setMarkers(
buyMarkers.map(marker => {
// 使用固定偏移而非百分比
const fixedOffset = PRICE_FIXED_OFFSET[marker.type] || 20;
// 为同一时间点的多个买点额外增加堆叠偏移
const stackOffset = marker.stackIndex > 0 ?
10 * marker.stackIndex : 0;
// 使用实际价格位置添加标记,不使用偏移
// 使用实际价格位置,买点显示在K线上方
return {
...marker,
position: 'aboveBar', // 显示在K线上方
// 使用原始价格
price: marker.price
position: 'aboveBar', // 买点:价格上方
price: marker.price,
// 隐藏形状,仅保留文字
size: 0,
color: 'rgba(0, 0, 0, 0)'
};
})
);
@@ -4874,29 +5010,31 @@
lastValueVisible: false,
priceLineVisible: false,
lineVisible: false,
color: 'transparent',
title: '卖点'
});
// 设置临时的数据点
sellMarkersSeries.setData([{ time: sellMarkers[0].time, value: 0 }]);
// 使用主K线的收盘价作为基准数据,保证卖点标记与价格在同一纵轴范围
if (Array.isArray(candles) && candles.length > 0) {
const baseData = candles.map(c => ({ time: c.time, value: c.close }));
sellMarkersSeries.setData(baseData);
} else {
// 兜底:至少一个数据点,避免报错
sellMarkersSeries.setData([{ time: sellMarkers[0].time, value: sellMarkers[0].price || 0 }]);
}
try {
// 设置卖点标记,并添加偏移
// 设置卖点标记:文字在价格下方,仅显示文字不显示形状
sellMarkersSeries.setMarkers(
sellMarkers.map(marker => {
// 使用固定偏移而非百分比
const fixedOffset = PRICE_FIXED_OFFSET[marker.type] || 20;
// 为同一时间点的多个卖点额外增加堆叠偏移
const stackOffset = marker.stackIndex > 0 ?
10 * marker.stackIndex : 0;
// 使用实际价格位置添加标记,不使用偏移
// 使用实际价格位置,卖点显示在K线下方
return {
...marker,
position: 'belowBar', // 显示在K线下方
// 使用原始价格
price: marker.price
position: 'belowBar', // 卖点:价格下方
price: marker.price,
// 隐藏形状,仅保留文字
size: 0,
color: 'rgba(0, 0, 0, 0)'
};
})
);
@@ -5663,10 +5801,18 @@
...allElementFxMarkers,
...(window.kluDivMarkersMain || []),
...(window.kluDivMarkersElement || []),
...trendMarkersToUse
...trendMarkersToUse,
...(window.bspMarkers || [])
];
if (combinedMarkers.length > 0) {
console.log('合并设置', combinedMarkers.length, '个标记(主周期分型:', (window.mainFxMarkers || []).length, '个,小周期分型:', allElementFxMarkers.length, '个,UnitTF:', (window.unittfMarkers || []).length, '个)');
console.log(
'合并设置', combinedMarkers.length, '个标记(主周期分型:',
(window.mainFxMarkers || []).length,
'个,小周期分型:', allElementFxMarkers.length,
'个,UnitTF:', (window.unittfMarkers || []).length,
'个,BSP标记:', (window.bspMarkers || []).length,
'个)'
);
// 根据当前主系列类型设置标记
const klineType = ($('#klineType').val() || (showOriginalKline ? 'candlestick' : 'line'));
@@ -5754,11 +5900,16 @@
});
trendMarkersToUse = trendMarkersToUse.concat(elementMarkers);
}
// 这里的 onlyMainAndU 实际上是「最终要挂到主K线上」的一组标记
// 之前没有把 window.bspMarkers 合进去,导致上面已经合并了 BSP 标记,
// 但在这里再次调用 setMarkers 时把 BSP 覆盖掉了,从而前端看不到买卖点。
// 修复:把 BSP 标记一并合并进来。
const onlyMainAndU = [
...(window.mainFxMarkers || []),
...(window.kluDivMarkersMain || []),
...(window.kluDivMarkersElement || []),
...trendMarkersToUse
...trendMarkersToUse,
...(window.bspMarkers || [])
];
if (onlyMainAndU.length > 0) {
console.log('仅设置', onlyMainAndU.length, '个主周期/UnitTF标记(主周期分型:', (window.mainFxMarkers || []).length, 'UnitTF:', (window.unittfMarkers || []).length, '');
@@ -7739,6 +7890,14 @@
window.showUOnElement = $('#toggleUOnElement').is(':checked');
refreshChartOnly();
});
// 买卖点显示开关
$('#showMainBsp').change(function() {
updateChartDisplay();
});
$('#showElementBsp').change(function() {
updateChartDisplay();
});
// 在控制台输出当前显示状态
console.log('当前显示状态:', {