Merge pull request #19 from jackyu66git/dev

Dev
This commit is contained in:
jackyu66git
2026-04-08 01:29:56 +08:00
committed by GitHub
7 changed files with 275 additions and 94 deletions
+5 -2
View File
@@ -4,7 +4,7 @@ from ChanEnum import Chan_BI_DIR
class ChanBI():
def __init__(self, klc: ChanKLC, index, ddir=Chan_BI_DIR.UP):
self.start_klc = klc
self.end_klc = None
self.end_klc = klc
self.next = None
self.pre = None
self.dir = ddir
@@ -15,7 +15,7 @@ class ChanBI():
self.sure_time = None
self.klc_list = []
self.klc_list.append(klc)
self.end_time = None
self.end_time = klc.end_time
self.start_time = klc.start_time
self.macd_hist = 0
self.macd_div = 0
@@ -112,6 +112,9 @@ class ChanBI():
if not added:
self.klc_list.append(klc)
#print(self.start_time, klc.start_time)
#print(klc.end_time, klc.index)
self.end_klc = klc
self.end_time = klc.klu_list[-1].time
self.cal_macdhist()
self.cal_macd_div()
def append_klc_list(self, klc_list):
+4
View File
@@ -42,6 +42,10 @@ class ChanBIZS():
def add_bi(self, bi: ChanBI):
if bi:
self.bi_list.append(bi)
if bi.high > self.gg:
self.gg = bi.high
if bi.low < self.dd:
self.dd = bi.low
bi.set_bi_zs(self)
self.classify_zs()
def set_pre(self, pre):
+4 -2
View File
@@ -45,7 +45,7 @@ class ChanSEG():
self.macd_div = macd_div
def set_end_bi(self, bi: ChanBI, sure_bi: ChanBI):
self.end_bi = bi
if bi:
if bi and bi.is_sure:
if self.dir == Chan_SEG_DIR.UP:
self.high = bi.high
else:
@@ -57,7 +57,7 @@ class ChanSEG():
self.format_bi_list()
def pre_set_end_bi(self, bi: ChanBI):
self.end_bi = bi
if bi:
if bi and bi.is_sure:
if self.dir == Chan_SEG_DIR.UP:
self.high = bi.high
else:
@@ -87,6 +87,8 @@ class ChanSEG():
if len(self.bi_list) > 0:
self.bi_list.append(bi)
bi.set_seg(self)
self.end_time = bi.end_time
self.end_bi = bi
def cal_bi_zs(self):
zs_list = []
if len(self.bi_list) > 3:
+4
View File
@@ -63,6 +63,10 @@ class ChanZS():
self.end_time = end_time
def add_klc(self, klc):
self.klc_list.append(klc)
def add_seg(self, seg):
self.seg_list.append(seg)
self.end_time = seg.end_time
self.end_seg = seg
def set_zg(self, zg):
self.zg = zg
def set_zd(self, zd):
+16 -12
View File
@@ -175,7 +175,7 @@ class TF_DF():
else:
klu_state_list.append("0")
return klu_state_list
def check_fx(self, klc):
def check_fx1(self, klc):
if klc.pre and klc.next:
if klc.high > klc.pre.high and klc.high > klc.next.high and klc.low > klc.pre.low and klc.low > klc.next.low:
if klc.pre.pre and klc.next.next:
@@ -192,7 +192,7 @@ class TF_DF():
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "BOTTOM")
return Chan_FX_TYPE.BOTTOM
return Chan_FX_TYPE.UNKNOWN
def check_fx1(self, klc):
def check_fx(self, klc):
if klc.pre and klc.next:
if klc.high > klc.pre.high and klc.high > klc.next.high and klc.low > klc.pre.low and klc.low > klc.next.low:
#if (klc.close > klc.ema52 or klc.next.close > klc.next.ema52) and klc.macd > 0:
@@ -954,6 +954,7 @@ class TF_DF():
if fx == Chan_FX_TYPE.UNKNOWN:
if len(bi_list) > 0:
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#continue
if len(bi_list) > 0 and klc.end_klu:
last_bi = bi_list[-1]
@@ -1074,7 +1075,7 @@ class TF_DF():
#print(klc.end_time, klc.fx, bi_list[-1].dir, "Last Top Change 2")
klc.set_klc_fx_type(Chan_KLC_FX.TOP2)
self.check_fx_pattern(klc)
bi_list[-1].add_klc(klc)
#bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, last_bottom.start_time, "Normal TOP Found, Confirm down bi 4")
# last bottom = None 初始化的时候用,其他时间不用
@@ -1189,7 +1190,7 @@ class TF_DF():
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 2")
klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM2)
self.check_fx_pattern(klc)
bi_list[-1].add_klc(klc)
#bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, last_top.start_time, "Normal Bottom Found, Confirm up bi 6")
# last_top = None 初始化的时候用,其他时间不用
@@ -1237,6 +1238,7 @@ class TF_DF():
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 5")
#print(klc.start_time, klc.fx, "笔买点Buy 4")
self.get_above_zero_bsp(klc_list)
#print(bi_list[-1].start_time, bi_list[-1].end_time, len(bi_list[-1].klc_list))
return bi_list
def get_above_zero_bsp(self, klc_list):
buy_bsp_list = []
@@ -1260,12 +1262,12 @@ class TF_DF():
#print(klc.end_time, "Sell BSP Found")
return buy_bsp_list
def check_top_fx(self, last_bottom, klc):
if (last_bottom.high > klc.pre.low or last_bottom.high > klc.next.low) and (klc.index - last_bottom.index < 10):
if (last_bottom.high > klc.pre.low or last_bottom.high > klc.next.low) and (klc.index - last_bottom.index < 100):
return False
return True
def check_bottom_fx(self, last_top, klc):
if (last_top.low < klc.pre.high or last_top.low < klc.next.high) and (klc.index - last_top.index < 10):
if (last_top.low < klc.pre.high or last_top.low < klc.next.high) and (klc.index - last_top.index < 100):
return False
return True
# 建议用这种方式生成笔中枢
@@ -1364,7 +1366,7 @@ class TF_DF():
zs.set_dd(min(bi_lows))
zs.bi_list = bis_for_zs
bi = bis_for_zs[-1]
if bi.end_klc:
if bi.is_sure:
zs.set_end_bi(bi, bi.sure_time)
start_idx = start_idx + len(added_after_leave)
@@ -1404,7 +1406,7 @@ class TF_DF():
break
if has_leave:
if last_bi_of_zs.end_klc:
if last_bi_of_zs.is_sure:
last_zs.set_end_bi(last_bi_of_zs, last_bi_of_zs.sure_time)
return bi_zs_list
def find_all_bsp(self, bi_list, bi_zs_list):
@@ -1434,12 +1436,12 @@ class TF_DF():
# 中枢结束后的第一笔(离开笔)
last_zs_bi = zs.bi_list[-1]
if last_zs_bi.dir == Chan_BI_DIR.UP:
if last_zs_bi.end_klc.high <= zs.zg or (last_zs_bi.next and last_zs_bi.next.is_sure and last_zs_bi.next.end_klc.low < zs.zd):
if last_zs_bi.is_sure and last_zs_bi.end_klc.high <= zs.zg or (last_zs_bi.next and last_zs_bi.next.is_sure and last_zs_bi.next.end_klc.low < zs.zd):
leave_bi = last_zs_bi.next
else:
leave_bi = last_zs_bi
else:
if last_zs_bi.end_klc.low >= zs.zd or (last_zs_bi.next and last_zs_bi.next.is_sure and last_zs_bi.next.end_klc.high > zs.zg):
if last_zs_bi.is_sure and last_zs_bi.end_klc.low >= zs.zd or (last_zs_bi.next and last_zs_bi.next.is_sure and last_zs_bi.next.end_klc.high > zs.zg):
leave_bi = last_zs_bi.next
else:
leave_bi = last_zs_bi
@@ -1578,13 +1580,13 @@ class TF_DF():
# 确定离开笔:中枢最后一笔之后的第一笔
if last_zs_bi.dir == Chan_BI_DIR.UP:
# 中枢最后一笔向上,如果没有真正离开中枢,取下一笔
if last_zs_bi.end_klc.high <= zs.zg:
if last_zs_bi.is_sure and last_zs_bi.end_klc.high <= zs.zg:
leave_bi = last_zs_bi.next
else:
leave_bi = last_zs_bi
else:
# 中枢最后一笔向下,如果没有真正离开中枢,取下一笔
if last_zs_bi.end_klc.low >= zs.zd:
if last_zs_bi.is_sure and last_zs_bi.end_klc.low >= zs.zd:
leave_bi = last_zs_bi.next
else:
leave_bi = last_zs_bi
@@ -1853,6 +1855,8 @@ class TF_DF():
continue
else:
is_break = True
else:
break
if is_break:
break
if added_after_leave:
+51 -21
View File
@@ -33,8 +33,11 @@ class TRADE_POINT_TYPE:
SELL3 = -3 # 三类卖点
app = Flask(__name__)
macd_factor = 2 *6
macd_factor = 2
smooth_factor = 1
macd_fast_period = 12 * macd_factor
macd_slow_period = 26 * macd_factor
macd_signal_period = 9 * smooth_factor
# 初始化交易所
exchange = ccxt.binance({
'enableRateLimit': True,
@@ -422,10 +425,8 @@ def get_a_stock_kl_data(symbol, timeframe, limit=100000, start_time=None, end_ti
return None
def add_indicators(df):
fast = 12 * macd_factor
slow = 26 * macd_factor
period = 9 * smooth_factor
macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period)
global macd_fast_period, macd_slow_period, macd_signal_period
macd = ta.MACD(df, fastperiod=macd_fast_period, slowperiod=macd_slow_period, signalperiod=macd_signal_period)
df['macd'] = macd['macd']
df['macdsignal'] = macd['macdsignal']
@@ -510,10 +511,11 @@ def add_indicators(df):
def calculate_macd(df):
"""计算MACD指标"""
exp1 = df['close'].ewm(span=12*macd_factor, adjust=False).mean()
exp2 = df['close'].ewm(span=26*macd_factor, adjust=False).mean()
global macd_fast_period, macd_slow_period, macd_signal_period
exp1 = df['close'].ewm(span=macd_fast_period, adjust=False).mean()
exp2 = df['close'].ewm(span=macd_slow_period, adjust=False).mean()
macd = exp1 - exp2
signal = macd.ewm(span=9 * smooth_factor, adjust=False).mean()
signal = macd.ewm(span=macd_signal_period, adjust=False).mean()
histogram = macd - signal
return {
@@ -1337,17 +1339,17 @@ def analyze():
'end_price': bi.end_klc.high if convert_direction(bi.dir) == 1 else bi.end_klc.low if bi.end_klc else None,
'direction': convert_direction(bi.dir),
'macd_div': float(bi.macd_div) if hasattr(bi, 'macd_div') else 0
} for bi in analysis_result['bi_list'] if bi.end_klc],
} for bi in analysis_result['bi_list'] if bi.is_sure],
# 添加未完成笔列表
'uncompleted_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': None, # 未完成笔没有结束时间
'end_time': bi.end_time, # 未完成笔没有结束时间
'sure_time': format_time_safely(bi.sure_time, client_tz) if bi.sure_time else None,
'start_price': bi.start_klc.low if convert_direction(bi.dir) == 1 else bi.start_klc.high,
'end_price': None, # 未完成笔没有结束价格
'end_price': bi.end_klc.low if convert_direction(bi.dir) == 1 else bi.end_klc.high, # 未完成笔没有结束价格
'direction': convert_direction(bi.dir),
'macd_div': float(bi.macd_div) if hasattr(bi, 'macd_div') else 0
} for bi in analysis_result['bi_list'] if not bi.end_klc],
} for bi in analysis_result['bi_list'] if not bi.is_sure],
'seg_list': [{
'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(),
'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()) if seg.end_bi else None,
@@ -1366,7 +1368,7 @@ def analyze():
'gg': zs.gg,
'dd': zs.dd,
'is_sure': zs.is_sure # 添加中枢是否完成的标志
} for zs in analysis_result['zs_list'] if zs.end_klc],
} for zs in analysis_result['zs_list'] if zs.is_sure],
# 添加主周期BI中枢列表(已完成)
'bi_zs_list': [{
'start_time': (
@@ -1532,17 +1534,17 @@ def analyze():
'end_price': bi.end_klc.high if convert_direction(bi.dir) == 1 else bi.end_klc.low if bi.end_klc else None,
'direction': convert_direction(bi.dir),
'macd_div': float(bi.macd_div) if hasattr(bi, 'macd_div') else 0
} for bi in element_analysis['bi_list'] if bi.end_klc]
} for bi in element_analysis['bi_list'] if bi.is_sure]
# 添加次周期未完成笔列表
result['element_uncompleted_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': None, # 未完成笔没有结束时间
'end_time': bi.end_time, # 未完成笔没有结束时间
'sure_time': format_time_safely(bi.sure_time, client_tz) if bi.sure_time else None,
'start_price': bi.start_klc.low if convert_direction(bi.dir) == 1 else bi.start_klc.high,
'end_price': None, # 未完成笔没有结束价格
'end_price': bi.end_klc.low if convert_direction(bi.dir) == 1 else bi.end_klc.high, # 未完成笔没有结束价格
'direction': convert_direction(bi.dir),
'macd_div': float(bi.macd_div) if hasattr(bi, 'macd_div') else 0
} for bi in element_analysis['bi_list'] if not bi.end_klc]
} for bi in element_analysis['bi_list'] if not bi.is_sure]
# 添加小周期K线数据
result['element_kline_data'] = clean_dataframe_for_json(element_df).to_dict('records')
@@ -1672,16 +1674,16 @@ def analyze():
'end_price': bi.end_klc.high if convert_direction(bi.dir) == 1 else bi.end_klc.low if bi.end_klc else None,
'direction': convert_direction(bi.dir),
'macd_div': float(bi.macd_div) if hasattr(bi, 'macd_div') else 0
} for bi in sub_sub_analysis['bi_list'] if bi.end_klc]
} for bi in sub_sub_analysis['bi_list'] if bi.is_sure]
result['sub_sub_uncompleted_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': None,
'end_time': bi.end_time,
'sure_time': format_time_safely(bi.sure_time, client_tz) if bi.sure_time else None,
'start_price': bi.start_klc.low if convert_direction(bi.dir) == 1 else bi.start_klc.high,
'end_price': None,
'end_price': bi.end_klc.low if convert_direction(bi.dir) == 1 else bi.end_klc.high,
'direction': convert_direction(bi.dir),
'macd_div': float(bi.macd_div) if hasattr(bi, 'macd_div') else 0
} for bi in sub_sub_analysis['bi_list'] if not bi.end_klc]
} for bi in sub_sub_analysis['bi_list'] if not bi.is_sure]
# 次次周期 KLC 列表
result['sub_sub_klc_list'] = [{
'date': klc.end_time if isinstance(klc.end_time, str) else klc.end_time.astimezone(client_tz).isoformat(),
@@ -1845,6 +1847,34 @@ def search_stock():
return jsonify({'error': str(e)})
@app.route('/api/macd_config', methods=['GET', 'POST'])
def macd_config():
"""获取或设置MACD参数"""
global macd_fast_period, macd_slow_period, macd_signal_period
if request.method == 'GET':
return jsonify({
'fast': macd_fast_period,
'slow': macd_slow_period,
'signal': macd_signal_period
})
else:
data = request.get_json(silent=True) or {}
fast = data.get('fast')
slow = data.get('slow')
signal = data.get('signal')
if fast is not None:
macd_fast_period = int(fast)
if slow is not None:
macd_slow_period = int(slow)
if signal is not None:
macd_signal_period = int(signal)
return jsonify({
'fast': macd_fast_period,
'slow': macd_slow_period,
'signal': macd_signal_period
})
def get_uncompleted_seg_list(seg_list, client_tz):
"""获取未完成线段列表,正确处理倒数第二个和最后一个未完成线段"""
uncompleted_segs = [seg for seg in seg_list if not seg.is_sure]
+191 -57
View File
@@ -764,6 +764,91 @@
width: 100%;
height: 100%;
}
.macd-config-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 10000;
display: none;
justify-content: center;
align-items: center;
}
.macd-config-content {
background: white;
border-radius: 8px;
padding: 20px;
width: 360px;
max-width: 90vw;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
}
.macd-config-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 20px;
color: #333;
}
.macd-config-form {
display: grid;
gap: 14px;
}
.macd-config-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.macd-config-label {
font-size: 14px;
font-weight: 500;
color: #555;
}
.macd-config-input {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.macd-config-input:focus {
outline: none;
border-color: #0d6efd;
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
}
.macd-config-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 20px;
}
.macd-config-btn {
padding: 8px 18px;
border: none;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.macd-config-btn-primary {
background: #0d6efd;
color: white;
}
.macd-config-btn-primary:hover {
background: #0b5ed7;
}
.macd-config-btn-secondary {
background: #f8f9fa;
color: #6c757d;
}
.macd-config-btn-secondary:hover {
background: #e9ecef;
}
.macd-config-hint {
font-size: 12px;
color: #999;
margin-top: 2px;
}
</style>
</head>
<body>
@@ -854,6 +939,7 @@
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showMacd" checked>
<label class="form-check-label" for="showMacd">ChanMACD</label>
<button class="btn btn-sm btn-outline-secondary ms-1 p-0" onclick="showMacdConfig()" style="width:22px;height:22px;line-height:1;font-size:12px;" title="MACD参数设置"></button>
</div>
<div class="d-flex align-items-center mb-2">
<label for="refreshInterval" class="form-label me-2 mb-0">自动刷新:</label>
@@ -1170,47 +1256,6 @@
// ======= 趋势筛选(币对) =======
let trendTable = null;
// 不在前端截断数据,保留完整历史,避免K线数量限制
function trimDataInPlace(payload, maxLen = 2000) {
if (!payload || typeof payload !== 'object') return;
delete payload.original_kline_data;
delete payload.original_macd;
// 截断大型数组,只保留最新 maxLen 条数据
const arrayKeys = [
'kline_data', 'element_kline_data', 'sub_sub_kline_data',
'bi_list', 'element_bi_list', 'sub_sub_bi_list',
'seg_list', 'element_seg_list', 'sub_sub_seg_list',
'zs_list', 'element_zs_list', 'sub_sub_zs_list',
'uncompleted_bi_list', 'uncompleted_seg_list', 'uncompleted_zs_list',
'element_uncompleted_bi_list', 'element_uncompleted_seg_list', 'element_uncompleted_zs_list',
'bsp_list', 'element_bsp_list', 'sub_sub_bsp_list',
'trade_points', 'element_trade_points'
];
for (const key of arrayKeys) {
if (Array.isArray(payload[key]) && payload[key].length > maxLen) {
payload[key] = payload[key].slice(-maxLen);
}
}
// 截断 MACD 子数组
const macdKeys = ['macd', 'element_macd', 'sub_sub_macd'];
for (const mk of macdKeys) {
const m = payload[mk];
if (m && typeof m === 'object') {
for (const sub of ['macd', 'signal', 'histogram']) {
if (Array.isArray(m[sub]) && m[sub].length > maxLen) {
m[sub] = m[sub].slice(-maxLen);
}
}
}
}
// 截断 ATR 数组
for (const ak of ['atr', 'element_atr', 'sub_sub_atr']) {
if (Array.isArray(payload[ak]) && payload[ak].length > maxLen) {
payload[ak] = payload[ak].slice(-maxLen);
}
}
}
let trendDetailTable = null;
let trendChart = null;
@@ -1784,6 +1829,56 @@
updateChartDisplay();
});
function showMacdConfig() {
$.get('/api/macd_config', function(data) {
$('#macdFastPeriod').val(data.fast);
$('#macdSlowPeriod').val(data.slow);
$('#macdSignalPeriod').val(data.signal);
$('#macdConfigModal').css('display', 'flex');
});
}
function hideMacdConfig() {
$('#macdConfigModal').css('display', 'none');
}
function resetMacdConfig() {
$('#macdFastPeriod').val(24);
$('#macdSlowPeriod').val(52);
$('#macdSignalPeriod').val(9);
}
function saveMacdConfig() {
const fast = parseInt($('#macdFastPeriod').val());
const slow = parseInt($('#macdSlowPeriod').val());
const signal = parseInt($('#macdSignalPeriod').val());
if (fast >= slow) {
alert('快线周期必须小于慢线周期');
return;
}
if (fast < 2 || slow < 2 || signal < 2) {
alert('周期值必须大于等于2');
return;
}
$.ajax({
url: '/api/macd_config',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({ fast: fast, slow: slow, signal: signal }),
success: function() {
hideMacdConfig();
updateChart();
},
error: function() {
alert('保存MACD参数失败');
}
});
}
$(document).on('click', '#macdConfigModal', function(e) {
if (e.target === this) hideMacdConfig();
});
// 添加原始K线复选框变更事件
$('#showOriginalKline').change(function() {
updateChartDisplay();
@@ -1945,16 +2040,21 @@
}
ensureSubSubLteElement();
});
let _lastKlinePeriod = 'main';
function updateChartDisplay() {
if (currentData) {
// 保存当前的可见逻辑范围
// 检测K线周期是否切换
const curPeriod = $('#subSubPeriodKline').is(':checked') ? 'subsub' :
($('#elementPeriodKline').is(':checked') ? 'element' : 'main');
const periodChanged = (curPeriod !== _lastKlinePeriod);
_lastKlinePeriod = curPeriod;
// 保存当前的可见逻辑范围(周期切换时不保留,避免范围越界)
let logicalRange = null;
let visibleRange = null;
if (tvWidget && tvWidget.mainChart) {
// 优先使用逻辑范围,这样在缩放级别上更准确
if (!periodChanged && tvWidget && tvWidget.mainChart) {
logicalRange = tvWidget.mainChart.timeScale().getVisibleLogicalRange();
// 备用方案,获取实际时间戳范围
visibleRange = tvWidget.mainChart.timeScale().getVisibleRange();
}
@@ -2143,9 +2243,6 @@
return;
}
// 截断数据以限制内存占用
trimDataInPlace(data, 2000);
// 保存当前数据
if (currentData) {
// 覆盖前断开旧引用,帮助GC尽快回收
@@ -6585,21 +6682,29 @@
// 首先同步时间轴设置
syncTimeScaleSettings();
// 然后让主图表适应内容
mainChart.timeScale().fitContent();
// 显示最近 200 根K线而非全部挤压(避免K线过多时重叠)
const totalBars = candles ? candles.length : 0;
const visibleBarsCount = 200;
if (totalBars > visibleBarsCount) {
const rangeFrom = totalBars - visibleBarsCount;
const rangeTo = totalBars + 12;
mainChart.timeScale().setVisibleLogicalRange({ from: rangeFrom, to: rangeTo });
} else {
mainChart.timeScale().fitContent();
}
// 立即同步其他图表到主图表的范围
setTimeout(() => {
const visibleRange = mainChart.timeScale().getVisibleRange();
if (visibleRange) {
console.log('🔧 同步可见范围:', visibleRange);
volumeChart.timeScale().setVisibleRange(visibleRange);
atrChart.timeScale().setVisibleRange(visibleRange);
const logRange = mainChart.timeScale().getVisibleLogicalRange();
if (logRange) {
console.log('🔧 同步可见范围:', logRange);
volumeChart.timeScale().setVisibleLogicalRange(logRange);
atrChart.timeScale().setVisibleLogicalRange(logRange);
if (showMacd && macdChart) {
macdChart.timeScale().setVisibleRange(visibleRange);
macdChart.timeScale().setVisibleLogicalRange(logRange);
}
if (showMacd && chanMacdChart) {
chanMacdChart.timeScale().setVisibleRange(visibleRange);
chanMacdChart.timeScale().setVisibleLogicalRange(logRange);
}
console.log('🔧 时间轴同步完成');
}
@@ -10461,5 +10566,34 @@
</div>
</div>
</div>
<!-- MACD参数配置弹窗 -->
<div id="macdConfigModal" class="macd-config-modal">
<div class="macd-config-content">
<div class="macd-config-title">ChanMACD 参数设置</div>
<div class="macd-config-form">
<div class="macd-config-group">
<label class="macd-config-label">快线周期 (Fast)</label>
<input type="number" id="macdFastPeriod" class="macd-config-input" value="24" min="2" max="200">
<div class="macd-config-hint">默认: 24 (12×2)</div>
</div>
<div class="macd-config-group">
<label class="macd-config-label">慢线周期 (Slow)</label>
<input type="number" id="macdSlowPeriod" class="macd-config-input" value="52" min="2" max="500">
<div class="macd-config-hint">默认: 52 (26×2)</div>
</div>
<div class="macd-config-group">
<label class="macd-config-label">信号线周期 (Signal)</label>
<input type="number" id="macdSignalPeriod" class="macd-config-input" value="9" min="2" max="200">
<div class="macd-config-hint">默认: 9</div>
</div>
</div>
<div class="macd-config-actions">
<button class="macd-config-btn macd-config-btn-secondary" onclick="hideMacdConfig()">取消</button>
<button class="macd-config-btn macd-config-btn-secondary" onclick="resetMacdConfig()">恢复默认</button>
<button class="macd-config-btn macd-config-btn-primary" onclick="saveMacdConfig()">应用</button>
</div>
</div>
</div>
</body>
</html>