主站重建前完整 dispose、去掉重复 sync 监听,自动刷新默认增量更新;顺带消除首屏重复 analyze、复用 ChanMACD,以及全版 TV 指标/未完成中枢/布局本地缓存。 Co-authored-by: Cursor <cursoragent@cursor.com>
548 lines
19 KiB
JavaScript
548 lines
19 KiB
JavaScript
/**
|
|
* 缠论自定义指标 — TradingView Advanced Chart
|
|
*
|
|
* 从 chanIndicator.ts 转换为 vanilla JS。
|
|
* 在 K 线上叠加:笔/段(实线+虚线)、中枢(填色区域)、买卖点(文字标签)。
|
|
*
|
|
* 依赖:
|
|
* window.chanLookupHolder — 当前 Chan 结构数据
|
|
* window.commitChanLookup — 累积合并新数据
|
|
* window.makeChanIndicator — 创建 TV study 定义
|
|
*/
|
|
|
|
(function () {
|
|
'use strict'
|
|
|
|
// ---- BSP 子类型枚举 ----
|
|
var BSP_SUBTYPES = ['T1', 'T1P', 'T2', 'T2S', 'T3A', 'T3B']
|
|
|
|
// ---- 全局状态: chanLookupHolder ----
|
|
window.chanLookupHolder = {
|
|
current: null,
|
|
key: null,
|
|
}
|
|
|
|
/**
|
|
* 累积/替换 chanLookup
|
|
* 同 key 累积合并(历史区间的 BSP 标签持续保留)
|
|
* 不同 key 整个替换
|
|
*/
|
|
window.commitChanLookup = function (fresh, key) {
|
|
var holder = window.chanLookupHolder
|
|
if (holder.key !== key || !holder.current) {
|
|
holder.current = fresh
|
|
holder.key = key
|
|
return
|
|
}
|
|
// 同 key 合并
|
|
var target = holder.current.byTimeMs
|
|
fresh.byTimeMs.forEach(function (e, t) {
|
|
var existed = target.get(t)
|
|
if (existed) {
|
|
Object.assign(existed, e)
|
|
} else {
|
|
target.set(t, e)
|
|
}
|
|
})
|
|
}
|
|
|
|
// ---- 工具函数 ----
|
|
|
|
function lowerBound(arr, v) {
|
|
var lo = 0, hi = arr.length
|
|
while (lo < hi) {
|
|
var mid = (lo + hi) >> 1
|
|
if (arr[mid] < v) lo = mid + 1
|
|
else hi = mid
|
|
}
|
|
return lo
|
|
}
|
|
|
|
function upperBound(arr, v) {
|
|
var lo = 0, hi = arr.length
|
|
while (lo < hi) {
|
|
var mid = (lo + hi) >> 1
|
|
if (arr[mid] <= v) lo = mid + 1
|
|
else hi = mid
|
|
}
|
|
return lo
|
|
}
|
|
|
|
/**
|
|
* 构建 ChanLookup:将 Chan 结构数据映射到每个 bar 的指标值
|
|
*
|
|
* @param {Object} slice - ChanSlice {bis, segs, zs, segzs, bsps, seg_bsps}
|
|
* @param {Array} bars - OHLCV bars [{t: ms, h, l}, ...]
|
|
* @returns {Object} {byTimeMs: Map<ms, BarEntry>}
|
|
*/
|
|
window.buildChanLookup = function (slice, bars) {
|
|
var byTimeMs = new Map()
|
|
|
|
function ensure(tsMs) {
|
|
// tsMs 已是毫秒(来自 data_provider 的 timestamp),无需再转换
|
|
var key = tsMs
|
|
var e = byTimeMs.get(key)
|
|
if (!e) {
|
|
e = {}
|
|
byTimeMs.set(key, e)
|
|
}
|
|
return e
|
|
}
|
|
|
|
var sortedBarTimes = bars.map(function (b) { return b.t }).sort(function (a, b) { return a - b })
|
|
|
|
// 线性插值填充笔/段到每个 bar
|
|
function fillLine(t0, t1, p0, p1, field) {
|
|
var lo = lowerBound(sortedBarTimes, t0)
|
|
var hi = upperBound(sortedBarTimes, t1)
|
|
var span = hi - 1 - lo
|
|
if (span <= 0) {
|
|
if (lo < sortedBarTimes.length) ensure(sortedBarTimes[lo])[field] = p0
|
|
return
|
|
}
|
|
var step = (p1 - p0) / span
|
|
for (var i = lo; i < hi; i++) {
|
|
ensure(sortedBarTimes[i])[field] = p0 + step * (i - lo)
|
|
}
|
|
}
|
|
|
|
// 笔
|
|
if (slice.bis) {
|
|
slice.bis.forEach(function (b) {
|
|
fillLine(b.t0, b.t1, b.p0, b.p1, b.sure ? 'bi' : 'bi_pending')
|
|
})
|
|
}
|
|
|
|
// 段
|
|
if (slice.segs) {
|
|
slice.segs.forEach(function (s) {
|
|
fillLine(s.t0, s.t1, s.p0, s.p1, s.sure ? 'seg' : 'seg_pending')
|
|
})
|
|
}
|
|
|
|
// 中枢填充:区间内每根 bar 写入 top/bottom
|
|
// 未完成中枢:右边界拉到最新 K(与主站 uncompleted_zs 一致)
|
|
function fillZs(t0, t1, high, low, topField, botField) {
|
|
var lo = lowerBound(sortedBarTimes, t0)
|
|
var hi = upperBound(sortedBarTimes, t1)
|
|
for (var i = lo; i < hi; i++) {
|
|
var e = ensure(sortedBarTimes[i])
|
|
e[topField] = high
|
|
e[botField] = low
|
|
}
|
|
}
|
|
|
|
var lastBarT = sortedBarTimes.length ? sortedBarTimes[sortedBarTimes.length - 1] : null
|
|
|
|
if (slice.zs) {
|
|
slice.zs.forEach(function (z) {
|
|
var t1 = z.t1
|
|
var sure = z.is_sure !== false && z.is_sure !== 0
|
|
if (!sure && lastBarT != null) t1 = Math.max(t1 || 0, lastBarT)
|
|
if (sure) {
|
|
fillZs(z.t0, t1, z.high || z.zg, z.low || z.zd, 'zs_top', 'zs_bottom')
|
|
} else {
|
|
fillZs(z.t0, t1, z.high || z.zg, z.low || z.zd, 'zs_pending_top', 'zs_pending_bottom')
|
|
}
|
|
})
|
|
}
|
|
if (slice.segzs) {
|
|
slice.segzs.forEach(function (z) {
|
|
var t1 = z.t1
|
|
var sure = z.is_sure !== false && z.is_sure !== 0
|
|
if (!sure && lastBarT != null) t1 = Math.max(t1 || 0, lastBarT)
|
|
if (sure) {
|
|
fillZs(z.t0, t1, z.high || z.zg, z.low || z.zd, 'segzs_top', 'segzs_bottom')
|
|
} else {
|
|
fillZs(z.t0, t1, z.high || z.zg, z.low || z.zd, 'segzs_pending_top', 'segzs_pending_bottom')
|
|
}
|
|
})
|
|
}
|
|
|
|
// BSP 买卖点标记
|
|
function placeBsps(list, prefix) {
|
|
if (!list) return
|
|
list.forEach(function (bsp) {
|
|
var dir = bsp.is_buy ? 'buy' : 'sell'
|
|
var e = ensure(bsp.t)
|
|
var types = bsp.types || []
|
|
types.forEach(function (raw) {
|
|
var t = String(raw).toUpperCase()
|
|
if (BSP_SUBTYPES.indexOf(t) === -1) return
|
|
var key = prefix + '_' + dir + '_' + t
|
|
e[key] = 1
|
|
})
|
|
})
|
|
}
|
|
|
|
placeBsps(slice.bsps, 'bi_bsp')
|
|
placeBsps(slice.seg_bsps, 'seg_bsp')
|
|
|
|
return { byTimeMs: byTimeMs }
|
|
}
|
|
|
|
// ---- 样式持久化 ----
|
|
|
|
function currentTheme() {
|
|
try { return localStorage.getItem('chart-theme') || 'light' }
|
|
catch (e) { return 'light' }
|
|
}
|
|
|
|
function chanStyleKey() {
|
|
return 'chan-indicator-styles-v7-' + currentTheme()
|
|
}
|
|
|
|
function loadSavedChanStyles() {
|
|
try {
|
|
var raw = localStorage.getItem(chanStyleKey())
|
|
return raw ? JSON.parse(raw) : null
|
|
} catch (e) {
|
|
return null
|
|
}
|
|
}
|
|
|
|
window.saveChanStyles = function (sv) {
|
|
try {
|
|
localStorage.setItem(chanStyleKey(), JSON.stringify({
|
|
styles: sv && sv.styles ? sv.styles : {},
|
|
filledAreasStyle: sv && sv.filledAreasStyle ? sv.filledAreasStyle : {},
|
|
}))
|
|
} catch (e) { /* ignore */ }
|
|
}
|
|
|
|
// ---- 主体:创建 TV 自定义指标定义 ----
|
|
|
|
window.makeChanIndicator = function () {
|
|
var saved = loadSavedChanStyles()
|
|
var isDark = currentTheme() === 'dark'
|
|
var biColor = isDark ? '#ffffff' : '#000000'
|
|
var segColor = isDark ? '#42a5f5' : '#1565c0'
|
|
|
|
function mergeStyle(id, base) {
|
|
var savedStyle = (saved && saved.styles && saved.styles[id]) || {}
|
|
var merged = {}
|
|
var keys = Object.keys(base).concat(Object.keys(savedStyle))
|
|
keys.forEach(function (k) {
|
|
if (k in savedStyle) merged[k] = savedStyle[k]
|
|
else merged[k] = base[k]
|
|
})
|
|
return merged
|
|
}
|
|
|
|
function mergeFill(id, base) {
|
|
var savedFill = (saved && saved.filledAreasStyle && saved.filledAreasStyle[id]) || {}
|
|
var merged = {}
|
|
var keys = Object.keys(base).concat(Object.keys(savedFill))
|
|
keys.forEach(function (k) {
|
|
if (k in savedFill) merged[k] = savedFill[k]
|
|
else merged[k] = base[k]
|
|
})
|
|
return merged
|
|
}
|
|
|
|
// 构建 plots 数组
|
|
var plots = [
|
|
{ id: 'bi', type: 'line' },
|
|
{ id: 'bi_pending', type: 'line' },
|
|
{ id: 'seg', type: 'line' },
|
|
{ id: 'seg_pending', type: 'line' },
|
|
{ id: 'zs_top', type: 'line' },
|
|
{ id: 'zs_bottom', type: 'line' },
|
|
{ id: 'segzs_top', type: 'line' },
|
|
{ id: 'segzs_bottom', type: 'line' },
|
|
{ id: 'zs_pending_top', type: 'line' },
|
|
{ id: 'zs_pending_bottom', type: 'line' },
|
|
{ id: 'segzs_pending_top', type: 'line' },
|
|
{ id: 'segzs_pending_bottom', type: 'line' },
|
|
]
|
|
|
|
BSP_SUBTYPES.forEach(function (t) {
|
|
plots.push({ id: 'bi_bsp_buy_' + t, type: 'chars' })
|
|
plots.push({ id: 'bi_bsp_sell_' + t, type: 'chars' })
|
|
plots.push({ id: 'seg_bsp_buy_' + t, type: 'chars' })
|
|
plots.push({ id: 'seg_bsp_sell_' + t, type: 'chars' })
|
|
})
|
|
|
|
// 构建 styles 对象
|
|
// bi_pending/seg_pending: 虚线(linestyle:2),加粗 + 高亮色,确保末完成笔/段清晰可见
|
|
var pendingBiColor = isDark ? '#ff9800' : '#e65100' // orange
|
|
var pendingSegColor = isDark ? '#e040fb' : '#aa00ff' // purple
|
|
var styles = {
|
|
bi: mergeStyle('bi', {
|
|
linestyle: 0, linewidth: 1, plottype: 0, trackPrice: false,
|
|
transparency: 0, visible: true, color: biColor, display: 3,
|
|
}),
|
|
bi_pending: mergeStyle('bi_pending', {
|
|
linestyle: 2, linewidth: 2, plottype: 0, trackPrice: false,
|
|
transparency: 0, visible: true, color: pendingBiColor, display: 3,
|
|
}),
|
|
seg: mergeStyle('seg', {
|
|
linestyle: 0, linewidth: 3, plottype: 0, trackPrice: false,
|
|
transparency: 0, visible: true, color: segColor, display: 3,
|
|
}),
|
|
seg_pending: mergeStyle('seg_pending', {
|
|
linestyle: 2, linewidth: 4, plottype: 0, trackPrice: false,
|
|
transparency: 0, visible: true, color: pendingSegColor, display: 3,
|
|
}),
|
|
zs_top: mergeStyle('zs_top', {
|
|
linestyle: 0, linewidth: 0, plottype: 0, trackPrice: false,
|
|
transparency: 100, visible: false, color: '#e4eaf1', display: 0,
|
|
}),
|
|
zs_bottom: mergeStyle('zs_bottom', {
|
|
linestyle: 0, linewidth: 0, plottype: 0, trackPrice: false,
|
|
transparency: 100, visible: false, color: '#1565c0', display: 0,
|
|
}),
|
|
segzs_top: mergeStyle('segzs_top', {
|
|
linestyle: 0, linewidth: 0, plottype: 0, trackPrice: false,
|
|
transparency: 100, visible: false, color: '#ef6c00', display: 0,
|
|
}),
|
|
segzs_bottom: mergeStyle('segzs_bottom', {
|
|
linestyle: 0, linewidth: 0, plottype: 0, trackPrice: false,
|
|
transparency: 100, visible: false, color: '#ef6c00', display: 0,
|
|
}),
|
|
zs_pending_top: mergeStyle('zs_pending_top', {
|
|
linestyle: 0, linewidth: 0, plottype: 0, trackPrice: false,
|
|
transparency: 100, visible: false, color: '#f1c40f', display: 0,
|
|
}),
|
|
zs_pending_bottom: mergeStyle('zs_pending_bottom', {
|
|
linestyle: 0, linewidth: 0, plottype: 0, trackPrice: false,
|
|
transparency: 100, visible: false, color: '#f1c40f', display: 0,
|
|
}),
|
|
segzs_pending_top: mergeStyle('segzs_pending_top', {
|
|
linestyle: 0, linewidth: 0, plottype: 0, trackPrice: false,
|
|
transparency: 100, visible: false, color: '#9b59b6', display: 0,
|
|
}),
|
|
segzs_pending_bottom: mergeStyle('segzs_pending_bottom', {
|
|
linestyle: 0, linewidth: 0, plottype: 0, trackPrice: false,
|
|
transparency: 100, visible: false, color: '#9b59b6', display: 0,
|
|
}),
|
|
}
|
|
|
|
// BSP 样式
|
|
BSP_SUBTYPES.forEach(function (t) {
|
|
styles['bi_bsp_buy_' + t] = mergeStyle('bi_bsp_buy_' + t, {
|
|
char: '●', location: 'BelowBar', visible: true, size: 'large',
|
|
color: '#d32f2f', display: 3,
|
|
})
|
|
styles['bi_bsp_sell_' + t] = mergeStyle('bi_bsp_sell_' + t, {
|
|
char: '●', location: 'AboveBar', visible: true, size: 'large',
|
|
color: '#2e7d32', display: 3,
|
|
})
|
|
styles['seg_bsp_buy_' + t] = mergeStyle('seg_bsp_buy_' + t, {
|
|
char: '●', location: 'BelowBar', visible: true, size: 'large',
|
|
color: '#d32f2f', display: 3,
|
|
})
|
|
styles['seg_bsp_sell_' + t] = mergeStyle('seg_bsp_sell_' + t, {
|
|
char: '●', location: 'AboveBar', visible: true, size: 'large',
|
|
color: '#2e7d32', display: 3,
|
|
})
|
|
})
|
|
|
|
// 构建 style titles
|
|
var styleTitles = {
|
|
bi: { title: '笔', histogramBase: 0 },
|
|
bi_pending: { title: '笔(虚)', histogramBase: 0 },
|
|
seg: { title: '段', histogramBase: 0 },
|
|
seg_pending: { title: '段(虚)', histogramBase: 0 },
|
|
zs_top: { title: '中枢上沿', histogramBase: 0, isHidden: true },
|
|
zs_bottom: { title: '中枢下沿', histogramBase: 0, isHidden: true },
|
|
segzs_top: { title: '段中枢上沿', histogramBase: 0, isHidden: true },
|
|
segzs_bottom: { title: '段中枢下沿', histogramBase: 0, isHidden: true },
|
|
zs_pending_top: { title: '未完成中枢上沿', histogramBase: 0, isHidden: true },
|
|
zs_pending_bottom: { title: '未完成中枢下沿', histogramBase: 0, isHidden: true },
|
|
segzs_pending_top: { title: '未完成段中枢上沿', histogramBase: 0, isHidden: true },
|
|
segzs_pending_bottom: { title: '未完成段中枢下沿', histogramBase: 0, isHidden: true },
|
|
}
|
|
|
|
BSP_SUBTYPES.forEach(function (t) {
|
|
// 类型名映射:T1/T2/T3A 是买点, T1P/T2S/T3B 是卖点
|
|
var typeInfo = {
|
|
T1: { cls: '一', side: 'buy', num: '1' },
|
|
T1P: { cls: '一', side: 'sell', num: '1' },
|
|
T2: { cls: '二', side: 'buy', num: '2' },
|
|
T2S: { cls: '二', side: 'sell', num: '2' },
|
|
T3A: { cls: '三', side: 'buy', num: '3' },
|
|
T3B: { cls: '三', side: 'sell', num: '3' },
|
|
}[t] || { cls: '', side: '', num: '' }
|
|
var buyText = 'B' + typeInfo.num
|
|
var sellText = 'S' + typeInfo.num
|
|
var isBuyType = typeInfo.side === 'buy'
|
|
var isSellType = typeInfo.side === 'sell'
|
|
|
|
// 笔中枢 BSP:全部可见
|
|
styleTitles['bi_bsp_buy_' + t] = {
|
|
title: '笔·' + typeInfo.cls + '类买点',
|
|
isHidden: !isBuyType,
|
|
text: buyText,
|
|
}
|
|
styleTitles['bi_bsp_sell_' + t] = {
|
|
title: '笔·' + typeInfo.cls + '类卖点',
|
|
isHidden: !isSellType,
|
|
text: sellText,
|
|
}
|
|
// 段中枢 BSP:只有一类买卖点有实际数据
|
|
var segBuyVisible = t === 'T1'
|
|
var segSellVisible = t === 'T1P'
|
|
styleTitles['seg_bsp_buy_' + t] = {
|
|
title: '段·一类买点',
|
|
isHidden: !segBuyVisible,
|
|
text: '段B1',
|
|
}
|
|
styleTitles['seg_bsp_sell_' + t] = {
|
|
title: '段·一类卖点',
|
|
isHidden: !segSellVisible,
|
|
text: '段S1',
|
|
}
|
|
})
|
|
|
|
return {
|
|
name: '缠论',
|
|
metainfo: {
|
|
_metainfoVersion: 53,
|
|
id: 'Chan@tv-basicstudies-6',
|
|
scriptIdPart: '',
|
|
description: 'Chan 缠论',
|
|
shortDescription: '缠论',
|
|
is_hidden_study: false,
|
|
isCustomIndicator: true,
|
|
is_price_study: true,
|
|
linkedToSeries: true,
|
|
format: { type: 'inherit' },
|
|
plots: plots,
|
|
filledAreas: [
|
|
{ id: 'zs_fill', objAId: 'zs_top', objBId: 'zs_bottom', type: 'plot_plot',
|
|
title: '中枢', isHidden: false },
|
|
{ id: 'segzs_fill', objAId: 'segzs_top', objBId: 'segzs_bottom', type: 'plot_plot',
|
|
title: '段中枢', isHidden: false },
|
|
{ id: 'zs_pending_fill', objAId: 'zs_pending_top', objBId: 'zs_pending_bottom', type: 'plot_plot',
|
|
title: '未完成中枢', isHidden: false },
|
|
{ id: 'segzs_pending_fill', objAId: 'segzs_pending_top', objBId: 'segzs_pending_bottom', type: 'plot_plot',
|
|
title: '未完成段中枢', isHidden: false },
|
|
],
|
|
defaults: {
|
|
styles: styles,
|
|
filledAreasStyle: {
|
|
zs_fill: mergeFill('zs_fill', { color: '#f1d96a', visible: true, transparency: 75 }),
|
|
segzs_fill: mergeFill('segzs_fill', { color: '#6361f7', visible: true, transparency: 75 }),
|
|
zs_pending_fill: mergeFill('zs_pending_fill', { color: '#f1c40f', visible: true, transparency: 55 }),
|
|
segzs_pending_fill: mergeFill('segzs_pending_fill', { color: '#9b59b6', visible: true, transparency: 55 }),
|
|
},
|
|
precision: 2,
|
|
inputs: { epoch: 0 },
|
|
},
|
|
styles: styleTitles,
|
|
inputs: [
|
|
{ id: 'epoch', name: 'epoch', type: 'integer', defval: 0, isHidden: true },
|
|
],
|
|
},
|
|
constructor: function () {
|
|
var self = this
|
|
this.init = function (ctx) {
|
|
self._context = ctx
|
|
}
|
|
this.main = function (context) {
|
|
// 36 个 plot: 12 结构 + 24 BSP
|
|
var NANS = new Array(36).fill(NaN)
|
|
// v31: sniffing pass 时 context.symbol.time 为 NaN
|
|
var t = context.symbol.time
|
|
if (isNaN(t)) return NANS
|
|
|
|
var lookup = window.chanLookupHolder.current
|
|
if (!lookup) return NANS
|
|
|
|
var e = lookup.byTimeMs.get(t)
|
|
if (!e) return NANS
|
|
|
|
var out = [
|
|
e.bi != null ? e.bi : NaN,
|
|
e.bi_pending != null ? e.bi_pending : NaN,
|
|
e.seg != null ? e.seg : NaN,
|
|
e.seg_pending != null ? e.seg_pending : NaN,
|
|
e.zs_top != null ? e.zs_top : NaN,
|
|
e.zs_bottom != null ? e.zs_bottom : NaN,
|
|
e.segzs_top != null ? e.segzs_top : NaN,
|
|
e.segzs_bottom != null ? e.segzs_bottom : NaN,
|
|
e.zs_pending_top != null ? e.zs_pending_top : NaN,
|
|
e.zs_pending_bottom != null ? e.zs_pending_bottom : NaN,
|
|
e.segzs_pending_top != null ? e.segzs_pending_top : NaN,
|
|
e.segzs_pending_bottom != null ? e.segzs_pending_bottom : NaN,
|
|
]
|
|
|
|
BSP_SUBTYPES.forEach(function (sub) {
|
|
out.push(
|
|
e['bi_bsp_buy_' + sub] != null ? e['bi_bsp_buy_' + sub] : NaN,
|
|
e['bi_bsp_sell_' + sub] != null ? e['bi_bsp_sell_' + sub] : NaN,
|
|
e['seg_bsp_buy_' + sub] != null ? e['seg_bsp_buy_' + sub] : NaN,
|
|
e['seg_bsp_sell_' + sub] != null ? e['seg_bsp_sell_' + sub] : NaN
|
|
)
|
|
})
|
|
|
|
return out
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
// ---- Epoch bump 机制 ----
|
|
var chanEpoch = 0
|
|
var CHAN_STUDY_DESC = 'Chan 缠论'
|
|
|
|
/**
|
|
* 确保缠论 study 存在并通过 epoch bump 触发重绘。
|
|
* 与 TradingViewChart.tsx 中 ensureAndPokeChanStudy 逻辑一致。
|
|
*/
|
|
window.ensureAndPokeChanStudy = function (chart) {
|
|
try {
|
|
var studies = chart.getAllStudies ? chart.getAllStudies() : []
|
|
var existingId = null
|
|
for (var i = 0; i < studies.length; i++) {
|
|
if (studies[i].name === CHAN_STUDY_DESC) {
|
|
existingId = studies[i].id
|
|
break
|
|
}
|
|
}
|
|
|
|
chanEpoch += 1
|
|
|
|
if (existingId) {
|
|
try {
|
|
var api = chart.getStudyById(existingId)
|
|
if (api && api.setInputValues) {
|
|
api.setInputValues([{ id: 'epoch', value: chanEpoch }])
|
|
}
|
|
} catch (err) {
|
|
console.warn('setInputValues Chan failed', err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// 新建 study — 必须是 chart.createStudy(...) 保持 this 绑定!
|
|
if (!chart.createStudy) return
|
|
var result = chart.createStudy(CHAN_STUDY_DESC, false, false, { epoch: chanEpoch })
|
|
// createStudy 返回 Promise<string>
|
|
if (result && typeof result.then === 'function') {
|
|
result.then(function (id) {
|
|
if (!id) {
|
|
console.warn('[缠论] createStudy 返回空 id(指标未注册成功)')
|
|
return
|
|
}
|
|
console.log('[缠论] study 已创建', id)
|
|
try {
|
|
var studyApi = chart.getStudyById(id)
|
|
if (studyApi && studyApi.bringToFront) studyApi.bringToFront()
|
|
} catch (err) {
|
|
console.warn('bringToFront Chan failed', err)
|
|
}
|
|
}).catch(function (err) {
|
|
console.warn('createStudy Chan failed', err)
|
|
})
|
|
} else if (result) {
|
|
// 同步返回(兜底)
|
|
console.log('[缠论] study 已创建 (sync)', result)
|
|
}
|
|
} catch (e) {
|
|
console.error('ensureAndPokeChanStudy error', e)
|
|
}
|
|
}
|
|
})()
|