@363045841yyt/klinechart-core 0.7.3 → 0.7.5-alpha.2
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/README.md +201 -201
- package/README.zh-CN.md +201 -201
- package/dist/engine/renderers/webgl/candleSurface.js +47 -47
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -2
- package/dist/version.js.map +1 -1
- package/package.json +129 -122
- package/src/__tests__/signal.test.ts +124 -124
- package/src/config/chartSettings.ts +66 -66
- package/src/controllers/__tests__/drawing.test.ts +214 -214
- package/src/controllers/__tests__/indicatorSelector.test.ts +481 -481
- package/src/controllers/__tests__/toolbar.test.ts +225 -225
- package/src/controllers/createChartController.ts +665 -665
- package/src/controllers/createDrawingController.ts +96 -96
- package/src/controllers/createIndicatorSelectorController.ts +307 -307
- package/src/controllers/createToolbarController.ts +146 -146
- package/src/controllers/index.ts +19 -19
- package/src/controllers/types.ts +284 -284
- package/src/engine/__tests__/chart.dpr.test.ts +401 -401
- package/src/engine/__tests__/paneRenderer.resize.test.ts +92 -92
- package/src/engine/chart-store.ts +121 -121
- package/src/engine/chart.d.ts +617 -617
- package/src/engine/chart.ts +2815 -2815
- package/src/engine/controller/__tests__/interaction.dpr.test.ts +259 -259
- package/src/engine/controller/interaction.ts +722 -722
- package/src/engine/controller/markerInteraction.ts +130 -130
- package/src/engine/controller/pinchTracker.ts +82 -82
- package/src/engine/controller/tooltipPosition.ts +48 -48
- package/src/engine/draw/__tests__/pixelAlign.spec.ts +176 -176
- package/src/engine/draw/pixelAlign.ts +259 -259
- package/src/engine/drawing/index.ts +655 -655
- package/src/engine/drawing/interaction.ts +842 -842
- package/src/engine/drawing/plugin.ts +343 -343
- package/src/engine/indicators/__tests__/__fixtures__/golden/atr.json +38 -38
- package/src/engine/indicators/__tests__/__fixtures__/golden/dema.json +14 -14
- package/src/engine/indicators/__tests__/__fixtures__/golden/hma.json +14 -14
- package/src/engine/indicators/__tests__/__fixtures__/golden/index.ts +55 -55
- package/src/engine/indicators/__tests__/__fixtures__/golden/kama.json +14 -14
- package/src/engine/indicators/__tests__/__fixtures__/golden/tema.json +14 -14
- package/src/engine/indicators/__tests__/__fixtures__/golden/wma.json +40 -40
- package/src/engine/indicators/__tests__/__fixtures__/synthetic.ts +65 -65
- package/src/engine/indicators/__tests__/_propertyAssertions.ts +76 -76
- package/src/engine/indicators/__tests__/atr.test.ts +153 -153
- package/src/engine/indicators/__tests__/calculators.test.ts +614 -614
- package/src/engine/indicators/__tests__/cmf-mfi.test.ts +100 -100
- package/src/engine/indicators/__tests__/dema.test.ts +73 -73
- package/src/engine/indicators/__tests__/donchian.test.ts +70 -70
- package/src/engine/indicators/__tests__/hma.test.ts +73 -73
- package/src/engine/indicators/__tests__/ichimoku.test.ts +105 -105
- package/src/engine/indicators/__tests__/kama.test.ts +80 -80
- package/src/engine/indicators/__tests__/keltner.test.ts +65 -65
- package/src/engine/indicators/__tests__/pivot-fib.test.ts +110 -110
- package/src/engine/indicators/__tests__/roc.test.ts +68 -68
- package/src/engine/indicators/__tests__/sar.test.ts +86 -86
- package/src/engine/indicators/__tests__/scheduler.test.ts +831 -831
- package/src/engine/indicators/__tests__/soa.test.ts +533 -533
- package/src/engine/indicators/__tests__/structure.test.ts +110 -110
- package/src/engine/indicators/__tests__/supertrend.test.ts +65 -65
- package/src/engine/indicators/__tests__/tema.test.ts +68 -68
- package/src/engine/indicators/__tests__/trix.test.ts +70 -70
- package/src/engine/indicators/__tests__/volatility.test.ts +117 -117
- package/src/engine/indicators/__tests__/volume.test.ts +115 -115
- package/src/engine/indicators/__tests__/volumeProfile.test.ts +74 -74
- package/src/engine/indicators/__tests__/vwap.test.ts +69 -69
- package/src/engine/indicators/__tests__/wma.test.ts +112 -112
- package/src/engine/indicators/__tests__/zones.test.ts +95 -95
- package/src/engine/indicators/atrState.ts +27 -27
- package/src/engine/indicators/bollState.ts +51 -51
- package/src/engine/indicators/calculators.ts +2593 -2593
- package/src/engine/indicators/cciState.ts +25 -25
- package/src/engine/indicators/chaikinVolState.ts +32 -32
- package/src/engine/indicators/cmfState.ts +27 -27
- package/src/engine/indicators/demaState.ts +27 -27
- package/src/engine/indicators/donchianState.ts +43 -43
- package/src/engine/indicators/eneState.ts +43 -43
- package/src/engine/indicators/expmaState.ts +43 -43
- package/src/engine/indicators/fastkState.ts +25 -25
- package/src/engine/indicators/fibState.ts +41 -41
- package/src/engine/indicators/hmaState.ts +27 -27
- package/src/engine/indicators/hvState.ts +28 -28
- package/src/engine/indicators/ichimokuState.ts +70 -70
- package/src/engine/indicators/indicator.worker.ts +169 -169
- package/src/engine/indicators/indicatorDefinitionRegistry.ts +62 -62
- package/src/engine/indicators/indicatorMetadata.ts +110 -110
- package/src/engine/indicators/indicatorRegistry.ts +106 -106
- package/src/engine/indicators/indicatorRuntime.ts +1548 -1548
- package/src/engine/indicators/kamaState.ts +34 -34
- package/src/engine/indicators/keltnerState.ts +49 -49
- package/src/engine/indicators/kstState.ts +42 -42
- package/src/engine/indicators/maState.ts +36 -36
- package/src/engine/indicators/macdState.ts +76 -76
- package/src/engine/indicators/mfiState.ts +27 -27
- package/src/engine/indicators/momState.ts +25 -25
- package/src/engine/indicators/obvState.ts +25 -25
- package/src/engine/indicators/parkinsonState.ts +28 -28
- package/src/engine/indicators/pivotState.ts +51 -51
- package/src/engine/indicators/pvtState.ts +25 -25
- package/src/engine/indicators/rocState.ts +27 -27
- package/src/engine/indicators/rsiState.ts +65 -65
- package/src/engine/indicators/sarState.ts +41 -41
- package/src/engine/indicators/scheduler.ts +1205 -1205
- package/src/engine/indicators/soa.ts +352 -352
- package/src/engine/indicators/stateComposer.ts +1262 -1262
- package/src/engine/indicators/stochState.ts +26 -26
- package/src/engine/indicators/structureState.ts +69 -69
- package/src/engine/indicators/supertrendState.ts +37 -37
- package/src/engine/indicators/temaState.ts +27 -27
- package/src/engine/indicators/trixState.ts +35 -35
- package/src/engine/indicators/vmaState.ts +27 -27
- package/src/engine/indicators/volumeProfileState.ts +63 -63
- package/src/engine/indicators/vwapState.ts +29 -29
- package/src/engine/indicators/wmaState.ts +27 -27
- package/src/engine/indicators/wmsrState.ts +25 -25
- package/src/engine/indicators/workerProtocol.ts +613 -613
- package/src/engine/indicators/zonesState.ts +47 -47
- package/src/engine/layout/pane.ts +161 -161
- package/src/engine/marker/registry.ts +265 -265
- package/src/engine/paneRenderer.ts +169 -169
- package/src/engine/renderers/Indicator/atr.ts +237 -237
- package/src/engine/renderers/Indicator/boll.ts +317 -317
- package/src/engine/renderers/Indicator/cci.ts +275 -275
- package/src/engine/renderers/Indicator/chaikinVol.ts +138 -138
- package/src/engine/renderers/Indicator/cmf.ts +137 -137
- package/src/engine/renderers/Indicator/dema.ts +136 -136
- package/src/engine/renderers/Indicator/donchian.ts +137 -137
- package/src/engine/renderers/Indicator/ene.ts +271 -271
- package/src/engine/renderers/Indicator/expma.ts +197 -197
- package/src/engine/renderers/Indicator/fastk.ts +316 -316
- package/src/engine/renderers/Indicator/fib.ts +141 -141
- package/src/engine/renderers/Indicator/hma.ts +136 -136
- package/src/engine/renderers/Indicator/hv.ts +124 -124
- package/src/engine/renderers/Indicator/ichimoku.ts +181 -181
- package/src/engine/renderers/Indicator/index.ts +241 -241
- package/src/engine/renderers/Indicator/indicatorData.ts +650 -650
- package/src/engine/renderers/Indicator/kama.ts +136 -136
- package/src/engine/renderers/Indicator/keltner.ts +137 -137
- package/src/engine/renderers/Indicator/kst.ts +302 -302
- package/src/engine/renderers/Indicator/ma.ts +200 -200
- package/src/engine/renderers/Indicator/macd.ts +477 -477
- package/src/engine/renderers/Indicator/macdLegend.ts +141 -141
- package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +272 -272
- package/src/engine/renderers/Indicator/mfi.ts +142 -142
- package/src/engine/renderers/Indicator/mom.ts +311 -311
- package/src/engine/renderers/Indicator/obv.ts +123 -123
- package/src/engine/renderers/Indicator/parkinson.ts +124 -124
- package/src/engine/renderers/Indicator/pivot.ts +131 -131
- package/src/engine/renderers/Indicator/pvt.ts +123 -123
- package/src/engine/renderers/Indicator/roc.ts +143 -143
- package/src/engine/renderers/Indicator/rsi.ts +390 -390
- package/src/engine/renderers/Indicator/sar.ts +113 -113
- package/src/engine/renderers/Indicator/scale/atr_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/cci_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/fastk_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/indicator_scale.ts +204 -204
- package/src/engine/renderers/Indicator/scale/kst_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/macd_scale.ts +22 -22
- package/src/engine/renderers/Indicator/scale/mom_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/rsi_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/stoch_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/volume_scale.ts +26 -26
- package/src/engine/renderers/Indicator/scale/wmsr_scale.ts +19 -19
- package/src/engine/renderers/Indicator/stoch.ts +359 -359
- package/src/engine/renderers/Indicator/structure.ts +126 -126
- package/src/engine/renderers/Indicator/subPaneConfig.ts +265 -265
- package/src/engine/renderers/Indicator/supertrend.ts +115 -115
- package/src/engine/renderers/Indicator/tema.ts +136 -136
- package/src/engine/renderers/Indicator/trix.ts +158 -158
- package/src/engine/renderers/Indicator/vma.ts +124 -124
- package/src/engine/renderers/Indicator/volumeProfile.ts +125 -125
- package/src/engine/renderers/Indicator/vwap.ts +123 -123
- package/src/engine/renderers/Indicator/wma.ts +136 -136
- package/src/engine/renderers/Indicator/wmsr.ts +328 -328
- package/src/engine/renderers/Indicator/zones.ts +104 -104
- package/src/engine/renderers/__tests__/boll.renderer.test.ts +314 -314
- package/src/engine/renderers/__tests__/ene.renderer.test.ts +305 -305
- package/src/engine/renderers/__tests__/expma.renderer.test.ts +279 -279
- package/src/engine/renderers/__tests__/ma.renderer.test.ts +426 -426
- package/src/engine/renderers/__tests__/mainIndicatorLegend.renderer.test.ts +502 -502
- package/src/engine/renderers/__tests__/yAxis.renderer.test.ts +173 -173
- package/src/engine/renderers/candle.ts +459 -459
- package/src/engine/renderers/crosshair.ts +69 -69
- package/src/engine/renderers/customMarkers.ts +162 -162
- package/src/engine/renderers/extremaMarkers.ts +246 -246
- package/src/engine/renderers/gridLines.ts +90 -90
- package/src/engine/renderers/lastPrice.ts +97 -97
- package/src/engine/renderers/paneTitle.ts +136 -136
- package/src/engine/renderers/subVolume.ts +236 -236
- package/src/engine/renderers/timeAxis.ts +121 -121
- package/src/engine/renderers/webgl/candleSurface.ts +955 -955
- package/src/engine/renderers/webgl/sharedWebGLSurface.ts +146 -146
- package/src/engine/renderers/yAxis.ts +105 -105
- package/src/engine/scale/__tests__/logFormula.spec.ts +148 -148
- package/src/engine/scale/logFormula.ts +130 -130
- package/src/engine/scale/price.ts +39 -39
- package/src/engine/scale/priceScale.ts +264 -264
- package/src/engine/subPaneManager.ts +427 -427
- package/src/engine/theme/colors.ts +642 -642
- package/src/engine/theme/fonts.ts +20 -20
- package/src/engine/utils/klineConfig.ts +49 -49
- package/src/engine/utils/tickCount.ts +11 -11
- package/src/engine/utils/tickPosition.ts +214 -214
- package/src/engine/utils/zoom.ts +83 -83
- package/src/engine/viewport/viewport.ts +67 -67
- package/src/index.ts +3 -3
- package/src/plugin/ConfigManager.ts +93 -93
- package/src/plugin/EventBus.ts +77 -77
- package/src/plugin/HookSystem.ts +106 -106
- package/src/plugin/PluginHost.ts +243 -243
- package/src/plugin/PluginRegistry.ts +92 -92
- package/src/plugin/StateStore.ts +73 -73
- package/src/plugin/index.ts +19 -19
- package/src/plugin/rendererPluginManager.ts +368 -368
- package/src/plugin/stateKeys.ts +8 -8
- package/src/plugin/types.ts +526 -526
- package/src/reactivity/index.ts +2 -2
- package/src/reactivity/signal.ts +119 -119
- package/src/semantic/controller.ts +251 -251
- package/src/semantic/drawShape.ts +260 -260
- package/src/semantic/index.ts +28 -28
- package/src/semantic/schema.json +256 -256
- package/src/semantic/types.ts +251 -251
- package/src/semantic/validator.ts +349 -349
- package/src/types/kLine.ts +13 -13
- package/src/types/price.ts +56 -56
- package/src/types/volumePrice.ts +33 -33
- package/src/utils/dateFormat.ts +208 -208
- package/src/utils/kLineDraw/axis.ts +562 -562
- package/src/utils/priceToY.ts +34 -34
- package/src/utils/volumePrice.ts +202 -202
- package/src/version.ts +1 -1
|
@@ -1,117 +1,117 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { calcHVData, calcParkinsonData, calcChaikinVolData } from '../calculators'
|
|
3
|
-
import {
|
|
4
|
-
empty,
|
|
5
|
-
constantPrice,
|
|
6
|
-
pureUptrend,
|
|
7
|
-
sideways,
|
|
8
|
-
spikeAtBar19,
|
|
9
|
-
} from './__fixtures__/synthetic'
|
|
10
|
-
|
|
11
|
-
describe('calcHVData — Historical Volatility', () => {
|
|
12
|
-
it('empty returns empty', () => {
|
|
13
|
-
expect(calcHVData(empty, 20, 252)).toEqual([])
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it('period <= 0 or annualization <= 0 returns all undefined', () => {
|
|
17
|
-
const out1 = calcHVData(pureUptrend, 0, 252)
|
|
18
|
-
for (const v of out1) expect(v).toBeUndefined()
|
|
19
|
-
const out2 = calcHVData(pureUptrend, 20, 0)
|
|
20
|
-
for (const v of out2) expect(v).toBeUndefined()
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it('on constantPrice HV = 0 (no return variance)', () => {
|
|
24
|
-
const out = calcHVData(constantPrice, 10, 252)
|
|
25
|
-
for (let t = 10; t < out.length; t++) {
|
|
26
|
-
expect(out[t]).toBeCloseTo(0, 9)
|
|
27
|
-
}
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it('on pureUptrend HV > 0 once warm-up complete', () => {
|
|
31
|
-
const out = calcHVData(pureUptrend, 10, 252)
|
|
32
|
-
for (let t = 10; t < out.length; t++) {
|
|
33
|
-
expect(out[t]).toBeGreaterThanOrEqual(0)
|
|
34
|
-
}
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('HV ≥ 0 always', () => {
|
|
38
|
-
for (const fx of [pureUptrend, sideways, spikeAtBar19]) {
|
|
39
|
-
for (const v of calcHVData(fx, 10, 252)) {
|
|
40
|
-
if (v !== undefined) expect(v).toBeGreaterThanOrEqual(0)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('higher annualization → higher HV value (sqrt scaling)', () => {
|
|
46
|
-
const low = calcHVData(spikeAtBar19, 10, 100)
|
|
47
|
-
const high = calcHVData(spikeAtBar19, 10, 400)
|
|
48
|
-
for (let t = 10; t < low.length; t++) {
|
|
49
|
-
if (low[t] !== undefined && high[t] !== undefined) {
|
|
50
|
-
expect(high[t]).toBeCloseTo(low[t]! * 2, 9) // sqrt(400)/sqrt(100) = 2
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
describe('calcParkinsonData — Parkinson Volatility', () => {
|
|
57
|
-
it('empty returns empty', () => {
|
|
58
|
-
expect(calcParkinsonData(empty, 20, 252)).toEqual([])
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('on constantPrice (H=L) Parkinson = 0', () => {
|
|
62
|
-
const out = calcParkinsonData(constantPrice, 10, 252)
|
|
63
|
-
for (let t = 9; t < out.length; t++) {
|
|
64
|
-
expect(out[t]).toBeCloseTo(0, 9)
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('Parkinson ≥ 0 always', () => {
|
|
69
|
-
for (const fx of [pureUptrend, sideways, spikeAtBar19]) {
|
|
70
|
-
for (const v of calcParkinsonData(fx, 10, 252)) {
|
|
71
|
-
if (v !== undefined) expect(v).toBeGreaterThanOrEqual(0)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('on spike fixture Parkinson responds to the spike bar', () => {
|
|
77
|
-
const out = calcParkinsonData(spikeAtBar19, 5, 252)
|
|
78
|
-
const preSpike = out[18]
|
|
79
|
-
const atSpike = out[19]
|
|
80
|
-
if (preSpike !== undefined && atSpike !== undefined) {
|
|
81
|
-
expect(atSpike).toBeGreaterThanOrEqual(preSpike)
|
|
82
|
-
}
|
|
83
|
-
})
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
describe('calcChaikinVolData — Chaikin Volatility', () => {
|
|
87
|
-
it('empty returns empty', () => {
|
|
88
|
-
expect(calcChaikinVolData(empty, 10, 10)).toEqual([])
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('on constantPrice ChaikinVol stays at 0 (EMA(H-L) constant)', () => {
|
|
92
|
-
const out = calcChaikinVolData(constantPrice, 10, 10)
|
|
93
|
-
for (let t = 10; t < out.length; t++) {
|
|
94
|
-
if (out[t] !== undefined) expect(out[t]).toBeCloseTo(0, 6)
|
|
95
|
-
}
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
it('values finite or undefined', () => {
|
|
99
|
-
for (const fx of [pureUptrend, sideways, spikeAtBar19]) {
|
|
100
|
-
for (const v of calcChaikinVolData(fx, 10, 10)) {
|
|
101
|
-
if (v !== undefined) expect(Number.isFinite(v)).toBe(true)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('extensional consistency on spike fixture', () => {
|
|
107
|
-
const full = calcChaikinVolData(spikeAtBar19, 5, 5)
|
|
108
|
-
for (let n = 12; n < spikeAtBar19.length; n++) {
|
|
109
|
-
const partial = calcChaikinVolData(spikeAtBar19.slice(0, n), 5, 5)
|
|
110
|
-
for (let i = 0; i < n; i++) {
|
|
111
|
-
if (full[i] !== undefined && partial[i] !== undefined) {
|
|
112
|
-
expect(partial[i]).toBeCloseTo(full[i]!, 9)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
})
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { calcHVData, calcParkinsonData, calcChaikinVolData } from '../calculators'
|
|
3
|
+
import {
|
|
4
|
+
empty,
|
|
5
|
+
constantPrice,
|
|
6
|
+
pureUptrend,
|
|
7
|
+
sideways,
|
|
8
|
+
spikeAtBar19,
|
|
9
|
+
} from './__fixtures__/synthetic'
|
|
10
|
+
|
|
11
|
+
describe('calcHVData — Historical Volatility', () => {
|
|
12
|
+
it('empty returns empty', () => {
|
|
13
|
+
expect(calcHVData(empty, 20, 252)).toEqual([])
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('period <= 0 or annualization <= 0 returns all undefined', () => {
|
|
17
|
+
const out1 = calcHVData(pureUptrend, 0, 252)
|
|
18
|
+
for (const v of out1) expect(v).toBeUndefined()
|
|
19
|
+
const out2 = calcHVData(pureUptrend, 20, 0)
|
|
20
|
+
for (const v of out2) expect(v).toBeUndefined()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('on constantPrice HV = 0 (no return variance)', () => {
|
|
24
|
+
const out = calcHVData(constantPrice, 10, 252)
|
|
25
|
+
for (let t = 10; t < out.length; t++) {
|
|
26
|
+
expect(out[t]).toBeCloseTo(0, 9)
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('on pureUptrend HV > 0 once warm-up complete', () => {
|
|
31
|
+
const out = calcHVData(pureUptrend, 10, 252)
|
|
32
|
+
for (let t = 10; t < out.length; t++) {
|
|
33
|
+
expect(out[t]).toBeGreaterThanOrEqual(0)
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('HV ≥ 0 always', () => {
|
|
38
|
+
for (const fx of [pureUptrend, sideways, spikeAtBar19]) {
|
|
39
|
+
for (const v of calcHVData(fx, 10, 252)) {
|
|
40
|
+
if (v !== undefined) expect(v).toBeGreaterThanOrEqual(0)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('higher annualization → higher HV value (sqrt scaling)', () => {
|
|
46
|
+
const low = calcHVData(spikeAtBar19, 10, 100)
|
|
47
|
+
const high = calcHVData(spikeAtBar19, 10, 400)
|
|
48
|
+
for (let t = 10; t < low.length; t++) {
|
|
49
|
+
if (low[t] !== undefined && high[t] !== undefined) {
|
|
50
|
+
expect(high[t]).toBeCloseTo(low[t]! * 2, 9) // sqrt(400)/sqrt(100) = 2
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('calcParkinsonData — Parkinson Volatility', () => {
|
|
57
|
+
it('empty returns empty', () => {
|
|
58
|
+
expect(calcParkinsonData(empty, 20, 252)).toEqual([])
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('on constantPrice (H=L) Parkinson = 0', () => {
|
|
62
|
+
const out = calcParkinsonData(constantPrice, 10, 252)
|
|
63
|
+
for (let t = 9; t < out.length; t++) {
|
|
64
|
+
expect(out[t]).toBeCloseTo(0, 9)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('Parkinson ≥ 0 always', () => {
|
|
69
|
+
for (const fx of [pureUptrend, sideways, spikeAtBar19]) {
|
|
70
|
+
for (const v of calcParkinsonData(fx, 10, 252)) {
|
|
71
|
+
if (v !== undefined) expect(v).toBeGreaterThanOrEqual(0)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('on spike fixture Parkinson responds to the spike bar', () => {
|
|
77
|
+
const out = calcParkinsonData(spikeAtBar19, 5, 252)
|
|
78
|
+
const preSpike = out[18]
|
|
79
|
+
const atSpike = out[19]
|
|
80
|
+
if (preSpike !== undefined && atSpike !== undefined) {
|
|
81
|
+
expect(atSpike).toBeGreaterThanOrEqual(preSpike)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('calcChaikinVolData — Chaikin Volatility', () => {
|
|
87
|
+
it('empty returns empty', () => {
|
|
88
|
+
expect(calcChaikinVolData(empty, 10, 10)).toEqual([])
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('on constantPrice ChaikinVol stays at 0 (EMA(H-L) constant)', () => {
|
|
92
|
+
const out = calcChaikinVolData(constantPrice, 10, 10)
|
|
93
|
+
for (let t = 10; t < out.length; t++) {
|
|
94
|
+
if (out[t] !== undefined) expect(out[t]).toBeCloseTo(0, 6)
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('values finite or undefined', () => {
|
|
99
|
+
for (const fx of [pureUptrend, sideways, spikeAtBar19]) {
|
|
100
|
+
for (const v of calcChaikinVolData(fx, 10, 10)) {
|
|
101
|
+
if (v !== undefined) expect(Number.isFinite(v)).toBe(true)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('extensional consistency on spike fixture', () => {
|
|
107
|
+
const full = calcChaikinVolData(spikeAtBar19, 5, 5)
|
|
108
|
+
for (let n = 12; n < spikeAtBar19.length; n++) {
|
|
109
|
+
const partial = calcChaikinVolData(spikeAtBar19.slice(0, n), 5, 5)
|
|
110
|
+
for (let i = 0; i < n; i++) {
|
|
111
|
+
if (full[i] !== undefined && partial[i] !== undefined) {
|
|
112
|
+
expect(partial[i]).toBeCloseTo(full[i]!, 9)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
})
|
|
@@ -1,115 +1,115 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { calcVMAData, calcOBVData, calcPVTData } from '../calculators'
|
|
3
|
-
import {
|
|
4
|
-
empty,
|
|
5
|
-
constantPrice,
|
|
6
|
-
pureUptrend,
|
|
7
|
-
pureDowntrend,
|
|
8
|
-
sideways,
|
|
9
|
-
spikeAtBar19,
|
|
10
|
-
} from './__fixtures__/synthetic'
|
|
11
|
-
|
|
12
|
-
describe('calcVMAData', () => {
|
|
13
|
-
it('empty returns empty', () => {
|
|
14
|
-
expect(calcVMAData(empty, 5)).toEqual([])
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
it('shorter than period returns all undefined', () => {
|
|
18
|
-
const out = calcVMAData(pureUptrend.slice(0, 3), 5)
|
|
19
|
-
for (const v of out) expect(v).toBeUndefined()
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('period <= 0 returns all undefined', () => {
|
|
23
|
-
const out = calcVMAData(pureUptrend, 0)
|
|
24
|
-
for (const v of out) expect(v).toBeUndefined()
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('on constantPrice (volume=1000) VMA = 1000 after warm-up', () => {
|
|
28
|
-
const out = calcVMAData(constantPrice, 5)
|
|
29
|
-
for (let t = 4; t < out.length; t++) {
|
|
30
|
-
expect(out[t]).toBe(1000)
|
|
31
|
-
}
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('mathematical exactness: VMA = mean of last `period` volumes', () => {
|
|
35
|
-
const period = 5
|
|
36
|
-
const out = calcVMAData(pureUptrend, period)
|
|
37
|
-
for (let t = period - 1; t < out.length; t++) {
|
|
38
|
-
let sum = 0
|
|
39
|
-
for (let k = 0; k < period; k++) sum += pureUptrend[t - k]!.volume ?? 0
|
|
40
|
-
expect(out[t]!).toBeCloseTo(sum / period, 9)
|
|
41
|
-
}
|
|
42
|
-
})
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
describe('calcOBVData', () => {
|
|
46
|
-
it('empty returns empty', () => {
|
|
47
|
-
expect(calcOBVData(empty)).toEqual([])
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('starts at 0', () => {
|
|
51
|
-
const out = calcOBVData(pureUptrend)
|
|
52
|
-
expect(out[0]).toBe(0)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('on pureUptrend OBV monotonically increases (every Δclose > 0 → +volume)', () => {
|
|
56
|
-
const out = calcOBVData(pureUptrend)
|
|
57
|
-
for (let t = 1; t < out.length; t++) {
|
|
58
|
-
expect(out[t]!).toBeGreaterThanOrEqual(out[t - 1]!)
|
|
59
|
-
}
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
it('on pureDowntrend OBV monotonically decreases', () => {
|
|
63
|
-
const out = calcOBVData(pureDowntrend)
|
|
64
|
-
for (let t = 1; t < out.length; t++) {
|
|
65
|
-
expect(out[t]!).toBeLessThanOrEqual(out[t - 1]!)
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it('on constantPrice (no Δclose) OBV stays at 0', () => {
|
|
70
|
-
const out = calcOBVData(constantPrice)
|
|
71
|
-
for (const v of out) expect(v).toBe(0)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('extensional consistency on spike fixture', () => {
|
|
75
|
-
const full = calcOBVData(spikeAtBar19)
|
|
76
|
-
for (let n = 5; n < spikeAtBar19.length; n++) {
|
|
77
|
-
const partial = calcOBVData(spikeAtBar19.slice(0, n))
|
|
78
|
-
for (let i = 0; i < n; i++) {
|
|
79
|
-
expect(partial[i]).toBe(full[i])
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
})
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
describe('calcPVTData', () => {
|
|
86
|
-
it('empty returns empty', () => {
|
|
87
|
-
expect(calcPVTData(empty)).toEqual([])
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('starts at 0', () => {
|
|
91
|
-
expect(calcPVTData(pureUptrend)[0]).toBe(0)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('on constantPrice PVT stays at 0', () => {
|
|
95
|
-
const out = calcPVTData(constantPrice)
|
|
96
|
-
for (const v of out) expect(v).toBe(0)
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
it('on pureUptrend PVT monotonically increases', () => {
|
|
100
|
-
const out = calcPVTData(pureUptrend)
|
|
101
|
-
for (let t = 1; t < out.length; t++) {
|
|
102
|
-
expect(out[t]!).toBeGreaterThanOrEqual(out[t - 1]!)
|
|
103
|
-
}
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('extensional consistency', () => {
|
|
107
|
-
const full = calcPVTData(sideways)
|
|
108
|
-
for (let n = 5; n < sideways.length; n++) {
|
|
109
|
-
const partial = calcPVTData(sideways.slice(0, n))
|
|
110
|
-
for (let i = 0; i < n; i++) {
|
|
111
|
-
expect(partial[i]).toBeCloseTo(full[i]!, 9)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
})
|
|
115
|
-
})
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { calcVMAData, calcOBVData, calcPVTData } from '../calculators'
|
|
3
|
+
import {
|
|
4
|
+
empty,
|
|
5
|
+
constantPrice,
|
|
6
|
+
pureUptrend,
|
|
7
|
+
pureDowntrend,
|
|
8
|
+
sideways,
|
|
9
|
+
spikeAtBar19,
|
|
10
|
+
} from './__fixtures__/synthetic'
|
|
11
|
+
|
|
12
|
+
describe('calcVMAData', () => {
|
|
13
|
+
it('empty returns empty', () => {
|
|
14
|
+
expect(calcVMAData(empty, 5)).toEqual([])
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('shorter than period returns all undefined', () => {
|
|
18
|
+
const out = calcVMAData(pureUptrend.slice(0, 3), 5)
|
|
19
|
+
for (const v of out) expect(v).toBeUndefined()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('period <= 0 returns all undefined', () => {
|
|
23
|
+
const out = calcVMAData(pureUptrend, 0)
|
|
24
|
+
for (const v of out) expect(v).toBeUndefined()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('on constantPrice (volume=1000) VMA = 1000 after warm-up', () => {
|
|
28
|
+
const out = calcVMAData(constantPrice, 5)
|
|
29
|
+
for (let t = 4; t < out.length; t++) {
|
|
30
|
+
expect(out[t]).toBe(1000)
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('mathematical exactness: VMA = mean of last `period` volumes', () => {
|
|
35
|
+
const period = 5
|
|
36
|
+
const out = calcVMAData(pureUptrend, period)
|
|
37
|
+
for (let t = period - 1; t < out.length; t++) {
|
|
38
|
+
let sum = 0
|
|
39
|
+
for (let k = 0; k < period; k++) sum += pureUptrend[t - k]!.volume ?? 0
|
|
40
|
+
expect(out[t]!).toBeCloseTo(sum / period, 9)
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('calcOBVData', () => {
|
|
46
|
+
it('empty returns empty', () => {
|
|
47
|
+
expect(calcOBVData(empty)).toEqual([])
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('starts at 0', () => {
|
|
51
|
+
const out = calcOBVData(pureUptrend)
|
|
52
|
+
expect(out[0]).toBe(0)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('on pureUptrend OBV monotonically increases (every Δclose > 0 → +volume)', () => {
|
|
56
|
+
const out = calcOBVData(pureUptrend)
|
|
57
|
+
for (let t = 1; t < out.length; t++) {
|
|
58
|
+
expect(out[t]!).toBeGreaterThanOrEqual(out[t - 1]!)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('on pureDowntrend OBV monotonically decreases', () => {
|
|
63
|
+
const out = calcOBVData(pureDowntrend)
|
|
64
|
+
for (let t = 1; t < out.length; t++) {
|
|
65
|
+
expect(out[t]!).toBeLessThanOrEqual(out[t - 1]!)
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('on constantPrice (no Δclose) OBV stays at 0', () => {
|
|
70
|
+
const out = calcOBVData(constantPrice)
|
|
71
|
+
for (const v of out) expect(v).toBe(0)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('extensional consistency on spike fixture', () => {
|
|
75
|
+
const full = calcOBVData(spikeAtBar19)
|
|
76
|
+
for (let n = 5; n < spikeAtBar19.length; n++) {
|
|
77
|
+
const partial = calcOBVData(spikeAtBar19.slice(0, n))
|
|
78
|
+
for (let i = 0; i < n; i++) {
|
|
79
|
+
expect(partial[i]).toBe(full[i])
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
describe('calcPVTData', () => {
|
|
86
|
+
it('empty returns empty', () => {
|
|
87
|
+
expect(calcPVTData(empty)).toEqual([])
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('starts at 0', () => {
|
|
91
|
+
expect(calcPVTData(pureUptrend)[0]).toBe(0)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('on constantPrice PVT stays at 0', () => {
|
|
95
|
+
const out = calcPVTData(constantPrice)
|
|
96
|
+
for (const v of out) expect(v).toBe(0)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('on pureUptrend PVT monotonically increases', () => {
|
|
100
|
+
const out = calcPVTData(pureUptrend)
|
|
101
|
+
for (let t = 1; t < out.length; t++) {
|
|
102
|
+
expect(out[t]!).toBeGreaterThanOrEqual(out[t - 1]!)
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('extensional consistency', () => {
|
|
107
|
+
const full = calcPVTData(sideways)
|
|
108
|
+
for (let n = 5; n < sideways.length; n++) {
|
|
109
|
+
const partial = calcPVTData(sideways.slice(0, n))
|
|
110
|
+
for (let i = 0; i < n; i++) {
|
|
111
|
+
expect(partial[i]).toBeCloseTo(full[i]!, 9)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
})
|
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { calcVolumeProfileData } from '../calculators'
|
|
3
|
-
import {
|
|
4
|
-
empty,
|
|
5
|
-
constantPrice,
|
|
6
|
-
pureUptrend,
|
|
7
|
-
sideways,
|
|
8
|
-
spikeAtBar19,
|
|
9
|
-
} from './__fixtures__/synthetic'
|
|
10
|
-
|
|
11
|
-
describe('calcVolumeProfileData', () => {
|
|
12
|
-
it('empty returns empty profile', () => {
|
|
13
|
-
const result = calcVolumeProfileData(empty, 24, 0, 0.7)
|
|
14
|
-
expect(result.bins).toEqual([])
|
|
15
|
-
expect(result.totalVolume).toBe(0)
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('bins <= 0 returns empty', () => {
|
|
19
|
-
const result = calcVolumeProfileData(pureUptrend, 0, 0, 0.7)
|
|
20
|
-
expect(result.bins).toEqual([])
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it('on constantPrice (H=L=100): degenerate range → empty bins (no meaningful distribution)', () => {
|
|
24
|
-
const result = calcVolumeProfileData(constantPrice, 10, 0, 0.7)
|
|
25
|
-
// When priceMax === priceMin, the binning has no range; the function returns empty bins
|
|
26
|
-
expect(result.bins).toEqual([])
|
|
27
|
-
expect(result.totalVolume).toBe(0)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it('on pureUptrend: total volume = sum of bar volumes', () => {
|
|
31
|
-
const result = calcVolumeProfileData(pureUptrend, 24, 0, 0.7)
|
|
32
|
-
let expectedTotal = 0
|
|
33
|
-
for (const bar of pureUptrend) expectedTotal += bar.volume ?? 0
|
|
34
|
-
expect(result.totalVolume).toBeCloseTo(expectedTotal, 6)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('POC is within [val, vah] (value area contains POC)', () => {
|
|
38
|
-
for (const fx of [pureUptrend, sideways, spikeAtBar19]) {
|
|
39
|
-
const result = calcVolumeProfileData(fx, 20, 0, 0.7)
|
|
40
|
-
if (result.bins.length === 0) continue
|
|
41
|
-
expect(result.poc).toBeGreaterThanOrEqual(result.val)
|
|
42
|
-
expect(result.poc).toBeLessThanOrEqual(result.vah)
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('VAH >= VAL invariant', () => {
|
|
47
|
-
for (const fx of [pureUptrend, sideways, spikeAtBar19]) {
|
|
48
|
-
const result = calcVolumeProfileData(fx, 20, 0, 0.7)
|
|
49
|
-
expect(result.vah).toBeGreaterThanOrEqual(result.val)
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('value area sums to >= valueAreaPercent of total volume', () => {
|
|
54
|
-
const result = calcVolumeProfileData(pureUptrend, 20, 0, 0.7)
|
|
55
|
-
const vaVolume = result.bins
|
|
56
|
-
.filter((b) => b.priceLow >= result.val - 1e-9 && b.priceHigh <= result.vah + 1e-9)
|
|
57
|
-
.reduce((a, b) => a + b.volume, 0)
|
|
58
|
-
expect(vaVolume).toBeGreaterThanOrEqual(result.totalVolume * 0.7 - 1e-6)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('bins cover [priceMin, priceMax] contiguously', () => {
|
|
62
|
-
const result = calcVolumeProfileData(pureUptrend, 20, 0, 0.7)
|
|
63
|
-
for (let i = 1; i < result.bins.length; i++) {
|
|
64
|
-
expect(result.bins[i]!.priceLow).toBeCloseTo(result.bins[i - 1]!.priceHigh, 9)
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('lookback limits the data window', () => {
|
|
69
|
-
const full = calcVolumeProfileData(pureUptrend, 20, 0, 0.7)
|
|
70
|
-
const last10 = calcVolumeProfileData(pureUptrend, 20, 10, 0.7)
|
|
71
|
-
// Lookback should use less data → lower total volume
|
|
72
|
-
expect(last10.totalVolume).toBeLessThan(full.totalVolume)
|
|
73
|
-
})
|
|
74
|
-
})
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { calcVolumeProfileData } from '../calculators'
|
|
3
|
+
import {
|
|
4
|
+
empty,
|
|
5
|
+
constantPrice,
|
|
6
|
+
pureUptrend,
|
|
7
|
+
sideways,
|
|
8
|
+
spikeAtBar19,
|
|
9
|
+
} from './__fixtures__/synthetic'
|
|
10
|
+
|
|
11
|
+
describe('calcVolumeProfileData', () => {
|
|
12
|
+
it('empty returns empty profile', () => {
|
|
13
|
+
const result = calcVolumeProfileData(empty, 24, 0, 0.7)
|
|
14
|
+
expect(result.bins).toEqual([])
|
|
15
|
+
expect(result.totalVolume).toBe(0)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('bins <= 0 returns empty', () => {
|
|
19
|
+
const result = calcVolumeProfileData(pureUptrend, 0, 0, 0.7)
|
|
20
|
+
expect(result.bins).toEqual([])
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('on constantPrice (H=L=100): degenerate range → empty bins (no meaningful distribution)', () => {
|
|
24
|
+
const result = calcVolumeProfileData(constantPrice, 10, 0, 0.7)
|
|
25
|
+
// When priceMax === priceMin, the binning has no range; the function returns empty bins
|
|
26
|
+
expect(result.bins).toEqual([])
|
|
27
|
+
expect(result.totalVolume).toBe(0)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('on pureUptrend: total volume = sum of bar volumes', () => {
|
|
31
|
+
const result = calcVolumeProfileData(pureUptrend, 24, 0, 0.7)
|
|
32
|
+
let expectedTotal = 0
|
|
33
|
+
for (const bar of pureUptrend) expectedTotal += bar.volume ?? 0
|
|
34
|
+
expect(result.totalVolume).toBeCloseTo(expectedTotal, 6)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('POC is within [val, vah] (value area contains POC)', () => {
|
|
38
|
+
for (const fx of [pureUptrend, sideways, spikeAtBar19]) {
|
|
39
|
+
const result = calcVolumeProfileData(fx, 20, 0, 0.7)
|
|
40
|
+
if (result.bins.length === 0) continue
|
|
41
|
+
expect(result.poc).toBeGreaterThanOrEqual(result.val)
|
|
42
|
+
expect(result.poc).toBeLessThanOrEqual(result.vah)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('VAH >= VAL invariant', () => {
|
|
47
|
+
for (const fx of [pureUptrend, sideways, spikeAtBar19]) {
|
|
48
|
+
const result = calcVolumeProfileData(fx, 20, 0, 0.7)
|
|
49
|
+
expect(result.vah).toBeGreaterThanOrEqual(result.val)
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('value area sums to >= valueAreaPercent of total volume', () => {
|
|
54
|
+
const result = calcVolumeProfileData(pureUptrend, 20, 0, 0.7)
|
|
55
|
+
const vaVolume = result.bins
|
|
56
|
+
.filter((b) => b.priceLow >= result.val - 1e-9 && b.priceHigh <= result.vah + 1e-9)
|
|
57
|
+
.reduce((a, b) => a + b.volume, 0)
|
|
58
|
+
expect(vaVolume).toBeGreaterThanOrEqual(result.totalVolume * 0.7 - 1e-6)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('bins cover [priceMin, priceMax] contiguously', () => {
|
|
62
|
+
const result = calcVolumeProfileData(pureUptrend, 20, 0, 0.7)
|
|
63
|
+
for (let i = 1; i < result.bins.length; i++) {
|
|
64
|
+
expect(result.bins[i]!.priceLow).toBeCloseTo(result.bins[i - 1]!.priceHigh, 9)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('lookback limits the data window', () => {
|
|
69
|
+
const full = calcVolumeProfileData(pureUptrend, 20, 0, 0.7)
|
|
70
|
+
const last10 = calcVolumeProfileData(pureUptrend, 20, 10, 0.7)
|
|
71
|
+
// Lookback should use less data → lower total volume
|
|
72
|
+
expect(last10.totalVolume).toBeLessThan(full.totalVolume)
|
|
73
|
+
})
|
|
74
|
+
})
|