@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.
- package/dist/controllers/index.d.ts +1 -1
- package/dist/controllers/index.d.ts.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.map +1 -1
- package/dist/engine/chart.js +18 -5
- package/dist/engine/chart.js.map +1 -1
- package/dist/engine/data/chartDataManager.d.ts +2 -0
- package/dist/engine/data/chartDataManager.d.ts.map +1 -1
- package/dist/engine/data/chartDataManager.js +9 -0
- package/dist/engine/data/chartDataManager.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/render/chartRenderer.d.ts.map +1 -1
- package/dist/engine/render/chartRenderer.js +27 -17
- package/dist/engine/render/chartRenderer.js.map +1 -1
- package/dist/engine/renderers/Indicator/mainIndicatorLegend.js +2 -2
- package/dist/engine/renderers/Indicator/mainIndicatorLegend.js.map +1 -1
- package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.d.ts +4 -3
- package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.js +13 -8
- package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.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/foundation/config/chartSettings.d.ts +18 -5
- package/dist/foundation/config/chartSettings.d.ts.map +1 -1
- package/dist/foundation/config/chartSettings.js +51 -0
- package/dist/foundation/config/chartSettings.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/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/controllers/index.ts +1 -1
- package/src/data/__tests__/timeShareBuffer.test.ts +45 -0
- package/src/data/timeShareBuffer.ts +15 -0
- package/src/engine/__tests__/chart.dpr.test.ts +28 -0
- package/src/engine/chart.ts +29 -6
- package/src/engine/data/chartDataManager.ts +10 -0
- 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/render/chartRenderer.ts +29 -18
- package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +2 -2
- package/src/engine/renderers/Indicator/mainIndicatorLegendContext.ts +19 -11
- package/src/engine/renderers/__tests__/mainIndicatorLegend.renderer.test.ts +4 -4
- package/src/engine/renderers/timeShare.ts +25 -9
- package/src/engine/state/__tests__/settingsState.test.ts +9 -0
- package/src/foundation/config/__tests__/chartSettings.test.ts +68 -1
- package/src/foundation/config/chartSettings.ts +56 -0
- 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/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.
|
|
1
|
+
export const VERSION = '0.9.0-alpha.5'
|