添加三类买卖点识别和图上标注,还不错,感觉可以赚钱了,明天继续优化一下
This commit is contained in:
+45
-2
@@ -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
@@ -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('当前显示状态:', {
|
||||
|
||||
Reference in New Issue
Block a user