@quantlife/qlchart 0.0.1

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 (321) hide show
  1. package/.idea/QLChart.iml +12 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/vcs.xml +6 -0
  4. package/README.md +75 -0
  5. package/demo/App.css +213 -0
  6. package/demo/App.tsx +46 -0
  7. package/demo/components/ControlPanel.tsx +13 -0
  8. package/demo/components/DemoNav.tsx +27 -0
  9. package/demo/index.html +16 -0
  10. package/demo/main.tsx +10 -0
  11. package/demo/pages/BasicChartDemo.tsx +61 -0
  12. package/demo/pages/DrawingDemo.tsx +22 -0
  13. package/demo/pages/IndicatorDemo.tsx +22 -0
  14. package/demo/pages/LayoutDemo.tsx +35 -0
  15. package/demo/pages/MultiPeriodDemo.tsx +31 -0
  16. package/demo/pages/ReplayDemo.tsx +195 -0
  17. package/demo/pages/SaveDemo.tsx +27 -0
  18. package/demo/pages/ThemeDemo.tsx +29 -0
  19. package/demo/standalone-demo.html +597 -0
  20. package/demo/vite.config.demo.ts +17 -0
  21. package/dist/index.d.ts +1973 -0
  22. package/dist/qlchart.js +23169 -0
  23. package/dist/style.css +1 -0
  24. package/doc/api/indicator-data-processor.md +35 -0
  25. package/doc/api-reference/.nojekyll +1 -0
  26. package/doc/api-reference/assets/hierarchy.js +1 -0
  27. package/doc/api-reference/assets/highlight.css +43 -0
  28. package/doc/api-reference/assets/icons.js +18 -0
  29. package/doc/api-reference/assets/icons.svg +1 -0
  30. package/doc/api-reference/assets/main.js +60 -0
  31. package/doc/api-reference/assets/navigation.js +1 -0
  32. package/doc/api-reference/assets/search.js +1 -0
  33. package/doc/api-reference/assets/style.css +1611 -0
  34. package/doc/api-reference/classes/ChartManager.html +16 -0
  35. package/doc/api-reference/classes/DataManager.html +13 -0
  36. package/doc/api-reference/classes/DrawingAdapter.html +64 -0
  37. package/doc/api-reference/classes/DrawingPersistence.html +21 -0
  38. package/doc/api-reference/classes/EventManager.html +12 -0
  39. package/doc/api-reference/classes/HollowCandlestickSeries.html +22 -0
  40. package/doc/api-reference/classes/IndicatorRenderer.html +20 -0
  41. package/doc/api-reference/classes/KlineReplay.html +31 -0
  42. package/doc/api-reference/classes/MockDataService.html +13 -0
  43. package/doc/api-reference/classes/MockIndicatorService.html +4 -0
  44. package/doc/api-reference/classes/OverlayIndicator.html +11 -0
  45. package/doc/api-reference/classes/PaneIndicator.html +16 -0
  46. package/doc/api-reference/classes/PaneManager.html +24 -0
  47. package/doc/api-reference/classes/RealtimeDataFeed.html +22 -0
  48. package/doc/api-reference/classes/RenkoSeries.html +22 -0
  49. package/doc/api-reference/classes/ScreenshotUtil.html +10 -0
  50. package/doc/api-reference/classes/SeriesManager.html +30 -0
  51. package/doc/api-reference/classes/ThemeManager.html +18 -0
  52. package/doc/api-reference/enums/ChartEvent.html +12 -0
  53. package/doc/api-reference/enums/IndicatorType.html +4 -0
  54. package/doc/api-reference/enums/SeriesType.html +13 -0
  55. package/doc/api-reference/functions/ChartFunctionMenu.html +1 -0
  56. package/doc/api-reference/functions/DrawingModule.html +8 -0
  57. package/doc/api-reference/functions/IndicatorPanel.html +2 -0
  58. package/doc/api-reference/functions/IndicatorTag.html +2 -0
  59. package/doc/api-reference/functions/KlineTypeSelector.html +1 -0
  60. package/doc/api-reference/functions/LayoutSwitcher.html +1 -0
  61. package/doc/api-reference/functions/PeriodSelector.html +1 -0
  62. package/doc/api-reference/functions/QLChartLayout.html +1 -0
  63. package/doc/api-reference/functions/QLChartPanel.html +10 -0
  64. package/doc/api-reference/functions/QLChartProvider.html +2 -0
  65. package/doc/api-reference/functions/QLToolbar.html +1 -0
  66. package/doc/api-reference/functions/ReplayController.html +1 -0
  67. package/doc/api-reference/functions/TimeBarModule.html +4 -0
  68. package/doc/api-reference/functions/TimeRangeSelector.html +1 -0
  69. package/doc/api-reference/functions/createIndicatorConfig.html +2 -0
  70. package/doc/api-reference/functions/getToolConfig.html +2 -0
  71. package/doc/api-reference/functions/getToolsByCategory.html +2 -0
  72. package/doc/api-reference/functions/getToolsByPriority.html +2 -0
  73. package/doc/api-reference/functions/mapLibTypeToOurs.html +2 -0
  74. package/doc/api-reference/functions/mapToolTypeToLib.html +3 -0
  75. package/doc/api-reference/functions/transformCandlestickData.html +3 -0
  76. package/doc/api-reference/functions/transformIndicatorData.html +2 -0
  77. package/doc/api-reference/functions/transformVolumeData.html +3 -0
  78. package/doc/api-reference/functions/useChart.html +4 -0
  79. package/doc/api-reference/functions/useChartStore.html +8 -0
  80. package/doc/api-reference/functions/useCrosshairSync.html +8 -0
  81. package/doc/api-reference/functions/useDrawingModule.html +1 -0
  82. package/doc/api-reference/functions/useDrawingStore.html +8 -0
  83. package/doc/api-reference/functions/useIndicatorStore.html +8 -0
  84. package/doc/api-reference/functions/useQLChartConfig.html +2 -0
  85. package/doc/api-reference/functions/useReplayStore.html +8 -0
  86. package/doc/api-reference/functions/useTheme.html +2 -0
  87. package/doc/api-reference/functions/useTimeBarStore.html +8 -0
  88. package/doc/api-reference/index.html +1 -0
  89. package/doc/api-reference/interfaces/CandlestickData.html +7 -0
  90. package/doc/api-reference/interfaces/CandlestickRawData.html +8 -0
  91. package/doc/api-reference/interfaces/ChartFunctionMenuProps.html +2 -0
  92. package/doc/api-reference/interfaces/ChartManagerCreateOptions.html +4 -0
  93. package/doc/api-reference/interfaces/ChartOptions.html +8 -0
  94. package/doc/api-reference/interfaces/ChartRequestParams.html +8 -0
  95. package/doc/api-reference/interfaces/ChartResponse.html +5 -0
  96. package/doc/api-reference/interfaces/ChartState.html +24 -0
  97. package/doc/api-reference/interfaces/ChartThemeOptions.html +5 -0
  98. package/doc/api-reference/interfaces/CrosshairData.html +5 -0
  99. package/doc/api-reference/interfaces/DrawingModuleProps.html +5 -0
  100. package/doc/api-reference/interfaces/DrawingState.html +48 -0
  101. package/doc/api-reference/interfaces/HistogramData.html +5 -0
  102. package/doc/api-reference/interfaces/HollowCandlestickData.html +14 -0
  103. package/doc/api-reference/interfaces/IndicatorConfig.html +11 -0
  104. package/doc/api-reference/interfaces/IndicatorDataPoint.html +4 -0
  105. package/doc/api-reference/interfaces/IndicatorDataResponse.html +5 -0
  106. package/doc/api-reference/interfaces/IndicatorDefinition.html +9 -0
  107. package/doc/api-reference/interfaces/IndicatorPanelProps.html +3 -0
  108. package/doc/api-reference/interfaces/IndicatorParamDef.html +8 -0
  109. package/doc/api-reference/interfaces/IndicatorRawData.html +4 -0
  110. package/doc/api-reference/interfaces/IndicatorState.html +19 -0
  111. package/doc/api-reference/interfaces/IndicatorTagProps.html +2 -0
  112. package/doc/api-reference/interfaces/KlineReplayOptions.html +4 -0
  113. package/doc/api-reference/interfaces/LayoutSwitcherProps.html +3 -0
  114. package/doc/api-reference/interfaces/LineData.html +4 -0
  115. package/doc/api-reference/interfaces/MockDataConfig.html +8 -0
  116. package/doc/api-reference/interfaces/MockIndicatorConfig.html +5 -0
  117. package/doc/api-reference/interfaces/PairInfo.html +6 -0
  118. package/doc/api-reference/interfaces/PaneInfo.html +6 -0
  119. package/doc/api-reference/interfaces/PanelConfig.html +9 -0
  120. package/doc/api-reference/interfaces/PersistenceConfig.html +12 -0
  121. package/doc/api-reference/interfaces/QLChartConfig.html +18 -0
  122. package/doc/api-reference/interfaces/QLChartLayoutProps.html +9 -0
  123. package/doc/api-reference/interfaces/QLChartPanelProps.html +13 -0
  124. package/doc/api-reference/interfaces/QLChartPanelRef.html +14 -0
  125. package/doc/api-reference/interfaces/QLToolbarProps.html +7 -0
  126. package/doc/api-reference/interfaces/RealtimeCandle.html +9 -0
  127. package/doc/api-reference/interfaces/RealtimeSubscribeFn.html +2 -0
  128. package/doc/api-reference/interfaces/RenkoData.html +16 -0
  129. package/doc/api-reference/interfaces/ReplayControllerProps.html +2 -0
  130. package/doc/api-reference/interfaces/ReplayState.html +19 -0
  131. package/doc/api-reference/interfaces/ThemeConfig.html +14 -0
  132. package/doc/api-reference/interfaces/TimeBarModuleProps.html +5 -0
  133. package/doc/api-reference/interfaces/TimeBarState.html +13 -0
  134. package/doc/api-reference/interfaces/UseChartReturn.html +4 -0
  135. package/doc/api-reference/interfaces/UseDrawingModuleOptions.html +11 -0
  136. package/doc/api-reference/interfaces/UseDrawingModuleReturn.html +6 -0
  137. package/doc/api-reference/interfaces/UseThemeReturn.html +5 -0
  138. package/doc/api-reference/types/EventHandler.html +2 -0
  139. package/doc/api-reference/types/FetchFn.html +2 -0
  140. package/doc/api-reference/types/LayoutMode.html +2 -0
  141. package/doc/api-reference/types/MarketTrend.html +2 -0
  142. package/doc/api-reference/types/ThemePreset.html +2 -0
  143. package/doc/api-reference/variables/BUILTIN_INDICATORS.html +2 -0
  144. package/doc/api-reference/variables/CATEGORY_LABELS.html +2 -0
  145. package/doc/api-reference/variables/DRAWING_TOOLS.html +3 -0
  146. package/doc/api-reference/variables/MARKET_PRESETS.html +1 -0
  147. package/doc/api-reference/variables/PAIR_PRESETS.html +1 -0
  148. package/doc/api-reference/variables/darkPreset.html +1 -0
  149. package/doc/api-reference/variables/lightPreset.html +1 -0
  150. package/doc/components/drawing-module.md +24 -0
  151. package/doc/components/indicator-list-panel.md +24 -0
  152. package/doc/components/indicator-panel.md +17 -0
  153. package/doc/components/pane-divider.md +25 -0
  154. package/doc/components/qlchart-layout.md +30 -0
  155. package/doc/components/qlchart-panel.md +93 -0
  156. package/doc/components/qlchart-provider.md +73 -0
  157. package/doc/components/qltoolbar.md +17 -0
  158. package/doc/components/replay-controller.md +23 -0
  159. package/doc/components/timebar-module.md +13 -0
  160. package/doc/core/chart-manager.md +14 -0
  161. package/doc/core/data-manager.md +33 -0
  162. package/doc/core/event-manager.md +26 -0
  163. package/doc/core/pane-manager.md +13 -0
  164. package/doc/core/series-manager.md +19 -0
  165. package/doc/core/theme-manager.md +21 -0
  166. package/doc/examples/basic-chart.md +24 -0
  167. package/doc/examples/data-format-guide.md +119 -0
  168. package/doc/examples/drawing-tools.md +30 -0
  169. package/doc/examples/indicator-properties.md +34 -0
  170. package/doc/examples/multi-pane.md +24 -0
  171. package/doc/examples/multi-panel.md +23 -0
  172. package/doc/examples/realtime-data.md +147 -0
  173. package/doc/examples/standalone-js.md +333 -0
  174. package/doc/guide/architecture.md +87 -0
  175. package/doc/guide/data-flow.md +310 -0
  176. package/doc/guide/deployment.md +59 -0
  177. package/doc/guide/drawing-properties.md +40 -0
  178. package/doc/guide/getting-started.md +94 -0
  179. package/doc/guide/pane-system.md +47 -0
  180. package/doc/guide/theme-switching.md +58 -0
  181. package/doc/hooks/use-chart.md +20 -0
  182. package/doc/hooks/use-crosshair-sync.md +14 -0
  183. package/doc/hooks/use-drawing-module.md +43 -0
  184. package/doc/hooks/use-theme.md +15 -0
  185. package/doc/index.md +33 -0
  186. package/doc/plugins/drawing/overview.md +36 -0
  187. package/doc/plugins/drawing/persistence.md +42 -0
  188. package/doc/plugins/drawing/tool-registry.md +29 -0
  189. package/doc/plugins/hollow-candlestick.md +18 -0
  190. package/doc/plugins/indicators.md +28 -0
  191. package/doc/plugins/renko.md +17 -0
  192. package/doc/plugins/replay.md +21 -0
  193. package/doc/plugins/screenshot.md +20 -0
  194. package/docs/api.md +94 -0
  195. package/package.json +54 -0
  196. package/python/qlchart/__init__.py +9 -0
  197. package/python/qlchart/__pycache__/__init__.cpython-311.pyc +0 -0
  198. package/python/qlchart/__pycache__/chart.cpython-311.pyc +0 -0
  199. package/python/qlchart/chart.py +333 -0
  200. package/python/qlchart/templates/chart_template.html +304 -0
  201. package/python/requirements.txt +1 -0
  202. package/python/setup.py +18 -0
  203. package/python/tests/__init__.py +1 -0
  204. package/python/tests/__pycache__/__init__.cpython-311.pyc +0 -0
  205. package/python/tests/__pycache__/test_chart.cpython-311-pytest-8.3.3.pyc +0 -0
  206. package/python/tests/test_chart.py +114 -0
  207. package/quantlife-qlchart-0.0.1.tgz +0 -0
  208. package/src/api/chartApi.ts +30 -0
  209. package/src/api/indicatorApi.ts +27 -0
  210. package/src/components/ChartFunctionMenu.tsx +64 -0
  211. package/src/components/PaneChartPanel.tsx +116 -0
  212. package/src/components/PaneDivider.tsx +66 -0
  213. package/src/components/QLChartLayout.tsx +151 -0
  214. package/src/components/QLChartPanel.tsx +560 -0
  215. package/src/components/QLChartProvider.tsx +90 -0
  216. package/src/components/context-menu/ChartContextMenu.tsx +139 -0
  217. package/src/components/context-menu/index.ts +2 -0
  218. package/src/components/drawing/DrawingModule.tsx +36 -0
  219. package/src/components/drawing/DrawingPropertyPanel.tsx +347 -0
  220. package/src/components/drawing/DrawingToolbar.tsx +305 -0
  221. package/src/components/drawing/index.ts +5 -0
  222. package/src/components/index.ts +43 -0
  223. package/src/components/indicator/IndicatorListPanel.tsx +94 -0
  224. package/src/components/indicator/IndicatorModal.tsx +171 -0
  225. package/src/components/indicator/IndicatorPanel.tsx +9 -0
  226. package/src/components/indicator/IndicatorPropertyPanel.tsx +130 -0
  227. package/src/components/indicator/IndicatorTag.tsx +173 -0
  228. package/src/components/indicator/index.ts +4 -0
  229. package/src/components/replay/ReplayController.css +97 -0
  230. package/src/components/replay/ReplayController.tsx +138 -0
  231. package/src/components/timebar/TimeBarModule.tsx +30 -0
  232. package/src/components/timebar/TimeRangeSelector.tsx +96 -0
  233. package/src/components/timebar/index.ts +3 -0
  234. package/src/components/toolbar/GlobalToolbar.tsx +58 -0
  235. package/src/components/toolbar/KlineTypeSelector.tsx +123 -0
  236. package/src/components/toolbar/LayoutSwitcher.tsx +45 -0
  237. package/src/components/toolbar/PeriodSelector.tsx +35 -0
  238. package/src/components/toolbar/QLToolbar.tsx +71 -0
  239. package/src/components/toolbar/TimeRangeSelector.tsx +89 -0
  240. package/src/components/ui/Modal.tsx +67 -0
  241. package/src/core/ChartManager.ts +95 -0
  242. package/src/core/DataManager.ts +427 -0
  243. package/src/core/EventManager.ts +63 -0
  244. package/src/core/IndicatorDataProcessor.ts +104 -0
  245. package/src/core/PaneManager.ts +121 -0
  246. package/src/core/RealtimeDataFeed.ts +110 -0
  247. package/src/core/SeriesManager.ts +210 -0
  248. package/src/core/ThemeManager.ts +59 -0
  249. package/src/core/index.ts +10 -0
  250. package/src/css.d.ts +4 -0
  251. package/src/hooks/useChart.ts +62 -0
  252. package/src/hooks/useCrosshairSync.ts +109 -0
  253. package/src/hooks/useDrawingModule.ts +475 -0
  254. package/src/hooks/useTheme.ts +31 -0
  255. package/src/index.ts +170 -0
  256. package/src/mock/MockDataService.ts +102 -0
  257. package/src/mock/MockIndicatorService.ts +40 -0
  258. package/src/mock/index.ts +5 -0
  259. package/src/mock/presets.ts +16 -0
  260. package/src/plugins/drawing/DrawingAdapter.ts +1762 -0
  261. package/src/plugins/drawing/DrawingPersistence.ts +273 -0
  262. package/src/plugins/drawing/DrawingPropertyTemplates.ts +327 -0
  263. package/src/plugins/drawing/DrawingSharedService.ts +125 -0
  264. package/src/plugins/drawing/DrawingToolRegistry.ts +684 -0
  265. package/src/plugins/drawing/TextLabelOverlay.ts +101 -0
  266. package/src/plugins/drawing/colorUtils.ts +53 -0
  267. package/src/plugins/drawing/index.ts +10 -0
  268. package/src/plugins/drawing/lineStyleMap.ts +46 -0
  269. package/src/plugins/drawing/migration.ts +105 -0
  270. package/src/plugins/drawing/patterns/PatternDefinitions.ts +57 -0
  271. package/src/plugins/drawing/patterns/index.ts +2 -0
  272. package/src/plugins/drawing/periodUtils.ts +51 -0
  273. package/src/plugins/indicators/AutoIndicatorRenderer.ts +204 -0
  274. package/src/plugins/indicators/IndicatorRenderer.ts +350 -0
  275. package/src/plugins/indicators/OverlayIndicator.ts +114 -0
  276. package/src/plugins/indicators/PaneIndicator.ts +137 -0
  277. package/src/plugins/indicators/index.ts +4 -0
  278. package/src/plugins/replay/KlineReplay.ts +163 -0
  279. package/src/plugins/replay/index.ts +2 -0
  280. package/src/plugins/screenshot/ScreenshotUtil.ts +123 -0
  281. package/src/plugins/screenshot/index.ts +1 -0
  282. package/src/plugins/series/HollowCandlestickSeries.ts +111 -0
  283. package/src/plugins/series/RenkoSeries.ts +104 -0
  284. package/src/plugins/series/VolumeCandlestickSeries.ts +127 -0
  285. package/src/plugins/series/index.ts +6 -0
  286. package/src/standalone.ts +386 -0
  287. package/src/store/useChartStore.ts +101 -0
  288. package/src/store/useDrawingStore.ts +135 -0
  289. package/src/store/useIndicatorStore.ts +100 -0
  290. package/src/store/usePanelRegistry.ts +50 -0
  291. package/src/store/useReplayStore.ts +42 -0
  292. package/src/store/useTimeBarStore.ts +34 -0
  293. package/src/styles/chart.css +312 -0
  294. package/src/styles/components.css +184 -0
  295. package/src/styles/context-menu.css +60 -0
  296. package/src/styles/drawing.css +524 -0
  297. package/src/styles/indicator-modal.css +216 -0
  298. package/src/styles/indicator-tag.css +210 -0
  299. package/src/styles/pane-chart.css +9 -0
  300. package/src/styles/responsive.css +71 -0
  301. package/src/styles/themes/dark.css +63 -0
  302. package/src/styles/themes/light.css +61 -0
  303. package/src/styles/toolbar.css +129 -0
  304. package/src/types/api.ts +36 -0
  305. package/src/types/chart.ts +44 -0
  306. package/src/types/drawing.ts +265 -0
  307. package/src/types/index.ts +40 -0
  308. package/src/types/indicator.ts +344 -0
  309. package/src/types/series.ts +53 -0
  310. package/src/types/theme.ts +48 -0
  311. package/src/utils/dataTransformer.ts +63 -0
  312. package/src/utils/heikinAshi.ts +41 -0
  313. package/src/utils/index.ts +3 -0
  314. package/src/utils/lineBreak.ts +88 -0
  315. package/src/utils/themePresets.ts +69 -0
  316. package/src/utils/timeFormatter.ts +29 -0
  317. package/src/utils/timeScaleUtils.ts +68 -0
  318. package/tsconfig.json +21 -0
  319. package/typedoc.json +10 -0
  320. package/vite.config.standalone.ts +31 -0
  321. package/vite.config.ts +24 -0
