diff --git a/__pycache__/ChanBI.cpython-312.pyc b/__pycache__/ChanBI.cpython-312.pyc index b454148..9b13e87 100644 Binary files a/__pycache__/ChanBI.cpython-312.pyc and b/__pycache__/ChanBI.cpython-312.pyc differ diff --git a/__pycache__/ChanBSP.cpython-312.pyc b/__pycache__/ChanBSP.cpython-312.pyc index 177daec..cc05b4c 100644 Binary files a/__pycache__/ChanBSP.cpython-312.pyc and b/__pycache__/ChanBSP.cpython-312.pyc differ diff --git a/__pycache__/ChanCTime.cpython-312.pyc b/__pycache__/ChanCTime.cpython-312.pyc index b9efe3f..c08bfc5 100644 Binary files a/__pycache__/ChanCTime.cpython-312.pyc and b/__pycache__/ChanCTime.cpython-312.pyc differ diff --git a/__pycache__/ChanEnum.cpython-312.pyc b/__pycache__/ChanEnum.cpython-312.pyc index 0e8de31..60e4aad 100644 Binary files a/__pycache__/ChanEnum.cpython-312.pyc and b/__pycache__/ChanEnum.cpython-312.pyc differ diff --git a/__pycache__/ChanKLC.cpython-312.pyc b/__pycache__/ChanKLC.cpython-312.pyc index 9b0f3f5..2436f2e 100644 Binary files a/__pycache__/ChanKLC.cpython-312.pyc and b/__pycache__/ChanKLC.cpython-312.pyc differ diff --git a/__pycache__/ChanKLU.cpython-312.pyc b/__pycache__/ChanKLU.cpython-312.pyc index c332c16..0729e0d 100644 Binary files a/__pycache__/ChanKLU.cpython-312.pyc and b/__pycache__/ChanKLU.cpython-312.pyc differ diff --git a/__pycache__/ChanLun.cpython-312.pyc b/__pycache__/ChanLun.cpython-312.pyc index 0ff7fa4..334f149 100644 Binary files a/__pycache__/ChanLun.cpython-312.pyc and b/__pycache__/ChanLun.cpython-312.pyc differ diff --git a/__pycache__/ChanSBI.cpython-312.pyc b/__pycache__/ChanSBI.cpython-312.pyc index 143d523..39238c4 100644 Binary files a/__pycache__/ChanSBI.cpython-312.pyc and b/__pycache__/ChanSBI.cpython-312.pyc differ diff --git a/__pycache__/ChanSEG.cpython-312.pyc b/__pycache__/ChanSEG.cpython-312.pyc index dac2f10..309ff07 100644 Binary files a/__pycache__/ChanSEG.cpython-312.pyc and b/__pycache__/ChanSEG.cpython-312.pyc differ diff --git a/__pycache__/ChanZS.cpython-312.pyc b/__pycache__/ChanZS.cpython-312.pyc index c3d3626..db18ccb 100644 Binary files a/__pycache__/ChanZS.cpython-312.pyc and b/__pycache__/ChanZS.cpython-312.pyc differ diff --git a/web/app.py b/web/app.py index 3bb3668..1a153c0 100644 --- a/web/app.py +++ b/web/app.py @@ -201,8 +201,6 @@ def get_crypto_kl_data(symbol, timeframe, limit=1000, start_time=None, end_time= print(f"未指定明确时间范围,应用默认限制,返回最新的 {limit} 条记录") df = df.tail(limit).reset_index(drop=True) - df = add_indicators(df) - # 如果过滤后没有数据,返回None if len(df) == 0: print("过滤后无数据") @@ -290,6 +288,19 @@ def add_indicators(df): df['ma30'] = (ta.EMA(df, timeperiod=30)).fillna(0) df['ma250'] = (ta.MA(df, timeperiod=250)).fillna(0) df['rsi'] = ta.RSI(df, timeperiod=14) + + # 计算布林带 (当前周期 - 20周期,2标准差) + bb = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0) + df['bb_upper'] = bb['upperband'].fillna(0) + df['bb_middle'] = bb['middleband'].fillna(0) + df['bb_lower'] = bb['lowerband'].fillna(0) + + # 计算次周期布林带 (14周期,2标准差) + bb_element = ta.BBANDS(df, timeperiod=14, nbdevup=2.0, nbdevdn=2.0, matype=0) + df['element_bb_upper'] = bb_element['upperband'].fillna(0) + df['element_bb_middle'] = bb_element['middleband'].fillna(0) + df['element_bb_lower'] = bb_element['lowerband'].fillna(0) + df['macd'] = df['macd'].fillna(0) df['macdsignal'] = df['macdsignal'].fillna(0) df['macdhist'] = df['macdhist'].fillna(0) @@ -532,25 +543,14 @@ def is_smaller_or_equal_timeframe(tf1, tf2): return tf1_value <= tf2_value def clean_dataframe_for_json(df): - """清理DataFrame中的NaN值,确保JSON序列化正常""" - # 创建副本以避免修改原数据 - df_clean = df.copy() + """清理DataFrame数据用于JSON序列化""" + # 创建副本避免修改原始数据 + clean_df = df.copy() - # 将NaN、inf、-inf替换为None - df_clean = df_clean.replace([np.nan, np.inf, -np.inf], None) + # 替换NaN值为None + clean_df = clean_df.where(pd.notnull(clean_df), None) - # 处理数值列,确保值为有限数字或None - numeric_columns = df_clean.select_dtypes(include=[np.number]).columns - for col in numeric_columns: - # 确保所有数值都是有限的 - df_clean[col] = df_clean[col].apply(lambda x: x if (x is not None and np.isfinite(x)) else None) - - # 处理时间列,确保格式正确 - datetime_columns = df_clean.select_dtypes(include=['datetime64']).columns - for col in datetime_columns: - df_clean[col] = df_clean[col].dt.strftime('%Y-%m-%d %H:%M:%S') - - return df_clean + return clean_df @app.route('/') def index(): @@ -615,6 +615,10 @@ def analyze(): # 如果不是只需要分形元素数据,则添加主周期数据 if not elements_only: print(f"处理主周期数据 (elements_only={elements_only})") + + # 添加技术指标(包括布林带) + df = add_indicators(df) + # 进行缠论分析 analysis_result = analyze_chan(df) @@ -661,6 +665,17 @@ def analyze(): 'desc': point['desc'] } for point in analysis_result['trade_points']], 'macd': macd_data, + # 添加布林带数据 + 'bollinger': { + 'upper': df['bb_upper'].tolist(), + 'middle': df['bb_middle'].tolist(), + 'lower': df['bb_lower'].tolist() + }, + 'element_bollinger': { + 'upper': df['element_bb_upper'].tolist(), + 'middle': df['element_bb_middle'].tolist(), + 'lower': df['element_bb_lower'].tolist() + }, # 添加K线分型信息 'klc_fx_info': [{ 'time': format_time_safely(point['time'], client_tz), @@ -682,6 +697,9 @@ def analyze(): element_df = get_kl_data(symbol, element_timeframe, start_time=start_time, end_time=end_time) if element_df is not None and len(element_df) > 0: + # 添加小周期技术指标(包括布林带) + element_df = add_indicators(element_df) + # 对小周期数据进行缠论分析 element_analysis = analyze_chan(element_df) @@ -692,6 +710,18 @@ def analyze(): result['element_timeframe'] = element_timeframe result['element_macd'] = element_macd_data # 添加小周期MACD数据 + # 添加小周期布林带数据 + result['element_bollinger'] = { + 'upper': element_df['bb_upper'].tolist(), + 'middle': element_df['bb_middle'].tolist(), + 'lower': element_df['bb_lower'].tolist() + } + result['element_element_bollinger'] = { + 'upper': element_df['element_bb_upper'].tolist(), + 'middle': element_df['element_bb_middle'].tolist(), + 'lower': element_df['element_bb_lower'].tolist() + } + result['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, @@ -720,7 +750,6 @@ def analyze(): 'is_sure': zs.is_sure # 添加中枢是否完成的标志 } for zs in element_analysis['zs_list'] if zs.end_klc] - # 添加小周期未完成中枢列表 result['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, # 未完成中枢没有结束时间 diff --git a/web/templates/index.html b/web/templates/index.html index e4860b3..ca2a1a1 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -327,7 +327,7 @@ {% endfor %} -
+
-
+
-
+
- +
-
+
@@ -397,6 +399,10 @@
+
+ + +
@@ -424,22 +430,25 @@
+
+ + +
-
-
- - {% for value, label in timeframes.items() %} {% endfor %} - 仅影响笔、线段和中枢分析
-
- - @@ -450,18 +459,18 @@ -
+
- +
-
- - @@ -479,8 +488,8 @@ 停止
- - @@ -733,7 +742,9 @@ elementSegSeries: [], elementZsSeries: [], elementUncompletedZsSeries: [], - tradePointSeries: [] + tradePointSeries: [], + mainBollingerSeries: [], + elementBollingerSeries: [] }, state: { isInitialized: false, @@ -955,6 +966,15 @@ refreshChartOnly(); }); + // 添加布林带显示变更事件 + $('#showMainBollinger').change(function() { + updateChartDisplay(); + }); + + $('#showElementBollinger').change(function() { + updateChartDisplay(); + }); + // 添加K线周期切换事件监听器 $('input[name="klinePeriod"]').change(function() { console.log('K线周期切换:', $(this).attr('id'), $(this).is(':checked')); @@ -1055,6 +1075,8 @@ console.log('- 显示小周期分型:', $('#showElementKlcFxType').is(':checked')); console.log('- 显示买卖点:', $('#showTradePoints').is(':checked')); console.log('- 显示原始K线:', $('#showOriginalKline').is(':checked')); + console.log('- 显示主周期布林带:', $('#showMainBollinger').is(':checked')); + console.log('- 显示次周期布林带:', $('#showElementBollinger').is(':checked')); // 重新初始化图表,这将清除旧图形并重新绘制 initTradingView($('#symbol').val(), $('#timeframe').val()); @@ -1302,7 +1324,9 @@ elementSegSeries: [], elementZsSeries: [], elementUncompletedZsSeries: [], - tradePointSeries: [] + tradePointSeries: [], + mainBollingerSeries: [], + elementBollingerSeries: [] }, state: { isInitialized: false, @@ -1914,33 +1938,32 @@ // 在笔的末端添加macd_div值标记 if (bi.macd_div && bi.macd_div !== 0 && $('#showMainMacdDiv').is(':checked')) { - console.log(`添加macd_div标记: ${bi.macd_div.toFixed(2)}, 在时间点: ${endTime}`); + console.log(`添加主周期macd_div标记: ${bi.macd_div.toFixed(2)}, 在时间点: ${endTime}`); const macdDivLabel = mainChart.addLineSeries({ lastValueVisible: false, priceLineVisible: false, + color: 'transparent', // 设置为透明色 + lineWidth: 0, // 线宽为0 }); - // 确定标记位置 - const markerPosition = bi.direction === 1 ? 'aboveBar' : 'belowBar'; - const labelValue = bi.direction === 1 ? endPrice + (endPrice * 0.005) : endPrice - (endPrice * 0.005); - const arrayShape = bi.direction === 1 ? 'arrowDown' : 'arrowUp'; - // 简化标记显示 + // 添加一个透明的数据点用于承载标记 macdDivLabel.setData([ - { time: endTime, value: labelValue } + { time: endTime, value: endPrice } ]); - // 使用不同方式添加文本标记 + // 主周期MACD背离标记根据笔方向显示,远离K线避免与分型重叠 + const markerPosition = bi.direction === 1 ? 'aboveBar' : 'belowBar'; const textColor = bi.macd_div > 0 ? '#dc3545' : '#28a745'; - const textSize = Math.min(14, Math.max(10, Math.abs(bi.macd_div) * 2)); + // 只使用标记,不添加数据点 macdDivLabel.setMarkers([ { time: endTime, position: markerPosition, color: textColor, - shape: arrayShape, - text: bi.macd_div.toFixed(2), + text: `${bi.macd_div.toFixed(2)}`, // 添加M前缀区分 + size: 0.6, // 更小的尺寸,远离分型标记 } ]); } @@ -1998,29 +2021,27 @@ const macdDivLabel = mainChart.addLineSeries({ lastValueVisible: false, priceLineVisible: false, + color: 'transparent', // 设置为透明色 + lineWidth: 0, // 线宽为0 }); - // 确定标记位置 - const markerPosition = bi.direction === 1 ? 'aboveBar' : 'belowBar'; - const labelValue = bi.direction === 1 ? endPrice + (endPrice * 0.005) : endPrice - (endPrice * 0.005); - const arrayShape = bi.direction === 1 ? 'arrowDown' : 'arrowUp'; - // 简化标记显示 + // 添加一个透明的数据点用于承载标记 macdDivLabel.setData([ - { time: endTime, value: labelValue } + { time: endTime, value: endPrice } ]); - // 使用不同方式添加文本标记 + // 次周期MACD背离标记使用不同位置,进一步避免重叠 + const markerPosition = bi.direction === 1 ? 'aboveBar' : 'belowBar'; const textColor = bi.macd_div > 0 ? '#9c27b0' : '#673ab7'; - const textSize = Math.min(14, Math.max(10, Math.abs(bi.macd_div) * 2)); + // 只使用标记,不添加数据点 macdDivLabel.setMarkers([ { time: endTime, position: markerPosition, - color: textColor, - shape: arrayShape, - text: bi.macd_div.toFixed(2), - transparent: true, + color: textColor, + text: `${bi.macd_div.toFixed(2)}`, // 添加E前缀区分次周期 + size: 0.4, // 更小的尺寸,让分型标记有更多空间 } ]); } @@ -2977,6 +2998,171 @@ console.log('绘制买卖点 - 已禁用'); } + // 绘制布林带 + if ($('#showMainBollinger').is(':checked') || $('#showElementBollinger').is(':checked')) { + console.log('绘制布林带 - 已启用'); + + // 主周期布林带 + if ($('#showMainBollinger').is(':checked') && currentData.bollinger && currentData.bollinger.upper && currentData.bollinger.lower && currentData.bollinger.middle) { + console.log(`绘制主周期布林带数据,共${currentData.bollinger.upper.length}条`); + + // 准备布林带数据 + const upperBandData = []; + const lowerBandData = []; + const middleBandData = []; + + // 主周期布林带始终使用主周期K线数据作为时间源 + const mainKlineData = currentData.kline_data; + + for (let i = 0; i < mainKlineData.length && i < currentData.bollinger.upper.length; i++) { + const kline = mainKlineData[i]; + const timestamp = Math.floor(new Date(kline.date).getTime() / 1000); + + // 只添加非0的有效数据点 + if (currentData.bollinger.upper[i] && currentData.bollinger.upper[i] !== 0) { + upperBandData.push({ + time: timestamp, + value: currentData.bollinger.upper[i] + }); + } + + if (currentData.bollinger.lower[i] && currentData.bollinger.lower[i] !== 0) { + lowerBandData.push({ + time: timestamp, + value: currentData.bollinger.lower[i] + }); + } + + if (currentData.bollinger.middle[i] && currentData.bollinger.middle[i] !== 0) { + middleBandData.push({ + time: timestamp, + value: currentData.bollinger.middle[i] + }); + } + } + + // 创建布林带上轨 + const upperBandSeries = mainChart.addLineSeries({ + color: '#2196F3', + lineWidth: 1, + lineStyle: 2, // 虚线 + lastValueVisible: false, + priceLineVisible: false, + title: '布林上轨' + }); + upperBandSeries.setData(upperBandData); + + // 创建布林带下轨 + const lowerBandSeries = mainChart.addLineSeries({ + color: '#2196F3', + lineWidth: 1, + lineStyle: 2, // 虚线 + lastValueVisible: false, + priceLineVisible: false, + title: '布林下轨' + }); + lowerBandSeries.setData(lowerBandData); + + // 创建布林带中轨(移动平均线) + const middleBandSeries = mainChart.addLineSeries({ + color: '#FF9800', + lineWidth: 1, + lastValueVisible: false, + priceLineVisible: false, + title: '布林中轨' + }); + middleBandSeries.setData(middleBandData); + + // 保存到tvWidget.series对象 + tvWidget.series.mainBollingerSeries.push(upperBandSeries); + tvWidget.series.mainBollingerSeries.push(lowerBandSeries); + tvWidget.series.mainBollingerSeries.push(middleBandSeries); + + console.log('主周期布林带绘制完成'); + } + + // 次周期布林带 + if ($('#showElementBollinger').is(':checked') && currentData.element_bollinger && currentData.element_bollinger.upper && currentData.element_bollinger.lower && currentData.element_bollinger.middle) { + console.log(`绘制次周期布林带数据,共${currentData.element_bollinger.upper.length}条`); + + // 准备次周期布林带数据 + const elementUpperBandData = []; + const elementLowerBandData = []; + const elementMiddleBandData = []; + + // 使用次周期K线数据 + const elementKlineData = currentData.element_kline_data || currentData.kline_data; + + for (let i = 0; i < elementKlineData.length && i < currentData.element_bollinger.upper.length; i++) { + const kline = elementKlineData[i]; + const timestamp = Math.floor(new Date(kline.date).getTime() / 1000); + + // 只添加非0的有效数据点 + if (currentData.element_bollinger.upper[i] && currentData.element_bollinger.upper[i] !== 0) { + elementUpperBandData.push({ + time: timestamp, + value: currentData.element_bollinger.upper[i] + }); + } + + if (currentData.element_bollinger.lower[i] && currentData.element_bollinger.lower[i] !== 0) { + elementLowerBandData.push({ + time: timestamp, + value: currentData.element_bollinger.lower[i] + }); + } + + if (currentData.element_bollinger.middle[i] && currentData.element_bollinger.middle[i] !== 0) { + elementMiddleBandData.push({ + time: timestamp, + value: currentData.element_bollinger.middle[i] + }); + } + } + + // 创建次周期布林带上轨 + const elementUpperBandSeries = mainChart.addLineSeries({ + color: '#9C27B0', + lineWidth: 1, + lineStyle: 2, // 虚线 + lastValueVisible: false, + priceLineVisible: false, + title: '次周期布林上轨' + }); + elementUpperBandSeries.setData(elementUpperBandData); + + // 创建次周期布林带下轨 + const elementLowerBandSeries = mainChart.addLineSeries({ + color: '#9C27B0', + lineWidth: 1, + lineStyle: 2, // 虚线 + lastValueVisible: false, + priceLineVisible: false, + title: '次周期布林下轨' + }); + elementLowerBandSeries.setData(elementLowerBandData); + + // 创建次周期布林带中轨 + const elementMiddleBandSeries = mainChart.addLineSeries({ + color: '#E91E63', + lineWidth: 1, + lastValueVisible: false, + priceLineVisible: false, + title: '次周期布林中轨' + }); + elementMiddleBandSeries.setData(elementMiddleBandData); + + // 保存到tvWidget.series对象 + tvWidget.series.elementBollingerSeries.push(elementUpperBandSeries); + tvWidget.series.elementBollingerSeries.push(elementLowerBandSeries); + tvWidget.series.elementBollingerSeries.push(elementMiddleBandSeries); + + console.log('次周期布林带绘制完成'); + } + } else { + console.log('绘制布林带 - 已禁用'); + } + // 绘制分型类型标签 console.log('=== 开始检查分型显示条件 ==='); console.log('showKlcFxType勾选状态:', $('#showKlcFxType').is(':checked')); @@ -3034,8 +3220,8 @@ is_strong_fx: fx.is_strong_fx }); let displayText = `${fx.fx_strength_level} ${fx.fx_strength.toFixed(1)}`; - if (fx.fx_strength < 1.4) { - displayText = '' + if (fx.fx_strength < 1.0) { // 降低阈值,让更多分型显示 + displayText = fx.fx_strength >= 0.8 ? '•' : '' // 0.8以上显示点,0.8以下不显示文本 } console.log('显示文本:', displayText); @@ -3044,9 +3230,9 @@ time: timestamp, position: fx.is_bottom ? 'belowBar' : 'aboveBar', color: strengthColor, - shape: fx.is_strong_fx ? 'square' : 'circle', + shape: 'circle', text: displayText, - size: fx.is_strong_fx ? 2 : 1 + size: fx.is_strong_fx ? 1 : 0.6 // 调整尺寸,强分型稍大,普通分型更小 }; console.log('标记配置:', markerConfig); @@ -3125,8 +3311,8 @@ } let displayText = `${fx.fx_strength_level} ${fx.fx_strength.toFixed(1)}`; // 构建小周期分型显示文本 - if (fx.fx_strength < 1){ - displayText = '' + if (fx.fx_strength < 0.8){ // 调整小周期阈值 + displayText = fx.fx_strength >= 0.6 ? '•' : '' // 0.6以上显示点 } // 小周期分型标记配置 - 根据分型类型使用正确的箭头形状 @@ -3134,8 +3320,9 @@ time: timestamp, position: fx.is_bottom ? 'belowBar' : 'aboveBar', color: strengthColor, + shape: 'circle', text: displayText, - size: fx.is_strong_fx ? 2 : 1 + size: fx.is_strong_fx ? 0.8 : 0.6 // 小周期标记整体更小一些 }; console.log('小周期分型标记配置:', markerConfig); @@ -4353,7 +4540,9 @@ elementSegSeries: [], elementZsSeries: [], elementUncompletedZsSeries: [], - tradePointSeries: [] + tradePointSeries: [], + mainBollingerSeries: [], + elementBollingerSeries: [] }; } catch (e) { console.error('销毁图表错误:', e); @@ -4797,6 +4986,15 @@ refreshChart(currentData); }); + // 绑定布林带显示变更事件 + $('#showMainBollinger').change(function() { + updateChartDisplay(); + }); + + $('#showElementBollinger').change(function() { + updateChartDisplay(); + }); + // 绑定K线周期切换 $('input[name="klinePeriod"]').change(function() { refreshChart(currentData); @@ -4814,6 +5012,8 @@ 'showKlcFxType': $('#showKlcFxType').is(':checked'), 'showElementKlcFxType': $('#showElementKlcFxType').is(':checked'), 'showTradePoints': $('#showTradePoints').is(':checked'), + 'showMainBollinger': $('#showMainBollinger').is(':checked'), + 'showElementBollinger': $('#showElementBollinger').is(':checked'), 'timeframe': $('#timeframe').val(), 'elementTimeframe': $('#elementTimeframe').val(), 'timezone': $('#timezone').val(),