@363045841yyt/klinechart-core 0.7.9 → 0.7.11
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/engine/chart.d.ts.map +1 -1
- package/dist/engine/chart.js +39 -229
- package/dist/engine/chart.js.map +1 -1
- package/dist/engine/indicators/indicatorDefinitionRegistry.d.ts +10 -3
- package/dist/engine/indicators/indicatorDefinitionRegistry.d.ts.map +1 -1
- package/dist/engine/indicators/indicatorDefinitionRegistry.js +29 -2
- package/dist/engine/indicators/indicatorDefinitionRegistry.js.map +1 -1
- package/dist/engine/indicators/indicatorMetadata.d.ts +79 -2
- package/dist/engine/indicators/indicatorMetadata.d.ts.map +1 -1
- package/dist/engine/indicators/indicatorMetadata.js.map +1 -1
- package/dist/engine/indicators/indicatorRegistry.d.ts +5 -0
- package/dist/engine/indicators/indicatorRegistry.d.ts.map +1 -1
- package/dist/engine/indicators/indicatorRegistry.js +49 -5
- package/dist/engine/indicators/indicatorRegistry.js.map +1 -1
- package/dist/engine/indicators/registerBuiltins.d.ts +42 -0
- package/dist/engine/indicators/registerBuiltins.d.ts.map +1 -0
- package/dist/engine/indicators/registerBuiltins.js +46 -0
- package/dist/engine/indicators/registerBuiltins.js.map +1 -0
- package/dist/engine/indicators/scheduler.d.ts +2 -2
- package/dist/engine/indicators/scheduler.d.ts.map +1 -1
- package/dist/engine/indicators/scheduler.js +41 -11
- package/dist/engine/indicators/scheduler.js.map +1 -1
- package/dist/engine/indicators/stateComposer.d.ts +6 -4
- package/dist/engine/indicators/stateComposer.d.ts.map +1 -1
- package/dist/engine/indicators/stateComposer.js +80 -984
- package/dist/engine/indicators/stateComposer.js.map +1 -1
- package/dist/engine/indicators/visibleStateComposers.d.ts +145 -0
- package/dist/engine/indicators/visibleStateComposers.d.ts.map +1 -0
- package/dist/engine/indicators/visibleStateComposers.js +469 -0
- package/dist/engine/indicators/visibleStateComposers.js.map +1 -0
- package/dist/engine/renderers/Indicator/atr.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/atr.js +8 -0
- package/dist/engine/renderers/Indicator/atr.js.map +1 -1
- package/dist/engine/renderers/Indicator/boll.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/boll.js +47 -0
- package/dist/engine/renderers/Indicator/boll.js.map +1 -1
- package/dist/engine/renderers/Indicator/cci.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/cci.js +8 -1
- package/dist/engine/renderers/Indicator/cci.js.map +1 -1
- package/dist/engine/renderers/Indicator/chaikinVol.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/chaikinVol.js +7 -1
- package/dist/engine/renderers/Indicator/chaikinVol.js.map +1 -1
- package/dist/engine/renderers/Indicator/cmf.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/cmf.js +7 -1
- package/dist/engine/renderers/Indicator/cmf.js.map +1 -1
- package/dist/engine/renderers/Indicator/dema.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/dema.js +8 -1
- package/dist/engine/renderers/Indicator/dema.js.map +1 -1
- package/dist/engine/renderers/Indicator/donchian.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/donchian.js +8 -1
- package/dist/engine/renderers/Indicator/donchian.js.map +1 -1
- package/dist/engine/renderers/Indicator/ene.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/ene.js +45 -0
- package/dist/engine/renderers/Indicator/ene.js.map +1 -1
- package/dist/engine/renderers/Indicator/expma.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/expma.js +45 -0
- package/dist/engine/renderers/Indicator/expma.js.map +1 -1
- package/dist/engine/renderers/Indicator/fastk.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/fastk.js +8 -1
- package/dist/engine/renderers/Indicator/fastk.js.map +1 -1
- package/dist/engine/renderers/Indicator/fib.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/fib.js +8 -1
- package/dist/engine/renderers/Indicator/fib.js.map +1 -1
- package/dist/engine/renderers/Indicator/hma.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/hma.js +8 -1
- package/dist/engine/renderers/Indicator/hma.js.map +1 -1
- package/dist/engine/renderers/Indicator/hv.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/hv.js +7 -0
- package/dist/engine/renderers/Indicator/hv.js.map +1 -1
- package/dist/engine/renderers/Indicator/ichimoku.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/ichimoku.js +8 -1
- package/dist/engine/renderers/Indicator/ichimoku.js.map +1 -1
- package/dist/engine/renderers/Indicator/index.d.ts +7 -2
- package/dist/engine/renderers/Indicator/index.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/index.js +2 -114
- package/dist/engine/renderers/Indicator/index.js.map +1 -1
- package/dist/engine/renderers/Indicator/kama.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/kama.js +8 -1
- package/dist/engine/renderers/Indicator/kama.js.map +1 -1
- package/dist/engine/renderers/Indicator/keltner.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/keltner.js +8 -1
- package/dist/engine/renderers/Indicator/keltner.js.map +1 -1
- package/dist/engine/renderers/Indicator/kst.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/kst.js +8 -0
- package/dist/engine/renderers/Indicator/kst.js.map +1 -1
- package/dist/engine/renderers/Indicator/ma.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/ma.js +63 -0
- package/dist/engine/renderers/Indicator/ma.js.map +1 -1
- package/dist/engine/renderers/Indicator/macd.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/macd.js +8 -1
- package/dist/engine/renderers/Indicator/macd.js.map +1 -1
- package/dist/engine/renderers/Indicator/mfi.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/mfi.js +7 -1
- package/dist/engine/renderers/Indicator/mfi.js.map +1 -1
- package/dist/engine/renderers/Indicator/mom.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/mom.js +8 -0
- package/dist/engine/renderers/Indicator/mom.js.map +1 -1
- package/dist/engine/renderers/Indicator/obv.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/obv.js +7 -1
- package/dist/engine/renderers/Indicator/obv.js.map +1 -1
- package/dist/engine/renderers/Indicator/parkinson.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/parkinson.js +7 -0
- package/dist/engine/renderers/Indicator/parkinson.js.map +1 -1
- package/dist/engine/renderers/Indicator/pivot.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/pivot.js +8 -1
- package/dist/engine/renderers/Indicator/pivot.js.map +1 -1
- package/dist/engine/renderers/Indicator/pvt.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/pvt.js +7 -1
- package/dist/engine/renderers/Indicator/pvt.js.map +1 -1
- package/dist/engine/renderers/Indicator/roc.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/roc.js +7 -1
- package/dist/engine/renderers/Indicator/roc.js.map +1 -1
- package/dist/engine/renderers/Indicator/rsi.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/rsi.js +8 -1
- package/dist/engine/renderers/Indicator/rsi.js.map +1 -1
- package/dist/engine/renderers/Indicator/sar.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/sar.js +8 -1
- package/dist/engine/renderers/Indicator/sar.js.map +1 -1
- package/dist/engine/renderers/Indicator/stoch.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/stoch.js +8 -1
- package/dist/engine/renderers/Indicator/stoch.js.map +1 -1
- package/dist/engine/renderers/Indicator/structure.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/structure.js +8 -1
- package/dist/engine/renderers/Indicator/structure.js.map +1 -1
- package/dist/engine/renderers/Indicator/subPaneConfig.d.ts +1 -1
- package/dist/engine/renderers/Indicator/subPaneConfig.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/subPaneConfig.js.map +1 -1
- package/dist/engine/renderers/Indicator/supertrend.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/supertrend.js +8 -1
- package/dist/engine/renderers/Indicator/supertrend.js.map +1 -1
- package/dist/engine/renderers/Indicator/tema.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/tema.js +8 -1
- package/dist/engine/renderers/Indicator/tema.js.map +1 -1
- package/dist/engine/renderers/Indicator/trix.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/trix.js +7 -0
- package/dist/engine/renderers/Indicator/trix.js.map +1 -1
- package/dist/engine/renderers/Indicator/vma.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/vma.js +7 -0
- package/dist/engine/renderers/Indicator/vma.js.map +1 -1
- package/dist/engine/renderers/Indicator/volumeProfile.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/volumeProfile.js +7 -1
- package/dist/engine/renderers/Indicator/volumeProfile.js.map +1 -1
- package/dist/engine/renderers/Indicator/vwap.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/vwap.js +7 -1
- package/dist/engine/renderers/Indicator/vwap.js.map +1 -1
- package/dist/engine/renderers/Indicator/wma.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/wma.js +8 -1
- package/dist/engine/renderers/Indicator/wma.js.map +1 -1
- package/dist/engine/renderers/Indicator/wmsr.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/wmsr.js +8 -1
- package/dist/engine/renderers/Indicator/wmsr.js.map +1 -1
- package/dist/engine/renderers/Indicator/zones.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/zones.js +8 -1
- package/dist/engine/renderers/Indicator/zones.js.map +1 -1
- package/dist/engine/renderers/extremaMarkers.d.ts.map +1 -1
- package/dist/engine/renderers/extremaMarkers.js +0 -22
- package/dist/engine/renderers/extremaMarkers.js.map +1 -1
- package/dist/engine/renderers/subVolume.d.ts.map +1 -1
- package/dist/engine/renderers/subVolume.js +3 -0
- package/dist/engine/renderers/subVolume.js.map +1 -1
- package/dist/engine/subPaneManager.d.ts +1 -0
- package/dist/engine/subPaneManager.d.ts.map +1 -1
- package/dist/engine/subPaneManager.js +30 -237
- package/dist/engine/subPaneManager.js.map +1 -1
- package/dist/semantic/controller.d.ts +9 -4
- package/dist/semantic/controller.d.ts.map +1 -1
- package/dist/semantic/controller.js +57 -53
- package/dist/semantic/controller.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/src/engine/chart.ts +49 -229
- package/src/engine/indicators/__tests__/indicatorDefinitionRegistry.test.ts +59 -0
- package/src/engine/indicators/__tests__/indicatorRegistry.test.ts +62 -0
- package/src/engine/indicators/__tests__/registerBuiltins.test.ts +380 -0
- package/src/engine/indicators/__tests__/scheduler.test.ts +62 -32
- package/src/engine/indicators/__tests__/stateComposer.test.ts +444 -0
- package/src/engine/indicators/indicatorDefinitionRegistry.ts +49 -4
- package/src/engine/indicators/indicatorMetadata.ts +107 -2
- package/src/engine/indicators/indicatorRegistry.ts +54 -5
- package/src/engine/indicators/registerBuiltins.ts +47 -0
- package/src/engine/indicators/scheduler.ts +61 -13
- package/src/engine/indicators/stateComposer.ts +110 -1040
- package/src/engine/indicators/visibleStateComposers.ts +734 -0
- package/src/engine/renderers/Indicator/__tests__/createSubIndicatorRenderer.test.ts +61 -0
- package/src/engine/renderers/Indicator/atr.ts +9 -1
- package/src/engine/renderers/Indicator/boll.ts +53 -1
- package/src/engine/renderers/Indicator/cci.ts +9 -2
- package/src/engine/renderers/Indicator/chaikinVol.ts +8 -2
- package/src/engine/renderers/Indicator/cmf.ts +8 -2
- package/src/engine/renderers/Indicator/dema.ts +9 -2
- package/src/engine/renderers/Indicator/donchian.ts +9 -2
- package/src/engine/renderers/Indicator/ene.ts +51 -1
- package/src/engine/renderers/Indicator/expma.ts +51 -1
- package/src/engine/renderers/Indicator/fastk.ts +9 -2
- package/src/engine/renderers/Indicator/fib.ts +9 -2
- package/src/engine/renderers/Indicator/hma.ts +9 -2
- package/src/engine/renderers/Indicator/hv.ts +8 -1
- package/src/engine/renderers/Indicator/ichimoku.ts +9 -2
- package/src/engine/renderers/Indicator/index.ts +9 -121
- package/src/engine/renderers/Indicator/kama.ts +9 -2
- package/src/engine/renderers/Indicator/keltner.ts +9 -2
- package/src/engine/renderers/Indicator/kst.ts +9 -1
- package/src/engine/renderers/Indicator/ma.ts +69 -0
- package/src/engine/renderers/Indicator/macd.ts +9 -2
- package/src/engine/renderers/Indicator/mfi.ts +8 -2
- package/src/engine/renderers/Indicator/mom.ts +9 -1
- package/src/engine/renderers/Indicator/obv.ts +8 -2
- package/src/engine/renderers/Indicator/parkinson.ts +8 -1
- package/src/engine/renderers/Indicator/pivot.ts +9 -2
- package/src/engine/renderers/Indicator/pvt.ts +8 -2
- package/src/engine/renderers/Indicator/roc.ts +8 -2
- package/src/engine/renderers/Indicator/rsi.ts +9 -2
- package/src/engine/renderers/Indicator/sar.ts +9 -2
- package/src/engine/renderers/Indicator/stoch.ts +9 -2
- package/src/engine/renderers/Indicator/structure.ts +9 -2
- package/src/engine/renderers/Indicator/subPaneConfig.ts +1 -1
- package/src/engine/renderers/Indicator/supertrend.ts +9 -2
- package/src/engine/renderers/Indicator/tema.ts +9 -2
- package/src/engine/renderers/Indicator/trix.ts +8 -1
- package/src/engine/renderers/Indicator/vma.ts +8 -1
- package/src/engine/renderers/Indicator/volumeProfile.ts +8 -2
- package/src/engine/renderers/Indicator/vwap.ts +8 -2
- package/src/engine/renderers/Indicator/wma.ts +9 -2
- package/src/engine/renderers/Indicator/wmsr.ts +9 -2
- package/src/engine/renderers/Indicator/zones.ts +9 -2
- package/src/engine/renderers/extremaMarkers.ts +0 -24
- package/src/engine/renderers/subVolume.ts +3 -0
- package/src/engine/subPaneManager.ts +34 -274
- package/src/semantic/__tests__/controller.test.ts +84 -0
- package/src/semantic/controller.ts +69 -65
- package/src/version.ts +1 -1
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import type { IndicatorMetadata } from '../indicatorMetadata'
|
|
3
|
+
import { composeRenderStates, composeVisibleSubIndicatorStates, computeMainIndicatorPriceRange } from '../stateComposer'
|
|
4
|
+
import type { IndicatorSeriesBundle } from '../workerProtocol'
|
|
5
|
+
import '../registerBuiltins'
|
|
6
|
+
import { getRegisteredIndicatorDefinition } from '../indicatorDefinitionRegistry'
|
|
7
|
+
|
|
8
|
+
function createBundle(): IndicatorSeriesBundle {
|
|
9
|
+
return {
|
|
10
|
+
ma: { series: {}, enabledPeriods: [] },
|
|
11
|
+
boll: { series: [], params: {} as never },
|
|
12
|
+
expma: { series: [], params: {} as never },
|
|
13
|
+
ene: { series: [], params: {} as never },
|
|
14
|
+
rsi: { series: {}, enabledPeriods: [], params: {} as never },
|
|
15
|
+
cci: { series: [], params: {} as never },
|
|
16
|
+
stoch: { series: [], params: {} as never },
|
|
17
|
+
mom: { series: [], params: {} as never },
|
|
18
|
+
wmsr: { series: [], params: {} as never },
|
|
19
|
+
kst: { series: [], params: {} as never },
|
|
20
|
+
fastk: { series: [], params: {} as never },
|
|
21
|
+
macd: { series: [], params: {} as never },
|
|
22
|
+
atr: { series: [], params: {} as never },
|
|
23
|
+
wma: { series: [], params: {} as never },
|
|
24
|
+
dema: { series: [], params: {} as never },
|
|
25
|
+
tema: { series: [], params: {} as never },
|
|
26
|
+
hma: { series: [], params: {} as never },
|
|
27
|
+
kama: { series: [], params: {} as never },
|
|
28
|
+
sar: { series: [], params: {} as never },
|
|
29
|
+
supertrend: { series: [], params: {} as never },
|
|
30
|
+
keltner: { series: [], params: {} as never },
|
|
31
|
+
donchian: { series: [], params: {} as never },
|
|
32
|
+
ichimoku: { series: [], params: {} as never },
|
|
33
|
+
roc: { series: [], params: {} as never },
|
|
34
|
+
trix: { series: [], signalSeries: [], params: {} as never },
|
|
35
|
+
hv: { series: [], params: {} as never },
|
|
36
|
+
parkinson: { series: [], params: {} as never },
|
|
37
|
+
chaikinVol: { series: [], params: {} as never },
|
|
38
|
+
vma: { series: [], params: {} as never },
|
|
39
|
+
obv: { series: [], params: {} as never },
|
|
40
|
+
pvt: { series: [], params: {} as never },
|
|
41
|
+
vwap: { series: [], params: {} as never },
|
|
42
|
+
cmf: { series: [], params: {} as never },
|
|
43
|
+
mfi: { series: [], params: {} as never },
|
|
44
|
+
pivot: { series: [], params: {} as never },
|
|
45
|
+
fib: { series: [], params: {} as never },
|
|
46
|
+
structure: { series: { swings: [], breakouts: [] }, params: {} as never },
|
|
47
|
+
zones: { series: [], params: {} as never },
|
|
48
|
+
volumeProfile: { series: { bins: [], vah: 0, val: 0, poc: 0 }, params: {} as never },
|
|
49
|
+
_changed: [],
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function createDefinition(range: { min: number; max: number } | null): IndicatorMetadata {
|
|
54
|
+
return {
|
|
55
|
+
name: 'test',
|
|
56
|
+
displayName: 'Test',
|
|
57
|
+
category: 'main',
|
|
58
|
+
stateKey: 'indicator:test:main',
|
|
59
|
+
defaultPaneId: 'main',
|
|
60
|
+
rendererFactory: vi.fn() as never,
|
|
61
|
+
mainPane: {
|
|
62
|
+
rendererName: 'test',
|
|
63
|
+
computePriceRange: vi.fn(() => range),
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function createComposerDefinition(id: string, state: unknown): IndicatorMetadata {
|
|
69
|
+
return {
|
|
70
|
+
name: id,
|
|
71
|
+
displayName: id.toUpperCase(),
|
|
72
|
+
category: 'main',
|
|
73
|
+
stateKey: `indicator:${id}:main`,
|
|
74
|
+
defaultPaneId: 'main',
|
|
75
|
+
rendererFactory: vi.fn() as never,
|
|
76
|
+
mainPane: {
|
|
77
|
+
rendererName: id,
|
|
78
|
+
composeRenderState: vi.fn(() => state),
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function createVisibleStateDefinition(id: string, state: unknown): IndicatorMetadata {
|
|
84
|
+
return {
|
|
85
|
+
name: id,
|
|
86
|
+
displayName: id.toUpperCase(),
|
|
87
|
+
category: 'oscillator',
|
|
88
|
+
stateKey: `indicator:${id}:sub_${id}`,
|
|
89
|
+
defaultPaneId: `sub_${id}`,
|
|
90
|
+
rendererFactory: vi.fn() as never,
|
|
91
|
+
visibleState: {
|
|
92
|
+
compose: vi.fn(() => state),
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getComposerMetadata(id: string): IndicatorMetadata | undefined {
|
|
98
|
+
return getRegisteredIndicatorDefinition(id)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
describe('stateComposer', () => {
|
|
102
|
+
it('computes main indicator price range through metadata', () => {
|
|
103
|
+
const definitions = new Map<string, IndicatorMetadata>([
|
|
104
|
+
['ma', createDefinition({ min: 10, max: 20 })],
|
|
105
|
+
['boll', createDefinition({ min: 5, max: 30 })],
|
|
106
|
+
])
|
|
107
|
+
|
|
108
|
+
const range = computeMainIndicatorPriceRange(
|
|
109
|
+
createBundle(),
|
|
110
|
+
{ start: 1, end: 3 },
|
|
111
|
+
new Set(['ma', 'boll']),
|
|
112
|
+
(indicatorId) => definitions.get(indicatorId),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
expect(range).toEqual({ min: 5, max: 30 })
|
|
116
|
+
expect(definitions.get('ma')?.mainPane?.computePriceRange).toHaveBeenCalledWith(createBundle(), { start: 1, end: 3 })
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('ignores inactive or missing main price range metadata', () => {
|
|
120
|
+
const definitions = new Map<string, IndicatorMetadata>([
|
|
121
|
+
['ma', createDefinition(null)],
|
|
122
|
+
['boll', { ...createDefinition({ min: 1, max: 2 }), mainPane: { rendererName: 'boll' } }],
|
|
123
|
+
])
|
|
124
|
+
|
|
125
|
+
expect(
|
|
126
|
+
computeMainIndicatorPriceRange(
|
|
127
|
+
createBundle(),
|
|
128
|
+
{ start: 0, end: 1 },
|
|
129
|
+
new Set(['ma', 'boll', 'missing']),
|
|
130
|
+
(indicatorId) => definitions.get(indicatorId),
|
|
131
|
+
),
|
|
132
|
+
).toBeNull()
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('composes main render states through metadata', () => {
|
|
136
|
+
const bundle = createBundle()
|
|
137
|
+
const timestamp = 1234
|
|
138
|
+
const visibleRange = { start: 1, end: 3 }
|
|
139
|
+
const definitions = new Map<string, IndicatorMetadata>([
|
|
140
|
+
['ma', createComposerDefinition('ma', { timestamp, series: { 5: [undefined, 10, 12] }, enabledPeriods: [5], visibleMin: 10, visibleMax: 12 })],
|
|
141
|
+
['boll', createComposerDefinition('boll', { timestamp, series: [], params: {}, visibleMin: 20, visibleMax: 30 })],
|
|
142
|
+
['expma', createComposerDefinition('expma', { timestamp, series: [], params: {}, visibleMin: 7, visibleMax: 9 })],
|
|
143
|
+
['ene', createComposerDefinition('ene', { timestamp, series: [], params: {}, visibleMin: 8, visibleMax: 11 })],
|
|
144
|
+
])
|
|
145
|
+
|
|
146
|
+
const states = composeRenderStates(bundle, visibleRange, timestamp, (indicatorId) => definitions.get(indicatorId))
|
|
147
|
+
|
|
148
|
+
expect(states.ma.visibleMin).toBe(10)
|
|
149
|
+
expect(states.boll.visibleMax).toBe(30)
|
|
150
|
+
expect(states.expma.visibleMin).toBe(7)
|
|
151
|
+
expect(states.ene.visibleMax).toBe(11)
|
|
152
|
+
expect(definitions.get('ma')?.mainPane?.composeRenderState).toHaveBeenCalledWith(bundle, visibleRange, timestamp)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('throws when main render state composer metadata is missing', () => {
|
|
156
|
+
const definition = { ...createDefinition(null), mainPane: { rendererName: 'ma' } }
|
|
157
|
+
|
|
158
|
+
expect(() => composeRenderStates(createBundle(), { start: 0, end: 1 }, 1, (id) => id === 'ma' ? definition : undefined)).toThrow(
|
|
159
|
+
'[StateComposer] Missing mainPane.composeRenderState for ma',
|
|
160
|
+
)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
it('applies symmetric abs padding for MOM via metadata composer', () => {
|
|
172
|
+
const bundle = createBundle()
|
|
173
|
+
bundle.mom.series = [undefined, -2, 4]
|
|
174
|
+
bundle.mom.params = { period: 10, showMOM: true } as never
|
|
175
|
+
const timestamp = 1000
|
|
176
|
+
const visibleRange = { start: 1, end: 3 }
|
|
177
|
+
|
|
178
|
+
const states = composeVisibleSubIndicatorStates(bundle, visibleRange, timestamp, { mom: true }, getComposerMetadata)
|
|
179
|
+
|
|
180
|
+
expect(states.mom.visibleMin).toBe(-2)
|
|
181
|
+
expect(states.mom.visibleMax).toBe(4)
|
|
182
|
+
expect(states.mom.valueMin).toBeCloseTo(-2.4)
|
|
183
|
+
expect(states.mom.valueMax).toBeCloseTo(4.4)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('applies range padding for KST via metadata composer', () => {
|
|
187
|
+
const bundle = createBundle()
|
|
188
|
+
bundle.kst.series = [{ kst: -3, signal: 5 }]
|
|
189
|
+
bundle.kst.params = { roc1: 10, roc2: 15, roc3: 20, roc4: 30, signalPeriod: 9, showKST: true, showSignal: true } as never
|
|
190
|
+
const timestamp = 1000
|
|
191
|
+
const visibleRange = { start: 0, end: 1 }
|
|
192
|
+
|
|
193
|
+
const states = composeVisibleSubIndicatorStates(bundle, visibleRange, timestamp, { kst: true }, getComposerMetadata)
|
|
194
|
+
|
|
195
|
+
expect(states.kst.visibleMin).toBe(-3)
|
|
196
|
+
expect(states.kst.visibleMax).toBe(5)
|
|
197
|
+
expect(states.kst.valueMin).toBeCloseTo(-3.8)
|
|
198
|
+
expect(states.kst.valueMax).toBeCloseTo(5.8)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('applies non-negative upper padding for ATR via metadata composer', () => {
|
|
202
|
+
const bundle = createBundle()
|
|
203
|
+
bundle.atr.series = [undefined, 1, 5]
|
|
204
|
+
bundle.atr.params = { period: 14, showATR: true } as never
|
|
205
|
+
const timestamp = 1000
|
|
206
|
+
const visibleRange = { start: 1, end: 3 }
|
|
207
|
+
|
|
208
|
+
const states = composeVisibleSubIndicatorStates(bundle, visibleRange, timestamp, { atr: true }, getComposerMetadata)
|
|
209
|
+
|
|
210
|
+
expect(states.atr.visibleMin).toBe(1)
|
|
211
|
+
expect(states.atr.visibleMax).toBe(5)
|
|
212
|
+
expect(states.atr.valueMin).toBe(0)
|
|
213
|
+
expect(states.atr.valueMax).toBeCloseTo(5.5)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('combines series and signalSeries extremes for TRIX via metadata composer', () => {
|
|
217
|
+
const bundle = createBundle()
|
|
218
|
+
bundle.trix.series = [undefined, 1, 3]
|
|
219
|
+
bundle.trix.signalSeries = [100, -2, 2]
|
|
220
|
+
bundle.trix.params = { period: 15, signalPeriod: 9, showTRIX: true, showSignal: true } as never
|
|
221
|
+
const timestamp = 1000
|
|
222
|
+
const visibleRange = { start: 1, end: 3 }
|
|
223
|
+
|
|
224
|
+
const states = composeVisibleSubIndicatorStates(bundle, visibleRange, timestamp, { trix: true }, getComposerMetadata)
|
|
225
|
+
|
|
226
|
+
expect(states.trix.visibleMin).toBe(-2)
|
|
227
|
+
expect(states.trix.visibleMax).toBe(3)
|
|
228
|
+
expect(states.trix.valueMin).toBeCloseTo(-2.25)
|
|
229
|
+
expect(states.trix.valueMax).toBeCloseTo(3.25)
|
|
230
|
+
expect((states.trix as any).signalSeries).toBe(bundle.trix.signalSeries)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
it('applies symmetric abs padding for MACD via metadata composer', () => {
|
|
234
|
+
const bundle = createBundle()
|
|
235
|
+
bundle.macd.series = [{ dif: -2, dea: 1, macd: 4 }]
|
|
236
|
+
bundle.macd.params = { fastPeriod: 12, slowPeriod: 26, signalPeriod: 9, showDIF: true, showDEA: true, showBAR: true } as never
|
|
237
|
+
const timestamp = 1000
|
|
238
|
+
const visibleRange = { start: 0, end: 1 }
|
|
239
|
+
|
|
240
|
+
const states = composeVisibleSubIndicatorStates(bundle, visibleRange, timestamp, { macd: true }, getComposerMetadata)
|
|
241
|
+
|
|
242
|
+
expect(states.macd.visibleMin).toBe(-2)
|
|
243
|
+
expect(states.macd.visibleMax).toBe(4)
|
|
244
|
+
expect(states.macd.valueMin).toBeCloseTo(-2.4)
|
|
245
|
+
expect(states.macd.valueMax).toBeCloseTo(4.4)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('sets MACD latestValues via metadata composer', () => {
|
|
249
|
+
const bundle = createBundle()
|
|
250
|
+
bundle.macd.series = [{ dif: -2, dea: 1, macd: 4 }, { dif: -1, dea: 2, macd: 5 }]
|
|
251
|
+
bundle.macd.params = { fastPeriod: 12, slowPeriod: 26, signalPeriod: 9, showDIF: true, showDEA: true, showBAR: true } as never
|
|
252
|
+
const timestamp = 1000
|
|
253
|
+
const visibleRange = { start: 0, end: 2 }
|
|
254
|
+
|
|
255
|
+
const states = composeVisibleSubIndicatorStates(bundle, visibleRange, timestamp, { macd: true }, getComposerMetadata)
|
|
256
|
+
|
|
257
|
+
expect(states.macd.latestValues).toEqual({ dif: -1, dea: 2, macd: 5 })
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('preserves empty MACD state when MACD is inactive via metadata composer', () => {
|
|
261
|
+
const bundle = createBundle()
|
|
262
|
+
bundle.macd.series = [{ dif: -2, dea: 1, macd: 4 }]
|
|
263
|
+
bundle.macd.params = { fastPeriod: 12, slowPeriod: 26, signalPeriod: 9, showDIF: true, showDEA: true, showBAR: true } as never
|
|
264
|
+
const timestamp = 1000
|
|
265
|
+
const visibleRange = { start: 0, end: 1 }
|
|
266
|
+
|
|
267
|
+
const states = composeVisibleSubIndicatorStates(bundle, visibleRange, timestamp, { macd: false }, getComposerMetadata)
|
|
268
|
+
|
|
269
|
+
expect(states.macd.valueMin).toBe(-Infinity)
|
|
270
|
+
expect(states.macd.valueMax).toBe(Infinity)
|
|
271
|
+
expect(states.macd.series).toBe(bundle.macd.series)
|
|
272
|
+
expect(states.macd.params).toBe(bundle.macd.params)
|
|
273
|
+
expect(states.macd.timestamp).toBe(timestamp)
|
|
274
|
+
expect(states.macd.latestValues).toBeUndefined()
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
it('applies maFamilyBounds padding for value-point overlay via metadata composer (sar)', () => {
|
|
280
|
+
const bundle = createBundle()
|
|
281
|
+
bundle.sar.series = [{ value: 5, trend: 'up' as const }, { value: 15, trend: 'up' as const }]
|
|
282
|
+
bundle.sar.params = { period: 20, maxPeriod: 22, minPeriod: 2, af: 0.02, afMax: 0.2, showSAR: true } as never
|
|
283
|
+
const timestamp = 1000
|
|
284
|
+
const visibleRange = { start: 0, end: 2 }
|
|
285
|
+
|
|
286
|
+
const states = composeVisibleSubIndicatorStates(bundle, visibleRange, timestamp, { sar: true }, getComposerMetadata)
|
|
287
|
+
|
|
288
|
+
expect(states.sar.visibleMin).toBe(5)
|
|
289
|
+
expect(states.sar.visibleMax).toBe(15)
|
|
290
|
+
expect(states.sar.valueMin).toBeCloseTo(4.5)
|
|
291
|
+
expect(states.sar.valueMax).toBeCloseTo(15.5)
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it('applies maFamilyBounds padding for band overlay via metadata composer (keltner)', () => {
|
|
295
|
+
const bundle = createBundle()
|
|
296
|
+
bundle.keltner.series = [{ upper: 20, middle: 15, lower: 10 }]
|
|
297
|
+
bundle.keltner.params = { period: 20, multiplier: 2, showKeltner: true, showMiddle: true } as never
|
|
298
|
+
const timestamp = 1000
|
|
299
|
+
const visibleRange = { start: 0, end: 1 }
|
|
300
|
+
|
|
301
|
+
const states = composeVisibleSubIndicatorStates(bundle, visibleRange, timestamp, { keltner: true }, getComposerMetadata)
|
|
302
|
+
|
|
303
|
+
expect(states.keltner.visibleMin).toBe(10)
|
|
304
|
+
expect(states.keltner.visibleMax).toBe(20)
|
|
305
|
+
expect(states.keltner.valueMin).toBeCloseTo(9.5)
|
|
306
|
+
expect(states.keltner.valueMax).toBeCloseTo(20.5)
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it('applies exact extremes for pivot overlay via metadata composer', () => {
|
|
310
|
+
const bundle = createBundle()
|
|
311
|
+
bundle.pivot.series = [{ pp: 100, r1: 105, r2: 110, r3: 115, s1: 95, s2: 90, s3: 85 }]
|
|
312
|
+
bundle.pivot.params = { showPivot: true, showR1: true, showS1: true } as never
|
|
313
|
+
const timestamp = 1000
|
|
314
|
+
const visibleRange = { start: 0, end: 1 }
|
|
315
|
+
|
|
316
|
+
const states = composeVisibleSubIndicatorStates(bundle, visibleRange, timestamp, { pivot: true }, getComposerMetadata)
|
|
317
|
+
|
|
318
|
+
expect(states.pivot.visibleMin).toBe(85)
|
|
319
|
+
expect(states.pivot.visibleMax).toBe(115)
|
|
320
|
+
expect(states.pivot.valueMin).toBe(85)
|
|
321
|
+
expect(states.pivot.valueMax).toBe(115)
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
it('uses fixed unit range for structure overlay via metadata composer', () => {
|
|
325
|
+
const bundle = createBundle()
|
|
326
|
+
bundle.structure.series = { swings: [], breakouts: [] }
|
|
327
|
+
bundle.structure.params = { showStructure: true } as never
|
|
328
|
+
const timestamp = 1000
|
|
329
|
+
const visibleRange = { start: 0, end: 1 }
|
|
330
|
+
|
|
331
|
+
const states = composeVisibleSubIndicatorStates(bundle, visibleRange, timestamp, { structure: true }, getComposerMetadata)
|
|
332
|
+
|
|
333
|
+
expect(states.structure.valueMin).toBe(0)
|
|
334
|
+
expect(states.structure.valueMax).toBe(1)
|
|
335
|
+
expect(states.structure.visibleMin).toBe(0)
|
|
336
|
+
expect(states.structure.visibleMax).toBe(1)
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
it('derives volumeProfile range from bins and val/vah via metadata composer', () => {
|
|
340
|
+
const bundle = createBundle()
|
|
341
|
+
bundle.volumeProfile.series = {
|
|
342
|
+
bins: [{ priceLow: 95, priceHigh: 105, volume: 10, poc: false }],
|
|
343
|
+
vah: 103,
|
|
344
|
+
val: 97,
|
|
345
|
+
poc: 100,
|
|
346
|
+
}
|
|
347
|
+
bundle.volumeProfile.params = { showVolumeProfile: true } as never
|
|
348
|
+
const timestamp = 1000
|
|
349
|
+
const visibleRange = { start: 0, end: 1 }
|
|
350
|
+
|
|
351
|
+
const states = composeVisibleSubIndicatorStates(bundle, visibleRange, timestamp, { volumeProfile: true }, getComposerMetadata)
|
|
352
|
+
|
|
353
|
+
expect(states.volumeProfile.valueMin).toBe(95)
|
|
354
|
+
expect(states.volumeProfile.valueMax).toBe(105)
|
|
355
|
+
expect(states.volumeProfile.visibleMin).toBe(97)
|
|
356
|
+
expect(states.volumeProfile.visibleMax).toBe(103)
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
it('applies CCI clamp bounds via metadata composer', () => {
|
|
360
|
+
const bundle = createBundle()
|
|
361
|
+
bundle.cci.series = [-200, 100]
|
|
362
|
+
bundle.cci.params = { period: 14, showCCI: true } as never
|
|
363
|
+
const timestamp = 1000
|
|
364
|
+
const visibleRange = { start: 0, end: 2 }
|
|
365
|
+
|
|
366
|
+
const states = composeVisibleSubIndicatorStates(
|
|
367
|
+
bundle, visibleRange, timestamp, { cci: true },
|
|
368
|
+
getComposerMetadata,
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
expect(states.cci.visibleMin).toBe(-200)
|
|
372
|
+
expect(states.cci.visibleMax).toBe(100)
|
|
373
|
+
expect(states.cci.valueMin).toBe(-200)
|
|
374
|
+
expect(states.cci.valueMax).toBe(150)
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
it('applies CCI lower clamp when extremes are within bounds', () => {
|
|
378
|
+
const bundle = createBundle()
|
|
379
|
+
bundle.cci.series = [-50, 30]
|
|
380
|
+
bundle.cci.params = { period: 14, showCCI: true } as never
|
|
381
|
+
const timestamp = 1000
|
|
382
|
+
const visibleRange = { start: 0, end: 2 }
|
|
383
|
+
|
|
384
|
+
const states = composeVisibleSubIndicatorStates(
|
|
385
|
+
bundle, visibleRange, timestamp, { cci: true },
|
|
386
|
+
getComposerMetadata,
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
expect(states.cci.visibleMin).toBe(-50)
|
|
390
|
+
expect(states.cci.visibleMax).toBe(30)
|
|
391
|
+
expect(states.cci.valueMin).toBe(-150)
|
|
392
|
+
expect(states.cci.valueMax).toBe(150)
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
it('preserves empty CCI state when CCI is inactive', () => {
|
|
396
|
+
const bundle = createBundle()
|
|
397
|
+
bundle.cci.series = [-200, 100]
|
|
398
|
+
bundle.cci.params = { period: 14, showCCI: true } as never
|
|
399
|
+
const timestamp = 1000
|
|
400
|
+
const visibleRange = { start: 0, end: 2 }
|
|
401
|
+
|
|
402
|
+
const states = composeVisibleSubIndicatorStates(
|
|
403
|
+
bundle, visibleRange, timestamp, { cci: false },
|
|
404
|
+
getComposerMetadata,
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
expect(states.cci.valueMin).toBe(-150)
|
|
408
|
+
expect(states.cci.valueMax).toBe(150)
|
|
409
|
+
expect(states.cci.visibleMin).toBe(Infinity)
|
|
410
|
+
expect(states.cci.visibleMax).toBe(-Infinity)
|
|
411
|
+
expect(states.cci.series).toBe(bundle.cci.series)
|
|
412
|
+
expect(states.cci.params).toBe(bundle.cci.params)
|
|
413
|
+
expect(states.cci.timestamp).toBe(1000)
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
it('throws when registered indicator lacks visibleState.compose', () => {
|
|
417
|
+
const missingCompose = (id: string) =>
|
|
418
|
+
id === 'cci'
|
|
419
|
+
? { name: 'cci', displayName: 'CCI', category: 'oscillator' as const, stateKey: '', defaultPaneId: '', rendererFactory: vi.fn() as never }
|
|
420
|
+
: undefined
|
|
421
|
+
|
|
422
|
+
expect(() =>
|
|
423
|
+
composeVisibleSubIndicatorStates(
|
|
424
|
+
createBundle(),
|
|
425
|
+
{ start: 0, end: 1 },
|
|
426
|
+
1000,
|
|
427
|
+
{},
|
|
428
|
+
missingCompose,
|
|
429
|
+
),
|
|
430
|
+
).toThrow('[StateComposer] Missing visibleState.compose for cci')
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
it('uses metadata path for all visible sub indicator states', () => {
|
|
434
|
+
const bundle = createBundle()
|
|
435
|
+
const timestamp = 1000
|
|
436
|
+
const visibleRange = { start: 0, end: 1 }
|
|
437
|
+
const states = composeVisibleSubIndicatorStates(
|
|
438
|
+
bundle, visibleRange, timestamp, {},
|
|
439
|
+
getComposerMetadata,
|
|
440
|
+
)
|
|
441
|
+
expect(states.cci.valueMin).toBe(-150)
|
|
442
|
+
expect(states.cci.valueMax).toBe(150)
|
|
443
|
+
})
|
|
444
|
+
})
|
|
@@ -1,15 +1,29 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
IndicatorMetadata,
|
|
3
|
+
IndicatorCategory,
|
|
4
|
+
StateKey,
|
|
5
|
+
RendererFactory,
|
|
6
|
+
ScaleRendererFactory,
|
|
7
|
+
IndicatorConfigUpdater,
|
|
8
|
+
} from './indicatorMetadata'
|
|
2
9
|
import type { PluginHost } from '../../plugin'
|
|
3
10
|
|
|
4
|
-
export
|
|
11
|
+
export type IndicatorDefinitionConfig<T = unknown> = {
|
|
5
12
|
name: string
|
|
13
|
+
aliases?: readonly string[]
|
|
6
14
|
displayName: string
|
|
7
15
|
category: IndicatorCategory
|
|
8
16
|
stateKey: StateKey
|
|
9
17
|
defaultPaneId: string
|
|
10
18
|
paneIdField?: keyof import('./workerProtocol').IndicatorConfigSnapshot
|
|
11
19
|
allowMainPane?: boolean
|
|
20
|
+
scaleRendererFactory?: ScaleRendererFactory
|
|
21
|
+
scale?: IndicatorMetadata['scale']
|
|
22
|
+
updateConfig?: IndicatorConfigUpdater
|
|
12
23
|
applyResult?: (host: PluginHost, state: unknown, paneId: string) => void
|
|
24
|
+
mainPane?: IndicatorMetadata['mainPane']
|
|
25
|
+
visibleState?: IndicatorMetadata['visibleState']
|
|
26
|
+
semantic?: IndicatorMetadata<T>['semantic']
|
|
13
27
|
}
|
|
14
28
|
|
|
15
29
|
type IndicatorDefinitionClass = {
|
|
@@ -18,6 +32,26 @@ type IndicatorDefinitionClass = {
|
|
|
18
32
|
}
|
|
19
33
|
|
|
20
34
|
const indicatorDefinitions = new Map<string, IndicatorMetadata>()
|
|
35
|
+
const indicatorDefinitionAliases = new Map<string, string>()
|
|
36
|
+
|
|
37
|
+
function normalizeIndicatorId(id: string): string {
|
|
38
|
+
return id.trim().toLowerCase().replace(/[^a-z0-9]/g, '')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function indexAlias(alias: string, name: string): void {
|
|
42
|
+
const normalized = normalizeIndicatorId(alias)
|
|
43
|
+
if (normalized) {
|
|
44
|
+
indicatorDefinitionAliases.set(normalized, name)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function removeAliasesFor(name: string): void {
|
|
49
|
+
for (const [alias, target] of indicatorDefinitionAliases) {
|
|
50
|
+
if (target === name) {
|
|
51
|
+
indicatorDefinitionAliases.delete(alias)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
21
55
|
|
|
22
56
|
/**
|
|
23
57
|
* 标准类装饰器:在模块加载时收集指标定义
|
|
@@ -36,13 +70,21 @@ export function Indicator(config: IndicatorDefinitionConfig) {
|
|
|
36
70
|
throw new Error(`[Indicator] '${config.name}' definition must expose static rendererFactory`)
|
|
37
71
|
}
|
|
38
72
|
|
|
39
|
-
|
|
73
|
+
const normalizedName = normalizeIndicatorId(config.name)
|
|
74
|
+
removeAliasesFor(normalizedName)
|
|
75
|
+
|
|
76
|
+
indicatorDefinitions.set(normalizedName, {
|
|
40
77
|
...config,
|
|
41
78
|
rendererFactory,
|
|
42
79
|
paneIdField: config.paneIdField,
|
|
43
80
|
allowMainPane: config.allowMainPane,
|
|
44
81
|
applyResult: config.applyResult,
|
|
45
82
|
})
|
|
83
|
+
indexAlias(config.name, normalizedName)
|
|
84
|
+
indexAlias(config.displayName, normalizedName)
|
|
85
|
+
for (const alias of config.aliases ?? []) {
|
|
86
|
+
indexAlias(alias, normalizedName)
|
|
87
|
+
}
|
|
46
88
|
})
|
|
47
89
|
|
|
48
90
|
return value
|
|
@@ -54,9 +96,12 @@ export function getRegisteredIndicatorDefinitions(): readonly IndicatorMetadata[
|
|
|
54
96
|
}
|
|
55
97
|
|
|
56
98
|
export function getRegisteredIndicatorDefinition(name: string): IndicatorMetadata | undefined {
|
|
57
|
-
|
|
99
|
+
const normalizedName = normalizeIndicatorId(name)
|
|
100
|
+
const canonicalName = indicatorDefinitionAliases.get(normalizedName) ?? normalizedName
|
|
101
|
+
return indicatorDefinitions.get(canonicalName)
|
|
58
102
|
}
|
|
59
103
|
|
|
60
104
|
export function clearRegisteredIndicatorDefinitionsForTest(): void {
|
|
61
105
|
indicatorDefinitions.clear()
|
|
106
|
+
indicatorDefinitionAliases.clear()
|
|
62
107
|
}
|
|
@@ -6,7 +6,23 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { PluginHost, RendererPluginWithHost } from '../../plugin'
|
|
9
|
-
import type { IndicatorConfigSnapshot } from './workerProtocol'
|
|
9
|
+
import type { IndicatorConfigSnapshot, IndicatorSeriesBundle } from './workerProtocol'
|
|
10
|
+
|
|
11
|
+
export type IndicatorId = string
|
|
12
|
+
|
|
13
|
+
export interface IndicatorRendererOptions {
|
|
14
|
+
paneId: string
|
|
15
|
+
indicatorId: IndicatorId
|
|
16
|
+
params?: Record<string, unknown>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface IndicatorScaleRendererOptions {
|
|
20
|
+
paneId: string
|
|
21
|
+
indicatorId: IndicatorId
|
|
22
|
+
axisWidth: number
|
|
23
|
+
yPaddingPx: number
|
|
24
|
+
getCrosshair: () => { y: number; price: number; activePaneId: string | null } | null
|
|
25
|
+
}
|
|
10
26
|
|
|
11
27
|
/**
|
|
12
28
|
* 指标分类:主图/副图
|
|
@@ -23,7 +39,51 @@ export type StateKey = string | ((paneId: string) => string)
|
|
|
23
39
|
/**
|
|
24
40
|
* 渲染器工厂函数
|
|
25
41
|
*/
|
|
26
|
-
export type RendererFactory = () => RendererPluginWithHost
|
|
42
|
+
export type RendererFactory = (options?: IndicatorRendererOptions) => RendererPluginWithHost
|
|
43
|
+
|
|
44
|
+
export type ScaleRendererFactory = (options: IndicatorScaleRendererOptions) => RendererPluginWithHost
|
|
45
|
+
|
|
46
|
+
export type IndicatorConfigUpdater = (
|
|
47
|
+
scheduler: unknown,
|
|
48
|
+
params: Record<string, unknown>,
|
|
49
|
+
paneId: string,
|
|
50
|
+
) => void
|
|
51
|
+
|
|
52
|
+
export interface IndicatorSemanticChartAdapter {
|
|
53
|
+
updateRendererConfig(name: string, config: Record<string, unknown>): void
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface IndicatorVisibleRange {
|
|
57
|
+
start: number
|
|
58
|
+
end: number
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface IndicatorPriceRange {
|
|
62
|
+
min: number
|
|
63
|
+
max: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type IndicatorPriceRangeComputer = (
|
|
67
|
+
bundle: IndicatorSeriesBundle,
|
|
68
|
+
visibleRange: IndicatorVisibleRange,
|
|
69
|
+
) => IndicatorPriceRange | null
|
|
70
|
+
|
|
71
|
+
export type IndicatorRenderStateComposer = (
|
|
72
|
+
bundle: IndicatorSeriesBundle,
|
|
73
|
+
visibleRange: IndicatorVisibleRange,
|
|
74
|
+
timestamp: number,
|
|
75
|
+
) => unknown
|
|
76
|
+
|
|
77
|
+
export interface IndicatorVisibleStateComposeContext {
|
|
78
|
+
bundle: IndicatorSeriesBundle
|
|
79
|
+
visibleRange: IndicatorVisibleRange
|
|
80
|
+
timestamp: number
|
|
81
|
+
active: boolean
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export type IndicatorVisibleStateComposer = (
|
|
85
|
+
context: IndicatorVisibleStateComposeContext,
|
|
86
|
+
) => unknown
|
|
27
87
|
|
|
28
88
|
/**
|
|
29
89
|
* 指标元数据接口
|
|
@@ -35,6 +95,11 @@ export interface IndicatorMetadata<T = unknown> {
|
|
|
35
95
|
*/
|
|
36
96
|
name: string
|
|
37
97
|
|
|
98
|
+
/**
|
|
99
|
+
* 可选别名,用于兼容 UI/API 中的大写 ID 或历史名称。
|
|
100
|
+
*/
|
|
101
|
+
aliases?: readonly string[]
|
|
102
|
+
|
|
38
103
|
/**
|
|
39
104
|
* 显示名称(用于日志和调试)
|
|
40
105
|
*/
|
|
@@ -64,6 +129,20 @@ export interface IndicatorMetadata<T = unknown> {
|
|
|
64
129
|
*/
|
|
65
130
|
rendererFactory: RendererFactory
|
|
66
131
|
|
|
132
|
+
/**
|
|
133
|
+
* 专用坐标轴渲染器工厂。未提供时可回退到 scale 通用配置。
|
|
134
|
+
*/
|
|
135
|
+
scaleRendererFactory?: ScaleRendererFactory
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 通用指标坐标轴配置。
|
|
139
|
+
*/
|
|
140
|
+
scale?: {
|
|
141
|
+
indicatorKey?: string
|
|
142
|
+
label?: string
|
|
143
|
+
decimals?: number
|
|
144
|
+
}
|
|
145
|
+
|
|
67
146
|
/**
|
|
68
147
|
* 默认 pane ID
|
|
69
148
|
* - 主图指标:'main'
|
|
@@ -77,6 +156,11 @@ export interface IndicatorMetadata<T = unknown> {
|
|
|
77
156
|
*/
|
|
78
157
|
isEnabled?: (config: IndicatorConfigSnapshot) => boolean
|
|
79
158
|
|
|
159
|
+
/**
|
|
160
|
+
* 指标配置更新入口。内置和用户自定义指标都应通过 metadata 分发。
|
|
161
|
+
*/
|
|
162
|
+
updateConfig?: IndicatorConfigUpdater
|
|
163
|
+
|
|
80
164
|
/**
|
|
81
165
|
* 将指标计算结果写入 StateStore
|
|
82
166
|
* @param host - PluginHost
|
|
@@ -91,6 +175,27 @@ export interface IndicatorMetadata<T = unknown> {
|
|
|
91
175
|
* - false/undefined:仅限副图显示
|
|
92
176
|
*/
|
|
93
177
|
allowMainPane?: boolean
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 主图指标启停相关配置。
|
|
181
|
+
*/
|
|
182
|
+
mainPane?: {
|
|
183
|
+
rendererName: string
|
|
184
|
+
toActiveConfig?: (params: Record<string, unknown>, active: boolean) => Record<string, unknown> | null
|
|
185
|
+
computePriceRange?: IndicatorPriceRangeComputer
|
|
186
|
+
composeRenderState?: IndicatorRenderStateComposer
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
visibleState?: {
|
|
190
|
+
compose: IndicatorVisibleStateComposer
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 语义配置应用入口。
|
|
195
|
+
*/
|
|
196
|
+
semantic?: {
|
|
197
|
+
apply?: (chart: IndicatorSemanticChartAdapter, indicator: T) => void
|
|
198
|
+
}
|
|
94
199
|
}
|
|
95
200
|
|
|
96
201
|
/**
|