@363045841yyt/klinechart-core 0.9.0-alpha.3 → 0.9.0-alpha.5

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.
Files changed (75) hide show
  1. package/dist/controllers/index.d.ts +1 -1
  2. package/dist/controllers/index.d.ts.map +1 -1
  3. package/dist/data/timeShareBuffer.d.ts +3 -0
  4. package/dist/data/timeShareBuffer.d.ts.map +1 -1
  5. package/dist/data/timeShareBuffer.js +13 -0
  6. package/dist/data/timeShareBuffer.js.map +1 -1
  7. package/dist/engine/chart.d.ts.map +1 -1
  8. package/dist/engine/chart.js +18 -5
  9. package/dist/engine/chart.js.map +1 -1
  10. package/dist/engine/data/chartDataManager.d.ts +2 -0
  11. package/dist/engine/data/chartDataManager.d.ts.map +1 -1
  12. package/dist/engine/data/chartDataManager.js +9 -0
  13. package/dist/engine/data/chartDataManager.js.map +1 -1
  14. package/dist/engine/modes/timeShareMath.d.ts +56 -0
  15. package/dist/engine/modes/timeShareMath.d.ts.map +1 -0
  16. package/dist/engine/modes/timeShareMath.js +90 -0
  17. package/dist/engine/modes/timeShareMath.js.map +1 -0
  18. package/dist/engine/modes/timeShareMode.d.ts +5 -0
  19. package/dist/engine/modes/timeShareMode.d.ts.map +1 -1
  20. package/dist/engine/modes/timeShareMode.js +27 -27
  21. package/dist/engine/modes/timeShareMode.js.map +1 -1
  22. package/dist/engine/render/chartRenderer.d.ts.map +1 -1
  23. package/dist/engine/render/chartRenderer.js +27 -17
  24. package/dist/engine/render/chartRenderer.js.map +1 -1
  25. package/dist/engine/renderers/Indicator/mainIndicatorLegend.js +2 -2
  26. package/dist/engine/renderers/Indicator/mainIndicatorLegend.js.map +1 -1
  27. package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.d.ts +4 -3
  28. package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.d.ts.map +1 -1
  29. package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.js +13 -8
  30. package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.js.map +1 -1
  31. package/dist/engine/renderers/timeShare.d.ts.map +1 -1
  32. package/dist/engine/renderers/timeShare.js +20 -9
  33. package/dist/engine/renderers/timeShare.js.map +1 -1
  34. package/dist/foundation/config/chartSettings.d.ts +18 -5
  35. package/dist/foundation/config/chartSettings.d.ts.map +1 -1
  36. package/dist/foundation/config/chartSettings.js +51 -0
  37. package/dist/foundation/config/chartSettings.js.map +1 -1
  38. package/dist/foundation/utils/kLineDraw/axis.d.ts +3 -0
  39. package/dist/foundation/utils/kLineDraw/axis.d.ts.map +1 -1
  40. package/dist/foundation/utils/kLineDraw/axis.js +40 -9
  41. package/dist/foundation/utils/kLineDraw/axis.js.map +1 -1
  42. package/dist/foundation/utils/sessionTimeLabels.d.ts +59 -0
  43. package/dist/foundation/utils/sessionTimeLabels.d.ts.map +1 -0
  44. package/dist/foundation/utils/sessionTimeLabels.js +170 -0
  45. package/dist/foundation/utils/sessionTimeLabels.js.map +1 -0
  46. package/dist/foundation/utils/timeShareAxisLabels.d.ts +27 -0
  47. package/dist/foundation/utils/timeShareAxisLabels.d.ts.map +1 -0
  48. package/dist/foundation/utils/timeShareAxisLabels.js +45 -0
  49. package/dist/foundation/utils/timeShareAxisLabels.js.map +1 -0
  50. package/dist/version.d.ts +1 -1
  51. package/dist/version.js +1 -1
  52. package/package.json +1 -1
  53. package/src/controllers/index.ts +1 -1
  54. package/src/data/__tests__/timeShareBuffer.test.ts +45 -0
  55. package/src/data/timeShareBuffer.ts +15 -0
  56. package/src/engine/__tests__/chart.dpr.test.ts +28 -0
  57. package/src/engine/chart.ts +29 -6
  58. package/src/engine/data/chartDataManager.ts +10 -0
  59. package/src/engine/modes/__tests__/timeShareMath.test.ts +185 -0
  60. package/src/engine/modes/__tests__/timeShareMode.test.ts +81 -0
  61. package/src/engine/modes/timeShareMath.ts +150 -0
  62. package/src/engine/modes/timeShareMode.ts +35 -28
  63. package/src/engine/render/chartRenderer.ts +29 -18
  64. package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +2 -2
  65. package/src/engine/renderers/Indicator/mainIndicatorLegendContext.ts +19 -11
  66. package/src/engine/renderers/__tests__/mainIndicatorLegend.renderer.test.ts +4 -4
  67. package/src/engine/renderers/timeShare.ts +25 -9
  68. package/src/engine/state/__tests__/settingsState.test.ts +9 -0
  69. package/src/foundation/config/__tests__/chartSettings.test.ts +68 -1
  70. package/src/foundation/config/chartSettings.ts +56 -0
  71. package/src/foundation/utils/__tests__/sessionTimeLabels.test.ts +91 -0
  72. package/src/foundation/utils/kLineDraw/axis.ts +50 -9
  73. package/src/foundation/utils/sessionTimeLabels.ts +237 -0
  74. package/src/foundation/utils/timeShareAxisLabels.ts +94 -0
  75. package/src/version.ts +1 -1
@@ -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'
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.9.0-alpha.3'
1
+ export const VERSION = '0.9.0-alpha.5'