修改排版,加入up,down,flat,unknown显示

This commit is contained in:
jackyu66git
2025-09-30 13:40:20 +08:00
parent 1507f9f929
commit 566055b045
7 changed files with 305 additions and 140 deletions
+7
View File
@@ -50,6 +50,13 @@ class ChanKLC():
self.trend = Chan_PRICE_TREND.UNKNOWN self.trend = Chan_PRICE_TREND.UNKNOWN
def set_trend(self, trend): def set_trend(self, trend):
self.trend = trend self.trend = trend
def to_string(self):
out = ""
start = self.start_time if self.start_time is not None else ""
end = self.end_time if self.end_time is not None else ""
price_diff = getattr(self, 'price_diff', None)
out += str(start) + " " + str(end) + " " + str(self.close) + " " + str(self.ema24) + " " + str(self.ema52) + " " + str(self.trend) + " " + str(self.close - self.ema52)
return out
def set_klc_fx_type(self, klc_fx_type): def set_klc_fx_type(self, klc_fx_type):
#print(self.start_time, klc_fx_type, self.get_feature_data()['klu_macd'], self.get_feature_data()['klu_macdhist'], self.get_feature_data()['klu_rsi']) #print(self.start_time, klc_fx_type, self.get_feature_data()['klu_macd'], self.get_feature_data()['klu_macdhist'], self.get_feature_data()['klu_rsi'])
self.klc_fx_type = klc_fx_type self.klc_fx_type = klc_fx_type
+11 -5
View File
@@ -74,18 +74,24 @@ class ChanLun():
if len(self.tf_df_dict) > 0: if len(self.tf_df_dict) > 0:
return {key: self.tf_df_dict[key].get_ema24() for key in self.ema_symbols} return {key: self.tf_df_dict[key].get_ema24() for key in self.ema_symbols}
return None return None
def get_current_klc_dict(self):
if len(self.tf_df_dict) > 0:
return {key: self.tf_df_dict[key].get_current_klc() for key in self.ema_symbols}
return None
def cal_bsp(self): def cal_bsp(self):
return return
def check_fx(self, klc): def check_fx(self, klc):
if klc.pre and klc.next: if klc.pre and klc.next:
if klc.high > klc.pre.high and klc.high > klc.next.high: if klc.high > klc.pre.high and klc.high > klc.next.high:
klc.set_fx(Chan_FX_TYPE.TOP) if klc.close > klc.ema52 or klc.next.close > klc.next.ema52:
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time,klc.fx, "TOP") klc.set_fx(Chan_FX_TYPE.TOP)
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time,klc.fx, "TOP")
return Chan_FX_TYPE.TOP return Chan_FX_TYPE.TOP
elif klc.low < klc.pre.low and klc.low < klc.next.low: elif klc.low < klc.pre.low and klc.low < klc.next.low:
klc.set_fx(Chan_FX_TYPE.BOTTOM) if klc.close < klc.ema52 or klc.next.close < klc.next.ema52:
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time,klc.fx, "BOTTOM") klc.set_fx(Chan_FX_TYPE.BOTTOM)
return Chan_FX_TYPE.BOTTOM #print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time,klc.fx, "BOTTOM")
return Chan_FX_TYPE.BOTTOM
return Chan_FX_TYPE.UNKNOWN return Chan_FX_TYPE.UNKNOWN
def add_indicators(self, df): def add_indicators(self, df):
fast = 12 fast = 12
+1 -1
View File
@@ -26,7 +26,7 @@ class ChanMACDHistSet():
def set_middle_klu(self, middle_klu): def set_middle_klu(self, middle_klu):
self.middle_klu = middle_klu self.middle_klu = middle_klu
#self.middle_area = abs(middle_klu.macdhist) #self.middle_area = abs(middle_klu.macdhist)
#self.middle_klu = None self.middle_klu = None
def set_unittf_div(self, unittf_div): def set_unittf_div(self, unittf_div):
self.unittf_div = unittf_div self.unittf_div = unittf_div
def add_klu(self, klu): def add_klu(self, klu):
+28 -5
View File
@@ -56,6 +56,10 @@ class TF_DF():
return None return None
return float(ema24_value) return float(ema24_value)
return None return None
def get_current_klc(self):
if len(self.klc_list) > 0:
return self.klc_list[-2]
return None
def add_indicators(self, df): def add_indicators(self, df):
fast = 12 fast = 12
slow = 26 slow = 26
@@ -93,14 +97,33 @@ class TF_DF():
df['macd'] = macd['macd'] df['macd'] = macd['macd']
df['macdsignal'] = macd['macdsignal'] df['macdsignal'] = macd['macdsignal']
df['macdhist'] = macd['macdhist'] df['macdhist'] = macd['macdhist']
df['ema5'] = ta.EMA(df, timeperiod=5) df['ema5'] = self.cal_ema(df, 5)
df['ema10'] = ta.EMA(df, timeperiod=10) df['ema10'] = self.cal_ema(df, 10)
df['ema24'] = ta.EMA(df, timeperiod=24) df['ema24'] = self.cal_ema(df, 24)
df['ema26'] = ta.EMA(df, timeperiod=26) df['ema26'] = self.cal_ema(df, 26)
df['ema52'] = ta.EMA(df, timeperiod=52) df['ema52'] = self.cal_ema(df, 52)
df['rsi'] = ta.RSI(df, timeperiod=14) df['rsi'] = ta.RSI(df, timeperiod=14)
df['volume_ratio'] = self.cal_volume_ratio(df) df['volume_ratio'] = self.cal_volume_ratio(df)
return df return df
@staticmethod
def cal_ema(df, timeperiod):
"""
计算 EMA,优先使用 pandas ewm(adjust=False) 以贴近前端/TradingView 显示;
必要时回退到 TA-Libabstract)。
"""
try:
series = df['close'].astype(float) if isinstance(df, pd.DataFrame) else pd.Series(df).astype(float)
return series.ewm(span=int(timeperiod), adjust=False).mean()
except Exception:
try:
if isinstance(df, pd.DataFrame):
return ta.EMA(df, timeperiod=int(timeperiod))
except Exception:
pass
# 最后回退:返回同索引的 NaN 序列
if isinstance(df, pd.DataFrame) and 'close' in df:
return pd.Series(np.nan, index=df.index)
return pd.Series(dtype=float)
def check_fx(self, klc): def check_fx(self, klc):
if klc.pre and klc.next: if klc.pre and klc.next:
if klc.high > klc.pre.high and klc.high > klc.next.high: if klc.high > klc.pre.high and klc.high > klc.next.high:
+5 -2
View File
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
# freqtrade trade -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies # freqtrade trade -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies
# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies --timerange=20250901- # freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies --timerange=20250901-
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_30.json -t 1m --pairs BTC/USDT:USDT --timerange=20250405- # freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_30.json -t 1m 1m 1h 1d 1M --pairs BTC/USDT:USDT --timerange=20250405-
# freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_30.json -t 1m 1h 1d 1M --pairs BTC/USDT --timerange=20170101- # freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_30.json -t 1m 1h 1d 1M --pairs BTC/USDT --timerange=20170101-
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_30.json -e 200 --timerange=20250201-20250901 # freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_30.json -e 200 --timerange=20250201-20250901
# freqtrade edge -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901 # freqtrade edge -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
@@ -120,13 +120,16 @@ class ChanLun_BTC(IStrategy):
dataframe_1d = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1d') dataframe_1d = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1d')
dataframe_1M = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1M') dataframe_1M = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1M')
self.chan.init_dataframes(dataframe_m, dataframe_1h, dataframe_1d, dataframe_1M) self.chan.init_dataframes(dataframe_m, dataframe_1h, dataframe_1d, dataframe_1M)
self.print_all_ema52() self.print_all_current_klc()
def print_all_ema52(self): def print_all_ema52(self):
for key, value in self.chan.get_ema52_dict().items(): for key, value in self.chan.get_ema52_dict().items():
print(key, value) print(key, value)
def print_all_ema24(self): def print_all_ema24(self):
for key, value in self.chan.get_ema24_dict().items(): for key, value in self.chan.get_ema24_dict().items():
print(key, value) print(key, value)
def print_all_current_klc(self):
for key, value in self.chan.get_current_klc_dict().items():
print(key, value.to_string())
def add_indicators(self, df): def add_indicators(self, df):
fast = 12 fast = 12
slow = 26 slow = 26
+46 -5
View File
@@ -352,12 +352,13 @@ def analyze_chan(df, symbol=None, timeframe=None):
# 初始化多时间周期数据以获取EMA52 # 初始化多时间周期数据以获取EMA52
ema52_dict = None ema52_dict = None
if symbol and timeframe: # 先暂时不用这个功能,太慢了
if symbol and timeframe and False:
try: try:
# 获取不同时间周期的数据用于初始化 # 获取不同时间周期的数据用于初始化
df_1h = get_kl_data(symbol, '1h', limit=800) if timeframe != '1h' else df df_1h = get_kl_data(symbol, '1h', limit=1500) if timeframe != '1h' else df
df_1d = get_kl_data(symbol, '1d', limit=800) if timeframe != '1d' else df df_1d = get_kl_data(symbol, '1d', limit=2000) if timeframe != '1d' else df
df_1M = get_kl_data(symbol, '1M', limit=800) if timeframe != '1M' else df df_1M = get_kl_data(symbol, '1M', limit=1500) if timeframe != '1M' else df
# 添加指标 # 添加指标
if df_1h is not None and len(df_1h) > 0: if df_1h is not None and len(df_1h) > 0:
@@ -1659,6 +1660,24 @@ def analyze():
else: else:
replay_data = None replay_data = None
# 基于已有 KLC 列表生成趋势标记(不做额外计算)
klc_trend = []
try:
for klc in analysis_result.get('klc_list', []):
trend_val = getattr(klc, 'trend', None)
t_obj = getattr(klc, 'end_time', None) or getattr(klc, 'start_time', None)
if trend_val is None or t_obj is None:
continue
# 统一成字符串:UP/DOWN/FLAT/UNKNOWN
trend_name = str(trend_val)
if '.' in trend_name:
trend_name = trend_name.split('.')[-1]
time_str = format_time_safely(t_obj, client_tz)
if time_str:
klc_trend.append({'time': time_str, 'trend': trend_name})
except Exception:
klc_trend = []
# 添加主周期分析结果到返回数据 # 添加主周期分析结果到返回数据
result.update({ result.update({
'kline_data': clean_dataframe_for_json(df).to_dict('records'), 'kline_data': clean_dataframe_for_json(df).to_dict('records'),
@@ -1753,7 +1772,9 @@ def analyze():
# 添加ChanMACD分析数据 # 添加ChanMACD分析数据
'chan_macd': serialize_chan_macd_data(analysis_result.get('chan_macd', {}), client_tz), 'chan_macd': serialize_chan_macd_data(analysis_result.get('chan_macd', {}), client_tz),
# 添加多时间周期EMA52数据 # 添加多时间周期EMA52数据
'ema52_dict': analysis_result.get('ema52_dict', {}) 'ema52_dict': analysis_result.get('ema52_dict', {}),
# 直接输出KLC趋势标记(使用已有trend字段)
'klc_trend': klc_trend
}) })
# 如果生成了回放数据,添加到返回结果中 # 如果生成了回放数据,添加到返回结果中
@@ -1775,6 +1796,23 @@ def analyze():
# 计算小周期MACD数据 # 计算小周期MACD数据
element_macd_data = calculate_macd(element_df) element_macd_data = calculate_macd(element_df)
# 组装小周期 KLC 趋势(仅提取已有 trend,不做重算)
try:
element_klc_trend = []
for klc in element_analysis.get('klc_list', []):
trend_val = getattr(klc, 'trend', None)
t_obj = getattr(klc, 'end_time', None) or getattr(klc, 'start_time', None)
if trend_val is None or t_obj is None:
continue
trend_name = str(trend_val)
if '.' in trend_name:
trend_name = trend_name.split('.')[-1]
time_str = format_time_safely(t_obj, client_tz)
if time_str:
element_klc_trend.append({'time': time_str, 'trend': trend_name})
except Exception:
element_klc_trend = []
# 添加小周期分析结果到返回数据 # 添加小周期分析结果到返回数据
result['element_timeframe'] = element_timeframe result['element_timeframe'] = element_timeframe
result['element_macd'] = element_macd_data # 添加小周期MACD数据 result['element_macd'] = element_macd_data # 添加小周期MACD数据
@@ -1880,6 +1918,9 @@ def analyze():
# 添加次周期ChanMACD分析数据 # 添加次周期ChanMACD分析数据
result['element_chan_macd'] = serialize_chan_macd_data(element_analysis.get('chan_macd', {}), client_tz) result['element_chan_macd'] = serialize_chan_macd_data(element_analysis.get('chan_macd', {}), client_tz)
# 添加小周期 KLC 趋势标记
result['element_klc_trend'] = element_klc_trend
pass pass
return jsonify(result) return jsonify(result)
+207 -122
View File
@@ -801,8 +801,8 @@
</div> </div>
<div class="controls"> <div class="controls">
<div class="row g-3 align-items-end"> <div class="row g-1 d-flex align-items-end">
<div class="col-md-1"> <div class="col-md-2">
<label for="dataSource" class="form-label">数据源:</label> <label for="dataSource" class="form-label">数据源:</label>
<select id="dataSource" class="form-select"> <select id="dataSource" class="form-select">
<option value="crypto" selected>加密货币</option> <option value="crypto" selected>加密货币</option>
@@ -825,14 +825,6 @@
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<div class="col-md-1">
<label for="timeframe" class="form-label">时间周期:</label>
<select id="timeframe" class="form-select">
{% for value, label in timeframes.items() %}
<option value="{{ value }}" {% if value == '5m' %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-1"> <div class="col-md-1">
<label for="timezone" class="form-label">时区:</label> <label for="timezone" class="form-label">时区:</label>
<select id="timezone" class="form-select"> <select id="timezone" class="form-select">
@@ -844,11 +836,11 @@
<option value="Asia/Tokyo">Asia/Tokyo (UTC+9)</option> <option value="Asia/Tokyo">Asia/Tokyo (UTC+9)</option>
</select> </select>
</div> </div>
<div class="col-md-3"> <div class="col-md-2">
<label for="start_time" class="form-label">开始时间:</label> <label for="start_time" class="form-label">开始时间:</label>
<input type="datetime-local" id="start_time" class="form-control"> <input type="datetime-local" id="start_time" class="form-control">
</div> </div>
<div class="col-md-3"> <div class="col-md-2">
<label for="end_time" class="form-label">结束时间:</label> <label for="end_time" class="form-label">结束时间:</label>
<input type="datetime-local" id="end_time" class="form-control"> <input type="datetime-local" id="end_time" class="form-control">
</div> </div>
@@ -860,7 +852,7 @@
</div> </div>
<div class="row mt-3"> <div class="row mt-3">
<div class="col-md-8"> <div class="col-md-16">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<label class="form-label me-3 mb-0">基础显示:</label> <label class="form-label me-3 mb-0">基础显示:</label>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
@@ -876,11 +868,41 @@
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="klinePeriod" id="elementPeriodKline"> <input class="form-check-input" type="radio" name="klinePeriod" id="elementPeriodKline">
<label class="form-check-label" for="elementPeriodKline">小周期</label> <label class="form-check-label" for="elementPeriodKline">小周期</label>
<i class="bi bi-info-circle" data-bs-toggle="tooltip" title="显示小周期K线,同时可以叠加大周期分型和笔段"></i> </div>
<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>
</div>
<div class="d-flex align-items-center mb-2">
<label for="refreshInterval" class="form-label me-2 mb-0">自动刷新:</label>
<select id="refreshInterval" class="form-select form-select-sm me-2" style="width: 80px;">
<option value="0.0833">5秒</option>
<option value="0.1667">10秒</option>
<option value="0.25">15秒</option>
<option value="0.5">30秒</option>
<option value="1">1分钟</option>
<option value="2">2分钟</option>
<option value="3">3分钟</option>
<option value="5" selected>5分钟</option>
<option value="10">10分钟</option>
</select>
<div class="form-check form-check-inline me-2">
<input class="form-check-input" type="checkbox" id="autoRefresh">
<label class="form-check-label" for="autoRefresh">启用</label>
</div>
<span id="nextRefreshTime" class="text-muted" style="display:none;font-size:0.85rem;"></span>
<div id="refreshLoadingSpinner" class="loading-spinner ms-2" style="display:none;"></div>
</div> </div>
</div> </div>
<div class="d-flex align-items-center mt-2"> <div class="d-flex align-items-center mt-2">
<label class="form-label me-3 mb-0">主周期:</label> <label class="form-label me-0 mb-0">主周期:</label>
<div class="form-check form-check-inline">
<select id="timeframe" class="form-select form-select-sm me-2" style="width: 100px;">
{% for value, label in timeframes.items() %}
<option value="{{ value }}" {% if value == '5m' %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showMainBi" checked> <input class="form-check-input" type="checkbox" id="showMainBi" checked>
<label class="form-check-label" for="showMainBi"></label> <label class="form-check-label" for="showMainBi"></label>
@@ -897,33 +919,28 @@
<input class="form-check-input" type="checkbox" id="showMainUncompletedZs"> <input class="form-check-input" type="checkbox" id="showMainUncompletedZs">
<label class="form-check-label" for="showMainUncompletedZs">未完成中枢</label> <label class="form-check-label" for="showMainUncompletedZs">未完成中枢</label>
</div> </div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showMainMacdDiv">
<label class="form-check-label" for="showMainMacdDiv">MACD背离</label>
</div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showKlcFxType" checked> <input class="form-check-input" type="checkbox" id="showKlcFxType" checked>
<label class="form-check-label" for="showKlcFxType">KLC分型</label> <label class="form-check-label" for="showKlcFxType">KLC分型</label>
</div> </div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showKluFxType"> <input class="form-check-input" type="checkbox" id="showMainTrend" checked>
<label class="form-check-label" for="showKluFxType">KLU分型</label> <label class="form-check-label" for="showMainTrend">Trend</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showMainBollinger">
<label class="form-check-label" for="showMainBollinger">布林带</label>
</div> </div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="toggleUOnMain" checked> <input class="form-check-input" type="checkbox" id="toggleUOnMain" checked>
<label class="form-check-label" for="toggleUOnMain">显示U</label> <label class="form-check-label" for="toggleUOnMain">显示U</label>
</div> </div>
<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>
</div>
</div> </div>
<div class="d-flex align-items-center mt-2"> <div class="d-flex align-items-center mt-2">
<label class="form-label me-3 mb-0">次周期:</label> <label class="form-label me-0 mb-0">次周期:</label>
<div class="form-check form-check-inline">
<select id="elementTimeframe" class="form-select form-select-sm me-2" style="width: 100px;">
{% for value, label in timeframes.items() %}
<option value="{{ value }}" {% if value == '1m' %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showElementBi"> <input class="form-check-input" type="checkbox" id="showElementBi">
<label class="form-check-label" for="showElementBi"></label> <label class="form-check-label" for="showElementBi"></label>
@@ -940,21 +957,13 @@
<input class="form-check-input" type="checkbox" id="showElementUncompletedZs"> <input class="form-check-input" type="checkbox" id="showElementUncompletedZs">
<label class="form-check-label" for="showElementUncompletedZs">未完成中枢</label> <label class="form-check-label" for="showElementUncompletedZs">未完成中枢</label>
</div> </div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showElementMacdDiv">
<label class="form-check-label" for="showElementMacdDiv">MACD背离</label>
</div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showElementKlcFxType"> <input class="form-check-input" type="checkbox" id="showElementKlcFxType">
<label class="form-check-label" for="showElementKlcFxType">KLC分型</label> <label class="form-check-label" for="showElementKlcFxType">KLC分型</label>
</div> </div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showElementKluFxType"> <input class="form-check-input" type="checkbox" id="showElementTrend">
<label class="form-check-label" for="showElementKluFxType">KLU分型</label> <label class="form-check-label" for="showElementTrend">Trend</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showElementBollinger">
<label class="form-check-label" for="showElementBollinger">布林带</label>
</div> </div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="toggleUOnElement"> <input class="form-check-input" type="checkbox" id="toggleUOnElement">
@@ -962,64 +971,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-4">
<div class="d-flex align-items-center mb-2">
<label for="elementTimeframe" class="form-label me-2 mb-0">次级别时间周期:</label>
<select id="elementTimeframe" class="form-select form-select-sm me-2" style="width: 120px;">
{% for value, label in timeframes.items() %}
<option value="{{ value }}" {% if value == '1m' %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<div class="d-flex align-items-center mb-2">
<label for="refreshInterval" class="form-label me-2 mb-0">自动刷新:</label>
<select id="refreshInterval" class="form-select form-select-sm me-2" style="width: 80px;">
<option value="0.0833">5秒</option>
<option value="0.1667">10秒</option>
<option value="0.25">15秒</option>
<option value="0.5">30秒</option>
<option value="1">1分钟</option>
<option value="2">2分钟</option>
<option value="3">3分钟</option>
<option value="5" selected>5分钟</option>
<option value="10">10分钟</option>
</select>
<div class="form-check form-check-inline me-2">
<input class="form-check-input" type="checkbox" id="autoRefresh">
<label class="form-check-label" for="autoRefresh">启用</label>
</div>
<span id="nextRefreshTime" class="text-muted" style="display:none;font-size:0.85rem;"></span>
<div id="refreshLoadingSpinner" class="loading-spinner ms-2" style="display:none;"></div>
</div>
<!-- 添加数据回放控制面板 -->
<div class="d-flex align-items-center">
<label for="replayInterval" class="form-label me-2 mb-0">数据回放:</label>
<select id="replayInterval" class="form-select form-select-sm me-2" style="width: 80px;">
<option value="0.5">0.5秒</option>
<option value="1" selected>1秒</option>
<option value="2">2秒</option>
<option value="3">3秒</option>
<option value="5">5秒</option>
</select>
<div class="btn-group me-2">
<button id="startReplay" class="btn btn-sm btn-success">
<i class="bi bi-play-fill"></i> 开始回放
</button>
<button id="pauseReplay" class="btn btn-sm btn-warning" style="display:none;">
<i class="bi bi-pause-fill"></i> 暂停
</button>
<button id="stopReplay" class="btn btn-sm btn-danger" style="display:none;">
<i class="bi bi-stop-fill"></i> 停止
</button>
</div>
<span id="replayStatus" class="text-muted" style="font-size:0.85rem;"></span>
<div id="replayProgress" class="progress ms-2" style="width: 80px; height: 8px; display: none;">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%"></div>
</div>
</div>
</div>
</div> </div>
</div> </div>
@@ -2025,19 +1976,9 @@
refreshChart(currentData); refreshChart(currentData);
}); });
// 添加分型类型复选框变更事件 // Trend 显示开关
$('#showKlcFxType').change(function() { $('#showMainTrend').change(function() { refreshChartOnly(); });
refreshChartOnly(); $('#showElementTrend').change(function() { refreshChartOnly(); });
});
// 添加布林带显示变更事件
$('#showMainBollinger').change(function() {
updateChartDisplay();
});
$('#showElementBollinger').change(function() {
updateChartDisplay();
});
// 添加K线周期切换事件监听器 // 添加K线周期切换事件监听器
$('input[name="klinePeriod"]').change(function() { $('input[name="klinePeriod"]').change(function() {
@@ -3029,8 +2970,8 @@
// 添加ChanMACD分析标注 // 添加ChanMACD分析标注
// 根据主/次周期开关与各自的“显示U”独立控制 // 根据主/次周期开关与各自的“显示U”独立控制
const cm = useElementPeriod ? (currentData.element_chan_macd || currentData.chan_macd) : currentData.chan_macd; const cm = useElementPeriod ? (currentData.element_chan_macd || currentData.chan_macd) : currentData.chan_macd;
const allowU = useElementPeriod ? (typeof window.showUOnElement === 'undefined' ? true : window.showUOnElement) // 默认不显示,必须用户勾选对应复选框
: (typeof window.showUOnMain === 'undefined' ? true : window.showUOnMain); const allowU = useElementPeriod ? !!window.showUOnElement : !!window.showUOnMain;
if (cm && allowU) { if (cm && allowU) {
console.log('添加ChanMACD分析标注:', { console.log('添加ChanMACD分析标注:', {
segListLength: cm.seg_list ? cm.seg_list.length : 0, segListLength: cm.seg_list ? cm.seg_list.length : 0,
@@ -3088,7 +3029,7 @@
); );
// 主周期 U 标记(蓝/橙,与原样式一致) // 主周期 U 标记(蓝/橙,与原样式一致)
if ((typeof window.showUOnMain === 'undefined' ? true : window.showUOnMain) && Array.isArray(mainCm.klu_list)) { if (window.showUOnMain && Array.isArray(mainCm.klu_list)) {
mainCm.klu_list.forEach((item) => { mainCm.klu_list.forEach((item) => {
if (!item || !item.time) return; if (!item || !item.time) return;
const ts = Math.floor(new Date(item.time).getTime() / 1000); const ts = Math.floor(new Date(item.time).getTime() / 1000);
@@ -3110,7 +3051,7 @@
} }
// 次周期 U 标记(使用不同配色以区分) // 次周期 U 标记(使用不同配色以区分)
if ((typeof window.showUOnElement === 'undefined' ? true : window.showUOnElement) && Array.isArray(elementCm.klu_list)) { if (window.showUOnElement && Array.isArray(elementCm.klu_list)) {
elementCm.klu_list.forEach((item) => { elementCm.klu_list.forEach((item) => {
if (!item || !item.time) return; if (!item || !item.time) return;
const ts = Math.floor(new Date(item.time).getTime() / 1000); const ts = Math.floor(new Date(item.time).getTime() / 1000);
@@ -5488,12 +5429,86 @@
window.fxMarkers = elementFxMarkers; window.fxMarkers = elementFxMarkers;
} }
// 合并主周期、小周期分型与 UnitTF 标记,统一设置到K线数据系列 // 基于后端提供的 KLC 趋势生成标记(不进行任何计算)
let klcTrendMarkers = [];
try {
if (currentData.klc_trend && currentData.klc_trend.length > 0) {
console.log('KLC趋势点数量:', currentData.klc_trend.length, currentData.klc_trend.slice(0, 3));
// 当前图表的bar时间集合(秒)用于对齐标记到最近的K线
const seriesTimes = (typeof candles !== 'undefined' && Array.isArray(candles)) ? new Set(candles.map(c => c.time)) : new Set();
const nearestTime = (target) => {
if (!Array.isArray(candles) || candles.length === 0) return target;
// 简单线性查找(数据量通常可接受),必要时可替换为二分
let best = candles[0].time;
let bestDiff = Math.abs(best - target);
for (let i = 1; i < candles.length; i++) {
const t = candles[i].time;
const d = Math.abs(t - target);
if (d < bestDiff) { best = t; bestDiff = d; }
}
return best;
};
klcTrendMarkers = currentData.klc_trend.map(t => {
const ts = Math.floor(new Date(t.time).getTime() / 1000);
const trendRaw = (t.trend || '').toString().toUpperCase();
let timeAligned = seriesTimes.has(ts) ? ts : nearestTime(ts);
let marker = { time: timeAligned, position: 'inBar', color: '#9E9E9E', shape: 'square', size: 0.8 };
if (trendRaw === 'UP') {
marker = { time: timeAligned, position: 'aboveBar', color: '#00C853', shape: 'arrowUp', size: 0.5 };
} else if (trendRaw === 'DOWN') {
marker = { time: timeAligned, position: 'belowBar', color: '#D32F2F', shape: 'arrowDown', size: 0.5 };
} else if (trendRaw === 'FLAT') {
marker = { time: timeAligned, position: 'inBar', color: '#9E9E9E', shape: 'circle', size: 0.8 };
} else {
// UNKNOWN 或其他
marker = { time: timeAligned, position: 'inBar', color: '#2196F3', shape: 'square', size: 0.8 };
}
return marker;
});
console.log('KLC趋势标记(对齐后)示例:', klcTrendMarkers.slice(0, 5));
}
} catch (e) {
klcTrendMarkers = [];
}
// 暴露到全局以便调试或后续合并
window.klcTrendMarkers = klcTrendMarkers;
// 无论当前显示主/小周期,只要勾选对应Trend,就叠加出来
let trendMarkersToUse = [];
if ($('#showMainTrend').is(':checked')) {
trendMarkersToUse = trendMarkersToUse.concat(window.klcTrendMarkers || []);
}
if ($('#showElementTrend').is(':checked') && currentData.element_klc_trend) {
const candlesTimes = (typeof candles !== 'undefined' && Array.isArray(candles)) ? new Set(candles.map(c => c.time)) : new Set();
const nearestTime = (target) => {
if (!Array.isArray(candles) || candles.length === 0) return target;
let best = candles[0].time, bestDiff = Math.abs(best - target);
for (let i = 1; i < candles.length; i++) {
const t = candles[i].time, d = Math.abs(t - target);
if (d < bestDiff) { best = t; bestDiff = d; }
}
return best;
};
const elementMarkers = currentData.element_klc_trend.map(t => {
const ts = Math.floor(new Date(t.time).getTime() / 1000);
const trendRaw = (t.trend || '').toString().toUpperCase();
const timeAligned = candlesTimes.has(ts) ? ts : nearestTime(ts);
if (trendRaw === 'UP') return { time: timeAligned, position: 'aboveBar', color: '#00C853', shape: 'arrowUp', size: 0.5 };
if (trendRaw === 'DOWN') return { time: timeAligned, position: 'belowBar', color: '#D32F2F', shape: 'arrowDown', size: 0.5 };
if (trendRaw === 'FLAT') return { time: timeAligned, position: 'inBar', color: '#9E9E9E', shape: 'circle', size: 0.8 };
return { time: timeAligned, position: 'inBar', color: '#2196F3', shape: 'square', size: 0.8 };
});
trendMarkersToUse = trendMarkersToUse.concat(elementMarkers);
}
// 合并标记并设置
const combinedMarkers = [ const combinedMarkers = [
...(window.mainFxMarkers || []), ...(window.mainFxMarkers || []),
...allElementFxMarkers, ...allElementFxMarkers,
...(window.kluDivMarkersMain || []), ...(window.kluDivMarkersMain || []),
...(window.kluDivMarkersElement || []) ...(window.kluDivMarkersElement || []),
...trendMarkersToUse
]; ];
if (combinedMarkers.length > 0) { 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, '个)');
@@ -5513,11 +5528,76 @@
} else { } else {
console.log('绘制小周期分型标记 - 已禁用或无数据'); console.log('绘制小周期分型标记 - 已禁用或无数据');
// 只设置主周期分型 + UnitTF 标记 // 计算并缓存KLC趋势标记(即使未启用小周期分型,也应显示趋势)
try {
let klcTrendMarkers = [];
if (currentData.klc_trend && currentData.klc_trend.length > 0) {
console.log('KLC趋势点数量:', currentData.klc_trend.length, currentData.klc_trend.slice(0, 3));
const seriesTimes = (typeof candles !== 'undefined' && Array.isArray(candles)) ? new Set(candles.map(c => c.time)) : new Set();
const nearestTime = (target) => {
if (!Array.isArray(candles) || candles.length === 0) return target;
let best = candles[0].time;
let bestDiff = Math.abs(best - target);
for (let i = 1; i < candles.length; i++) {
const t = candles[i].time;
const d = Math.abs(t - target);
if (d < bestDiff) { best = t; bestDiff = d; }
}
return best;
};
klcTrendMarkers = currentData.klc_trend.map(t => {
const ts = Math.floor(new Date(t.time).getTime() / 1000);
const trendRaw = (t.trend || '').toString().toUpperCase();
const timeAligned = seriesTimes.has(ts) ? ts : nearestTime(ts);
if (trendRaw === 'UP') {
return { time: timeAligned, position: 'aboveBar', color: '#00C853', shape: 'arrowUp', size: 0.5 };
} else if (trendRaw === 'DOWN') {
return { time: timeAligned, position: 'belowBar', color: '#D32F2F', shape: 'arrowDown', size: 0.5 };
} else if (trendRaw === 'FLAT') {
return { time: timeAligned, position: 'inBar', color: '#9E9E9E', shape: 'circle', size: 0.8 };
} else {
return { time: timeAligned, position: 'inBar', color: '#2196F3', shape: 'square', size: 0.8 };
}
});
console.log('KLC趋势标记(对齐后)示例:', klcTrendMarkers.slice(0, 5));
}
window.klcTrendMarkers = klcTrendMarkers;
} catch (e) {
window.klcTrendMarkers = [];
}
// 与上方一致:勾选哪个Trend就显示哪个
let trendMarkersToUse = [];
if ($('#showMainTrend').is(':checked')) {
trendMarkersToUse = trendMarkersToUse.concat(window.klcTrendMarkers || []);
}
if ($('#showElementTrend').is(':checked') && currentData.element_klc_trend) {
const candlesTimes = (typeof candles !== 'undefined' && Array.isArray(candles)) ? new Set(candles.map(c => c.time)) : new Set();
const nearestTime = (target) => {
if (!Array.isArray(candles) || candles.length === 0) return target;
let best = candles[0].time, bestDiff = Math.abs(best - target);
for (let i = 1; i < candles.length; i++) {
const t = candles[i].time, d = Math.abs(t - target);
if (d < bestDiff) { best = t; bestDiff = d; }
}
return best;
};
const elementMarkers = currentData.element_klc_trend.map(t => {
const ts = Math.floor(new Date(t.time).getTime() / 1000);
const trendRaw = (t.trend || '').toString().toUpperCase();
const timeAligned = candlesTimes.has(ts) ? ts : nearestTime(ts);
if (trendRaw === 'UP') return { time: timeAligned, position: 'aboveBar', color: '#00C853', shape: 'arrowUp', size: 0.5 };
if (trendRaw === 'DOWN') return { time: timeAligned, position: 'belowBar', color: '#D32F2F', shape: 'arrowDown', size: 0.5 };
if (trendRaw === 'FLAT') return { time: timeAligned, position: 'inBar', color: '#9E9E9E', shape: 'circle', size: 0.8 };
return { time: timeAligned, position: 'inBar', color: '#2196F3', shape: 'square', size: 0.8 };
});
trendMarkersToUse = trendMarkersToUse.concat(elementMarkers);
}
const onlyMainAndU = [ const onlyMainAndU = [
...(window.mainFxMarkers || []), ...(window.mainFxMarkers || []),
...(window.kluDivMarkersMain || []), ...(window.kluDivMarkersMain || []),
...(window.kluDivMarkersElement || []) ...(window.kluDivMarkersElement || []),
...trendMarkersToUse
]; ];
if (onlyMainAndU.length > 0) { if (onlyMainAndU.length > 0) {
console.log('仅设置', onlyMainAndU.length, '个主周期/UnitTF标记(主周期分型:', (window.mainFxMarkers || []).length, 'UnitTF:', (window.unittfMarkers || []).length, ''); console.log('仅设置', onlyMainAndU.length, '个主周期/UnitTF标记(主周期分型:', (window.mainFxMarkers || []).length, 'UnitTF:', (window.unittfMarkers || []).length, '');
@@ -5675,6 +5755,9 @@
} }
// 添加买卖点提示 // 添加买卖点提示
// 初始化 tooltip 与 U 显示状态
window.showUOnMain = $('#toggleUOnMain').is(':checked');
window.showUOnElement = $('#toggleUOnElement').is(':checked');
setupTooltip(mainChart, [], [], mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, chanMacdChartContainer, volumeChart, atrChart, macdChart, chanMacdChart, showMacd); setupTooltip(mainChart, [], [], mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, chanMacdChartContainer, volumeChart, atrChart, macdChart, chanMacdChart, showMacd);
// 显示买卖点 // 显示买卖点
@@ -5895,7 +5978,8 @@
try { try {
if (typeof clearChanMacdMarkers === 'function') clearChanMacdMarkers(); if (typeof clearChanMacdMarkers === 'function') clearChanMacdMarkers();
const cm = useElementPeriod ? (currentData.element_chan_macd || currentData.chan_macd) : currentData.chan_macd; const cm = useElementPeriod ? (currentData.element_chan_macd || currentData.chan_macd) : currentData.chan_macd;
if (cm) { const allowU = useElementPeriod ? !!window.showUOnElement : !!window.showUOnMain;
if (cm && allowU) {
addAllChanMacdMarkers( addAllChanMacdMarkers(
cm.seg_list || [], cm.seg_list || [],
cm.unittf_list || [], cm.unittf_list || [],
@@ -10279,9 +10363,10 @@
}); });
} }
// 保存到全局,供主图与分型一起统一合并绘制 // 保存到全局,供主图与分型一起统一合并绘制(仅在开关开启时)
console.log('DEBUG: U 标记数量:', signalMarkers.length); console.log('DEBUG: U 标记数量:', signalMarkers.length);
window.unittfMarkers = [...signalMarkers, ...boundaryMarkers]; const allowUMerge = (window.showUOnMain && window.showUOnElement);
window.unittfMarkers = allowUMerge ? [...signalMarkers, ...boundaryMarkers] : [];
if (uTooltipMarkers.length > 0) { if (uTooltipMarkers.length > 0) {
if (window.fxMarkers) { if (window.fxMarkers) {
window.fxMarkers = [ ...window.fxMarkers, ...uTooltipMarkers ]; window.fxMarkers = [ ...window.fxMarkers, ...uTooltipMarkers ];