diff --git a/ChanKLU.py b/ChanKLU.py
index 8a91bdf..2056857 100644
--- a/ChanKLU.py
+++ b/ChanKLU.py
@@ -22,7 +22,10 @@ class ChanKLU:
self.ma250 = 0
self.rsi = 0
self.volume_ratio = 0
-
+ self.bbp120 = 0
+ self.bbp365 = 0
+ self.bb120 = 0
+ self.bb365 = 0
# === 新增:K线类型 ===
self.kline_type = None # K线类型:大阳线、大阴线、小阳线、小阴线
@@ -575,7 +578,10 @@ class ChanKLU:
self.rsi = float(item['rsi']) if 'rsi' in item and item['rsi'] else 0
self.volume_ratio = float(item['volume_ratio']) if 'volume_ratio' in item and item['volume_ratio'] else 0
-
+ self.bbp120 = float(item['bbp120']) if 'bbp120' in item and item['bbp120'] else 0
+ self.bbp365 = float(item['bbp365']) if 'bbp365' in item and item['bbp365'] else 0
+ self.bb120 = float(item['bb120']) if 'bb120' in item and item['bb120'] else 0
+ self.bb365 = float(item['bb365']) if 'bb365' in item and item['bb365'] else 0
# 设置指标后更新实时分析
self.update_realtime_analysis()
diff --git a/ChanLun.py b/ChanLun.py
index 77f0087..f7d5d1e 100644
--- a/ChanLun.py
+++ b/ChanLun.py
@@ -106,10 +106,10 @@ class ChanLun():
klc_index += 1
if klc.klc_fx_type == Chan_KLC_FX.TOP3:
state_list.append("10")
- print(klc.start_time, klc.end_time, klc.klc_fx_type)
+ #print(klc.start_time, klc.end_time, klc.klc_fx_type)
elif klc.klc_fx_type == Chan_KLC_FX.BOTTOM3:
state_list.append("-10")
- print(klc.start_time, klc.end_time, klc.klc_fx_type)
+ #print(klc.start_time, klc.end_time, klc.klc_fx_type)
else:
state_list.append("00")
else:
diff --git a/strategies/ChanLun_BTC_30.py b/strategies/ChanLun_BTC_30.py
index 432d278..6eade46 100644
--- a/strategies/ChanLun_BTC_30.py
+++ b/strategies/ChanLun_BTC_30.py
@@ -115,7 +115,7 @@ class ChanLun_BTC_30(IStrategy):
state_list = self.chan.get_klc_state_list(dataframe_15)
dataframe_15['state'] = state_list
dataframe_15['fx'] = state_list
-
+ state_list = self.chan.get_klc_state_list(dataframe_30)
#bi_list_1 = self.chan.get_bi_list(dataframe)
#bi_list_5 = self.chan.get_bi_list(dataframe_5)
#bi_list_15 = self.chan.get_bi_list(dataframe_15)
@@ -154,12 +154,17 @@ class ChanLun_BTC_30(IStrategy):
period = 6
macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period)
bb365 = ta.BBANDS(df, timeperiod=365, nbdevup=3.0, nbdevdn=3.0, matype=0)
- bbp365 = ta.BBP(df, timeperiod=365)
bb120 = ta.BBANDS(df, timeperiod=120, nbdevup=3.0, nbdevdn=3.0, matype=0)
- bbp120 = ta.BBP(df, timeperiod=120)
+
+ # 手动计算布林带 %B 指标 (BBP)
+ # %B = (Price - Lower Band) / (Upper Band - Lower Band)
+ bbp365 = (df['close'] - bb365['lowerband']) / (bb365['upperband'] - bb365['lowerband'])
+ bbp120 = (df['close'] - bb120['lowerband']) / (bb120['upperband'] - bb120['lowerband'])
+
df['bb365'] = bb365['upperband']
df['bbp365'] = bbp365
df['bb120'] = bb120['upperband']
+ df['bbp120'] = bbp120
df['macd'] = macd['macd']
df['macdsignal'] = macd['macdsignal']
df['macdhist'] = macd['macdhist']
@@ -279,8 +284,8 @@ class ChanLun_BTC_30(IStrategy):
ema10 = 'resample_{}_ema10'.format(self.get_ticker_indicator()*self.time30)
ema26 = 'resample_{}_ema26'.format(self.get_ticker_indicator()*self.time30)
ema52 = 'resample_{}_ema52'.format(self.get_ticker_indicator()*self.time30)
- print(last_candle[ema5], last_candle[ema10], last_candle[ema26], last_candle[ema52])
- print(last_candle['close'])
+ #print(last_candle[ema5], last_candle[ema10], last_candle[ema26], last_candle[ema52])
+ #print(last_candle['close'])
klc_list = self.chan.get_klc_list(resample_to_interval(dataframe, self.get_ticker_indicator() * self.time30))
bi_list = self.chan.cal_bi_list(klc_list)
if self.last_order is None:
diff --git a/web/templates/index.html b/web/templates/index.html
index af531e0..e4bce9f 100644
--- a/web/templates/index.html
+++ b/web/templates/index.html
@@ -48,7 +48,7 @@
}
.chart-container {
width: 100%;
- height: 700px;
+ height: 900px;
margin-top: 10px;
border: 1px solid #e9ecef;
border-radius: 8px;
@@ -355,11 +355,14 @@
margin-right: 4px;
}
- .add-ma-btn {
+ .indicator-dropdown {
position: absolute;
top: 10px;
right: 120px;
z-index: 100;
+ }
+
+ .add-indicator-btn {
background-color: #0d6efd;
color: white;
border: none;
@@ -368,12 +371,134 @@
font-size: 13px;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+ display: flex;
+ align-items: center;
+ gap: 5px;
}
- .add-ma-btn:hover {
+ .add-indicator-btn:hover {
background-color: #0a58ca;
}
+ .add-indicator-btn:focus {
+ box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
+ }
+
+ .dropdown-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: 1000;
+ display: none;
+ min-width: 160px;
+ padding: 0.5rem 0;
+ margin: 0.125rem 0 0;
+ font-size: 0.875rem;
+ color: #212529;
+ text-align: left;
+ background-color: #fff;
+ background-clip: padding-box;
+ border: 1px solid rgba(0,0,0,.15);
+ border-radius: 0.375rem;
+ box-shadow: 0 0.5rem 1rem rgba(0,0,0,.15);
+ }
+
+ .dropdown-menu.show {
+ display: block;
+ }
+
+ .dropdown-item {
+ display: block;
+ width: 100%;
+ padding: 0.375rem 1rem;
+ clear: both;
+ font-weight: 400;
+ color: #212529;
+ text-align: inherit;
+ text-decoration: none;
+ white-space: nowrap;
+ background-color: transparent;
+ border: 0;
+ cursor: pointer;
+ }
+
+ .dropdown-item:hover,
+ .dropdown-item:focus {
+ background-color: #f8f9fa;
+ color: #16181b;
+ }
+
+ .dropdown-item i {
+ margin-right: 8px;
+ }
+
+ .indicator-panel {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ z-index: 1000;
+ background: rgba(255, 255, 255, 0.95);
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ padding: 8px;
+ max-width: 350px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ }
+
+ .indicator-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 4px 0;
+ font-size: 12px;
+ border-bottom: 1px solid #f0f0f0;
+ }
+
+ .indicator-item:last-child {
+ border-bottom: none;
+ }
+
+ .indicator-info {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .indicator-label {
+ font-weight: 500;
+ }
+
+ .indicator-value {
+ color: #666;
+ }
+
+ .indicator-actions {
+ display: flex;
+ gap: 4px;
+ }
+
+ .indicator-action-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 2px 4px;
+ border-radius: 3px;
+ font-size: 11px;
+ color: #666;
+ }
+
+ .indicator-action-btn:hover {
+ background-color: #f0f0f0;
+ color: #333;
+ }
+
+ .indicator-color-indicator {
+ width: 12px;
+ height: 2px;
+ border-radius: 1px;
+ margin-right: 4px;
+ }
+
/* 配置弹窗样式 */
.ma-config-modal {
position: fixed;
@@ -483,6 +608,190 @@
width: 80%;
height: 20px;
}
+
+ /* 布林带面板样式 */
+ .bb-panel {
+ position: absolute;
+ bottom: 20px;
+ left: 20px;
+ background: rgba(255, 255, 255, 0.95);
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ padding: 12px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ min-width: 200px;
+ max-width: 300px;
+ z-index: 50;
+ }
+
+ .bb-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 6px 0;
+ border-bottom: 1px solid #f0f0f0;
+ font-size: 12px;
+ }
+
+ .bb-item:last-child {
+ border-bottom: none;
+ }
+
+ .bb-info {
+ flex: 1;
+ margin-right: 10px;
+ }
+
+ .bb-label {
+ font-weight: 600;
+ color: #333;
+ }
+
+ .bb-value {
+ color: #666;
+ font-size: 11px;
+ }
+
+ .bb-actions {
+ display: flex;
+ gap: 6px;
+ }
+
+ .bb-action-btn {
+ width: 20px;
+ height: 20px;
+ border: none;
+ background: #f8f9fa;
+ border-radius: 3px;
+ cursor: pointer;
+ font-size: 10px;
+ color: #666;
+ }
+
+ .bb-action-btn:hover {
+ background: #e9ecef;
+ color: #333;
+ }
+
+ .bb-color-indicator {
+ width: 12px;
+ height: 12px;
+ border-radius: 2px;
+ margin-right: 4px;
+ display: inline-block;
+ }
+
+
+
+ .bb-config-modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.5);
+ display: none;
+ justify-content: center;
+ align-items: center;
+ z-index: 1000;
+ }
+
+ .bb-config-content {
+ background: white;
+ border-radius: 8px;
+ padding: 16px;
+ width: 400px;
+ max-width: 90vw;
+ max-height: 85vh;
+ overflow-y: auto;
+ }
+
+ .bb-config-title {
+ font-size: 18px;
+ font-weight: 600;
+ margin-bottom: 20px;
+ color: #333;
+ }
+
+ .bb-config-form {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ }
+
+ .bb-config-group {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ }
+
+ .bb-config-label {
+ font-weight: 500;
+ color: #555;
+ font-size: 14px;
+ }
+
+ .bb-config-input, .bb-config-select {
+ padding: 8px 12px;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ font-size: 14px;
+ transition: border-color 0.2s;
+ }
+
+ .bb-config-input:focus, .bb-config-select:focus {
+ outline: none;
+ border-color: #667eea;
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
+ }
+
+ .bb-config-actions {
+ display: flex;
+ gap: 12px;
+ justify-content: flex-end;
+ margin-top: 20px;
+ }
+
+ .bb-config-btn {
+ padding: 10px 20px;
+ border: none;
+ border-radius: 6px;
+ font-size: 14px;
+ cursor: pointer;
+ transition: all 0.2s;
+ }
+
+ .bb-config-btn-primary {
+ background: #667eea;
+ color: white;
+ }
+
+ .bb-config-btn-primary:hover {
+ background: #5a67d8;
+ }
+
+ .bb-config-btn-secondary {
+ background: #f8f9fa;
+ color: #6c757d;
+ }
+
+ .bb-config-btn-secondary:hover {
+ background: #e9ecef;
+ }
+
+ .bb-line-preview {
+ height: 30px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ margin-top: 8px;
+ background: linear-gradient(to right, #f8f9fa, #fff);
+ position: relative;
+ }
+
+ .bb-line-preview svg {
+ width: 100%;
+ height: 100%;
+ }
@@ -710,12 +1019,22 @@
@@ -962,7 +1281,9 @@
elementUncompletedZsSeries: [],
tradePointSeries: [],
mainBollingerSeries: [],
- elementBollingerSeries: []
+ elementBollingerSeries: [],
+ maSeries: [], // 添加均线系列
+ bbSeries: [] // 添加布林带系列
},
state: {
isInitialized: false,
@@ -1586,7 +1907,8 @@
tradePointSeries: [],
mainBollingerSeries: [],
elementBollingerSeries: [],
- maSeries: [] // 添加均线系列数组
+ maSeries: [], // 添加均线系列数组
+ bbSeries: [] // 添加布林带系列数组
},
state: {
isInitialized: false,
@@ -3975,8 +4297,11 @@
// 添加均线到图表
addMovingAveragesToChart(candles);
- // 更新均线面板显示
- updateMAPanel();
+ // 添加布林带到图表
+ addBollingerBandsToChart(candles);
+
+ // 更新技术指标面板显示
+ updateIndicatorPanel();
// 绑定同步事件
bindSyncEvents(mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, mainChart, volumeChart, atrChart, macdChart, showMacd);
@@ -4089,6 +4414,9 @@
// 更新均线数据
addMovingAveragesToChart(candles);
+ // 更新布林带数据
+ addBollingerBandsToChart(candles);
+
// 更新成交量数据
let volumes = [];
if (useElementPeriod && currentData.element_kline_data && Array.isArray(currentData.element_kline_data)) {
@@ -5318,7 +5646,9 @@
elementUncompletedZsSeries: [],
tradePointSeries: [],
mainBollingerSeries: [],
- elementBollingerSeries: []
+ elementBollingerSeries: [],
+ maSeries: [], // 添加均线系列
+ bbSeries: [] // 添加布林带系列
};
} catch (e) {
console.error('销毁图表错误:', e);
@@ -6803,74 +7133,52 @@
});
}
- // 获取分型强度对应的CSS类
- function getStrengthClass(strength) {
- if (strength >= 2.0) return 'text-danger fw-bold';
- else if (strength >= 1.5) return 'text-warning fw-bold';
- else if (strength >= 1.0) return 'text-info';
- else return '';
- }
-
- // 分析特定股票
- function analyzeStock(symbol) {
- // 切换到主分析页面
- $('#stock-filter-tab').removeClass('active');
- $('#kline-tab').addClass('active');
- $('#stock-filter').removeClass('show active');
- $('#kline').addClass('show active');
-
- // 切换数据源为A股
- $('#dataSource').val('a_stock');
- $('#dataSource').trigger('change');
-
- // 等待数据源切换完成后设置股票代码
- setTimeout(() => {
- $('#astockSymbol').val(symbol);
- // 触发分析
- updateChart();
- }, 500);
- }
-
- // 导出筛选结果
- function exportFilterResults() {
- const tbody = $('#stockFilterTable tbody tr');
-
- if (tbody.length === 0 || (tbody.length === 1 && tbody.find('td').length === 1)) {
- alert('没有可导出的数据');
- return;
- }
-
- // 创建CSV内容
- const headers = ['股票代码', '股票名称', '分型时间', '分型类型', '分型强度', '分型价格', '当前价格', '涨跌幅(%)'];
- let csvContent = headers.join(',') + '\n';
-
- tbody.each(function() {
- const cells = $(this).find('td');
- if (cells.length > 1) { // 排除"没有数据"的行
- const row = [];
- cells.slice(0, 8).each(function() { // 只取前8列,排除操作列
- row.push($(this).text().trim());
- });
- csvContent += row.join(',') + '\n';
- }
- });
-
- // 创建下载链接
- const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' });
- const link = document.createElement('a');
- const url = URL.createObjectURL(blob);
- link.setAttribute('href', url);
- link.setAttribute('download', `股票筛选结果_${new Date().toISOString().slice(0, 10)}.csv`);
- link.style.visibility = 'hidden';
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- }
-
// 均线系统全局变量
let movingAverages = []; // 存储所有均线配置
let maIdCounter = 0; // 均线ID计数器
+ // 布林带系统全局变量
+ let bollingerBands = []; // 存储所有布林带配置
+ let bbIdCounter = 0; // 布林带ID计数器
+
+ // 获取随机颜色
+ function getRandomColor() {
+ const colors = ['#2962FF', '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4',
+ '#FECA57', '#48CAE4', '#F38BA8', '#A8DADC', '#F1C0E8'];
+ return colors[Math.floor(Math.random() * colors.length)];
+ }
+
+ // 显示布林带配置弹窗
+ function showBBConfig() {
+ console.log('📂 显示布林带配置弹窗,设置为添加模式');
+ $('#bbConfigModal').css('display', 'flex');
+
+ // 重置表单
+ $('#bbType').val('Bollinger Bands');
+ $('#bbLength').val(20);
+ $('#bbStdDev').val(2);
+ $('#bbSource').val('close');
+ $('#bbLineWidth').val(2);
+ $('#bbLineStyle').val(0);
+ $('#bbColor').val(getRandomColor());
+
+ // 移除所有现有的点击事件,避免事件冲突
+ $('#bbConfigSubmitBtn').off('click');
+
+ // 设置按钮为添加模式
+ $('#bbConfigSubmitBtn').text('添加').on('click', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ console.log('🖱️ 点击了添加按钮(来自showBBConfig),准备添加新布林带');
+ addBollingerBand();
+ });
+
+ console.log('🔄 按钮已设置为添加模式');
+
+ // 更新预览
+ setTimeout(updateBBLinePreview, 50);
+ }
+
// 显示均线配置弹窗
function showMAConfig() {
console.log('📂 显示均线配置弹窗,设置为添加模式');
@@ -6917,13 +7225,6 @@
console.log('🔄 按钮已重置为添加模式,等待用户主动点击"添加均线"按钮');
}
- // 获取随机颜色
- function getRandomColor() {
- const colors = ['#2962FF', '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4',
- '#FECA57', '#48CAE4', '#F38BA8', '#A8DADC', '#F1C0E8'];
- return colors[Math.floor(Math.random() * colors.length)];
- }
-
// 添加均线
function addMovingAverage() {
console.log('🚨🚨🚨 ➕ 警告:addMovingAverage函数被调用了!!!');
@@ -7086,18 +7387,19 @@
return result;
}
- // 更新均线面板
- function updateMAPanel() {
- const panel = $('#maPanel');
+ // 更新技术指标面板(统一面板)
+ function updateIndicatorPanel() {
+ const panel = $('#indicatorPanel');
- if (movingAverages.length === 0) {
+ if (movingAverages.length === 0 && bollingerBands.length === 0) {
panel.hide();
return;
}
let html = '';
- let hasVisibleMA = false;
+ let hasVisibleIndicator = false;
+ // 添加均线指标
movingAverages.forEach(ma => {
// 获取最新价格
const latestValue = ma.data && ma.data.length > 0 ?
@@ -7116,20 +7418,20 @@
const eyeIcon = ma.visible ? 'bi-eye' : 'bi-eye-slash';
html += `
-
-
-
-
${ma.type}(${ma.length})${smoothText}${styleText}
-
${ma.visible ? latestValue : '--'}
+
+
+
+
${ma.type}(${ma.length})${smoothText}${styleText}
+
${ma.visible ? latestValue : '--'}
-
-
+
+
+
+
+
添加布林带
+
+
+ 取消
+ 添加
+
+
+