@363045841yyt/klinechart-core 0.9.0-alpha.2 → 0.9.0-alpha.4
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/controllers/createChartController.d.ts.map +1 -1
- package/dist/controllers/createChartController.js +9 -8
- package/dist/controllers/createChartController.js.map +1 -1
- package/dist/controllers/index.d.ts +1 -0
- package/dist/controllers/index.d.ts.map +1 -1
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/types.d.ts +5 -0
- package/dist/controllers/types.d.ts.map +1 -1
- package/dist/data/binance.d.ts +1 -1
- package/dist/data/binance.d.ts.map +1 -1
- package/dist/data/binance.js +1 -1
- package/dist/data/binance.js.map +1 -1
- package/dist/data/timeShareBuffer.d.ts +3 -0
- package/dist/data/timeShareBuffer.d.ts.map +1 -1
- package/dist/data/timeShareBuffer.js +13 -0
- package/dist/data/timeShareBuffer.js.map +1 -1
- package/dist/engine/chart.d.ts +9 -9
- package/dist/engine/chart.d.ts.map +1 -1
- package/dist/engine/chart.js +87 -101
- package/dist/engine/chart.js.map +1 -1
- package/dist/engine/chartTypes.d.ts +5 -0
- package/dist/engine/chartTypes.d.ts.map +1 -1
- package/dist/engine/controller/interaction.d.ts +4 -2
- package/dist/engine/controller/interaction.d.ts.map +1 -1
- package/dist/engine/controller/interaction.js +18 -10
- package/dist/engine/controller/interaction.js.map +1 -1
- package/dist/engine/data/chartDataManager.d.ts +14 -18
- package/dist/engine/data/chartDataManager.d.ts.map +1 -1
- package/dist/engine/data/chartDataManager.js +55 -37
- package/dist/engine/data/chartDataManager.js.map +1 -1
- package/dist/engine/data/incrementalLoadHint.d.ts +2 -4
- package/dist/engine/data/incrementalLoadHint.d.ts.map +1 -1
- package/dist/engine/data/incrementalLoadHint.js +5 -2
- package/dist/engine/data/incrementalLoadHint.js.map +1 -1
- package/dist/engine/data/scrollCompensator.d.ts +3 -13
- package/dist/engine/data/scrollCompensator.d.ts.map +1 -1
- package/dist/engine/data/scrollCompensator.js +13 -14
- package/dist/engine/data/scrollCompensator.js.map +1 -1
- package/dist/engine/indicators/chartIndicatorManager.d.ts +16 -17
- package/dist/engine/indicators/chartIndicatorManager.d.ts.map +1 -1
- package/dist/engine/indicators/chartIndicatorManager.js +37 -37
- package/dist/engine/indicators/chartIndicatorManager.js.map +1 -1
- package/dist/engine/layout/chartPaneLayout.d.ts +11 -7
- package/dist/engine/layout/chartPaneLayout.d.ts.map +1 -1
- package/dist/engine/layout/chartPaneLayout.js +58 -16
- package/dist/engine/layout/chartPaneLayout.js.map +1 -1
- package/dist/engine/modes/timeShareMath.d.ts +56 -0
- package/dist/engine/modes/timeShareMath.d.ts.map +1 -0
- package/dist/engine/modes/timeShareMath.js +90 -0
- package/dist/engine/modes/timeShareMath.js.map +1 -0
- package/dist/engine/modes/timeShareMode.d.ts +5 -0
- package/dist/engine/modes/timeShareMode.d.ts.map +1 -1
- package/dist/engine/modes/timeShareMode.js +27 -27
- package/dist/engine/modes/timeShareMode.js.map +1 -1
- package/dist/engine/paneRenderer.d.ts +2 -0
- package/dist/engine/paneRenderer.d.ts.map +1 -1
- package/dist/engine/paneRenderer.js +15 -5
- package/dist/engine/paneRenderer.js.map +1 -1
- package/dist/engine/render/chartRenderer.d.ts +28 -22
- package/dist/engine/render/chartRenderer.d.ts.map +1 -1
- package/dist/engine/render/chartRenderer.js +143 -85
- package/dist/engine/render/chartRenderer.js.map +1 -1
- package/dist/engine/render/layers/leftYAxisLayer.d.ts +9 -2
- package/dist/engine/render/layers/leftYAxisLayer.d.ts.map +1 -1
- package/dist/engine/render/layers/leftYAxisLayer.js +11 -2
- package/dist/engine/render/layers/leftYAxisLayer.js.map +1 -1
- package/dist/engine/render/layers/mainIndicatorLegendLayer.d.ts +6 -4
- package/dist/engine/render/layers/mainIndicatorLegendLayer.d.ts.map +1 -1
- package/dist/engine/render/layers/mainIndicatorLegendLayer.js +6 -9
- package/dist/engine/render/layers/mainIndicatorLegendLayer.js.map +1 -1
- package/dist/engine/render/layers/mainIndicatorLegendLayer.test.d.ts +2 -0
- package/dist/engine/render/layers/mainIndicatorLegendLayer.test.d.ts.map +1 -0
- package/dist/engine/render/layers/mainIndicatorLegendLayer.test.js +19 -0
- package/dist/engine/render/layers/mainIndicatorLegendLayer.test.js.map +1 -0
- package/dist/engine/render/layers/yAxisLayer.d.ts +9 -2
- package/dist/engine/render/layers/yAxisLayer.d.ts.map +1 -1
- package/dist/engine/render/layers/yAxisLayer.js +11 -2
- package/dist/engine/render/layers/yAxisLayer.js.map +1 -1
- package/dist/engine/renderers/Indicator/mainIndicatorLegend.d.ts +7 -3
- package/dist/engine/renderers/Indicator/mainIndicatorLegend.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/mainIndicatorLegend.js +234 -330
- package/dist/engine/renderers/Indicator/mainIndicatorLegend.js.map +1 -1
- package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.d.ts +73 -0
- package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.d.ts.map +1 -0
- package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.js +182 -0
- package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.js.map +1 -0
- package/dist/engine/renderers/leftYAxis.d.ts +15 -2
- package/dist/engine/renderers/leftYAxis.d.ts.map +1 -1
- package/dist/engine/renderers/leftYAxis.js +56 -16
- package/dist/engine/renderers/leftYAxis.js.map +1 -1
- package/dist/engine/renderers/timeShare.d.ts.map +1 -1
- package/dist/engine/renderers/timeShare.js +20 -9
- package/dist/engine/renderers/timeShare.js.map +1 -1
- package/dist/engine/renderers/yAxis.d.ts +16 -6
- package/dist/engine/renderers/yAxis.d.ts.map +1 -1
- package/dist/engine/renderers/yAxis.js +49 -16
- package/dist/engine/renderers/yAxis.js.map +1 -1
- package/dist/engine/state/viewportState.d.ts +4 -1
- package/dist/engine/state/viewportState.d.ts.map +1 -1
- package/dist/engine/state/viewportState.js +40 -8
- package/dist/engine/state/viewportState.js.map +1 -1
- package/dist/engine/subPaneManager.d.ts +5 -3
- package/dist/engine/subPaneManager.d.ts.map +1 -1
- package/dist/engine/subPaneManager.js +3 -2
- package/dist/engine/subPaneManager.js.map +1 -1
- package/dist/engine/utils/chartZoomController.d.ts +9 -7
- package/dist/engine/utils/chartZoomController.d.ts.map +1 -1
- package/dist/engine/utils/chartZoomController.js +13 -12
- package/dist/engine/utils/chartZoomController.js.map +1 -1
- package/dist/engine/viewport/chartViewportManager.d.ts +5 -16
- package/dist/engine/viewport/chartViewportManager.d.ts.map +1 -1
- package/dist/engine/viewport/chartViewportManager.js +7 -48
- package/dist/engine/viewport/chartViewportManager.js.map +1 -1
- package/dist/engine/viewport/viewport.d.ts +14 -0
- package/dist/engine/viewport/viewport.d.ts.map +1 -1
- package/dist/engine/viewport/viewport.js +10 -0
- package/dist/engine/viewport/viewport.js.map +1 -1
- package/dist/foundation/config/chartSettings.d.ts +0 -5
- package/dist/foundation/config/chartSettings.d.ts.map +1 -1
- package/dist/foundation/config/chartSettings.js +14 -0
- package/dist/foundation/config/chartSettings.js.map +1 -1
- package/dist/foundation/plugin/rendererPluginManager.test.js +15 -0
- package/dist/foundation/plugin/rendererPluginManager.test.js.map +1 -1
- package/dist/foundation/plugin/types.d.ts +3 -0
- package/dist/foundation/plugin/types.d.ts.map +1 -1
- package/dist/foundation/plugin/types.js.map +1 -1
- package/dist/foundation/reactivity/frameTransaction.d.ts +11 -0
- package/dist/foundation/reactivity/frameTransaction.d.ts.map +1 -1
- package/dist/foundation/reactivity/frameTransaction.js +17 -2
- package/dist/foundation/reactivity/frameTransaction.js.map +1 -1
- package/dist/foundation/utils/kLineDraw/axis.d.ts +3 -0
- package/dist/foundation/utils/kLineDraw/axis.d.ts.map +1 -1
- package/dist/foundation/utils/kLineDraw/axis.js +40 -9
- package/dist/foundation/utils/kLineDraw/axis.js.map +1 -1
- package/dist/foundation/utils/sessionTimeLabels.d.ts +59 -0
- package/dist/foundation/utils/sessionTimeLabels.d.ts.map +1 -0
- package/dist/foundation/utils/sessionTimeLabels.js +170 -0
- package/dist/foundation/utils/sessionTimeLabels.js.map +1 -0
- package/dist/foundation/utils/timeShareAxisLabels.d.ts +27 -0
- package/dist/foundation/utils/timeShareAxisLabels.d.ts.map +1 -0
- package/dist/foundation/utils/timeShareAxisLabels.js +45 -0
- package/dist/foundation/utils/timeShareAxisLabels.js.map +1 -0
- package/dist/rendering/render/Renderer.d.ts +58 -74
- package/dist/rendering/render/Renderer.d.ts.map +1 -1
- package/dist/rendering/render/Renderer.js +8 -18
- package/dist/rendering/render/Renderer.js.map +1 -1
- package/dist/rendering/render/createWebGPURenderer.d.ts.map +1 -1
- package/dist/rendering/render/createWebGPURenderer.js +30 -17
- package/dist/rendering/render/createWebGPURenderer.js.map +1 -1
- package/dist/rendering/render/createWebGPUSurfaceBackend.js +3 -3
- package/dist/rendering/render/createWebGPUSurfaceBackend.js.map +1 -1
- package/dist/rendering/render/physicalLine.d.ts +4 -0
- package/dist/rendering/render/physicalLine.d.ts.map +1 -0
- package/dist/rendering/render/physicalLine.js +37 -0
- package/dist/rendering/render/physicalLine.js.map +1 -0
- package/dist/rendering/render/physicalRegion.d.ts +14 -0
- package/dist/rendering/render/physicalRegion.d.ts.map +1 -0
- package/dist/rendering/render/physicalRegion.js +15 -0
- package/dist/rendering/render/physicalRegion.js.map +1 -0
- package/dist/rendering/scene/createLayerFromPlugin.d.ts.map +1 -1
- package/dist/rendering/scene/createLayerFromPlugin.js +8 -3
- package/dist/rendering/scene/createLayerFromPlugin.js.map +1 -1
- package/dist/rendering/scene/createScene.d.ts.map +1 -1
- package/dist/rendering/scene/createScene.js +4 -3
- package/dist/rendering/scene/createScene.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/__tests__/stateKernel.test.ts +1 -0
- package/src/controllers/createChartController.ts +9 -8
- package/src/controllers/index.ts +11 -0
- package/src/controllers/types.ts +7 -0
- package/src/data/__tests__/timeShareBuffer.test.ts +45 -0
- package/src/data/binance.ts +1 -1
- package/src/data/timeShareBuffer.ts +15 -0
- package/src/engine/__tests__/chart.dpr.test.ts +31 -2
- package/src/engine/__tests__/paneRenderer.resize.test.ts +52 -54
- package/src/engine/__tests__/subPaneManager.test.ts +5 -3
- package/src/engine/chart.ts +105 -115
- package/src/engine/chartTypes.ts +5 -0
- package/src/engine/controller/__tests__/interaction.dpr.test.ts +39 -2
- package/src/engine/controller/interaction.ts +18 -10
- package/src/engine/data/__tests__/chartDataManager.incrementalLoad.test.ts +37 -17
- package/src/engine/data/__tests__/scrollCompensator.geometry.test.ts +87 -75
- package/src/engine/data/chartDataManager.ts +64 -59
- package/src/engine/data/incrementalLoadHint.ts +8 -7
- package/src/engine/data/scrollCompensator.ts +16 -24
- package/src/engine/indicators/__tests__/chartIndicatorManager.test.ts +18 -26
- package/src/engine/indicators/chartIndicatorManager.ts +62 -62
- package/src/engine/layout/chartPaneLayout.ts +73 -21
- package/src/engine/modes/__tests__/timeShareMath.test.ts +185 -0
- package/src/engine/modes/__tests__/timeShareMode.test.ts +81 -0
- package/src/engine/modes/timeShareMath.ts +150 -0
- package/src/engine/modes/timeShareMode.ts +35 -28
- package/src/engine/paneRenderer.ts +17 -10
- package/src/engine/render/chartRenderer.ts +214 -145
- package/src/engine/render/layers/leftYAxisLayer.ts +29 -7
- package/src/engine/render/layers/mainIndicatorLegendLayer.test.ts +26 -0
- package/src/engine/render/layers/mainIndicatorLegendLayer.ts +13 -10
- package/src/engine/render/layers/yAxisLayer.ts +29 -7
- package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +270 -403
- package/src/engine/renderers/Indicator/mainIndicatorLegendContext.ts +290 -0
- package/src/engine/renderers/__tests__/mainIndicatorLegend.renderer.test.ts +73 -2
- package/src/engine/renderers/__tests__/yAxis.renderer.test.ts +10 -6
- package/src/engine/renderers/leftYAxis.ts +60 -18
- package/src/engine/renderers/timeShare.ts +25 -9
- package/src/engine/renderers/yAxis.ts +57 -22
- package/src/engine/state/__tests__/settingsState.test.ts +9 -0
- package/src/engine/state/viewportState.ts +44 -8
- package/src/engine/subPaneManager.ts +8 -5
- package/src/engine/utils/chartZoomController.ts +22 -19
- package/src/engine/viewport/__tests__/visibleRange.clamp.test.ts +50 -0
- package/src/engine/viewport/chartViewportManager.ts +9 -60
- package/src/engine/viewport/viewport.ts +14 -0
- package/src/foundation/config/chartSettings.ts +13 -0
- package/src/foundation/plugin/rendererPluginManager.test.ts +18 -0
- package/src/foundation/plugin/types.ts +3 -0
- package/src/foundation/reactivity/frameTransaction.ts +20 -6
- package/src/foundation/utils/__tests__/sessionTimeLabels.test.ts +91 -0
- package/src/foundation/utils/kLineDraw/axis.ts +50 -9
- package/src/foundation/utils/sessionTimeLabels.ts +237 -0
- package/src/foundation/utils/timeShareAxisLabels.ts +94 -0
- package/src/rendering/render/Renderer.ts +69 -85
- package/src/rendering/render/__tests__/physicalLine.test.ts +51 -0
- package/src/rendering/render/__tests__/physicalRegion.test.ts +23 -0
- package/src/rendering/render/__tests__/webgpuRenderer.test.ts +31 -1
- package/src/rendering/render/__tests__/webgpuSurfaceBackend.test.ts +11 -0
- package/src/rendering/render/createWebGPURenderer.ts +30 -17
- package/src/rendering/render/createWebGPUSurfaceBackend.ts +3 -3
- package/src/rendering/render/physicalLine.ts +45 -0
- package/src/rendering/render/physicalRegion.ts +32 -0
- package/src/rendering/scene/createLayerFromPlugin.ts +8 -3
- package/src/rendering/scene/createScene.ts +4 -3
- package/src/version.ts +1 -1
|
@@ -10,6 +10,17 @@
|
|
|
10
10
|
* 一帧固定走 capture → derive → seal → render → publish → complete。
|
|
11
11
|
* render 或 publish 期间的 writeInput 一律进入下一代 pending。
|
|
12
12
|
* 非 idle 时调用 flush 不会嵌套发布,仅保留 dirty 供外层完成后调度。
|
|
13
|
+
*
|
|
14
|
+
* @remarks 适用范围
|
|
15
|
+
* 这是一个半通用的 rAF 合帧器(semi-general frame coalescer),
|
|
16
|
+
* 专为 ChartRenderer 的绘制管线设计,并非全场景通用的高频处理方案。
|
|
17
|
+
*
|
|
18
|
+
* @Todo 如需在其他场景(WebSocket 消息合并、键盘事件批处理)复用,需补充:
|
|
19
|
+
* - 深层合并 / 自定义 merge 策略(目前 mergeInput 仅浅合并)
|
|
20
|
+
* - 快照深层不可变保证(目前 sealSnapshotRoot 只冻根对象)
|
|
21
|
+
* - 超时/失帧策略(目前纯乐观逐帧)
|
|
22
|
+
* - 背压与优先级集成(FrameBudget 在 scheduler 层但未在此集成)
|
|
23
|
+
* - 非 Record 输入类型支持(目前 TInput 约束为 Record)
|
|
13
24
|
*/
|
|
14
25
|
|
|
15
26
|
import { createSignal, type ReadonlySignal } from './signal'
|
|
@@ -88,10 +99,9 @@ export interface FrameTransaction<TInput extends Record<string, unknown>, TSnaps
|
|
|
88
99
|
* @typeParam TInput - 可合并的输入形状(浅层 Partial 合并)
|
|
89
100
|
* @typeParam TSnapshot - derive 产出的不可变快照类型
|
|
90
101
|
*/
|
|
91
|
-
export function createFrameTransaction<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
>(options: FrameTransactionOptions<TInput, TSnapshot>): FrameTransaction<TInput, TSnapshot> {
|
|
102
|
+
export function createFrameTransaction<TInput extends Record<string, unknown>, TSnapshot>(
|
|
103
|
+
options: FrameTransactionOptions<TInput, TSnapshot>,
|
|
104
|
+
): FrameTransaction<TInput, TSnapshot> {
|
|
95
105
|
const { derive, render } = options
|
|
96
106
|
const schedule =
|
|
97
107
|
options.schedule ??
|
|
@@ -145,24 +155,27 @@ export function createFrameTransaction<
|
|
|
145
155
|
}
|
|
146
156
|
|
|
147
157
|
let sealedInput: TInput | undefined
|
|
158
|
+
// 封存输入,后续写入只能进下一代
|
|
148
159
|
phase = 'capturing'
|
|
149
160
|
try {
|
|
150
161
|
sealedInput = pending
|
|
151
162
|
dirty = false
|
|
152
|
-
// 本帧 capture 之后的新写入进入 nextPending,不得污染 sealedInput
|
|
153
163
|
pending = { ...sealedInput }
|
|
154
164
|
|
|
165
|
+
// 从封存输入纯推导不可变快照
|
|
155
166
|
phase = 'deriving'
|
|
156
167
|
const nextGeneration = generation + 1
|
|
157
168
|
const snapshot = derive(sealedInput, nextGeneration)
|
|
158
169
|
|
|
170
|
+
// 冻结快照根对象
|
|
159
171
|
phase = 'sealing'
|
|
160
172
|
sealSnapshotRoot(snapshot)
|
|
161
173
|
|
|
174
|
+
// 用本帧快照绘制
|
|
162
175
|
phase = 'rendering'
|
|
163
176
|
render?.(snapshot)
|
|
164
177
|
|
|
165
|
-
//
|
|
178
|
+
// 先推进代际再发布,保证订阅者读到一致代际
|
|
166
179
|
phase = 'publishing'
|
|
167
180
|
generation = nextGeneration
|
|
168
181
|
published.set(snapshot)
|
|
@@ -191,6 +204,7 @@ export function createFrameTransaction<
|
|
|
191
204
|
}
|
|
192
205
|
|
|
193
206
|
function scheduleFlush(): void {
|
|
207
|
+
// 已有 raf 帧,
|
|
194
208
|
if (scheduleQueued) return
|
|
195
209
|
if (!dirty && nextPending === null) return
|
|
196
210
|
scheduleQueued = true
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ASHARE_MARKET_SESSION,
|
|
5
|
+
HK_MARKET_SESSION,
|
|
6
|
+
US_MARKET_SESSION,
|
|
7
|
+
computeSessionTimeLabels,
|
|
8
|
+
countSessionSlots,
|
|
9
|
+
minuteOfDayToTimestamp,
|
|
10
|
+
resolveMarketSessionSlots,
|
|
11
|
+
type MarketSessionConfig,
|
|
12
|
+
type OpenTimeRange,
|
|
13
|
+
} from '../sessionTimeLabels'
|
|
14
|
+
|
|
15
|
+
function hm(h: number, m: number): number {
|
|
16
|
+
return h * 60 + m
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('countSessionSlots / resolveMarketSessionSlots', () => {
|
|
20
|
+
it('A-share is 240 one-minute slots', () => {
|
|
21
|
+
expect(countSessionSlots(ASHARE_MARKET_SESSION.sessions)).toBe(240)
|
|
22
|
+
expect(resolveMarketSessionSlots(ASHARE_MARKET_SESSION)).toBe(240)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('HK is 330, US is 390 (not maxed to 240)', () => {
|
|
26
|
+
expect(resolveMarketSessionSlots(HK_MARKET_SESSION)).toBe(330)
|
|
27
|
+
expect(resolveMarketSessionSlots(US_MARKET_SESSION)).toBe(390)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('respects slotMinutes (e.g. 5-min bars)', () => {
|
|
31
|
+
const cfg: MarketSessionConfig = {
|
|
32
|
+
...ASHARE_MARKET_SESSION,
|
|
33
|
+
slotMinutes: 5,
|
|
34
|
+
}
|
|
35
|
+
// 240 分钟 / 5 = 48
|
|
36
|
+
expect(resolveMarketSessionSlots(cfg)).toBe(48)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('does not expand slots by arrived data length (sessions are SSOT)', () => {
|
|
40
|
+
expect(resolveMarketSessionSlots(ASHARE_MARKET_SESSION, 300)).toBe(240)
|
|
41
|
+
expect(resolveMarketSessionSlots(HK_MARKET_SESSION, 100)).toBe(330)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('computeSessionTimeLabels multi-market', () => {
|
|
46
|
+
it('A-share closed-side endpoints only', () => {
|
|
47
|
+
const labels = computeSessionTimeLabels(ASHARE_MARKET_SESSION.sessions, { axisWidth: 800 })
|
|
48
|
+
expect(labels.map((l) => l.minuteOfDay)).toEqual([hm(9, 30), hm(13, 0), hm(15, 0)])
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('HK endpoints: 09:30 / 13:00 / 16:00', () => {
|
|
52
|
+
const labels = computeSessionTimeLabels(HK_MARKET_SESSION.sessions, { axisWidth: 800 })
|
|
53
|
+
expect(labels.map((l) => l.minuteOfDay)).toEqual([hm(9, 30), hm(13, 0), hm(16, 0)])
|
|
54
|
+
expect(labels.map((l) => l.slotIndex)).toEqual([0, 150, 329])
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('US single continuous session endpoints: 09:30 / 16:00', () => {
|
|
58
|
+
const labels = computeSessionTimeLabels(US_MARKET_SESSION.sessions, { axisWidth: 800 })
|
|
59
|
+
expect(labels.map((l) => l.minuteOfDay)).toEqual([hm(9, 30), hm(16, 0)])
|
|
60
|
+
expect(labels.map((l) => l.slotIndex)).toEqual([0, 389])
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('accepts arbitrary sessions via config.sessions', () => {
|
|
64
|
+
const night: OpenTimeRange[] = [
|
|
65
|
+
{ open: hm(21, 0), close: hm(23, 0) },
|
|
66
|
+
{ open: hm(0, 0), close: hm(2, 30) },
|
|
67
|
+
]
|
|
68
|
+
// 两段同日分钟:21:00-23:00 (120) + 0:00-2:30 (150) = 270
|
|
69
|
+
// 闭侧端点:21:00, 0:00, 2:30
|
|
70
|
+
const labels = computeSessionTimeLabels(night, { axisWidth: 800 })
|
|
71
|
+
expect(labels.map((l) => l.minuteOfDay)).toEqual([hm(21, 0), hm(0, 0), hm(2, 30)])
|
|
72
|
+
expect(countSessionSlots(night)).toBe(270)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('minuteOfDayToTimestamp timeZone', () => {
|
|
77
|
+
it('maps wall clock in Asia/Shanghai independently of host local TZ', () => {
|
|
78
|
+
// 任意 UTC 锚点落在 2026-07-21 上海日历日
|
|
79
|
+
const base = Date.UTC(2026, 6, 21, 4, 0, 0) // 12:00 CST
|
|
80
|
+
const ts = minuteOfDayToTimestamp(base, hm(9, 30), 'Asia/Shanghai')
|
|
81
|
+
// 2026-07-21 09:30 Asia/Shanghai = 01:30 UTC
|
|
82
|
+
expect(ts).toBe(Date.UTC(2026, 6, 21, 1, 30, 0))
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('maps wall clock in America/New_York for US session open', () => {
|
|
86
|
+
// 2026-07-21 是美东夏令时 EDT (UTC-4)
|
|
87
|
+
const base = Date.UTC(2026, 6, 21, 16, 0, 0) // 12:00 EDT
|
|
88
|
+
const ts = minuteOfDayToTimestamp(base, hm(9, 30), 'America/New_York')
|
|
89
|
+
expect(ts).toBe(Date.UTC(2026, 6, 21, 13, 30, 0)) // 09:30 EDT
|
|
90
|
+
})
|
|
91
|
+
})
|
|
@@ -12,6 +12,14 @@ import {
|
|
|
12
12
|
findDayBoundaries,
|
|
13
13
|
} from '../dateFormat'
|
|
14
14
|
import { priceToY, yToPrice } from '../priceToY'
|
|
15
|
+
import {
|
|
16
|
+
ASHARE_MARKET_SESSION,
|
|
17
|
+
computeTimeShareTimeLabels,
|
|
18
|
+
minuteOfDayToTimestamp,
|
|
19
|
+
resolveMarketSessionSlots,
|
|
20
|
+
timeShareSlotCenterX,
|
|
21
|
+
type MarketSessionConfig,
|
|
22
|
+
} from '../timeShareAxisLabels'
|
|
15
23
|
|
|
16
24
|
const textWidthCache = new Map<string, number>()
|
|
17
25
|
const TEXT_WIDTH_CACHE_LIMIT = 512
|
|
@@ -63,6 +71,8 @@ export interface TimeAxisOptions {
|
|
|
63
71
|
monthKeys?: Int32Array
|
|
64
72
|
/** 预计算的日期键值数组(year*366+dayOfYear),与 data 长度一致 */
|
|
65
73
|
dayKeys?: Int32Array
|
|
74
|
+
/** 分时市场 session;默认 A 股 */
|
|
75
|
+
marketSession?: MarketSessionConfig
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
export interface LastPriceLineOptions {
|
|
@@ -362,16 +372,47 @@ export function drawTimeAxis(
|
|
|
362
372
|
let labelFn: (ts: number) => { text: string; isYear: boolean }
|
|
363
373
|
|
|
364
374
|
if (isTimeShare) {
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
375
|
+
// 只画各区间闭侧端点;全天首/末槽贴边 left/right 对齐
|
|
376
|
+
const market = opts.marketSession ?? ASHARE_MARKET_SESSION
|
|
377
|
+
const sessionSlots = resolveMarketSessionSlots(market)
|
|
378
|
+
const labels = computeTimeShareTimeLabels({
|
|
379
|
+
axisWidth: width,
|
|
380
|
+
marketSession: market,
|
|
381
|
+
minLabelSpacingPx: 56,
|
|
382
|
+
})
|
|
383
|
+
const baseTs = data[0]?.timestamp ?? Date.now()
|
|
384
|
+
setCanvasFont(ctx, regularFont)
|
|
385
|
+
ctx.textBaseline = 'middle'
|
|
386
|
+
const edgePad = 2
|
|
387
|
+
const lastSlot = Math.max(0, sessionSlots - 1)
|
|
388
|
+
for (const label of labels) {
|
|
389
|
+
const ts = minuteOfDayToTimestamp(baseTs, label.minuteOfDay, market.timeZone)
|
|
390
|
+
const text = formatTimeLabel(ts)
|
|
391
|
+
const isDayStart = label.slotIndex === 0
|
|
392
|
+
const isDayEnd = label.slotIndex === lastSlot
|
|
393
|
+
|
|
394
|
+
let align: CanvasTextAlign = 'center'
|
|
395
|
+
let drawX: number
|
|
396
|
+
if (isDayStart) {
|
|
397
|
+
align = 'left'
|
|
398
|
+
drawX = x + edgePad
|
|
399
|
+
} else if (isDayEnd) {
|
|
400
|
+
align = 'right'
|
|
401
|
+
drawX = x + width - edgePad
|
|
402
|
+
} else {
|
|
403
|
+
const centerX = timeShareSlotCenterX(label.slotIndex, width, sessionSlots, dpr)
|
|
404
|
+
drawX = centerX - scrollLeft
|
|
405
|
+
if (drawX < edgePad || drawX > width - edgePad) continue
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
ctx.textAlign = align
|
|
409
|
+
ctx.fillText(text, roundToPhysicalPixel(drawX, dpr), alignToPhysicalPixelCenter(textY, dpr))
|
|
372
410
|
}
|
|
373
|
-
|
|
374
|
-
|
|
411
|
+
ctx.textAlign = 'center'
|
|
412
|
+
return
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (isMinuteData) {
|
|
375
416
|
boundaries = findDayBoundaries(data, opts.dayKeys)
|
|
376
417
|
labelFn = formatDay
|
|
377
418
|
} else {
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 开盘时段:minuteOfDay 为自 0 点起的分钟数(交易所本地墙钟)。
|
|
3
|
+
* sessions[0..n-2] 左闭右开 [open, close);sessions[n-1] 左闭右闭 [open, close]。
|
|
4
|
+
* 只打印闭侧端点:半开段不展示 close。
|
|
5
|
+
*/
|
|
6
|
+
export type OpenTimeRange = {
|
|
7
|
+
open: number
|
|
8
|
+
close: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** 市场分时 session 配置(几何 / 标签共用) */
|
|
12
|
+
export type MarketSessionConfig = {
|
|
13
|
+
/** IANA 时区,如 Asia/Shanghai */
|
|
14
|
+
timeZone: string
|
|
15
|
+
sessions: ReadonlyArray<OpenTimeRange>
|
|
16
|
+
/** 每个 bar 槽对应的分钟数,默认 1 */
|
|
17
|
+
slotMinutes?: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type SessionTimeLabel = {
|
|
21
|
+
/** 自 0 点起的分钟数(交易所墙钟) */
|
|
22
|
+
minuteOfDay: number
|
|
23
|
+
/** 在展开后的槽序列中的索引(0-based) */
|
|
24
|
+
slotIndex: number
|
|
25
|
+
/** 是否为闭侧端点 */
|
|
26
|
+
isEndpoint: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type SessionTimeLabelOptions = {
|
|
30
|
+
axisWidth: number
|
|
31
|
+
/** 保留字段,端点-only 模式下不参与密度计算 */
|
|
32
|
+
minLabelSpacingPx?: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function hm(h: number, m: number): number {
|
|
36
|
+
return h * 60 + m
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A 股默认 */
|
|
40
|
+
export const ASHARE_OPEN_SESSIONS: readonly OpenTimeRange[] = [
|
|
41
|
+
{ open: hm(9, 30), close: hm(11, 30) },
|
|
42
|
+
{ open: hm(13, 0), close: hm(15, 0) },
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
export const ASHARE_MARKET_SESSION: MarketSessionConfig = {
|
|
46
|
+
timeZone: 'Asia/Shanghai',
|
|
47
|
+
sessions: ASHARE_OPEN_SESSIONS,
|
|
48
|
+
slotMinutes: 1,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 港股 */
|
|
52
|
+
export const HK_MARKET_SESSION: MarketSessionConfig = {
|
|
53
|
+
timeZone: 'Asia/Hong_Kong',
|
|
54
|
+
sessions: [
|
|
55
|
+
{ open: hm(9, 30), close: hm(12, 0) },
|
|
56
|
+
{ open: hm(13, 0), close: hm(16, 0) },
|
|
57
|
+
],
|
|
58
|
+
slotMinutes: 1,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 美股(常规盘,无午休) */
|
|
62
|
+
export const US_MARKET_SESSION: MarketSessionConfig = {
|
|
63
|
+
timeZone: 'America/New_York',
|
|
64
|
+
sessions: [{ open: hm(9, 30), close: hm(16, 0) }],
|
|
65
|
+
slotMinutes: 1,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 只输出各区间闭侧端点。
|
|
70
|
+
* - 非末段 [open, close):仅 open
|
|
71
|
+
* - 末段 [open, close]:open 与 close
|
|
72
|
+
* close 标签映射到 close-1 槽(bar 序列为各段 [open, close))。
|
|
73
|
+
*/
|
|
74
|
+
export function computeSessionTimeLabels(
|
|
75
|
+
sessions: ReadonlyArray<OpenTimeRange>,
|
|
76
|
+
options: SessionTimeLabelOptions,
|
|
77
|
+
): SessionTimeLabel[] {
|
|
78
|
+
if (!(options.axisWidth > 0) || sessions.length === 0) return []
|
|
79
|
+
|
|
80
|
+
const labels: SessionTimeLabel[] = []
|
|
81
|
+
let slotOffset = 0
|
|
82
|
+
|
|
83
|
+
for (let s = 0; s < sessions.length; s++) {
|
|
84
|
+
const range = sessions[s]!
|
|
85
|
+
if (!(range.close > range.open)) continue
|
|
86
|
+
const slotCount = range.close - range.open
|
|
87
|
+
const isLast = s === sessions.length - 1
|
|
88
|
+
|
|
89
|
+
labels.push({
|
|
90
|
+
minuteOfDay: range.open,
|
|
91
|
+
slotIndex: slotOffset,
|
|
92
|
+
isEndpoint: true,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
if (isLast) {
|
|
96
|
+
labels.push({
|
|
97
|
+
minuteOfDay: range.close,
|
|
98
|
+
slotIndex: slotOffset + slotCount - 1,
|
|
99
|
+
isEndpoint: true,
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
slotOffset += slotCount
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const bySlot = new Map<number, SessionTimeLabel>()
|
|
107
|
+
for (const label of labels) {
|
|
108
|
+
bySlot.set(label.slotIndex, label)
|
|
109
|
+
}
|
|
110
|
+
return [...bySlot.values()].sort((a, b) => a.slotIndex - b.slotIndex)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** 槽总数(各段 [open, close) 分钟数之和;未除 slotMinutes) */
|
|
114
|
+
export function countSessionSlots(sessions: ReadonlyArray<OpenTimeRange>): number {
|
|
115
|
+
let n = 0
|
|
116
|
+
for (const r of sessions) {
|
|
117
|
+
if (r.close > r.open) n += r.close - r.open
|
|
118
|
+
}
|
|
119
|
+
return n
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 全天 bar 槽数 = 分钟总和 / slotMinutes。
|
|
124
|
+
* sessions 为 SSOT,不因 arrivedCount 放大。
|
|
125
|
+
*/
|
|
126
|
+
export function resolveMarketSessionSlots(
|
|
127
|
+
config: MarketSessionConfig,
|
|
128
|
+
_arrivedCount?: number,
|
|
129
|
+
): number {
|
|
130
|
+
const minutes = countSessionSlots(config.sessions)
|
|
131
|
+
const step = config.slotMinutes && config.slotMinutes > 0 ? config.slotMinutes : 1
|
|
132
|
+
return Math.max(0, Math.floor(minutes / step))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 将交易所墙钟 minuteOfDay 转为 UTC 毫秒。
|
|
137
|
+
* baseTimestamp 用于确定「哪一天」(按 timeZone 日历日)。
|
|
138
|
+
*/
|
|
139
|
+
export function minuteOfDayToTimestamp(
|
|
140
|
+
baseTimestamp: number,
|
|
141
|
+
minuteOfDay: number,
|
|
142
|
+
timeZone: string = 'Asia/Shanghai',
|
|
143
|
+
): number {
|
|
144
|
+
const { year, month, day } = getCalendarPartsInTimeZone(baseTimestamp, timeZone)
|
|
145
|
+
return zonedWallTimeToUtc(year, month, day, minuteOfDay, timeZone)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** 槽中心 X */
|
|
149
|
+
export function sessionSlotCenterX(
|
|
150
|
+
slotIndex: number,
|
|
151
|
+
axisWidth: number,
|
|
152
|
+
sessionSlots: number,
|
|
153
|
+
dpr: number,
|
|
154
|
+
): number {
|
|
155
|
+
if (!(axisWidth > 0) || !(sessionSlots > 0)) return 0
|
|
156
|
+
const step = axisWidth / sessionSlots
|
|
157
|
+
return Math.round((slotIndex + 0.5) * step * dpr) / dpr
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── 时区工具(无第三方依赖,Intl) ──
|
|
161
|
+
|
|
162
|
+
function getCalendarPartsInTimeZone(
|
|
163
|
+
timestamp: number,
|
|
164
|
+
timeZone: string,
|
|
165
|
+
): { year: number; month: number; day: number } {
|
|
166
|
+
const parts = new Intl.DateTimeFormat('en-US', {
|
|
167
|
+
timeZone,
|
|
168
|
+
year: 'numeric',
|
|
169
|
+
month: '2-digit',
|
|
170
|
+
day: '2-digit',
|
|
171
|
+
}).formatToParts(new Date(timestamp))
|
|
172
|
+
let year = 0
|
|
173
|
+
let month = 1
|
|
174
|
+
let day = 1
|
|
175
|
+
for (const p of parts) {
|
|
176
|
+
if (p.type === 'year') year = Number(p.value)
|
|
177
|
+
else if (p.type === 'month') month = Number(p.value)
|
|
178
|
+
else if (p.type === 'day') day = Number(p.value)
|
|
179
|
+
}
|
|
180
|
+
return { year, month, day }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 将某时区墙钟 (y-m-d + minuteOfDay) 转为 UTC epoch。
|
|
185
|
+
* 用迭代修正 UTC 猜测,覆盖 DST。
|
|
186
|
+
*/
|
|
187
|
+
function zonedWallTimeToUtc(
|
|
188
|
+
year: number,
|
|
189
|
+
month: number,
|
|
190
|
+
day: number,
|
|
191
|
+
minuteOfDay: number,
|
|
192
|
+
timeZone: string,
|
|
193
|
+
): number {
|
|
194
|
+
const hour = Math.floor(minuteOfDay / 60)
|
|
195
|
+
const minute = minuteOfDay % 60
|
|
196
|
+
// 初值:当作 UTC 墙钟
|
|
197
|
+
let utc = Date.UTC(year, month - 1, day, hour, minute, 0, 0)
|
|
198
|
+
for (let i = 0; i < 4; i++) {
|
|
199
|
+
const wall = getWallClockInTimeZone(utc, timeZone)
|
|
200
|
+
const desiredMin = hour * 60 + minute
|
|
201
|
+
const actualMin = wall.hour * 60 + wall.minute
|
|
202
|
+
const dayDelta =
|
|
203
|
+
Date.UTC(year, month - 1, day) - Date.UTC(wall.year, wall.month - 1, wall.day)
|
|
204
|
+
const minDelta = desiredMin - actualMin + dayDelta / 60_000
|
|
205
|
+
if (minDelta === 0) break
|
|
206
|
+
utc += minDelta * 60_000
|
|
207
|
+
}
|
|
208
|
+
return utc
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getWallClockInTimeZone(
|
|
212
|
+
timestamp: number,
|
|
213
|
+
timeZone: string,
|
|
214
|
+
): { year: number; month: number; day: number; hour: number; minute: number } {
|
|
215
|
+
const parts = new Intl.DateTimeFormat('en-US', {
|
|
216
|
+
timeZone,
|
|
217
|
+
year: 'numeric',
|
|
218
|
+
month: '2-digit',
|
|
219
|
+
day: '2-digit',
|
|
220
|
+
hour: '2-digit',
|
|
221
|
+
minute: '2-digit',
|
|
222
|
+
hourCycle: 'h23',
|
|
223
|
+
}).formatToParts(new Date(timestamp))
|
|
224
|
+
let year = 0
|
|
225
|
+
let month = 1
|
|
226
|
+
let day = 1
|
|
227
|
+
let hour = 0
|
|
228
|
+
let minute = 0
|
|
229
|
+
for (const p of parts) {
|
|
230
|
+
if (p.type === 'year') year = Number(p.value)
|
|
231
|
+
else if (p.type === 'month') month = Number(p.value)
|
|
232
|
+
else if (p.type === 'day') day = Number(p.value)
|
|
233
|
+
else if (p.type === 'hour') hour = Number(p.value)
|
|
234
|
+
else if (p.type === 'minute') minute = Number(p.value)
|
|
235
|
+
}
|
|
236
|
+
return { year, month, day, hour, minute }
|
|
237
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ASHARE_MARKET_SESSION,
|
|
3
|
+
ASHARE_OPEN_SESSIONS,
|
|
4
|
+
computeSessionTimeLabels,
|
|
5
|
+
countSessionSlots,
|
|
6
|
+
minuteOfDayToTimestamp,
|
|
7
|
+
resolveMarketSessionSlots,
|
|
8
|
+
sessionSlotCenterX,
|
|
9
|
+
type MarketSessionConfig,
|
|
10
|
+
type SessionTimeLabel,
|
|
11
|
+
} from './sessionTimeLabels'
|
|
12
|
+
|
|
13
|
+
/** A 股默认全天 1 分钟槽位数(兼容旧导出) */
|
|
14
|
+
export const ASHARE_TIMESHARE_SESSION_SLOTS = resolveMarketSessionSlots(ASHARE_MARKET_SESSION)
|
|
15
|
+
|
|
16
|
+
/** 分时时间标签最小水平间距(逻辑像素) */
|
|
17
|
+
export const TIMESHARE_MIN_LABEL_SPACING_PX = 56
|
|
18
|
+
|
|
19
|
+
export type TimeShareTimeLabelInput = {
|
|
20
|
+
axisWidth: number
|
|
21
|
+
/** 市场 session;默认 A 股 */
|
|
22
|
+
marketSession?: MarketSessionConfig
|
|
23
|
+
minLabelSpacingPx?: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 分时时间标签索引(委托 computeSessionTimeLabels)。
|
|
28
|
+
* 默认 A 股;传入 marketSession 可换港股/美股等。
|
|
29
|
+
*/
|
|
30
|
+
export function computeTimeShareTimeLabelIndices(input: TimeShareTimeLabelInput): number[] {
|
|
31
|
+
return computeTimeShareTimeLabels(input).map((l) => l.slotIndex)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 完整标签(含 minuteOfDay / isEndpoint) */
|
|
35
|
+
export function computeTimeShareTimeLabels(input: TimeShareTimeLabelInput): SessionTimeLabel[] {
|
|
36
|
+
const market = input.marketSession ?? ASHARE_MARKET_SESSION
|
|
37
|
+
return computeSessionTimeLabels(market.sessions, {
|
|
38
|
+
axisWidth: input.axisWidth,
|
|
39
|
+
minLabelSpacingPx: input.minLabelSpacingPx ?? TIMESHARE_MIN_LABEL_SPACING_PX,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function timeShareSlotCenterX(
|
|
44
|
+
slotIndex: number,
|
|
45
|
+
axisWidth: number,
|
|
46
|
+
sessionSlots: number,
|
|
47
|
+
dpr: number,
|
|
48
|
+
): number {
|
|
49
|
+
return sessionSlotCenterX(slotIndex, axisWidth, sessionSlots, dpr)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 由开盘日基准时间戳 + slot 索引推算标签时间(按 market session)。
|
|
54
|
+
*/
|
|
55
|
+
export function resolveTimeShareSlotTimestamp(
|
|
56
|
+
baseTimestamp: number,
|
|
57
|
+
slotIndex: number,
|
|
58
|
+
marketSession: MarketSessionConfig = ASHARE_MARKET_SESSION,
|
|
59
|
+
): number {
|
|
60
|
+
const step = marketSession.slotMinutes && marketSession.slotMinutes > 0 ? marketSession.slotMinutes : 1
|
|
61
|
+
let remaining = slotIndex * step
|
|
62
|
+
for (const range of marketSession.sessions) {
|
|
63
|
+
const len = range.close - range.open
|
|
64
|
+
if (remaining < len) {
|
|
65
|
+
return minuteOfDayToTimestamp(
|
|
66
|
+
baseTimestamp,
|
|
67
|
+
range.open + remaining,
|
|
68
|
+
marketSession.timeZone,
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
remaining -= len
|
|
72
|
+
}
|
|
73
|
+
const last = marketSession.sessions[marketSession.sessions.length - 1]
|
|
74
|
+
if (last) {
|
|
75
|
+
return minuteOfDayToTimestamp(baseTimestamp, last.close, marketSession.timeZone)
|
|
76
|
+
}
|
|
77
|
+
return baseTimestamp
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
ASHARE_MARKET_SESSION,
|
|
82
|
+
ASHARE_OPEN_SESSIONS,
|
|
83
|
+
computeSessionTimeLabels,
|
|
84
|
+
countSessionSlots,
|
|
85
|
+
minuteOfDayToTimestamp,
|
|
86
|
+
resolveMarketSessionSlots,
|
|
87
|
+
sessionSlotCenterX,
|
|
88
|
+
}
|
|
89
|
+
export type {
|
|
90
|
+
MarketSessionConfig,
|
|
91
|
+
OpenTimeRange,
|
|
92
|
+
SessionTimeLabel,
|
|
93
|
+
} from './sessionTimeLabels'
|
|
94
|
+
export { HK_MARKET_SESSION, US_MARKET_SESSION } from './sessionTimeLabels'
|