@continuumdao/continuum-node-sdk 1.3.36 → 1.3.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-host-catalog.json +9 -0
- package/dist/core/chart/analysis/analysis-agent-view.d.ts.map +1 -1
- package/dist/core/chart/analysis/analysis-agent-view.js +33 -4
- package/dist/core/chart/analysis/analysis-agent-view.js.map +1 -1
- package/dist/core/chart/analysis/analysis-catalog.d.ts.map +1 -1
- package/dist/core/chart/analysis/analysis-catalog.js +47 -1
- package/dist/core/chart/analysis/analysis-catalog.js.map +1 -1
- package/dist/core/chart/analysis/analyze-tools.d.ts +167 -11
- package/dist/core/chart/analysis/analyze-tools.d.ts.map +1 -1
- package/dist/core/chart/analysis/analyze-tools.js +47 -55
- package/dist/core/chart/analysis/analyze-tools.js.map +1 -1
- package/dist/core/chart/analysis/chart-patterns-tools.d.ts +4 -4
- package/dist/core/chart/analysis/divergence/detect.d.ts +21 -0
- package/dist/core/chart/analysis/divergence/detect.d.ts.map +1 -0
- package/dist/core/chart/analysis/divergence/detect.js +223 -0
- package/dist/core/chart/analysis/divergence/detect.js.map +1 -0
- package/dist/core/chart/analysis/divergence/index.d.ts +4 -0
- package/dist/core/chart/analysis/divergence/index.d.ts.map +1 -0
- package/dist/core/chart/analysis/divergence/index.js +4 -0
- package/dist/core/chart/analysis/divergence/index.js.map +1 -0
- package/dist/core/chart/analysis/divergence/peaks.d.ts +11 -0
- package/dist/core/chart/analysis/divergence/peaks.d.ts.map +1 -0
- package/dist/core/chart/analysis/divergence/peaks.js +108 -0
- package/dist/core/chart/analysis/divergence/peaks.js.map +1 -0
- package/dist/core/chart/analysis/divergence/types.d.ts +30 -0
- package/dist/core/chart/analysis/divergence/types.d.ts.map +1 -0
- package/dist/core/chart/analysis/divergence/types.js +2 -0
- package/dist/core/chart/analysis/divergence/types.js.map +1 -0
- package/dist/core/chart/analysis/divergence-analyze-tools.d.ts +239 -0
- package/dist/core/chart/analysis/divergence-analyze-tools.d.ts.map +1 -0
- package/dist/core/chart/analysis/divergence-analyze-tools.js +283 -0
- package/dist/core/chart/analysis/divergence-analyze-tools.js.map +1 -0
- package/dist/core/chart/analysis/divergence-drawings-tools.d.ts +457 -0
- package/dist/core/chart/analysis/divergence-drawings-tools.d.ts.map +1 -0
- package/dist/core/chart/analysis/divergence-drawings-tools.js +380 -0
- package/dist/core/chart/analysis/divergence-drawings-tools.js.map +1 -0
- package/dist/core/chart/analysis/divergence-highlight.d.ts +19 -0
- package/dist/core/chart/analysis/divergence-highlight.d.ts.map +1 -0
- package/dist/core/chart/analysis/divergence-highlight.js +39 -0
- package/dist/core/chart/analysis/divergence-highlight.js.map +1 -0
- package/dist/core/chart/analysis/donchian-analyze-tools.d.ts +113 -0
- package/dist/core/chart/analysis/donchian-analyze-tools.d.ts.map +1 -0
- package/dist/core/chart/analysis/donchian-analyze-tools.js +218 -0
- package/dist/core/chart/analysis/donchian-analyze-tools.js.map +1 -0
- package/dist/core/chart/analysis/donchian-highlight.d.ts +31 -0
- package/dist/core/chart/analysis/donchian-highlight.d.ts.map +1 -0
- package/dist/core/chart/analysis/donchian-highlight.js +41 -0
- package/dist/core/chart/analysis/donchian-highlight.js.map +1 -0
- package/dist/core/chart/analysis/elliott-waves-analyze-tools.d.ts +35 -2
- package/dist/core/chart/analysis/elliott-waves-analyze-tools.d.ts.map +1 -1
- package/dist/core/chart/analysis/ichimoku-analyze-tools.d.ts +124 -0
- package/dist/core/chart/analysis/ichimoku-analyze-tools.d.ts.map +1 -0
- package/dist/core/chart/analysis/ichimoku-analyze-tools.js +243 -0
- package/dist/core/chart/analysis/ichimoku-analyze-tools.js.map +1 -0
- package/dist/core/chart/analysis/ichimoku-highlight.d.ts +36 -0
- package/dist/core/chart/analysis/ichimoku-highlight.d.ts.map +1 -0
- package/dist/core/chart/analysis/ichimoku-highlight.js +40 -0
- package/dist/core/chart/analysis/ichimoku-highlight.js.map +1 -0
- package/dist/core/chart/analysis/key-fib-drawings-tools.d.ts +2 -6
- package/dist/core/chart/analysis/key-fib-drawings-tools.d.ts.map +1 -1
- package/dist/core/chart/analysis/key-fib-drawings-tools.js +5 -17
- package/dist/core/chart/analysis/key-fib-drawings-tools.js.map +1 -1
- package/dist/core/chart/analysis/key-level-drawings-shared.d.ts +3 -12
- package/dist/core/chart/analysis/key-level-drawings-shared.d.ts.map +1 -1
- package/dist/core/chart/analysis/key-level-drawings-shared.js +6 -36
- package/dist/core/chart/analysis/key-level-drawings-shared.js.map +1 -1
- package/dist/core/chart/analysis/key-level-menu-summary.d.ts +15 -22
- package/dist/core/chart/analysis/key-level-menu-summary.d.ts.map +1 -1
- package/dist/core/chart/analysis/key-level-menu-summary.js +63 -124
- package/dist/core/chart/analysis/key-level-menu-summary.js.map +1 -1
- package/dist/core/chart/analysis/key-levels-dataset.d.ts +1 -0
- package/dist/core/chart/analysis/key-levels-dataset.d.ts.map +1 -1
- package/dist/core/chart/analysis/key-levels-dataset.js +4 -7
- package/dist/core/chart/analysis/key-levels-dataset.js.map +1 -1
- package/dist/core/chart/analysis/supertrend-analyze-tools.d.ts +112 -0
- package/dist/core/chart/analysis/supertrend-analyze-tools.d.ts.map +1 -0
- package/dist/core/chart/analysis/supertrend-analyze-tools.js +214 -0
- package/dist/core/chart/analysis/supertrend-analyze-tools.js.map +1 -0
- package/dist/core/chart/analysis/supertrend-highlight.d.ts +25 -0
- package/dist/core/chart/analysis/supertrend-highlight.d.ts.map +1 -0
- package/dist/core/chart/analysis/supertrend-highlight.js +37 -0
- package/dist/core/chart/analysis/supertrend-highlight.js.map +1 -0
- package/dist/core/chart/analysis/trade-setups/build-trade-uniswap.d.ts.map +1 -1
- package/dist/core/chart/analysis/trade-setups/build-trade-uniswap.js +21 -0
- package/dist/core/chart/analysis/trade-setups/build-trade-uniswap.js.map +1 -1
- package/dist/core/chart/analysis/trade-setups/build-trade.d.ts.map +1 -1
- package/dist/core/chart/analysis/trade-setups/build-trade.js +23 -5
- package/dist/core/chart/analysis/trade-setups/build-trade.js.map +1 -1
- package/dist/core/chart/analysis/trade-setups/divergence-trade-setup.d.ts +78 -0
- package/dist/core/chart/analysis/trade-setups/divergence-trade-setup.d.ts.map +1 -0
- package/dist/core/chart/analysis/trade-setups/divergence-trade-setup.js +184 -0
- package/dist/core/chart/analysis/trade-setups/divergence-trade-setup.js.map +1 -0
- package/dist/core/chart/analysis/trade-setups/donchian-trade-setup.d.ts +133 -0
- package/dist/core/chart/analysis/trade-setups/donchian-trade-setup.d.ts.map +1 -0
- package/dist/core/chart/analysis/trade-setups/donchian-trade-setup.js +419 -0
- package/dist/core/chart/analysis/trade-setups/donchian-trade-setup.js.map +1 -0
- package/dist/core/chart/analysis/trade-setups/ichimoku-trade-setup.d.ts +154 -0
- package/dist/core/chart/analysis/trade-setups/ichimoku-trade-setup.d.ts.map +1 -0
- package/dist/core/chart/analysis/trade-setups/ichimoku-trade-setup.js +396 -0
- package/dist/core/chart/analysis/trade-setups/ichimoku-trade-setup.js.map +1 -0
- package/dist/core/chart/analysis/trade-setups/index.d.ts +5 -0
- package/dist/core/chart/analysis/trade-setups/index.d.ts.map +1 -1
- package/dist/core/chart/analysis/trade-setups/index.js +5 -0
- package/dist/core/chart/analysis/trade-setups/index.js.map +1 -1
- package/dist/core/chart/analysis/trade-setups/key-level-fib-retrace-trade-setup.d.ts +7 -41
- package/dist/core/chart/analysis/trade-setups/key-level-fib-retrace-trade-setup.d.ts.map +1 -1
- package/dist/core/chart/analysis/trade-setups/key-level-fib-retrace-trade-setup.js +33 -235
- package/dist/core/chart/analysis/trade-setups/key-level-fib-retrace-trade-setup.js.map +1 -1
- package/dist/core/chart/analysis/trade-setups/key-levels-trade-setup.d.ts +2 -8
- package/dist/core/chart/analysis/trade-setups/key-levels-trade-setup.d.ts.map +1 -1
- package/dist/core/chart/analysis/trade-setups/key-levels-trade-setup.js +0 -27
- package/dist/core/chart/analysis/trade-setups/key-levels-trade-setup.js.map +1 -1
- package/dist/core/chart/analysis/trade-setups/shared.d.ts +1 -1
- package/dist/core/chart/analysis/trade-setups/shared.d.ts.map +1 -1
- package/dist/core/chart/analysis/trade-setups/shared.js +10 -0
- package/dist/core/chart/analysis/trade-setups/shared.js.map +1 -1
- package/dist/core/chart/analysis/trade-setups/supertrend-trade-setup.d.ts +119 -0
- package/dist/core/chart/analysis/trade-setups/supertrend-trade-setup.d.ts.map +1 -0
- package/dist/core/chart/analysis/trade-setups/supertrend-trade-setup.js +301 -0
- package/dist/core/chart/analysis/trade-setups/supertrend-trade-setup.js.map +1 -0
- package/dist/core/chart/analysis/trade-setups/trade-desk-universal-input.d.ts +34 -0
- package/dist/core/chart/analysis/trade-setups/trade-desk-universal-input.d.ts.map +1 -1
- package/dist/core/chart/analysis/trade-setups/trade-desk-universal-input.js +43 -0
- package/dist/core/chart/analysis/trade-setups/trade-desk-universal-input.js.map +1 -1
- package/dist/core/chart/analysis/trade-setups/trade-idea-list.d.ts +15 -0
- package/dist/core/chart/analysis/trade-setups/trade-idea-list.d.ts.map +1 -1
- package/dist/core/chart/analysis/trade-setups/trade-idea-list.js +89 -0
- package/dist/core/chart/analysis/trade-setups/trade-idea-list.js.map +1 -1
- package/dist/core/chart/analysis/trade-setups/trade-idea.d.ts +32 -0
- package/dist/core/chart/analysis/trade-setups/trade-idea.d.ts.map +1 -1
- package/dist/core/chart/analysis/trade-setups/trade-idea.js +32 -0
- package/dist/core/chart/analysis/trade-setups/trade-idea.js.map +1 -1
- package/dist/core/chart/analysis/trade-setups/trade-purpose-format.d.ts +2 -2
- package/dist/core/chart/analysis/trade-setups/trade-purpose-format.d.ts.map +1 -1
- package/dist/core/chart/analysis/trade-setups/trade-purpose-format.js +2 -6
- package/dist/core/chart/analysis/trade-setups/trade-purpose-format.js.map +1 -1
- package/dist/core/chart/analysis/trade-setups/trade-setup-selection.d.ts +42 -8
- package/dist/core/chart/analysis/trade-setups/trade-setup-selection.d.ts.map +1 -1
- package/dist/core/chart/analysis/trade-setups/trade-setup-selection.js +72 -17
- package/dist/core/chart/analysis/trade-setups/trade-setup-selection.js.map +1 -1
- package/dist/core/chart/analysis/trade-setups/z-score-trade-setup.d.ts +89 -0
- package/dist/core/chart/analysis/trade-setups/z-score-trade-setup.d.ts.map +1 -0
- package/dist/core/chart/analysis/trade-setups/z-score-trade-setup.js +195 -0
- package/dist/core/chart/analysis/trade-setups/z-score-trade-setup.js.map +1 -0
- package/dist/core/chart/analysis/z-score-analyze-tools.d.ts +111 -0
- package/dist/core/chart/analysis/z-score-analyze-tools.d.ts.map +1 -0
- package/dist/core/chart/analysis/z-score-analyze-tools.js +240 -0
- package/dist/core/chart/analysis/z-score-analyze-tools.js.map +1 -0
- package/dist/core/chart/analysis/z-score-highlight.d.ts +29 -0
- package/dist/core/chart/analysis/z-score-highlight.d.ts.map +1 -0
- package/dist/core/chart/analysis/z-score-highlight.js +39 -0
- package/dist/core/chart/analysis/z-score-highlight.js.map +1 -0
- package/dist/core/chart/customization-catalog.d.ts.map +1 -1
- package/dist/core/chart/customization-catalog.js +36 -0
- package/dist/core/chart/customization-catalog.js.map +1 -1
- package/dist/core/chart/display-downsample.d.ts.map +1 -1
- package/dist/core/chart/display-downsample.js +4 -14
- package/dist/core/chart/display-downsample.js.map +1 -1
- package/dist/core/chart/overlay-schemas.d.ts +496 -0
- package/dist/core/chart/overlay-schemas.d.ts.map +1 -1
- package/dist/core/chart/overlay-schemas.js +137 -0
- package/dist/core/chart/overlay-schemas.js.map +1 -1
- package/dist/core/chart/overlays.d.ts +2 -0
- package/dist/core/chart/overlays.d.ts.map +1 -1
- package/dist/core/chart/overlays.js +620 -2
- package/dist/core/chart/overlays.js.map +1 -1
- package/dist/core/chart/prepare-from-rows.d.ts +143 -0
- package/dist/core/chart/prepare-from-rows.d.ts.map +1 -1
- package/dist/core/chart/schemas.d.ts +491 -0
- package/dist/core/chart/schemas.d.ts.map +1 -1
- package/dist/core/coinmarketcap/schemas.d.ts +7 -7
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/agent-host-catalog.d.ts +1 -1
- package/dist/mcp/agent-host-catalog.d.ts.map +1 -1
- package/dist/mcp/agent-host-catalog.js +1 -0
- package/dist/mcp/agent-host-catalog.js.map +1 -1
- package/dist/mcp/chart.d.ts.map +1 -1
- package/dist/mcp/chart.js +61 -2
- package/dist/mcp/chart.js.map +1 -1
- package/dist/mcp/deferred/tool-group-map.d.ts.map +1 -1
- package/dist/mcp/deferred/tool-group-map.js +8 -0
- package/dist/mcp/deferred/tool-group-map.js.map +1 -1
- package/dist/mcp/defi/morpho-input.d.ts +3 -0
- package/dist/mcp/defi/morpho-input.d.ts.map +1 -1
- package/dist/mcp/defi/morpho-input.js +116 -1
- package/dist/mcp/defi/morpho-input.js.map +1 -1
- package/dist/mcp/ohlcv-session-wrapper.d.ts.map +1 -1
- package/dist/mcp/ohlcv-session-wrapper.js +8 -0
- package/dist/mcp/ohlcv-session-wrapper.js.map +1 -1
- package/dist/mcp/resources/chart-analysis.md +9 -0
- package/dist/mcp/resources/chart.md +3 -0
- package/dist/mcp/trade-tools.d.ts.map +1 -1
- package/dist/mcp/trade-tools.js +2 -0
- package/dist/mcp/trade-tools.js.map +1 -1
- package/package.json +65 -60
- package/dist/core/agent/mcp-servers-catalog.d.ts +0 -15
- package/dist/core/agent/mcp-servers-catalog.d.ts.map +0 -1
- package/dist/core/agent/mcp-servers-catalog.js +0 -125
- package/dist/core/agent/mcp-servers-catalog.js.map +0 -1
- package/dist/core/agent/webhooks-catalog.d.ts +0 -10
- package/dist/core/agent/webhooks-catalog.d.ts.map +0 -1
- package/dist/core/agent/webhooks-catalog.js +0 -19
- package/dist/core/agent/webhooks-catalog.js.map +0 -1
- package/dist/mcp/resources/trade-defaults-skill.md +0 -69
- package/dist/mcp/ta/resources/indicators.md +0 -86
|
@@ -5,7 +5,28 @@ const DEFAULT_MA_PERIOD = 20;
|
|
|
5
5
|
const DEFAULT_BOLLINGER_PERIOD = 20;
|
|
6
6
|
const DEFAULT_BOLLINGER_STD_DEV = 2;
|
|
7
7
|
const DEFAULT_BOLLINGER_FILL_COLOR = '#6366f133';
|
|
8
|
+
/** Analysis/overlay desk default (aligned with TA catalog donchianchannels). */
|
|
9
|
+
const DEFAULT_DONCHIAN_PERIOD = 20;
|
|
10
|
+
const DEFAULT_DONCHIAN_FILL_COLOR = '#0ea5e933';
|
|
11
|
+
const DEFAULT_SUPERTREND_PERIOD = 10;
|
|
12
|
+
const DEFAULT_SUPERTREND_MULTIPLIER = 3;
|
|
13
|
+
const SUPERTREND_UP_COLOR = '#22c55e';
|
|
14
|
+
const SUPERTREND_DOWN_COLOR = '#ef4444';
|
|
15
|
+
/** Cap colored ST segments so total chart series stay under the expand limit. */
|
|
16
|
+
const MAX_SUPERTREND_SEGMENTS = 10;
|
|
17
|
+
const DEFAULT_ICHIMOKU_CONVERSION = 9;
|
|
18
|
+
const DEFAULT_ICHIMOKU_BASE = 26;
|
|
19
|
+
const DEFAULT_ICHIMOKU_SPAN = 52;
|
|
20
|
+
const DEFAULT_ICHIMOKU_DISPLACEMENT = 26;
|
|
21
|
+
const ICHIMOKU_TENKAN_COLOR = '#3b82f6';
|
|
22
|
+
const ICHIMOKU_KIJUN_COLOR = '#ef4444';
|
|
23
|
+
const ICHIMOKU_CHIKOU_COLOR = '#a855f7';
|
|
24
|
+
const ICHIMOKU_CLOUD_BULL_COLOR = '#22c55e33';
|
|
25
|
+
const ICHIMOKU_CLOUD_BEAR_COLOR = '#ef444433';
|
|
8
26
|
const DEFAULT_RSI_PERIOD = 14;
|
|
27
|
+
const DEFAULT_ZSCORE_PERIOD = 20;
|
|
28
|
+
const DEFAULT_ZSCORE_ENTRY = 2;
|
|
29
|
+
const DEFAULT_ZSCORE_EXIT = 0.5;
|
|
9
30
|
const MACD_HIST_UP = '#22c55e';
|
|
10
31
|
const MACD_HIST_DOWN = '#ef4444';
|
|
11
32
|
const ALL_FIB_LEVELS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
|
|
@@ -16,8 +37,11 @@ function findSourceSeries(seriesList, sourceSeriesId) {
|
|
|
16
37
|
}
|
|
17
38
|
const times = [];
|
|
18
39
|
const closes = [];
|
|
40
|
+
const highs = [];
|
|
41
|
+
const lows = [];
|
|
19
42
|
let high = Number.NEGATIVE_INFINITY;
|
|
20
43
|
let low = Number.POSITIVE_INFINITY;
|
|
44
|
+
let hasOhlc = false;
|
|
21
45
|
for (const row of source.data) {
|
|
22
46
|
const time = row.time;
|
|
23
47
|
if (time == null) {
|
|
@@ -35,6 +59,9 @@ function findSourceSeries(seriesList, sourceSeriesId) {
|
|
|
35
59
|
}
|
|
36
60
|
times.push(time);
|
|
37
61
|
closes.push(close);
|
|
62
|
+
highs.push(rowHigh);
|
|
63
|
+
lows.push(rowLow);
|
|
64
|
+
hasOhlc = true;
|
|
38
65
|
high = Math.max(high, rowHigh);
|
|
39
66
|
low = Math.min(low, rowLow);
|
|
40
67
|
continue;
|
|
@@ -59,7 +86,16 @@ function findSourceSeries(seriesList, sourceSeriesId) {
|
|
|
59
86
|
if (!Number.isFinite(high) || !Number.isFinite(low)) {
|
|
60
87
|
return { ok: false, reason: `Overlay source "${sourceSeriesId}" has invalid high/low range.` };
|
|
61
88
|
}
|
|
62
|
-
return {
|
|
89
|
+
return {
|
|
90
|
+
ok: true,
|
|
91
|
+
data: {
|
|
92
|
+
times,
|
|
93
|
+
closes,
|
|
94
|
+
...(hasOhlc ? { highs, lows } : {}),
|
|
95
|
+
high,
|
|
96
|
+
low,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
63
99
|
}
|
|
64
100
|
function primaryTimeSpan(seriesList) {
|
|
65
101
|
const primary = seriesList.find(s => s.type === 'candlestick' || s.type === 'line');
|
|
@@ -252,6 +288,405 @@ function computeBollingerOverlay(overlay, source) {
|
|
|
252
288
|
],
|
|
253
289
|
};
|
|
254
290
|
}
|
|
291
|
+
function computeDonchianOverlay(overlay, source) {
|
|
292
|
+
const period = overlay.period ?? DEFAULT_DONCHIAN_PERIOD;
|
|
293
|
+
const highs = source.highs;
|
|
294
|
+
const lows = source.lows;
|
|
295
|
+
if (!highs?.length || !lows?.length || highs.length !== source.closes.length) {
|
|
296
|
+
return {
|
|
297
|
+
ok: false,
|
|
298
|
+
reason: 'Donchian overlay requires a candlestick source series with high/low.',
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
const result = calculateTechnicalIndicator({
|
|
302
|
+
indicator: 'donchianchannels',
|
|
303
|
+
params: { period },
|
|
304
|
+
input: { high: highs, low: lows, close: source.closes },
|
|
305
|
+
options: { maxPoints: highs.length },
|
|
306
|
+
});
|
|
307
|
+
if (!result.ok) {
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
const rows = result.data.result;
|
|
311
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
312
|
+
return { ok: false, reason: 'Overlay donchian returned no data.' };
|
|
313
|
+
}
|
|
314
|
+
const upper = [];
|
|
315
|
+
const middle = [];
|
|
316
|
+
const lower = [];
|
|
317
|
+
const bandFill = [];
|
|
318
|
+
const aligned = alignObjectIndicatorRows(source.times, rows, result.data.warmupCount);
|
|
319
|
+
for (const { time, row } of aligned) {
|
|
320
|
+
const u = pickNumber(row, ['upper', 'Upper']);
|
|
321
|
+
const m = pickNumber(row, ['middle', 'Middle']);
|
|
322
|
+
const l = pickNumber(row, ['lower', 'Lower']);
|
|
323
|
+
if (u != null) {
|
|
324
|
+
upper.push({ time, value: u });
|
|
325
|
+
}
|
|
326
|
+
if (m != null) {
|
|
327
|
+
middle.push({ time, value: m });
|
|
328
|
+
}
|
|
329
|
+
if (l != null) {
|
|
330
|
+
lower.push({ time, value: l });
|
|
331
|
+
}
|
|
332
|
+
if (u != null && l != null) {
|
|
333
|
+
bandFill.push({ time, upper: u, lower: l });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (middle.length === 0) {
|
|
337
|
+
return {
|
|
338
|
+
ok: false,
|
|
339
|
+
reason: `Overlay donchian(${period}) has no points after warmup.`,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
const prefix = overlay.id ?? `dc${period}_${overlay.sourceSeriesId}`;
|
|
343
|
+
const baseStyle = overlay.style ?? { lineWidth: 1 };
|
|
344
|
+
const showFill = overlay.fill !== false;
|
|
345
|
+
const fillColor = overlay.style?.color ?? DEFAULT_DONCHIAN_FILL_COLOR;
|
|
346
|
+
const seriesOut = [];
|
|
347
|
+
if (showFill && bandFill.length > 0) {
|
|
348
|
+
seriesOut.push({
|
|
349
|
+
id: `${prefix}_fill`,
|
|
350
|
+
type: 'band',
|
|
351
|
+
label: `DC fill (${period})`,
|
|
352
|
+
data: bandFill,
|
|
353
|
+
priceScaleId: overlay.priceScaleId ?? 'right',
|
|
354
|
+
overlay: overlay.overlay ?? true,
|
|
355
|
+
lastValueVisible: false,
|
|
356
|
+
style: { color: fillColor, lineWidth: 1 },
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
ok: true,
|
|
361
|
+
data: [
|
|
362
|
+
...seriesOut,
|
|
363
|
+
{
|
|
364
|
+
id: `${prefix}_upper`,
|
|
365
|
+
type: 'line',
|
|
366
|
+
label: `DC upper (${period})`,
|
|
367
|
+
data: upper,
|
|
368
|
+
priceScaleId: overlay.priceScaleId ?? 'right',
|
|
369
|
+
overlay: overlay.overlay ?? true,
|
|
370
|
+
style: { ...baseStyle, lineStyle: 'dashed' },
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
id: `${prefix}_middle`,
|
|
374
|
+
type: 'line',
|
|
375
|
+
label: `DC middle (${period})`,
|
|
376
|
+
data: middle,
|
|
377
|
+
priceScaleId: overlay.priceScaleId ?? 'right',
|
|
378
|
+
overlay: overlay.overlay ?? true,
|
|
379
|
+
style: { ...baseStyle, lineStyle: 'solid' },
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
id: `${prefix}_lower`,
|
|
383
|
+
type: 'line',
|
|
384
|
+
label: `DC lower (${period})`,
|
|
385
|
+
data: lower,
|
|
386
|
+
priceScaleId: overlay.priceScaleId ?? 'right',
|
|
387
|
+
overlay: overlay.overlay ?? true,
|
|
388
|
+
style: { ...baseStyle, lineStyle: 'dashed' },
|
|
389
|
+
},
|
|
390
|
+
],
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function computeSupertrendOverlay(overlay, source) {
|
|
394
|
+
const period = overlay.period ?? DEFAULT_SUPERTREND_PERIOD;
|
|
395
|
+
const multiplier = overlay.multiplier ?? DEFAULT_SUPERTREND_MULTIPLIER;
|
|
396
|
+
const highs = source.highs;
|
|
397
|
+
const lows = source.lows;
|
|
398
|
+
if (!highs?.length || !lows?.length || highs.length !== source.closes.length) {
|
|
399
|
+
return {
|
|
400
|
+
ok: false,
|
|
401
|
+
reason: 'Supertrend overlay requires a candlestick source series with high/low.',
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
const result = calculateTechnicalIndicator({
|
|
405
|
+
indicator: 'supertrend',
|
|
406
|
+
params: { period, multiplier },
|
|
407
|
+
input: { high: highs, low: lows, close: source.closes },
|
|
408
|
+
options: { maxPoints: highs.length },
|
|
409
|
+
});
|
|
410
|
+
if (!result.ok) {
|
|
411
|
+
return result;
|
|
412
|
+
}
|
|
413
|
+
const rows = result.data.result;
|
|
414
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
415
|
+
return { ok: false, reason: 'Overlay supertrend returned no data.' };
|
|
416
|
+
}
|
|
417
|
+
const aligned = alignObjectIndicatorRows(source.times, rows, result.data.warmupCount);
|
|
418
|
+
const points = [];
|
|
419
|
+
for (const { time, row } of aligned) {
|
|
420
|
+
const value = pickNumber(row, ['supertrend', 'SuperTrend', 'Supertrend']);
|
|
421
|
+
const direction = pickNumber(row, ['direction', 'Direction']);
|
|
422
|
+
if (value == null || direction == null || !Number.isFinite(value)) {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
points.push({ time, value, direction: direction >= 0 ? 1 : -1 });
|
|
426
|
+
}
|
|
427
|
+
if (points.length === 0) {
|
|
428
|
+
return {
|
|
429
|
+
ok: false,
|
|
430
|
+
reason: `Overlay supertrend(${period}, ${multiplier}) has no points after warmup.`,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
const segments = [];
|
|
434
|
+
for (const pt of points) {
|
|
435
|
+
const last = segments[segments.length - 1];
|
|
436
|
+
if (!last || last.direction !== pt.direction) {
|
|
437
|
+
segments.push({ direction: pt.direction, data: [{ time: pt.time, value: pt.value }] });
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
last.data.push({ time: pt.time, value: pt.value });
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const kept = segments.length > MAX_SUPERTREND_SEGMENTS
|
|
444
|
+
? segments.slice(segments.length - MAX_SUPERTREND_SEGMENTS)
|
|
445
|
+
: segments;
|
|
446
|
+
const prefix = overlay.id ?? `st${period}x${multiplier}_${overlay.sourceSeriesId}`;
|
|
447
|
+
const lineWidth = overlay.style?.lineWidth ?? 2;
|
|
448
|
+
const seriesOut = kept.map((seg, i) => ({
|
|
449
|
+
id: `${prefix}_seg${i}`,
|
|
450
|
+
type: 'line',
|
|
451
|
+
label: i === kept.length - 1 ? `Supertrend (${period}, ${multiplier})` : `ST seg ${i + 1}`,
|
|
452
|
+
data: seg.data,
|
|
453
|
+
priceScaleId: overlay.priceScaleId ?? 'right',
|
|
454
|
+
overlay: overlay.overlay ?? true,
|
|
455
|
+
lastValueVisible: i === kept.length - 1,
|
|
456
|
+
style: {
|
|
457
|
+
lineWidth,
|
|
458
|
+
lineStyle: 'solid',
|
|
459
|
+
color: seg.direction >= 0 ? SUPERTREND_UP_COLOR : SUPERTREND_DOWN_COLOR,
|
|
460
|
+
},
|
|
461
|
+
}));
|
|
462
|
+
return { ok: true, data: seriesOut };
|
|
463
|
+
}
|
|
464
|
+
function medianBarIntervalSec(times) {
|
|
465
|
+
const secs = [];
|
|
466
|
+
for (const t of times) {
|
|
467
|
+
const s = chartTimeSec(t);
|
|
468
|
+
if (s != null) {
|
|
469
|
+
secs.push(s);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (secs.length < 2) {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
const deltas = [];
|
|
476
|
+
for (let i = 1; i < secs.length; i++) {
|
|
477
|
+
const d = secs[i] - secs[i - 1];
|
|
478
|
+
if (d > 0) {
|
|
479
|
+
deltas.push(d);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (deltas.length === 0) {
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
deltas.sort((a, b) => a - b);
|
|
486
|
+
return deltas[Math.floor(deltas.length / 2)];
|
|
487
|
+
}
|
|
488
|
+
function extendChartTime(last, intervalSec, steps) {
|
|
489
|
+
const sec = chartTimeSec(last);
|
|
490
|
+
if (sec == null || typeof last !== 'number') {
|
|
491
|
+
// Business-day charts: fall back to unix seconds from last if possible.
|
|
492
|
+
if (sec == null) {
|
|
493
|
+
return last;
|
|
494
|
+
}
|
|
495
|
+
return sec + intervalSec * steps;
|
|
496
|
+
}
|
|
497
|
+
return last + intervalSec * steps;
|
|
498
|
+
}
|
|
499
|
+
function computeIchimokuOverlay(overlay, source) {
|
|
500
|
+
const conversionPeriod = overlay.conversionPeriod ?? DEFAULT_ICHIMOKU_CONVERSION;
|
|
501
|
+
const basePeriod = overlay.basePeriod ?? DEFAULT_ICHIMOKU_BASE;
|
|
502
|
+
const spanPeriod = overlay.spanPeriod ?? DEFAULT_ICHIMOKU_SPAN;
|
|
503
|
+
const displacement = overlay.displacement ?? DEFAULT_ICHIMOKU_DISPLACEMENT;
|
|
504
|
+
const highs = source.highs;
|
|
505
|
+
const lows = source.lows;
|
|
506
|
+
if (!highs?.length || !lows?.length || highs.length !== source.closes.length) {
|
|
507
|
+
return {
|
|
508
|
+
ok: false,
|
|
509
|
+
reason: 'Ichimoku overlay requires a candlestick source series with high/low.',
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
const result = calculateTechnicalIndicator({
|
|
513
|
+
indicator: 'ichimokukinkouhyou',
|
|
514
|
+
params: { conversionPeriod, basePeriod, spanPeriod, displacement },
|
|
515
|
+
input: { high: highs, low: lows, close: source.closes },
|
|
516
|
+
options: { maxPoints: highs.length },
|
|
517
|
+
});
|
|
518
|
+
if (!result.ok) {
|
|
519
|
+
return result;
|
|
520
|
+
}
|
|
521
|
+
const rows = result.data.result;
|
|
522
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
523
|
+
return { ok: false, reason: 'Overlay ichimoku returned no data.' };
|
|
524
|
+
}
|
|
525
|
+
const aligned = alignObjectIndicatorRows(source.times, rows, result.data.warmupCount);
|
|
526
|
+
const tenkan = [];
|
|
527
|
+
const kijun = [];
|
|
528
|
+
const spanPoints = [];
|
|
529
|
+
const intervalSec = medianBarIntervalSec(source.times) ?? 3600;
|
|
530
|
+
const alignOffset = rows.length < source.times.length ? source.times.length - rows.length : 0;
|
|
531
|
+
for (let i = 0; i < aligned.length; i++) {
|
|
532
|
+
const { time, row } = aligned[i];
|
|
533
|
+
const conversion = pickNumber(row, ['conversion', 'Conversion', 'tenkan', 'Tenkan']);
|
|
534
|
+
const base = pickNumber(row, ['base', 'Base', 'kijun', 'Kijun']);
|
|
535
|
+
const spanA = pickNumber(row, ['spanA', 'SpanA', 'senkouA', 'SenkouA']);
|
|
536
|
+
const spanB = pickNumber(row, ['spanB', 'SpanB', 'senkouB', 'SenkouB']);
|
|
537
|
+
if (conversion != null) {
|
|
538
|
+
tenkan.push({ time, value: conversion });
|
|
539
|
+
}
|
|
540
|
+
if (base != null) {
|
|
541
|
+
kijun.push({ time, value: base });
|
|
542
|
+
}
|
|
543
|
+
if (spanA != null && spanB != null) {
|
|
544
|
+
const sourceIndex = alignOffset + i;
|
|
545
|
+
const targetIndex = sourceIndex + displacement;
|
|
546
|
+
let cloudTime;
|
|
547
|
+
if (targetIndex >= 0 && targetIndex < source.times.length) {
|
|
548
|
+
cloudTime = source.times[targetIndex];
|
|
549
|
+
}
|
|
550
|
+
else if (source.times.length > 0 && targetIndex >= source.times.length) {
|
|
551
|
+
const last = source.times[source.times.length - 1];
|
|
552
|
+
const stepsBeyond = targetIndex - (source.times.length - 1);
|
|
553
|
+
cloudTime = extendChartTime(last, intervalSec, stepsBeyond);
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
spanPoints.push({ time: cloudTime, spanA, spanB });
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (tenkan.length === 0 && kijun.length === 0) {
|
|
562
|
+
return {
|
|
563
|
+
ok: false,
|
|
564
|
+
reason: `Overlay ichimoku(${conversionPeriod}/${basePeriod}/${spanPeriod}) has no points after warmup.`,
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
const prefix = overlay.id ?? `ichi_${overlay.sourceSeriesId}`;
|
|
568
|
+
const priceScaleId = overlay.priceScaleId ?? 'right';
|
|
569
|
+
const isOverlay = overlay.overlay ?? true;
|
|
570
|
+
const showFill = overlay.fill !== false;
|
|
571
|
+
const showChikou = overlay.chikou !== false;
|
|
572
|
+
const seriesOut = [];
|
|
573
|
+
if (showFill && spanPoints.length > 0) {
|
|
574
|
+
const bullBands = [];
|
|
575
|
+
const bearBands = [];
|
|
576
|
+
for (const pt of spanPoints) {
|
|
577
|
+
const upper = Math.max(pt.spanA, pt.spanB);
|
|
578
|
+
const lower = Math.min(pt.spanA, pt.spanB);
|
|
579
|
+
const bandPt = { time: pt.time, upper, lower };
|
|
580
|
+
if (pt.spanA >= pt.spanB) {
|
|
581
|
+
bullBands.push(bandPt);
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
bearBands.push(bandPt);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (bullBands.length > 0) {
|
|
588
|
+
seriesOut.push({
|
|
589
|
+
id: `${prefix}_cloud_bull`,
|
|
590
|
+
type: 'band',
|
|
591
|
+
label: 'Ichimoku cloud (bull)',
|
|
592
|
+
data: bullBands,
|
|
593
|
+
priceScaleId,
|
|
594
|
+
overlay: isOverlay,
|
|
595
|
+
lastValueVisible: false,
|
|
596
|
+
style: { color: ICHIMOKU_CLOUD_BULL_COLOR, lineWidth: 1 },
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
if (bearBands.length > 0) {
|
|
600
|
+
seriesOut.push({
|
|
601
|
+
id: `${prefix}_cloud_bear`,
|
|
602
|
+
type: 'band',
|
|
603
|
+
label: 'Ichimoku cloud (bear)',
|
|
604
|
+
data: bearBands,
|
|
605
|
+
priceScaleId,
|
|
606
|
+
overlay: isOverlay,
|
|
607
|
+
lastValueVisible: false,
|
|
608
|
+
style: { color: ICHIMOKU_CLOUD_BEAR_COLOR, lineWidth: 1 },
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (tenkan.length > 0) {
|
|
613
|
+
seriesOut.push({
|
|
614
|
+
id: `${prefix}_tenkan`,
|
|
615
|
+
type: 'line',
|
|
616
|
+
label: `Tenkan (${conversionPeriod})`,
|
|
617
|
+
data: tenkan,
|
|
618
|
+
priceScaleId,
|
|
619
|
+
overlay: isOverlay,
|
|
620
|
+
style: { color: ICHIMOKU_TENKAN_COLOR, lineWidth: 1, lineStyle: 'solid' },
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
if (kijun.length > 0) {
|
|
624
|
+
seriesOut.push({
|
|
625
|
+
id: `${prefix}_kijun`,
|
|
626
|
+
type: 'line',
|
|
627
|
+
label: `Kijun (${basePeriod})`,
|
|
628
|
+
data: kijun,
|
|
629
|
+
priceScaleId,
|
|
630
|
+
overlay: isOverlay,
|
|
631
|
+
style: { color: ICHIMOKU_KIJUN_COLOR, lineWidth: 1, lineStyle: 'solid' },
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
// Senkou outline lines (forward-displaced).
|
|
635
|
+
const spanALine = [];
|
|
636
|
+
const spanBLine = [];
|
|
637
|
+
for (const pt of spanPoints) {
|
|
638
|
+
spanALine.push({ time: pt.time, value: pt.spanA });
|
|
639
|
+
spanBLine.push({ time: pt.time, value: pt.spanB });
|
|
640
|
+
}
|
|
641
|
+
if (spanALine.length > 0) {
|
|
642
|
+
seriesOut.push({
|
|
643
|
+
id: `${prefix}_spanA`,
|
|
644
|
+
type: 'line',
|
|
645
|
+
label: 'Senkou A',
|
|
646
|
+
data: spanALine,
|
|
647
|
+
priceScaleId,
|
|
648
|
+
overlay: isOverlay,
|
|
649
|
+
lastValueVisible: false,
|
|
650
|
+
style: { color: '#22c55e', lineWidth: 1, lineStyle: 'dashed' },
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
if (spanBLine.length > 0) {
|
|
654
|
+
seriesOut.push({
|
|
655
|
+
id: `${prefix}_spanB`,
|
|
656
|
+
type: 'line',
|
|
657
|
+
label: 'Senkou B',
|
|
658
|
+
data: spanBLine,
|
|
659
|
+
priceScaleId,
|
|
660
|
+
overlay: isOverlay,
|
|
661
|
+
lastValueVisible: false,
|
|
662
|
+
style: { color: '#ef4444', lineWidth: 1, lineStyle: 'dashed' },
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
if (showChikou && displacement > 0 && source.closes.length > displacement) {
|
|
666
|
+
const chikou = [];
|
|
667
|
+
for (let i = displacement; i < source.closes.length; i++) {
|
|
668
|
+
const close = source.closes[i];
|
|
669
|
+
const time = source.times[i - displacement];
|
|
670
|
+
if (time == null || !Number.isFinite(close)) {
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
chikou.push({ time, value: close });
|
|
674
|
+
}
|
|
675
|
+
if (chikou.length > 0) {
|
|
676
|
+
seriesOut.push({
|
|
677
|
+
id: `${prefix}_chikou`,
|
|
678
|
+
type: 'line',
|
|
679
|
+
label: `Chikou (${displacement})`,
|
|
680
|
+
data: chikou,
|
|
681
|
+
priceScaleId,
|
|
682
|
+
overlay: isOverlay,
|
|
683
|
+
lastValueVisible: false,
|
|
684
|
+
style: { color: ICHIMOKU_CHIKOU_COLOR, lineWidth: 1, lineStyle: 'dotted' },
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return { ok: true, data: seriesOut };
|
|
689
|
+
}
|
|
255
690
|
function horizontalLineData(timeStart, timeEnd, value) {
|
|
256
691
|
return [
|
|
257
692
|
{ time: timeStart, value },
|
|
@@ -795,6 +1230,97 @@ function oscillatorPaneId(overlay, index) {
|
|
|
795
1230
|
const base = overlay.id ?? `${overlay.type}_${index}`;
|
|
796
1231
|
return `osc_${base.replace(/[^a-zA-Z0-9_-]/g, '_')}`;
|
|
797
1232
|
}
|
|
1233
|
+
/** Resolve pane ids for rsi / stochasticrsi overlays in the same order expandChartOverlays uses. */
|
|
1234
|
+
export function resolveOscillatorPaneIds(overlays) {
|
|
1235
|
+
const out = {};
|
|
1236
|
+
if (!overlays?.length) {
|
|
1237
|
+
return out;
|
|
1238
|
+
}
|
|
1239
|
+
let oscillatorIndex = 0;
|
|
1240
|
+
for (const overlay of overlays) {
|
|
1241
|
+
if (overlay.type === 'rsi' ||
|
|
1242
|
+
overlay.type === 'zscore' ||
|
|
1243
|
+
overlay.type === 'macd' ||
|
|
1244
|
+
overlay.type === 'stochasticrsi') {
|
|
1245
|
+
const paneId = oscillatorPaneId(overlay, oscillatorIndex++);
|
|
1246
|
+
if (out[overlay.type] == null) {
|
|
1247
|
+
out[overlay.type] = paneId;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
return out;
|
|
1252
|
+
}
|
|
1253
|
+
function divergenceSegmentStyle(kind, base) {
|
|
1254
|
+
const bullish = kind === 'regular_bullish' || kind === 'hidden_bullish';
|
|
1255
|
+
const hidden = kind === 'hidden_bullish' || kind === 'hidden_bearish';
|
|
1256
|
+
return {
|
|
1257
|
+
color: bullish ? '#3b82f6' : '#ef4444',
|
|
1258
|
+
lineWidth: 2,
|
|
1259
|
+
lineStyle: hidden ? 'dashed' : 'solid',
|
|
1260
|
+
...base,
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
function chartTimeToUnix(time) {
|
|
1264
|
+
if (typeof time === 'number' && Number.isFinite(time)) {
|
|
1265
|
+
return time;
|
|
1266
|
+
}
|
|
1267
|
+
if (time && typeof time === 'object' && 'year' in time) {
|
|
1268
|
+
return Math.floor(Date.UTC(time.year, time.month - 1, time.day) / 1000);
|
|
1269
|
+
}
|
|
1270
|
+
return null;
|
|
1271
|
+
}
|
|
1272
|
+
function computeDivergenceOverlay(overlay, paneByOsc) {
|
|
1273
|
+
const prefix = overlay.id ?? 'divergence';
|
|
1274
|
+
const seriesOut = [];
|
|
1275
|
+
for (let i = 0; i < overlay.segments.length; i++) {
|
|
1276
|
+
const seg = overlay.segments[i];
|
|
1277
|
+
const paneId = seg.oscillatorPaneId ?? paneByOsc[seg.oscillator];
|
|
1278
|
+
if (!paneId) {
|
|
1279
|
+
return {
|
|
1280
|
+
ok: false,
|
|
1281
|
+
reason: `Divergence segment needs ${seg.oscillator} pane — ensure that oscillator overlay is present.`,
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
const style = divergenceSegmentStyle(seg.kind, overlay.style);
|
|
1285
|
+
const pA = chartTimeToUnix(seg.price.pointA.time);
|
|
1286
|
+
const pB = chartTimeToUnix(seg.price.pointB.time);
|
|
1287
|
+
const oA = chartTimeToUnix(seg.oscillatorLine.pointA.time);
|
|
1288
|
+
const oB = chartTimeToUnix(seg.oscillatorLine.pointB.time);
|
|
1289
|
+
if (pA == null || pB == null || oA == null || oB == null) {
|
|
1290
|
+
return { ok: false, reason: 'Divergence segment has invalid times.' };
|
|
1291
|
+
}
|
|
1292
|
+
const label = seg.label?.trim() || seg.kind.replace(/_/g, ' ');
|
|
1293
|
+
seriesOut.push({
|
|
1294
|
+
id: `${prefix}_price_${i}`,
|
|
1295
|
+
type: 'line',
|
|
1296
|
+
label: `${label} (price)`,
|
|
1297
|
+
data: [
|
|
1298
|
+
{ time: pA, value: seg.price.pointA.price },
|
|
1299
|
+
{ time: pB, value: seg.price.pointB.price },
|
|
1300
|
+
],
|
|
1301
|
+
priceScaleId: 'right',
|
|
1302
|
+
overlay: true,
|
|
1303
|
+
style,
|
|
1304
|
+
});
|
|
1305
|
+
seriesOut.push({
|
|
1306
|
+
id: `${prefix}_osc_${i}`,
|
|
1307
|
+
type: 'line',
|
|
1308
|
+
label: `${label} (${seg.oscillator})`,
|
|
1309
|
+
data: [
|
|
1310
|
+
{ time: oA, value: seg.oscillatorLine.pointA.value },
|
|
1311
|
+
{ time: oB, value: seg.oscillatorLine.pointB.value },
|
|
1312
|
+
],
|
|
1313
|
+
priceScaleId: 'right',
|
|
1314
|
+
overlay: true,
|
|
1315
|
+
paneId,
|
|
1316
|
+
style,
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
if (!seriesOut.length) {
|
|
1320
|
+
return { ok: false, reason: 'divergence produced no drawable geometry.' };
|
|
1321
|
+
}
|
|
1322
|
+
return { ok: true, data: seriesOut };
|
|
1323
|
+
}
|
|
798
1324
|
function tagSeriesPane(series, paneId) {
|
|
799
1325
|
return series.map(s => ({ ...s, paneId }));
|
|
800
1326
|
}
|
|
@@ -844,6 +1370,80 @@ function computeRsiOverlay(overlay, source, paneId) {
|
|
|
844
1370
|
], paneId),
|
|
845
1371
|
};
|
|
846
1372
|
}
|
|
1373
|
+
function computeZScoreOverlay(overlay, source, paneId) {
|
|
1374
|
+
const period = overlay.period ?? DEFAULT_ZSCORE_PERIOD;
|
|
1375
|
+
const entryZ = overlay.entryZ ?? DEFAULT_ZSCORE_ENTRY;
|
|
1376
|
+
const exitZ = overlay.exitZ ?? DEFAULT_ZSCORE_EXIT;
|
|
1377
|
+
const smaResult = calculateTechnicalIndicator({
|
|
1378
|
+
indicator: 'sma',
|
|
1379
|
+
params: { period },
|
|
1380
|
+
input: { values: source.closes },
|
|
1381
|
+
options: { maxPoints: source.closes.length },
|
|
1382
|
+
});
|
|
1383
|
+
if (!smaResult.ok) {
|
|
1384
|
+
return smaResult;
|
|
1385
|
+
}
|
|
1386
|
+
const sdResult = calculateTechnicalIndicator({
|
|
1387
|
+
indicator: 'sd',
|
|
1388
|
+
params: { period },
|
|
1389
|
+
input: { values: source.closes },
|
|
1390
|
+
options: { maxPoints: source.closes.length },
|
|
1391
|
+
});
|
|
1392
|
+
if (!sdResult.ok) {
|
|
1393
|
+
return sdResult;
|
|
1394
|
+
}
|
|
1395
|
+
const smaAligned = alignNumericIndicator(source.times, smaResult.data.result, smaResult.data.warmupCount);
|
|
1396
|
+
const sdAligned = alignNumericIndicator(source.times, sdResult.data.result, sdResult.data.warmupCount);
|
|
1397
|
+
const sdByTime = new Map(sdAligned.map(p => [String(p.time), p.value]));
|
|
1398
|
+
const zLine = [];
|
|
1399
|
+
for (let i = 0; i < source.closes.length; i++) {
|
|
1400
|
+
const time = source.times[i];
|
|
1401
|
+
const close = source.closes[i];
|
|
1402
|
+
const smaPoint = smaAligned.find(p => p.time === time);
|
|
1403
|
+
const sd = sdByTime.get(String(time));
|
|
1404
|
+
if (smaPoint == null || sd == null || !Number.isFinite(sd) || sd <= 0) {
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
zLine.push({ time, value: (close - smaPoint.value) / sd });
|
|
1408
|
+
}
|
|
1409
|
+
if (zLine.length === 0) {
|
|
1410
|
+
return { ok: false, reason: `Overlay zscore(${period}) has no points after warmup.` };
|
|
1411
|
+
}
|
|
1412
|
+
const prefix = overlay.id ?? `zscore${period}_${overlay.sourceSeriesId}`;
|
|
1413
|
+
const guide = (level, idSuffix, label, dashed = true) => ({
|
|
1414
|
+
id: `${prefix}_${idSuffix}`,
|
|
1415
|
+
type: 'line',
|
|
1416
|
+
label,
|
|
1417
|
+
data: zLine.map(p => ({ time: p.time, value: level })),
|
|
1418
|
+
priceScaleId: 'right',
|
|
1419
|
+
overlay: true,
|
|
1420
|
+
lastValueVisible: false,
|
|
1421
|
+
style: {
|
|
1422
|
+
lineStyle: dashed ? 'dashed' : 'dotted',
|
|
1423
|
+
lineWidth: 1,
|
|
1424
|
+
color: '#94a3b8',
|
|
1425
|
+
},
|
|
1426
|
+
});
|
|
1427
|
+
const seriesOut = [
|
|
1428
|
+
{
|
|
1429
|
+
id: `${prefix}_z`,
|
|
1430
|
+
type: 'line',
|
|
1431
|
+
label: overlay.label ?? `Z(${period})`,
|
|
1432
|
+
data: zLine,
|
|
1433
|
+
priceScaleId: 'right',
|
|
1434
|
+
overlay: true,
|
|
1435
|
+
style: overlay.style ?? { lineStyle: 'solid', lineWidth: 2, color: '#7c3aed' },
|
|
1436
|
+
},
|
|
1437
|
+
guide(0, 'zero', 'Z=0', false),
|
|
1438
|
+
guide(entryZ, 'entry_pos', `+${entryZ}`),
|
|
1439
|
+
guide(-entryZ, 'entry_neg', `−${entryZ}`),
|
|
1440
|
+
];
|
|
1441
|
+
if (exitZ > 0) {
|
|
1442
|
+
seriesOut.push(guide(exitZ, 'exit_pos', `+${exitZ}`));
|
|
1443
|
+
seriesOut.push(guide(-exitZ, 'exit_neg', `−${exitZ}`));
|
|
1444
|
+
}
|
|
1445
|
+
return { ok: true, data: tagSeriesPane(seriesOut, paneId) };
|
|
1446
|
+
}
|
|
847
1447
|
function computeMacdOverlay(overlay, source, paneId) {
|
|
848
1448
|
const params = {
|
|
849
1449
|
...(overlay.fastPeriod != null ? { fastPeriod: overlay.fastPeriod } : {}),
|
|
@@ -1005,13 +1605,15 @@ export function expandChartOverlays(baseSeries, overlays) {
|
|
|
1005
1605
|
}
|
|
1006
1606
|
const expanded = [...baseSeries];
|
|
1007
1607
|
let oscillatorIndex = 0;
|
|
1608
|
+
const paneByOsc = resolveOscillatorPaneIds(overlays);
|
|
1008
1609
|
for (const overlay of overlays) {
|
|
1009
1610
|
let overlaySeries;
|
|
1010
1611
|
if (overlay.type === 'horizontal_levels' ||
|
|
1011
1612
|
overlay.type === 'pivot_levels' ||
|
|
1012
1613
|
overlay.type === 'trend_lines' ||
|
|
1013
1614
|
overlay.type === 'chart_pattern' ||
|
|
1014
|
-
overlay.type === 'elliott_waves'
|
|
1615
|
+
overlay.type === 'elliott_waves' ||
|
|
1616
|
+
overlay.type === 'divergence') {
|
|
1015
1617
|
const span = primaryTimeSpan(baseSeries);
|
|
1016
1618
|
if (!span.ok) {
|
|
1017
1619
|
return span;
|
|
@@ -1028,6 +1630,9 @@ export function expandChartOverlays(baseSeries, overlays) {
|
|
|
1028
1630
|
else if (overlay.type === 'elliott_waves') {
|
|
1029
1631
|
overlaySeries = computeElliottWavesOverlay(overlay, span.data.timeStart, span.data.timeEnd);
|
|
1030
1632
|
}
|
|
1633
|
+
else if (overlay.type === 'divergence') {
|
|
1634
|
+
overlaySeries = computeDivergenceOverlay(overlay, paneByOsc);
|
|
1635
|
+
}
|
|
1031
1636
|
else {
|
|
1032
1637
|
overlaySeries = computeTrendLinesOverlay(overlay, span.data.timeStart, span.data.timeEnd);
|
|
1033
1638
|
}
|
|
@@ -1055,6 +1660,7 @@ export function expandChartOverlays(baseSeries, overlays) {
|
|
|
1055
1660
|
overlaySeries = computeFibonacciOverlay(overlay, span.data.timeStart, span.data.timeEnd, range);
|
|
1056
1661
|
}
|
|
1057
1662
|
else if (overlay.type === 'rsi' ||
|
|
1663
|
+
overlay.type === 'zscore' ||
|
|
1058
1664
|
overlay.type === 'macd' ||
|
|
1059
1665
|
overlay.type === 'stochasticrsi') {
|
|
1060
1666
|
const sourceResult = findSourceSeries(baseSeries, overlay.sourceSeriesId);
|
|
@@ -1065,6 +1671,9 @@ export function expandChartOverlays(baseSeries, overlays) {
|
|
|
1065
1671
|
if (overlay.type === 'rsi') {
|
|
1066
1672
|
overlaySeries = computeRsiOverlay(overlay, sourceResult.data, paneId);
|
|
1067
1673
|
}
|
|
1674
|
+
else if (overlay.type === 'zscore') {
|
|
1675
|
+
overlaySeries = computeZScoreOverlay(overlay, sourceResult.data, paneId);
|
|
1676
|
+
}
|
|
1068
1677
|
else if (overlay.type === 'macd') {
|
|
1069
1678
|
overlaySeries = computeMacdOverlay(overlay, sourceResult.data, paneId);
|
|
1070
1679
|
}
|
|
@@ -1086,6 +1695,15 @@ export function expandChartOverlays(baseSeries, overlays) {
|
|
|
1086
1695
|
else if (overlay.type === 'bollinger') {
|
|
1087
1696
|
overlaySeries = computeBollingerOverlay(overlay, sourceResult.data);
|
|
1088
1697
|
}
|
|
1698
|
+
else if (overlay.type === 'donchian') {
|
|
1699
|
+
overlaySeries = computeDonchianOverlay(overlay, sourceResult.data);
|
|
1700
|
+
}
|
|
1701
|
+
else if (overlay.type === 'supertrend') {
|
|
1702
|
+
overlaySeries = computeSupertrendOverlay(overlay, sourceResult.data);
|
|
1703
|
+
}
|
|
1704
|
+
else if (overlay.type === 'ichimoku') {
|
|
1705
|
+
overlaySeries = computeIchimokuOverlay(overlay, sourceResult.data);
|
|
1706
|
+
}
|
|
1089
1707
|
else {
|
|
1090
1708
|
return { ok: false, reason: `Unsupported overlay type.` };
|
|
1091
1709
|
}
|