修改缠论顶低分型判断和笔生成顶低分型判断
This commit is contained in:
+27
-13
@@ -103,16 +103,16 @@ class ChanLun():
|
||||
return
|
||||
def check_fx(self, klc):
|
||||
if klc.pre and klc.next:
|
||||
if klc.high > klc.pre.high and klc.high > klc.next.high:
|
||||
if (klc.close > klc.ema52 or klc.next.close > klc.next.ema52) and klc.macd > 0 and klc.macd > klc.macdhist:
|
||||
klc.set_fx(Chan_FX_TYPE.TOP)
|
||||
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "TOP")
|
||||
return Chan_FX_TYPE.TOP
|
||||
elif 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 and klc.macd < klc.macdhist:
|
||||
klc.set_fx(Chan_FX_TYPE.BOTTOM)
|
||||
#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
|
||||
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 and klc.macd > klc.macdhist:
|
||||
klc.set_fx(Chan_FX_TYPE.TOP)
|
||||
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "TOP")
|
||||
return Chan_FX_TYPE.TOP
|
||||
elif klc.low < klc.pre.low and klc.low < klc.next.low and klc.high < klc.pre.high and klc.high < klc.next.high:
|
||||
#if (klc.close < klc.ema52 or klc.next.close < klc.next.ema52) and klc.macd < 0 and klc.macd < klc.macdhist:
|
||||
klc.set_fx(Chan_FX_TYPE.BOTTOM)
|
||||
#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 add_indicators1(self, df):
|
||||
fast = 12
|
||||
@@ -787,13 +787,28 @@ class ChanLun():
|
||||
#print(klc.start_time, klc.end_time, klc.close, klc.ema24, klc.ema52, klc.macd, klc.signal, klc.macdhist, klc.trend, price_diff, score)
|
||||
#print(klc.start_time, klc.end_time, klc.trend, price_diff, score)
|
||||
return klc_list
|
||||
def check_top_fx(self, last_bottom, klc):
|
||||
if last_bottom.high > klc.pre.low or last_bottom.high > klc.next.low:
|
||||
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:
|
||||
return False
|
||||
return True
|
||||
def cal_bi_list(self, klc_list):
|
||||
bi_list = []
|
||||
last_top = None
|
||||
last_bottom = None
|
||||
for klc in klc_list:
|
||||
fx = self.check_fx(klc)
|
||||
|
||||
if fx == Chan_FX_TYPE.TOP:
|
||||
if last_bottom:
|
||||
if self.check_top_fx(last_bottom, klc) == False:
|
||||
fx = Chan_FX_TYPE.UNKNOWN
|
||||
if fx == Chan_FX_TYPE.BOTTOM:
|
||||
if last_top:
|
||||
if self.check_bottom_fx(last_top, klc) == False:
|
||||
fx = Chan_FX_TYPE.UNKNOWN
|
||||
# Do nothing
|
||||
if fx == Chan_FX_TYPE.UNKNOWN:
|
||||
continue
|
||||
@@ -1115,7 +1130,6 @@ class ChanLun():
|
||||
zd = max(seg.low, seg.next.low, seg.next.next.low)
|
||||
gg = max(seg.high, seg.next.high, seg.next.next.high)
|
||||
dd = min(seg.low, seg.next.low, seg.next.next.low)
|
||||
ddir = Chan_ZS_DIR.UP
|
||||
ddir = None
|
||||
if last_zs:
|
||||
if zg < last_zs.zd:
|
||||
@@ -1156,7 +1170,7 @@ class ChanLun():
|
||||
# SEG is not in ZS
|
||||
if seg.is_sure:
|
||||
if ((seg.low > last_zs.zg and seg.high > last_zs.zg) or (seg.high < last_zs.zd and seg.low < last_zs.zd)):
|
||||
last_zs.set_end_klc(last_zs.last_bi_in.end_klc, seg.sure_time, bi_out_count, seg)
|
||||
last_zs.set_end_klc(seg.pre.pre.end_bi.end_klc, seg.sure_time, bi_out_count, seg)
|
||||
bi_out_count = 0
|
||||
#print(seg.start_bi.start_klc.start_time)
|
||||
first_bi_out = None
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"$schema": "https://schema.freqtrade.io/schema.json",
|
||||
"max_open_trades": 1,
|
||||
"stake_currency": "USDT",
|
||||
"stake_amount": "unlimited",
|
||||
"tradable_balance_ratio": 0.99,
|
||||
"fiat_display_currency": "USD",
|
||||
"dry_run": true,
|
||||
"db_url": "sqlite:///tradesv3.heikinashi_btc.sqlite",
|
||||
"dry_run_wallet": 1000,
|
||||
"cancel_open_orders_on_exit": true,
|
||||
"trading_mode": "futures",
|
||||
"margin_mode": "isolated",
|
||||
"can_short" : true,
|
||||
"timeframe" : "1m",
|
||||
"process_only_new_candles" : false,
|
||||
"unfilledtimeout": {
|
||||
"entry": 1,
|
||||
"exit": 1,
|
||||
"exit_timeout_count": 5,
|
||||
"unit": "minutes"
|
||||
},
|
||||
"entry_pricing": {
|
||||
"price_side": "same",
|
||||
"use_order_book": true,
|
||||
"order_book_top": 1,
|
||||
"price_last_balance": 0.0,
|
||||
"check_depth_of_market": {
|
||||
"enabled": false,
|
||||
"bids_to_ask_delta": 1
|
||||
}
|
||||
},
|
||||
"exit_pricing":{
|
||||
"price_side": "same",
|
||||
"use_order_book": true,
|
||||
"order_book_top": 1
|
||||
},
|
||||
"exchange": {
|
||||
"name": "binance",
|
||||
"key": "hvoXanRExQvcN4tyGFvEnsSF4gqxXp6ZJnBu5lnhvlVuHaDbj2PhLBQGCLkkyeI8",
|
||||
"secret": "3UKA2oyDj7OoXrausmnaLwLlNfXmlNf2imBdmQqqKHArcJfk6X9xjaUF19wzu82l",
|
||||
"ccxt_config": {},
|
||||
"ccxt_async_config": {},
|
||||
"pair_whitelist": [
|
||||
"BTC/USDT:USDT"
|
||||
],
|
||||
"pair_blacklist": [
|
||||
"BNB/.*"
|
||||
]
|
||||
},
|
||||
"pairlists": [
|
||||
{
|
||||
"method": "StaticPairList",
|
||||
"number_assets": 1,
|
||||
"sort_key": "quoteVolume",
|
||||
"min_value": 0,
|
||||
"refresh_period": 1800
|
||||
}
|
||||
],
|
||||
"telegram": {
|
||||
"enabled": true,
|
||||
"token": "7677670958:AAFL_jgZvNUTPR3R3vWieREX_tDVi9w2C1Y",
|
||||
"chat_id": "580807463"
|
||||
},
|
||||
"api_server": {
|
||||
"enabled": true,
|
||||
"listen_ip_address": "0.0.0.0",
|
||||
"listen_port": 8814,
|
||||
"verbosity": "error",
|
||||
"enable_openapi": false,
|
||||
"jwt_secret_key": "14d3510740e2c39a973a8895f1aa2704d98d08b86170260085709fa5ea48251d",
|
||||
"ws_token": "dtKKDnafBrX4icq_ZCw7acJTahTK4h_yvg",
|
||||
"CORS_origins": [],
|
||||
"username": "freqtrader",
|
||||
"password": "FreqTrade007"
|
||||
},
|
||||
"bot_name": "freqtrade",
|
||||
"initial_state": "running",
|
||||
"force_entry_enable": false,
|
||||
"internals": {
|
||||
"process_throttle_secs": 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
# --- Do not remove these libs ---
|
||||
from statistics import median
|
||||
from freqtrade.strategy import IStrategy
|
||||
from technical.util import resample_to_interval, resampled_merge
|
||||
from pandas import DataFrame
|
||||
import talib.abstract as ta
|
||||
from technical import qtpylib
|
||||
|
||||
### Now you can use logger.info('asfd') to log
|
||||
# freqtrade plot-dataframe --strategy HeikinAshi_BTC --datadir user_data/data/binance -c ./user_data/Chan/HeikinAshi_BTC.json --timerange=20250309-
|
||||
# freqtrade backtesting -c ./user_data/Chan/config/HeikinAshi_BTC.json --strategy HeikinAshi_BTC --strategy-path ./user_data/Chan/strategies --timerange=20251008-
|
||||
# freqtrade download-data -c ./user_data/Chan/config/HeikinAshi_BTC.json -t 1m 1m 1h 1d 1M --pairs BTC/USDT:USDT --timerange=20250405-
|
||||
# freqtrade download-data -c ./user_data/Chan/config/HeikinAshi_BTC.json -t 1m 1h 1d 1M --pairs BTC/USDT --timerange=20170101-
|
||||
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy HeikinAshi_BTC --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/HeikinAshi_BTC.json -e 200 --timerange=20250201-20250901
|
||||
# freqtrade edge -c ./user_data/Chan/config/HeikinAshi_BTC.json --strategy HeikinAshi_BTC --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
|
||||
# freqtrade plot-dataframe -c ./user_data/Chan/config/HeikinAshi_BTC.json --strategy HeikinAshi_BTC --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901
|
||||
|
||||
# sudo docker compose run --rm heikinashi_btc backtesting -c ./user_data/Chan/config/HeikinAshi_BTC.json --strategy HeikinAshi_BTC --strategy-path ./user_data/Chan/strategies --timerange=20250721-
|
||||
# sudo docker compose run --rm heikinashi_btc download-data -c ./user_data/Chan/config/HeikinAshi_BTC.json --pairs BTC/USDT:USDT -t 1m --timerange 20240101-
|
||||
# sudo docker compose run --rm heikinashi_btc trade -c ./user_data/Chan/config/HeikinAshi_BTC.json --strategy HeikinAshi_BTC --strategy-path ./user_data/Chan/strategies
|
||||
|
||||
class HeikinAshi_BTC(IStrategy):
|
||||
"""
|
||||
使用 Heikin Ashi 蜡烛的双均线趋势策略,支持多/空。
|
||||
|
||||
逻辑摘要:
|
||||
- 指标:ha_open/ha_close/ha_high/ha_low + ha_close 的快/慢 EMA
|
||||
- 做多:ha_close > ha_open 且 ha_ema_fast > ha_ema_slow
|
||||
- 做空:ha_close < ha_open 且 ha_ema_fast < ha_ema_slow
|
||||
- 退出:相反信号或均线反转
|
||||
"""
|
||||
|
||||
INTERFACE_VERSION: int = 3
|
||||
time1h = 1440
|
||||
can_short: bool = True
|
||||
timeframe: str = "1h"
|
||||
process_only_new_candles: bool = True
|
||||
|
||||
# ROI 与止损可根据需要在配置中覆盖
|
||||
minimal_roi = {
|
||||
"120": 0.01,
|
||||
"60": 0.02,
|
||||
"0": 0.03,
|
||||
}
|
||||
|
||||
stoploss: float = -0.30
|
||||
|
||||
use_exit_signal: bool = True
|
||||
exit_profit_only: bool = False
|
||||
ignore_roi_if_entry_signal: bool = False
|
||||
|
||||
# 需要的历史K线数量(包含EMA等指标预热)
|
||||
startup_candle_count: int = 200
|
||||
|
||||
plot_config = {
|
||||
"main_plot": {
|
||||
"ha_close": {"color": "orange"},
|
||||
"ha_ema_fast": {"color": "green"},
|
||||
"ha_ema_slow": {"color": "red"},
|
||||
},
|
||||
"subplots": {},
|
||||
}
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""计算 Heikin Ashi 相关指标与均线。"""
|
||||
if dataframe is None or dataframe.empty:
|
||||
return dataframe
|
||||
dataframe_1h = resample_to_interval(dataframe, self.get_ticker_indicator() * self.time1h)
|
||||
ha = qtpylib.heikinashi(dataframe_1h)
|
||||
dataframe_1h["ha_open"] = ha["open"]
|
||||
dataframe_1h["ha_close"] = ha["close"]
|
||||
dataframe_1h["ha_high"] = ha["high"]
|
||||
dataframe_1h["ha_low"] = ha["low"]
|
||||
|
||||
# 对 Heikin Ashi close 做 EMA 平滑,减少噪声
|
||||
dataframe_1h["ha_ema_fast"] = ta.EMA(dataframe_1h["ha_close"], timeperiod=24)
|
||||
dataframe_1h["ha_ema_slow"] = ta.EMA(dataframe_1h["ha_close"], timeperiod=52)
|
||||
dataframe = resampled_merge(dataframe, dataframe_1h)
|
||||
return dataframe
|
||||
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""根据 HA 趋势定义进场信号(多/空)。"""
|
||||
if dataframe is None or dataframe.empty:
|
||||
return dataframe
|
||||
ha_close = 'resample_{}_ha_close'.format(self.get_ticker_indicator()*self.time1h)
|
||||
ha_open = 'resample_{}_ha_open'.format(self.get_ticker_indicator()*self.time1h)
|
||||
ha_ema_fast = 'resample_{}_ha_ema_fast'.format(self.get_ticker_indicator()*self.time1h)
|
||||
ha_ema_slow = 'resample_{}_ha_ema_slow'.format(self.get_ticker_indicator()*self.time1h)
|
||||
volume = 'resample_{}_volume'.format(self.get_ticker_indicator()*self.time1h)
|
||||
# 做多:HA 实体向上 且 快线上穿慢线(金叉)
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe[ha_close] > dataframe[ha_open]) &
|
||||
(qtpylib.crossed_above(dataframe[ha_ema_fast], dataframe[ha_ema_slow])) &
|
||||
(dataframe["volume"] > 0)
|
||||
),
|
||||
["enter_long", "enter_tag"],
|
||||
] = (1, "ha_trend_long")
|
||||
|
||||
# 做空:HA 实体向下 且 快线下穿慢线(死叉)
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe[ha_close] < dataframe[ha_open]) &
|
||||
(qtpylib.crossed_below(dataframe[ha_ema_fast], dataframe[ha_ema_slow])) &
|
||||
(dataframe["volume"] > 0)
|
||||
),
|
||||
["enter_short", "enter_tag"],
|
||||
] = (1, "ha_trend_short")
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""根据 HA 反转或均线反转定义出场信号(多/空)。"""
|
||||
if dataframe is None or dataframe.empty:
|
||||
return dataframe
|
||||
ha_close = 'resample_{}_ha_close'.format(self.get_ticker_indicator()*self.time1h)
|
||||
ha_open = 'resample_{}_ha_open'.format(self.get_ticker_indicator()*self.time1h)
|
||||
ha_ema_fast = 'resample_{}_ha_ema_fast'.format(self.get_ticker_indicator()*self.time1h)
|
||||
ha_ema_slow = 'resample_{}_ha_ema_slow'.format(self.get_ticker_indicator()*self.time1h)
|
||||
volume = 'resample_{}_volume'.format(self.get_ticker_indicator()*self.time1h)
|
||||
# 多单退出:HA 变为阴 或 快线下穿慢线(死叉)
|
||||
dataframe.loc[
|
||||
(
|
||||
(
|
||||
(dataframe[ha_close] < dataframe[ha_open]) |
|
||||
(qtpylib.crossed_below(dataframe[ha_ema_fast], dataframe[ha_ema_slow]))
|
||||
)
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
["exit_long", "exit_tag"],
|
||||
] = (1, "ha_long_exit")
|
||||
|
||||
# 空单退出:HA 变为阳 或 快线上穿慢线(金叉)
|
||||
dataframe.loc[
|
||||
(
|
||||
(
|
||||
(dataframe[ha_close] > dataframe[ha_open]) |
|
||||
(qtpylib.crossed_above(dataframe[ha_ema_fast], dataframe[ha_ema_slow]))
|
||||
)
|
||||
& (dataframe["volume"] > 0)
|
||||
),
|
||||
["exit_short", "exit_tag"],
|
||||
] = (1, "ha_short_exit")
|
||||
|
||||
return dataframe
|
||||
def get_ticker_indicator(self):
|
||||
return int(self.timeframe[:-1])
|
||||
+314
-114
@@ -811,8 +811,17 @@
|
||||
<div class="d-flex align-items-center">
|
||||
<label class="form-label me-3 mb-0">基础显示:</label>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="checkbox" id="showOriginalKline" checked>
|
||||
<label class="form-check-label" for="showOriginalKline">原始K线</label>
|
||||
<select id="klineType" class="form-select form-select-sm me-2" style="width: 120px;">
|
||||
<option value="candlestick" selected>蜡烛线</option>
|
||||
<option value="bar">柱条</option>
|
||||
<option value="line">折线</option>
|
||||
<option value="area">面积</option>
|
||||
<option value="baseline">基线</option>
|
||||
<option value="renko">砖型</option>
|
||||
<option value="heikin">平均K</option>
|
||||
</select>
|
||||
<!-- 为兼容现有逻辑,保留隐藏的原始K线开关,与下拉同步 -->
|
||||
<input class="form-check-input" type="checkbox" id="showOriginalKline" checked style="display:none;">
|
||||
</div>
|
||||
|
||||
<!-- 添加K线周期切换 -->
|
||||
@@ -1093,46 +1102,10 @@
|
||||
// ======= 趋势筛选(币对) =======
|
||||
let trendTable = null;
|
||||
|
||||
// 限长截断,减少前端长期持有历史数组导致的内存增长
|
||||
// 不在前端截断数据,保留完整历史,避免K线数量限制
|
||||
function trimDataInPlace(payload, maxLen = 2000) {
|
||||
const keepTail = (arr) => (Array.isArray(arr) && arr.length > maxLen) ? arr.slice(-maxLen) : arr;
|
||||
if (!payload || typeof payload !== 'object') return;
|
||||
|
||||
// 主/次周期K线
|
||||
payload.kline_data = keepTail(payload.kline_data);
|
||||
payload.element_kline_data = keepTail(payload.element_kline_data);
|
||||
|
||||
// MACD
|
||||
if (payload.macd) {
|
||||
payload.macd.macd = keepTail(payload.macd.macd);
|
||||
payload.macd.signal = keepTail(payload.macd.signal);
|
||||
payload.macd.histogram = keepTail(payload.macd.histogram);
|
||||
}
|
||||
if (payload.element_macd) {
|
||||
payload.element_macd.macd = keepTail(payload.element_macd.macd);
|
||||
payload.element_macd.signal = keepTail(payload.element_macd.signal);
|
||||
payload.element_macd.histogram = keepTail(payload.element_macd.histogram);
|
||||
}
|
||||
|
||||
// ATR
|
||||
payload.atr = keepTail(payload.atr);
|
||||
payload.element_atr = keepTail(payload.element_atr);
|
||||
|
||||
// 分形/线段/中枢/买卖点
|
||||
payload.bi_list = keepTail(payload.bi_list);
|
||||
payload.seg_list = keepTail(payload.seg_list);
|
||||
payload.zs_list = keepTail(payload.zs_list);
|
||||
payload.uncompleted_zs_list = keepTail(payload.uncompleted_zs_list);
|
||||
|
||||
payload.element_bi_list = keepTail(payload.element_bi_list);
|
||||
payload.element_seg_list = keepTail(payload.element_seg_list);
|
||||
payload.element_zs_list = keepTail(payload.element_zs_list);
|
||||
payload.element_uncompleted_zs_list = keepTail(payload.element_uncompleted_zs_list);
|
||||
|
||||
payload.trade_points = keepTail(payload.trade_points);
|
||||
payload.element_trade_points = keepTail(payload.element_trade_points);
|
||||
|
||||
// 移除可能保存的旧快照,避免重复持有
|
||||
// 仅清理可能保留的旧快照引用
|
||||
delete payload.original_kline_data;
|
||||
delete payload.original_macd;
|
||||
}
|
||||
@@ -1501,7 +1474,11 @@
|
||||
chanMacdChart: null, // 新增ChanMACD图表
|
||||
series: {
|
||||
candleSeries: null,
|
||||
barSeries: null,
|
||||
lineSeries: null,
|
||||
areaSeries: null,
|
||||
baselineSeries: null,
|
||||
renkoSeries: null,
|
||||
volumeSeries: null,
|
||||
atrLineSeries: null,
|
||||
macdLineSeries: null,
|
||||
@@ -1714,6 +1691,13 @@
|
||||
updateChartDisplay();
|
||||
});
|
||||
|
||||
// 添加K线形态下拉变更事件(同步隐藏的原始K线开关并重绘)
|
||||
$('#klineType').change(function() {
|
||||
const type = $(this).val();
|
||||
$('#showOriginalKline').prop('checked', type === 'candlestick');
|
||||
updateChartDisplay();
|
||||
});
|
||||
|
||||
// 添加笔复选框变更事件
|
||||
$('#showMainBi').change(function() {
|
||||
updateChartDisplay();
|
||||
@@ -1845,7 +1829,6 @@
|
||||
setSmallerOrEqualTimeframe(mainTimeframe);
|
||||
}
|
||||
});
|
||||
|
||||
function updateChartDisplay() {
|
||||
if (currentData) {
|
||||
// 保存当前的可见逻辑范围
|
||||
@@ -2164,7 +2147,6 @@
|
||||
candles = filterTradingHours(candles, symbolConfig);
|
||||
console.log(`A股数据过滤: ${originalLength} -> ${candles.length} 条记录`);
|
||||
}
|
||||
|
||||
// 清除图表容器(释放旧 DOM 与 Canvas)
|
||||
const chartRoot = document.getElementById('tradingview_chart');
|
||||
if (chartRoot) chartRoot.innerHTML = '';
|
||||
@@ -2472,36 +2454,94 @@
|
||||
chanMacdChart = LightweightCharts.createChart(chanMacdChartContainer, createChartOptions(false, 'chanmacd'));
|
||||
}
|
||||
|
||||
// 创建蜡烛图系列并设置数据
|
||||
if (showOriginalKline) {
|
||||
const candleSeries = mainChart.addCandlestickSeries({
|
||||
upColor: '#dc3545',
|
||||
downColor: '#28a745',
|
||||
borderVisible: false,
|
||||
wickUpColor: '#dc3545',
|
||||
wickDownColor: '#28a745',
|
||||
});
|
||||
candleSeries.setData(candles);
|
||||
tvWidget.series.candleSeries = candleSeries;
|
||||
} else {
|
||||
// 如果不显示原始K线,只显示线图
|
||||
const lineSeries = mainChart.addLineSeries({
|
||||
color: '#2962FF',
|
||||
lineWidth: 2,
|
||||
crosshairMarkerVisible: true,
|
||||
lastValueVisible: true,
|
||||
priceLineVisible: true,
|
||||
});
|
||||
|
||||
// 提取收盘价数据
|
||||
const lineData = candles.map(candle => ({
|
||||
time: candle.time,
|
||||
value: candle.close
|
||||
}));
|
||||
|
||||
lineSeries.setData(lineData);
|
||||
tvWidget.series.lineSeries = lineSeries;
|
||||
}
|
||||
// 创建主价格系列并设置数据(支持多种图表类型)
|
||||
(function(){
|
||||
const klineType = ($('#klineType').val() || 'candlestick');
|
||||
// 先清空旧的主系列引用
|
||||
tvWidget.series.candleSeries = null;
|
||||
tvWidget.series.lineSeries = null;
|
||||
tvWidget.series.barSeries = null;
|
||||
tvWidget.series.areaSeries = null;
|
||||
tvWidget.series.baselineSeries = null;
|
||||
tvWidget.series.renkoSeries = null;
|
||||
tvWidget.series.heikinSeries = null;
|
||||
|
||||
if (klineType === 'candlestick') {
|
||||
const series = mainChart.addCandlestickSeries({
|
||||
upColor: '#dc3545',
|
||||
downColor: '#28a745',
|
||||
borderVisible: false,
|
||||
wickUpColor: '#dc3545',
|
||||
wickDownColor: '#28a745',
|
||||
});
|
||||
series.setData(candles);
|
||||
tvWidget.series.candleSeries = series;
|
||||
} else if (klineType === 'renko') {
|
||||
const series = mainChart.addCandlestickSeries({
|
||||
upColor: '#dc3545',
|
||||
downColor: '#28a745',
|
||||
borderVisible: false,
|
||||
wickUpColor: '#dc3545',
|
||||
wickDownColor: '#28a745',
|
||||
});
|
||||
const bricks = buildRenkoFromCandles(candles);
|
||||
series.setData(bricks);
|
||||
tvWidget.series.renkoSeries = series;
|
||||
} else if (klineType === 'heikin') {
|
||||
const series = mainChart.addCandlestickSeries({
|
||||
upColor: '#dc3545',
|
||||
downColor: '#28a745',
|
||||
borderVisible: false,
|
||||
wickUpColor: '#dc3545',
|
||||
wickDownColor: '#28a745',
|
||||
});
|
||||
const hk = buildHeikinFromCandles(candles);
|
||||
series.setData(hk);
|
||||
tvWidget.series.heikinSeries = series;
|
||||
} else if (klineType === 'bar') {
|
||||
const series = mainChart.addBarSeries({
|
||||
upColor: '#dc3545',
|
||||
downColor: '#28a745',
|
||||
thinBars: false
|
||||
});
|
||||
series.setData(candles);
|
||||
tvWidget.series.barSeries = series;
|
||||
} else if (klineType === 'line') {
|
||||
const series = mainChart.addLineSeries({
|
||||
color: '#2962FF',
|
||||
lineWidth: 2,
|
||||
crosshairMarkerVisible: true,
|
||||
lastValueVisible: true,
|
||||
priceLineVisible: true,
|
||||
});
|
||||
const lineData = candles.map(c => ({ time: c.time, value: c.close }));
|
||||
series.setData(lineData);
|
||||
tvWidget.series.lineSeries = series;
|
||||
} else if (klineType === 'area') {
|
||||
const series = mainChart.addAreaSeries({
|
||||
topColor: 'rgba(41, 98, 255, 0.4)',
|
||||
bottomColor: 'rgba(41, 98, 255, 0.0)',
|
||||
lineColor: '#2962FF',
|
||||
lineWidth: 2,
|
||||
});
|
||||
const areaData = candles.map(c => ({ time: c.time, value: c.close }));
|
||||
series.setData(areaData);
|
||||
tvWidget.series.areaSeries = series;
|
||||
} else if (klineType === 'baseline') {
|
||||
const series = mainChart.addBaselineSeries({
|
||||
baseValue: { type: 'price', price: candles.length ? candles[candles.length - 1].close : 0 },
|
||||
topLineColor: '#26a69a',
|
||||
bottomLineColor: '#ef5350',
|
||||
topFillColor1: 'rgba(38, 166, 154, 0.28)',
|
||||
topFillColor2: 'rgba(38, 166, 154, 0.05)',
|
||||
bottomFillColor1: 'rgba(239, 83, 80, 0.28)',
|
||||
bottomFillColor2: 'rgba(239, 83, 80, 0.05)'
|
||||
});
|
||||
const baseData = candles.map(c => ({ time: c.time, value: c.close }));
|
||||
series.setData(baseData);
|
||||
tvWidget.series.baselineSeries = series;
|
||||
}
|
||||
})();
|
||||
|
||||
// 转换成交量数据 - 始终使用主K线周期数据
|
||||
let volumes = [];
|
||||
@@ -2797,7 +2837,7 @@
|
||||
console.log('✅ ChanMACD图表系列已保存到tvWidget');
|
||||
|
||||
// 添加ChanMACD分析标注
|
||||
// 根据主/次周期开关与各自的“显示U”独立控制
|
||||
// 根据主/次周期开关与各自的"显示U"独立控制
|
||||
const cm = useElementPeriod ? (currentData.element_chan_macd || currentData.chan_macd) : currentData.chan_macd;
|
||||
// 默认不显示,必须用户勾选对应复选框
|
||||
const allowU = useElementPeriod ? !!window.showUOnElement : !!window.showUOnMain;
|
||||
@@ -2918,7 +2958,6 @@
|
||||
} else {
|
||||
console.log('⚠️ ChanMACD图表创建条件不满足');
|
||||
}
|
||||
|
||||
// 独立于当前显示周期:计算主/次周期 SD/CD 标记(用于主图合并显示)
|
||||
try {
|
||||
const mainCmAll = currentData.chan_macd || {};
|
||||
@@ -4753,15 +4792,38 @@
|
||||
// 获取当前价格 - 通过param.seriesPrices获取
|
||||
let priceInfo = '';
|
||||
if (param.seriesPrices && param.seriesPrices.size > 0) {
|
||||
// 尝试从蜡烛图系列获取价格
|
||||
// 依次从当前可能的主系列中获取价格
|
||||
if (tvWidget.series.candleSeries && param.seriesPrices.get(tvWidget.series.candleSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.candleSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
}
|
||||
// 如果没有蜡烛图系列价格,尝试从线图系列获取
|
||||
else if (tvWidget.series.lineSeries && param.seriesPrices.get(tvWidget.series.lineSeries)) {
|
||||
} else if (tvWidget.series.renkoSeries && param.seriesPrices.get(tvWidget.series.renkoSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.renkoSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
} else if (tvWidget.series.heikinSeries && param.seriesPrices.get(tvWidget.series.heikinSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.heikinSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
} else if (tvWidget.series.barSeries && param.seriesPrices.get(tvWidget.series.barSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.barSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
} else if (tvWidget.series.lineSeries && param.seriesPrices.get(tvWidget.series.lineSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.lineSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
} else if (tvWidget.series.areaSeries && param.seriesPrices.get(tvWidget.series.areaSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.areaSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
} else if (tvWidget.series.baselineSeries && param.seriesPrices.get(tvWidget.series.baselineSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.baselineSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
}
|
||||
// 如果没有蜡烛图系列价格,尝试从区域图系列获取
|
||||
else if (tvWidget.series.areaSeries && param.seriesPrices.get(tvWidget.series.areaSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.areaSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
}
|
||||
// 如果没有蜡烛图系列价格,尝试从基线图系列获取
|
||||
else if (tvWidget.series.baselineSeries && param.seriesPrices.get(tvWidget.series.baselineSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.baselineSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5342,13 +5404,18 @@
|
||||
if (combinedMarkers.length > 0) {
|
||||
console.log('合并设置', combinedMarkers.length, '个标记(主周期分型:', (window.mainFxMarkers || []).length, '个,小周期分型:', allElementFxMarkers.length, '个,UnitTF:', (window.unittfMarkers || []).length, '个)');
|
||||
|
||||
// 尝试在K线系列上设置合并后的标记
|
||||
if (showOriginalKline && tvWidget.series.candleSeries) {
|
||||
tvWidget.series.candleSeries.setMarkers(combinedMarkers);
|
||||
console.log('合并标记已设置到蜡烛图系列');
|
||||
} else if (!showOriginalKline && tvWidget.series.lineSeries) {
|
||||
tvWidget.series.lineSeries.setMarkers(combinedMarkers);
|
||||
console.log('合并标记已设置到线图系列');
|
||||
// 根据当前主系列类型设置标记
|
||||
const klineType = ($('#klineType').val() || (showOriginalKline ? 'candlestick' : 'line'));
|
||||
let targetSeries = null;
|
||||
if (klineType === 'candlestick') targetSeries = tvWidget.series.candleSeries;
|
||||
else if (klineType === 'renko') targetSeries = tvWidget.series.renkoSeries;
|
||||
else if (klineType === 'heikin') targetSeries = tvWidget.series.heikinSeries;
|
||||
else if (klineType === 'bar') targetSeries = tvWidget.series.barSeries;
|
||||
else if (klineType === 'line') targetSeries = tvWidget.series.lineSeries;
|
||||
else if (klineType === 'area') targetSeries = tvWidget.series.areaSeries;
|
||||
else if (klineType === 'baseline') targetSeries = tvWidget.series.baselineSeries;
|
||||
if (targetSeries) {
|
||||
targetSeries.setMarkers(combinedMarkers);
|
||||
} else {
|
||||
console.log('未找到主数据系列,无法设置标记');
|
||||
}
|
||||
@@ -5431,23 +5498,35 @@
|
||||
if (onlyMainAndU.length > 0) {
|
||||
console.log('仅设置', onlyMainAndU.length, '个主周期/UnitTF标记(主周期分型:', (window.mainFxMarkers || []).length, ',UnitTF:', (window.unittfMarkers || []).length, ')');
|
||||
|
||||
// 尝试在K线系列上设置标记
|
||||
if (showOriginalKline && tvWidget.series.candleSeries) {
|
||||
tvWidget.series.candleSeries.setMarkers(onlyMainAndU);
|
||||
console.log('主周期标记已设置到蜡烛图系列');
|
||||
} else if (!showOriginalKline && tvWidget.series.lineSeries) {
|
||||
tvWidget.series.lineSeries.setMarkers(onlyMainAndU);
|
||||
console.log('主周期标记已设置到线图系列');
|
||||
// 根据当前主系列类型设置标记
|
||||
const klineType2 = ($('#klineType').val() || (showOriginalKline ? 'candlestick' : 'line'));
|
||||
let targetSeries2 = null;
|
||||
if (klineType2 === 'candlestick') targetSeries2 = tvWidget.series.candleSeries;
|
||||
else if (klineType2 === 'renko') targetSeries2 = tvWidget.series.renkoSeries;
|
||||
else if (klineType2 === 'heikin') targetSeries2 = tvWidget.series.heikinSeries;
|
||||
else if (klineType2 === 'bar') targetSeries2 = tvWidget.series.barSeries;
|
||||
else if (klineType2 === 'line') targetSeries2 = tvWidget.series.lineSeries;
|
||||
else if (klineType2 === 'area') targetSeries2 = tvWidget.series.areaSeries;
|
||||
else if (klineType2 === 'baseline') targetSeries2 = tvWidget.series.baselineSeries;
|
||||
if (targetSeries2) {
|
||||
targetSeries2.setMarkers(onlyMainAndU);
|
||||
} else {
|
||||
console.log('未找到主数据系列,无法设置标记');
|
||||
}
|
||||
} else {
|
||||
console.log('没有分型标记需要显示,清空图表标记');
|
||||
// 清空图表上的所有标记
|
||||
if (showOriginalKline && tvWidget.series.candleSeries) {
|
||||
tvWidget.series.candleSeries.setMarkers([]);
|
||||
} else if (!showOriginalKline && tvWidget.series.lineSeries) {
|
||||
tvWidget.series.lineSeries.setMarkers([]);
|
||||
// 清空图表上的所有主系列标记
|
||||
const klineType3 = ($('#klineType').val() || (showOriginalKline ? 'candlestick' : 'line'));
|
||||
let targetSeries3 = null;
|
||||
if (klineType3 === 'candlestick') targetSeries3 = tvWidget.series.candleSeries;
|
||||
else if (klineType3 === 'renko') targetSeries3 = tvWidget.series.renkoSeries;
|
||||
else if (klineType3 === 'heikin') targetSeries3 = tvWidget.series.heikinSeries;
|
||||
else if (klineType3 === 'bar') targetSeries3 = tvWidget.series.barSeries;
|
||||
else if (klineType3 === 'line') targetSeries3 = tvWidget.series.lineSeries;
|
||||
else if (klineType3 === 'area') targetSeries3 = tvWidget.series.areaSeries;
|
||||
else if (klineType3 === 'baseline') targetSeries3 = tvWidget.series.baselineSeries;
|
||||
if (targetSeries3) {
|
||||
targetSeries3.setMarkers([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5590,7 +5669,6 @@
|
||||
console.log('🔧 最终时间轴对齐完成');
|
||||
}
|
||||
}, 150);
|
||||
|
||||
// 只有在时间输入框都为空时才设置图表默认时间范围
|
||||
if (!$('#start_time').val() && !$('#end_time').val()) {
|
||||
setDefaultTimeRange();
|
||||
@@ -5673,17 +5751,27 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 更新K线数据
|
||||
if (showOriginalKline && tvWidget.series.candleSeries) {
|
||||
// 更新主系列数据(根据klineType)
|
||||
const klineType = ($('#klineType').val() || (showOriginalKline ? 'candlestick' : 'line'));
|
||||
if (klineType === 'candlestick' && tvWidget.series.candleSeries) {
|
||||
tvWidget.series.candleSeries.setData(candles);
|
||||
} else if (!showOriginalKline && tvWidget.series.lineSeries) {
|
||||
// 提取收盘价数据
|
||||
const lineData = candles.map(candle => ({
|
||||
time: candle.time,
|
||||
value: candle.close
|
||||
}));
|
||||
|
||||
} else if (klineType === 'renko' && tvWidget.series.renkoSeries) {
|
||||
const bricks = buildRenkoFromCandles(candles);
|
||||
tvWidget.series.renkoSeries.setData(bricks);
|
||||
} else if (klineType === 'heikin' && tvWidget.series.heikinSeries) {
|
||||
const hk = buildHeikinFromCandles(candles);
|
||||
tvWidget.series.heikinSeries.setData(hk);
|
||||
} else if (klineType === 'bar' && tvWidget.series.barSeries) {
|
||||
tvWidget.series.barSeries.setData(candles);
|
||||
} else if (klineType === 'line' && tvWidget.series.lineSeries) {
|
||||
const lineData = candles.map(c => ({ time: c.time, value: c.close }));
|
||||
tvWidget.series.lineSeries.setData(lineData);
|
||||
} else if (klineType === 'area' && tvWidget.series.areaSeries) {
|
||||
const areaData = candles.map(c => ({ time: c.time, value: c.close }));
|
||||
tvWidget.series.areaSeries.setData(areaData);
|
||||
} else if (klineType === 'baseline' && tvWidget.series.baselineSeries) {
|
||||
const baseData = candles.map(c => ({ time: c.time, value: c.close }));
|
||||
tvWidget.series.baselineSeries.setData(baseData);
|
||||
}
|
||||
|
||||
// 更新均线数据
|
||||
@@ -6310,15 +6398,38 @@
|
||||
// 获取当前价格 - 通过param.seriesPrices获取
|
||||
let priceInfo = '';
|
||||
if (param.seriesPrices && param.seriesPrices.size > 0) {
|
||||
// 尝试从蜡烛图系列获取价格
|
||||
// 依次从当前可能的主系列中获取价格
|
||||
if (tvWidget.series.candleSeries && param.seriesPrices.get(tvWidget.series.candleSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.candleSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
}
|
||||
// 如果没有蜡烛图系列价格,尝试从线图系列获取
|
||||
else if (tvWidget.series.lineSeries && param.seriesPrices.get(tvWidget.series.lineSeries)) {
|
||||
} else if (tvWidget.series.renkoSeries && param.seriesPrices.get(tvWidget.series.renkoSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.renkoSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
} else if (tvWidget.series.heikinSeries && param.seriesPrices.get(tvWidget.series.heikinSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.heikinSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
} else if (tvWidget.series.barSeries && param.seriesPrices.get(tvWidget.series.barSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.barSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
} else if (tvWidget.series.lineSeries && param.seriesPrices.get(tvWidget.series.lineSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.lineSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
} else if (tvWidget.series.areaSeries && param.seriesPrices.get(tvWidget.series.areaSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.areaSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
} else if (tvWidget.series.baselineSeries && param.seriesPrices.get(tvWidget.series.baselineSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.baselineSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
}
|
||||
// 如果没有蜡烛图系列价格,尝试从区域图系列获取
|
||||
else if (tvWidget.series.areaSeries && param.seriesPrices.get(tvWidget.series.areaSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.areaSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
}
|
||||
// 如果没有蜡烛图系列价格,尝试从基线图系列获取
|
||||
else if (tvWidget.series.baselineSeries && param.seriesPrices.get(tvWidget.series.baselineSeries)) {
|
||||
const price = param.seriesPrices.get(tvWidget.series.baselineSeries);
|
||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7068,6 +7179,10 @@
|
||||
tvWidget.series = {
|
||||
candleSeries: null,
|
||||
lineSeries: null,
|
||||
barSeries: null,
|
||||
areaSeries: null,
|
||||
baselineSeries: null,
|
||||
renkoSeries: null,
|
||||
volumeSeries: null,
|
||||
atrLineSeries: null,
|
||||
macdLineSeries: null,
|
||||
@@ -7155,7 +7270,6 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 开始自动刷新
|
||||
function startAutoRefresh() {
|
||||
// 停止已有的刷新定时器
|
||||
@@ -7954,7 +8068,6 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 获取随机颜色
|
||||
function getRandomColor() {
|
||||
const colors = ['#2962FF', '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4',
|
||||
@@ -8476,6 +8589,93 @@
|
||||
return candles;
|
||||
}
|
||||
|
||||
// 从蜡烛数据生成 Heikin-Ashi(平均K)
|
||||
function buildHeikinFromCandles(candles) {
|
||||
if (!Array.isArray(candles) || candles.length === 0) return [];
|
||||
const result = [];
|
||||
let prevHaClose = (candles[0].open + candles[0].high + candles[0].low + candles[0].close) / 4;
|
||||
let prevHaOpen = (candles[0].open + candles[0].close) / 2;
|
||||
const firstHaHigh = Math.max(candles[0].high, prevHaOpen, prevHaClose);
|
||||
const firstHaLow = Math.min(candles[0].low, prevHaOpen, prevHaClose);
|
||||
result.push({ time: candles[0].time, open: prevHaOpen, high: firstHaHigh, low: firstHaLow, close: prevHaClose });
|
||||
for (let i = 1; i < candles.length; i++) {
|
||||
const c = candles[i];
|
||||
const haClose = (c.open + c.high + c.low + c.close) / 4;
|
||||
const haOpen = (prevHaOpen + prevHaClose) / 2;
|
||||
const haHigh = Math.max(c.high, haOpen, haClose);
|
||||
const haLow = Math.min(c.low, haOpen, haClose);
|
||||
result.push({ time: c.time, open: haOpen, high: haHigh, low: haLow, close: haClose });
|
||||
prevHaOpen = haOpen;
|
||||
prevHaClose = haClose;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 计算默认砖大小(优先使用ATR的最新非零值,否则按收盘价的0.5%)
|
||||
function computeDefaultBrickSize(candles) {
|
||||
try {
|
||||
const useElementPeriod = $('#elementPeriodKline').is(':checked') && currentData.element_kline_data && Array.isArray(currentData.element_kline_data);
|
||||
const atrArr = useElementPeriod ? (currentData.element_atr || currentData.atr) : currentData.atr;
|
||||
if (Array.isArray(atrArr) && atrArr.length > 0) {
|
||||
for (let i = atrArr.length - 1; i >= 0; i--) {
|
||||
const v = parseFloat(atrArr[i]);
|
||||
if (!isNaN(v) && v > 0) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
const last = candles && candles.length ? candles[candles.length - 1].close : 0;
|
||||
const size = last * 0.005; // 默认0.5%
|
||||
return size > 0 ? size : 1;
|
||||
}
|
||||
|
||||
// 从蜡烛数据生成Renko砖(返回伪K线数据用于CandlestickSeries显示)
|
||||
function buildRenkoFromCandles(candles) {
|
||||
if (!Array.isArray(candles) || candles.length === 0) return [];
|
||||
|
||||
const tryBuild = (size) => {
|
||||
const bricks = [];
|
||||
let lastClose = candles[0].close;
|
||||
for (let i = 1; i < candles.length; i++) {
|
||||
// 确保同一根K内多砖的时间唯一且递增
|
||||
let nextTime = candles[i].time;
|
||||
const price = candles[i].close;
|
||||
let diff = price - lastClose;
|
||||
// 向上生成砖
|
||||
while (diff >= size) {
|
||||
const open = lastClose;
|
||||
const close = lastClose + size;
|
||||
bricks.push({ time: nextTime, open: open, high: close, low: open, close: close });
|
||||
lastClose = close;
|
||||
diff = price - lastClose;
|
||||
nextTime += 1; // 递增1秒,保证唯一
|
||||
}
|
||||
// 向下生成砖
|
||||
while (diff <= -size) {
|
||||
const open = lastClose;
|
||||
const close = lastClose - size;
|
||||
bricks.push({ time: nextTime, open: open, high: open, low: close, close: close });
|
||||
lastClose = close;
|
||||
diff = price - lastClose;
|
||||
nextTime += 1; // 递增1秒,保证唯一
|
||||
}
|
||||
}
|
||||
return bricks;
|
||||
};
|
||||
|
||||
// 计算初始砖大小,若无砖则逐步缩小后重试,避免不显示
|
||||
let brickSize = computeDefaultBrickSize(candles);
|
||||
let result = tryBuild(brickSize);
|
||||
let attempts = 0;
|
||||
while (result.length === 0 && attempts < 4) {
|
||||
brickSize *= 0.5; // 缩小一半后重试
|
||||
result = tryBuild(brickSize);
|
||||
attempts += 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 指标统一刷新(若图表未就绪则自动重试)
|
||||
function refreshIndicatorsNowOrLater(retries) {
|
||||
try {
|
||||
@@ -9062,7 +9262,7 @@
|
||||
});
|
||||
}
|
||||
});
|
||||
// 识别“U 结束与新 U 开始同一根K”的边界,并显示合成标记(即使前一个U是 invalid 也显示边界)
|
||||
// 识别"U 结束与新 U 开始同一根K"的边界,并显示合成标记(即使前一个U是 invalid 也显示边界)
|
||||
for (let i = 0; i + 1 < unittfList.length; i++) {
|
||||
const cur = unittfList[i];
|
||||
const nxt = unittfList[i + 1];
|
||||
|
||||
Reference in New Issue
Block a user