@@ -0,0 +1,427 @@
1
+ import type { LogicalRange, IChartApi, ISeriesApi, UTCTimestamp } from 'lightweight-charts';
2
+ import type { ChartRequestParams, ChartFetchResult, CandlestickRawData, CandlestickData } from '../types/index.js';
3
+ import { transformCandlestickData } from '../utils/dataTransformer.js';
4
+
5
+ /** 数据获取回调函数类型 */
6
+ export type FetchFn = (params: ChartRequestParams) => Promise<unknown>;
7
+
8
+ /** 分片缓存条目 */
9
+ interface CacheSlice {
10
+ data: CandlestickRawData[];
11
+ startTime: number;
12
+ endTime: number;
13
+ timestamp: number;
14
+ }
15
+
16
+ /**
17
+ * 数据管理器(增强版)
18
+ *
19
+ * 三大核心能力:
20
+ * 1. 拖动加载(ScrollLoader)— 监听 visibleLogicalRangeChange,触底自动加载历史
21
+ * 2. 定时获取(RealtimePoller)— setInterval 拉取最新 K 线
22
+ * 3. 分片缓存(SliceCache)— 按 pair+product+period 维度缓存已加载的分片
23
+ *
24
+ * 同时保持对旧 fetchChartData() 的向后兼容。
25
+ */
26
+ export class DataManager {
27
+ private fetchFn: FetchFn;
28
+
29
+ // ── 旧缓存系统(保留向后兼容)──
30
+ private cache = new Map<string, { data: unknown; timestamp: number }>();
31
+ private cacheTTL = 5 * 60 * 1000;
32
+
33
+ // ── Chart 绑定状态 ──
34
+ private chart: IChartApi | null = null;
35
+ private series: ISeriesApi<any> | null = null;
36
+ private pairId = '';
37
+ private product = '';
38
+ private period = '';
39
+
40
+ // ── 拖动加载 ──
41
+ private isLoadingHistory = false;
42
+ private hasMoreHistory = true;
43
+ private scrollHandler: ((range: LogicalRange | null) => void) | null = null;
44
+
45
+ // ── 定时获取 ──
46
+ private realtimeTimer: ReturnType<typeof setInterval> | null = null;
47
+
48
+ // ── 全量数据(方案B:维护内存数组)──
49
+ private allData: CandlestickData[] = [];
50
+
51
+ // ── 分片缓存 ──
52
+ private slices = new Map<string, CacheSlice[]>();
53
+
54
+ constructor(fetchFn: FetchFn) {
55
+ this.fetchFn = fetchFn;
56
+ }
57
+
58
+ // ════════════════════════════════════════
59
+ // Chart 绑定 / 解绑
60
+ // ════════════════════════════════════════
61
+
62
+ /**
63
+ * 绑定到图表实例,启用拖动加载
64
+ */
65
+ attachToChart(
66
+ chart: IChartApi,
67
+ series: ISeriesApi<any>,
68
+ params: { pairId: string; product: string; period: string },
69
+ ): void {
70
+ this.chart = chart;
71
+ this.series = series;
72
+ this.pairId = params.pairId;
73
+ this.product = params.product;
74
+ this.period = params.period;
75
+
76
+ // 重置状态
77
+ this.hasMoreHistory = true;
78
+ this.isLoadingHistory = false;
79
+
80
+ // 初始化全量数据(从当前 series 获取已有数据)
81
+ this.syncAllDataFromSeries();
82
+
83
+ // 注册拖动监听
84
+ this.scrollHandler = (range: LogicalRange | null) => {
85
+ if (!range) return;
86
+ // 当可见范围左边缘接近起点时触发历史加载
87
+ if (range.from < 10 && this.hasMoreHistory && !this.isLoadingHistory) {
88
+ this.loadMoreHistory();
89
+ }
90
+ };
91
+ chart.timeScale().subscribeVisibleLogicalRangeChange(this.scrollHandler);
92
+ }
93
+
94
+ /**
95
+ * 解绑图表,清理所有监听和定时器
96
+ */
97
+ detach(): void {
98
+ if (this.scrollHandler && this.chart) {
99
+ this.chart.timeScale().unsubscribeVisibleLogicalRangeChange(this.scrollHandler);
100
+ }
101
+ this.stopRealtimeFetch();
102
+ this.chart = null;
103
+ this.series = null;
104
+ this.scrollHandler = null;
105
+ this.allData = [];
106
+ }
107
+
108
+ // ════════════════════════════════════════
109
+ // 拖动加载(ScrollLoader)
110
+ // ════════════════════════════════════════
111
+
112
+ /**
113
+ * 加载更早的历史数据
114
+ */
115
+ private async loadMoreHistory(): Promise<void> {
116
+ this.isLoadingHistory = true;
117
+ try {
118
+ const earliestTime = this.getEarliestDataTime();
119
+ const result = await this.fetchFn({
120
+ pairId: this.pairId,
121
+ product: this.product,
122
+ period: this.period,
123
+ endTime: earliestTime,
124
+ limit: 500,
125
+ }) as ChartFetchResult | { data: CandlestickRawData[] } | undefined;
126
+
127
+ if (!result) {
128
+ this.hasMoreHistory = false;
129
+ return;
130
+ }
131
+
132
+ // 兼容 ChartFetchResult 和旧格式
133
+ const rawData = (result as ChartFetchResult).data ?? (result as { data?: CandlestickRawData[] }).data ?? [];
134
+ const hasMore = (result as ChartFetchResult).hasMoreHistory;
135
+
136
+ if (rawData.length > 0) {
137
+ this.prependData(rawData);
138
+ this.saveSlice(rawData);
139
+ }
140
+
141
+ // 更新 hasMoreHistory(未明确返回 false 则默认 true)
142
+ this.hasMoreHistory = hasMore !== false && rawData.length > 0;
143
+ } catch (e) {
144
+ console.error('[DataManager] loadMoreHistory failed:', e);
145
+ } finally {
146
+ this.isLoadingHistory = false;
147
+ }
148
+ }
149
+
150
+ /**
151
+ * 在 series 前面追加历史数据(方案B:维护全量数组)
152
+ */
153
+ private prependData(newData: CandlestickRawData[]): void {
154
+ if (!this.series) return;
155
+ const newTransformed = transformCandlestickData(newData);
156
+ // 合并到全量数组前面
157
+ this.allData = [...newTransformed, ...this.allData];
158
+ // 去重(按 time)
159
+ const seen = new Set<number>();
160
+ this.allData = this.allData.filter((d) => {
161
+ const t = d.time as number;
162
+ if (seen.has(t)) return false;
163
+ seen.add(t);
164
+ return true;
165
+ });
166
+ // 全量 setData
167
+ this.series.setData(this.allData);
168
+ }
169
+
170
+ // ════════════════════════════════════════
171
+ // 定时获取最新(RealtimePoller)
172
+ // ════════════════════════════════════════
173
+
174
+ /**
175
+ * 启动定时获取最新数据
176
+ */
177
+ startRealtimeFetch(intervalMs: number = 5000): void {
178
+ if (this.realtimeTimer) clearInterval(this.realtimeTimer);
179
+ this.realtimeTimer = setInterval(() => this.fetchLatest(), intervalMs);
180
+ }
181
+
182
+ /**
183
+ * 停止定时获取
184
+ */
185
+ stopRealtimeFetch(): void {
186
+ if (this.realtimeTimer) {
187
+ clearInterval(this.realtimeTimer);
188
+ this.realtimeTimer = null;
189
+ }
190
+ }
191
+
192
+ /**
193
+ * 获取最新 K 线并增量更新
194
+ */
195
+ private async fetchLatest(): Promise<void> {
196
+ if (!this.series) return;
197
+ try {
198
+ const latestTime = this.getLatestDataTime();
199
+ const now = Date.now();
200
+ const result = await this.fetchFn({
201
+ pairId: this.pairId,
202
+ product: this.product,
203
+ period: this.period,
204
+ startTime: latestTime,
205
+ endTime: now,
206
+ }) as ChartFetchResult | { data: CandlestickRawData[] } | undefined;
207
+
208
+ if (!result) return;
209
+
210
+ const rawData = (result as ChartFetchResult).data ?? (result as { data?: CandlestickRawData[] }).data ?? [];
211
+
212
+ if (rawData.length > 0) {
213
+ // 增量更新:逐条 update(最后一条可能是更新而非新增)
214
+ for (const candle of rawData) {
215
+ const point = {
216
+ time: (candle.time / 1000) as UTCTimestamp,
217
+ open: candle.open,
218
+ high: candle.high,
219
+ low: candle.low,
220
+ close: candle.close,
221
+ };
222
+ this.series.update(point);
223
+
224
+ // 同步 allData
225
+ const timeSec = candle.time / 1000;
226
+ const existingIdx = this.allData.findIndex((d) => (d.time as number) === timeSec);
227
+ if (existingIdx >= 0) {
228
+ this.allData[existingIdx] = point;
229
+ } else {
230
+ this.allData.push(point);
231
+ this.allData.sort((a, b) => (a.time as number) - (b.time as number));
232
+ }
233
+ }
234
+ }
235
+ } catch (e) {
236
+ console.error('[DataManager] fetchLatest failed:', e);
237
+ }
238
+ }
239
+
240
+ // ════════════════════════════════════════
241
+ // 分片缓存(SliceCache)
242
+ // ════════════════════════════════════════
243
+
244
+ private getCacheKey(): string {
245
+ return `${this.pairId}_${this.product}_${this.period}`;
246
+ }
247
+
248
+ /**
249
+ * 保存分片到缓存
250
+ */
251
+ private saveSlice(data: CandlestickRawData[]): void {
252
+ if (data.length === 0) return;
253
+ const key = this.getCacheKey();
254
+ const times = data.map((d) => d.time);
255
+ const slice: CacheSlice = {
256
+ data,
257
+ startTime: Math.min(...times),
258
+ endTime: Math.max(...times),
259
+ timestamp: Date.now(),
260
+ };
261
+ const existing = this.slices.get(key) ?? [];
262
+ existing.push(slice);
263
+ // 过期清理
264
+ const validSlices = existing.filter((s) => Date.now() - s.timestamp < this.cacheTTL);
265
+ this.slices.set(key, validSlices);
266
+ }
267
+
268
+ /**
269
+ * 查询缓存中是否包含指定时间范围的数据
270
+ */
271
+ private getCachedSlices(startTime: number, endTime: number): CandlestickRawData[] {
272
+ const key = this.getCacheKey();
273
+ const slices = this.slices.get(key);
274
+ if (!slices) return [];
275
+
276
+ const merged: CandlestickRawData[] = [];
277
+ const seen = new Set<number>();
278
+ for (const slice of slices) {
279
+ // 分片与请求范围有交集
280
+ if (slice.startTime <= endTime && slice.endTime >= startTime) {
281
+ for (const d of slice.data) {
282
+ if (d.time >= startTime && d.time <= endTime && !seen.has(d.time)) {
283
+ merged.push(d);
284
+ seen.add(d.time);
285
+ }
286
+ }
287
+ }
288
+ }
289
+ return merged.sort((a, b) => a.time - b.time);
290
+ }
291
+
292
+ /**
293
+ * 检查分片缓存是否覆盖了请求范围
294
+ */
295
+ private isRangeCached(startTime: number, endTime: number): boolean {
296
+ const key = this.getCacheKey();
297
+ const slices = this.slices.get(key);
298
+ if (!slices || slices.length === 0) return false;
299
+
300
+ // 合并所有有效分片的时间范围
301
+ let cachedMin = Infinity;
302
+ let cachedMax = -Infinity;
303
+ for (const slice of slices) {
304
+ if (Date.now() - slice.timestamp >= this.cacheTTL) continue;
305
+ cachedMin = Math.min(cachedMin, slice.startTime);
306
+ cachedMax = Math.max(cachedMax, slice.endTime);
307
+ }
308
+ return cachedMin <= startTime && cachedMax >= endTime;
309
+ }
310
+
311
+ // ════════════════════════════════════════
312
+ // 辅助方法
313
+ // ════════════════════════════════════════
314
+
315
+ /**
316
+ * 从当前 allData 获取最早时间戳(毫秒)
317
+ */
318
+ private getEarliestDataTime(): number {
319
+ if (this.allData.length === 0) return Date.now();
320
+ const earliest = this.allData[0].time as number;
321
+ return earliest * 1000;
322
+ }
323
+
324
+ /**
325
+ * 从当前 allData 获取最新时间戳(毫秒)
326
+ */
327
+ private getLatestDataTime(): number {
328
+ if (this.allData.length === 0) return Date.now() - 86400000;
329
+ const latest = this.allData[this.allData.length - 1].time as number;
330
+ return latest * 1000;
331
+ }
332
+
333
+ /**
334
+ * 从 series 同步全量数据到 allData
335
+ */
336
+ private syncAllDataFromSeries(): void {
337
+ if (!this.series) return;
338
+ try {
339
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
340
+ const data = (this.series as any).data?.() as CandlestickData[] | undefined;
341
+ if (data && Array.isArray(data)) {
342
+ this.allData = [...data];
343
+ }
344
+ } catch {
345
+ // LWC API 可能不支持 data() 方法,忽略
346
+ }
347
+ }
348
+
349
+ /**
350
+ * 手动设置全量数据(外部调用 setData 后同步)
351
+ */
352
+ setAllData(data: CandlestickData[]): void {
353
+ this.allData = [...data];
354
+ }
355
+
356
+ // ════════════════════════════════════════
357
+ // 旧 API(向后兼容)
358
+ // ════════════════════════════════════════
359
+
360
+ /**
361
+ * 获取图表数据(带缓存,向后兼容)
362
+ */
363
+ async fetchChartData(params: ChartRequestParams): Promise<unknown> {
364
+ const cacheKey = this.getLegacyCacheKey(params);
365
+ const cached = this.getCacheEntry(cacheKey);
366
+ if (cached) {
367
+ return cached;
368
+ }
369
+
370
+ const data = await this.fetchFn(params);
371
+ this.setCache(cacheKey, data);
372
+
373
+ // 同时保存到分片缓存
374
+ const result = data as ChartFetchResult | { data?: CandlestickRawData[] };
375
+ const rawData = (result as ChartFetchResult)?.data ?? (result as { data?: CandlestickRawData[] })?.data;
376
+ if (rawData && Array.isArray(rawData) && rawData.length > 0) {
377
+ // 临时设置 pair/product/period 以便 getCacheKey 工作
378
+ const savedPair = this.pairId;
379
+ const savedProduct = this.product;
380
+ const savedPeriod = this.period;
381
+ this.pairId = params.pairId;
382
+ this.product = params.product;
383
+ this.period = params.period;
384
+ this.saveSlice(rawData);
385
+ this.pairId = savedPair;
386
+ this.product = savedProduct;
387
+ this.period = savedPeriod;
388
+ }
389
+
390
+ return data;
391
+ }
392
+
393
+ private getLegacyCacheKey(params: ChartRequestParams): string {
394
+ return `${params.pairId}_${params.product}_${params.period}_${params.startTime ?? ''}_${params.endTime ?? ''}`;
395
+ }
396
+
397
+ private getCacheEntry(key: string): unknown | null {
398
+ const entry = this.cache.get(key);
399
+ if (entry && Date.now() - entry.timestamp < this.cacheTTL) {
400
+ return entry.data;
401
+ }
402
+ if (entry) {
403
+ this.cache.delete(key);
404
+ }
405
+ return null;
406
+ }
407
+
408
+ private setCache(key: string, data: unknown): void {
409
+ this.cache.set(key, { data, timestamp: Date.now() });
410
+ }
411
+
412
+ /** 手动设置缓存数据 */
413
+ setCacheData(params: ChartRequestParams, data: unknown): void {
414
+ this.setCache(this.getLegacyCacheKey(params), data);
415
+ }
416
+
417
+ /** 清除所有缓存(旧缓存 + 分片缓存) */
418
+ clearCache(): void {
419
+ this.cache.clear();
420
+ this.slices.clear();
421
+ }
422
+
423
+ /** 设置缓存TTL(毫秒) */
424
+ setCacheTTL(ttl: number): void {
425
+ this.cacheTTL = ttl;
426
+ }
427
+ }
@@ -0,0 +1,63 @@
1
+ /** 图表事件枚举 */
2
+ export enum ChartEvent {
3
+ ChartCreated = 'chart:created',
4
+ ChartDisposed = 'chart:disposed',
5
+ DataLoaded = 'data:loaded',
6
+ PeriodChanged = 'period:changed',
7
+ CrosshairMove = 'crosshair:move',
8
+ SeriesAdded = 'series:added',
9
+ SeriesRemoved = 'series:removed',
10
+ ThemeChanged = 'theme:changed',
11
+ LayoutChanged = 'layout:changed',
12
+ Error = 'error',
13
+ }
14
+
15
+ /** 事件处理器类型 */
16
+ export type EventHandler<T = unknown> = (data: T) => void;
17
+
18
+ /**
19
+ * 泛型事件总线
20
+ * 用于图表各模块间的解耦通信
21
+ */
22
+ export class EventManager {
23
+ private listeners = new Map<string, Set<EventHandler>>();
24
+
25
+ /** 注册事件监听 */
26
+ on<T = unknown>(event: string, handler: EventHandler<T>): () => void {
27
+ if (!this.listeners.has(event)) {
28
+ this.listeners.set(event, new Set());
29
+ }
30
+ const set = this.listeners.get(event)!;
31
+ set.add(handler as EventHandler);
32
+
33
+ // 返回取消监听函数
34
+ return () => this.off(event, handler);
35
+ }
36
+
37
+ /** 取消事件监听 */
38
+ off<T = unknown>(event: string, handler: EventHandler<T>): void {
39
+ const set = this.listeners.get(event);
40
+ if (set) {
41
+ set.delete(handler as EventHandler);
42
+ }
43
+ }
44
+
45
+ /** 触发事件 */
46
+ emit<T = unknown>(event: string, data?: T): void {
47
+ const set = this.listeners.get(event);
48
+ if (set) {
49
+ set.forEach((handler) => {
50
+ try {
51
+ handler(data);
52
+ } catch (err) {
53
+ console.error(`[EventManager] Error in handler for "${event}":`, err);
54
+ }
55
+ });
56
+ }
57
+ }
58
+
59
+ /** 清除所有监听 */
60
+ clear(): void {
61
+ this.listeners.clear();
62
+ }
63
+ }
@@ -0,0 +1,104 @@
1
+ import type { UTCTimestamp } from 'lightweight-charts';
2
+ import type { ChartDataResponse, IndicatorMeta } from '../types/indicator.js';
3
+ import { IndicatorType, IndicatorDisplayType } from '../types/indicator.js';
4
+
5
+ /** 解析后的单条指标线数据 */
6
+ export interface ParsedIndicatorLine {
7
+ line: IndicatorMeta['lines'][number];
8
+ data: Array<{ time: UTCTimestamp; value: number }>;
9
+ }
10
+
11
+ /** 解析后的单条指标 */
12
+ export interface ParsedIndicator {
13
+ meta: IndicatorMeta;
14
+ lineData: ParsedIndicatorLine[];
15
+ }
16
+
17
+ /** IndicatorDataProcessor 解析结果 */
18
+ export interface ParsedChartData {
19
+ candlestick: Array<{ time: UTCTimestamp; open: number; high: number; low: number; close: number }>;
20
+ volume: Array<{ time: UTCTimestamp; value: number; color: string }>;
21
+ overlayIndicators: ParsedIndicator[];
22
+ paneIndicators: ParsedIndicator[];
23
+ }
24
+
25
+ /**
26
+ * IndicatorDataProcessor
27
+ *
28
+ * 将后端返回的 ChartDataResponse(二维数组格式)解析为可渲染的指标序列数据。
29
+ * 前端不需要知道指标算法,只按 dataIndex 取值渲染。
30
+ */
31
+ export class IndicatorDataProcessor {
32
+ /**
33
+ * 解析 ChartDataResponse 为可渲染数据
34
+ */
35
+ static parse(response: ChartDataResponse): ParsedChartData {
36
+ const { timestamps, data, ohlcIndex, indicators } = response;
37
+
38
+ // 解析K线数据
39
+ const candlestick = timestamps.map((time, i) => ({
40
+ time: (time / 1000) as UTCTimestamp,
41
+ open: data[i]?.[ohlcIndex.open] ?? 0,
42
+ high: data[i]?.[ohlcIndex.high] ?? 0,
43
+ low: data[i]?.[ohlcIndex.low] ?? 0,
44
+ close: data[i]?.[ohlcIndex.close] ?? 0,
45
+ }));
46
+
47
+ // 解析成交量
48
+ const volume = timestamps.map((time, i) => ({
49
+ time: (time / 1000) as UTCTimestamp,
50
+ value: data[i]?.[ohlcIndex.volume] ?? 0,
51
+ color: (data[i]?.[ohlcIndex.close] ?? 0) >= (data[i]?.[ohlcIndex.open] ?? 0)
52
+ ? 'rgba(38, 166, 154, 0.35)'
53
+ : 'rgba(239, 83, 80, 0.35)',
54
+ }));
55
+
56
+ // 按指标类型分组解析
57
+ const overlayIndicators: ParsedIndicator[] = [];
58
+ const paneIndicators: ParsedIndicator[] = [];
59
+
60
+ for (const meta of indicators) {
61
+ const parsed: ParsedIndicator = {
62
+ meta,
63
+ lineData: meta.lines.map(line => ({
64
+ line,
65
+ data: timestamps
66
+ .map((time, i) => ({
67
+ time: (time / 1000) as UTCTimestamp,
68
+ value: data[i]?.[line.dataIndex] ?? NaN,
69
+ }))
70
+ .filter(d => !Number.isNaN(d.value) && d.value != null),
71
+ })),
72
+ };
73
+
74
+ if (meta.displayType === IndicatorDisplayType.Pane) {
75
+ paneIndicators.push(parsed);
76
+ } else {
77
+ overlayIndicators.push(parsed);
78
+ }
79
+ }
80
+
81
+ return { candlestick, volume, overlayIndicators, paneIndicators };
82
+ }
83
+
84
+ /**
85
+ * 判断fetchFn返回的是否为v2格式
86
+ */
87
+ static isV2Format(response: unknown): response is ChartDataResponse {
88
+ return (
89
+ typeof response === 'object' && response !== null &&
90
+ 'timestamps' in response &&
91
+ 'data' in response &&
92
+ 'ohlcIndex' in response &&
93
+ 'indicators' in response
94
+ );
95
+ }
96
+
97
+ /**
98
+ * 兼容旧枚举值映射
99
+ */
100
+ static mapDisplayType(displayType: string): IndicatorType {
101
+ if (displayType === 'pane') return IndicatorType.Pane;
102
+ return IndicatorType.Overlay;
103
+ }
104
+ }