@363045841yyt/klinechart-core 0.7.3 → 0.7.5-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +201 -201
- package/README.zh-CN.md +201 -201
- package/dist/engine/renderers/webgl/candleSurface.js +47 -47
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -2
- package/dist/version.js.map +1 -1
- package/package.json +129 -122
- package/src/__tests__/signal.test.ts +124 -124
- package/src/config/chartSettings.ts +66 -66
- package/src/controllers/__tests__/drawing.test.ts +214 -214
- package/src/controllers/__tests__/indicatorSelector.test.ts +481 -481
- package/src/controllers/__tests__/toolbar.test.ts +225 -225
- package/src/controllers/createChartController.ts +665 -665
- package/src/controllers/createDrawingController.ts +96 -96
- package/src/controllers/createIndicatorSelectorController.ts +307 -307
- package/src/controllers/createToolbarController.ts +146 -146
- package/src/controllers/index.ts +19 -19
- package/src/controllers/types.ts +284 -284
- package/src/engine/__tests__/chart.dpr.test.ts +401 -401
- package/src/engine/__tests__/paneRenderer.resize.test.ts +92 -92
- package/src/engine/chart-store.ts +121 -121
- package/src/engine/chart.d.ts +617 -617
- package/src/engine/chart.ts +2815 -2815
- package/src/engine/controller/__tests__/interaction.dpr.test.ts +259 -259
- package/src/engine/controller/interaction.ts +722 -722
- package/src/engine/controller/markerInteraction.ts +130 -130
- package/src/engine/controller/pinchTracker.ts +82 -82
- package/src/engine/controller/tooltipPosition.ts +48 -48
- package/src/engine/draw/__tests__/pixelAlign.spec.ts +176 -176
- package/src/engine/draw/pixelAlign.ts +259 -259
- package/src/engine/drawing/index.ts +655 -655
- package/src/engine/drawing/interaction.ts +842 -842
- package/src/engine/drawing/plugin.ts +343 -343
- package/src/engine/indicators/__tests__/__fixtures__/golden/atr.json +38 -38
- package/src/engine/indicators/__tests__/__fixtures__/golden/dema.json +14 -14
- package/src/engine/indicators/__tests__/__fixtures__/golden/hma.json +14 -14
- package/src/engine/indicators/__tests__/__fixtures__/golden/index.ts +55 -55
- package/src/engine/indicators/__tests__/__fixtures__/golden/kama.json +14 -14
- package/src/engine/indicators/__tests__/__fixtures__/golden/tema.json +14 -14
- package/src/engine/indicators/__tests__/__fixtures__/golden/wma.json +40 -40
- package/src/engine/indicators/__tests__/__fixtures__/synthetic.ts +65 -65
- package/src/engine/indicators/__tests__/_propertyAssertions.ts +76 -76
- package/src/engine/indicators/__tests__/atr.test.ts +153 -153
- package/src/engine/indicators/__tests__/calculators.test.ts +614 -614
- package/src/engine/indicators/__tests__/cmf-mfi.test.ts +100 -100
- package/src/engine/indicators/__tests__/dema.test.ts +73 -73
- package/src/engine/indicators/__tests__/donchian.test.ts +70 -70
- package/src/engine/indicators/__tests__/hma.test.ts +73 -73
- package/src/engine/indicators/__tests__/ichimoku.test.ts +105 -105
- package/src/engine/indicators/__tests__/kama.test.ts +80 -80
- package/src/engine/indicators/__tests__/keltner.test.ts +65 -65
- package/src/engine/indicators/__tests__/pivot-fib.test.ts +110 -110
- package/src/engine/indicators/__tests__/roc.test.ts +68 -68
- package/src/engine/indicators/__tests__/sar.test.ts +86 -86
- package/src/engine/indicators/__tests__/scheduler.test.ts +831 -831
- package/src/engine/indicators/__tests__/soa.test.ts +533 -533
- package/src/engine/indicators/__tests__/structure.test.ts +110 -110
- package/src/engine/indicators/__tests__/supertrend.test.ts +65 -65
- package/src/engine/indicators/__tests__/tema.test.ts +68 -68
- package/src/engine/indicators/__tests__/trix.test.ts +70 -70
- package/src/engine/indicators/__tests__/volatility.test.ts +117 -117
- package/src/engine/indicators/__tests__/volume.test.ts +115 -115
- package/src/engine/indicators/__tests__/volumeProfile.test.ts +74 -74
- package/src/engine/indicators/__tests__/vwap.test.ts +69 -69
- package/src/engine/indicators/__tests__/wma.test.ts +112 -112
- package/src/engine/indicators/__tests__/zones.test.ts +95 -95
- package/src/engine/indicators/atrState.ts +27 -27
- package/src/engine/indicators/bollState.ts +51 -51
- package/src/engine/indicators/calculators.ts +2593 -2593
- package/src/engine/indicators/cciState.ts +25 -25
- package/src/engine/indicators/chaikinVolState.ts +32 -32
- package/src/engine/indicators/cmfState.ts +27 -27
- package/src/engine/indicators/demaState.ts +27 -27
- package/src/engine/indicators/donchianState.ts +43 -43
- package/src/engine/indicators/eneState.ts +43 -43
- package/src/engine/indicators/expmaState.ts +43 -43
- package/src/engine/indicators/fastkState.ts +25 -25
- package/src/engine/indicators/fibState.ts +41 -41
- package/src/engine/indicators/hmaState.ts +27 -27
- package/src/engine/indicators/hvState.ts +28 -28
- package/src/engine/indicators/ichimokuState.ts +70 -70
- package/src/engine/indicators/indicator.worker.ts +169 -169
- package/src/engine/indicators/indicatorDefinitionRegistry.ts +62 -62
- package/src/engine/indicators/indicatorMetadata.ts +110 -110
- package/src/engine/indicators/indicatorRegistry.ts +106 -106
- package/src/engine/indicators/indicatorRuntime.ts +1548 -1548
- package/src/engine/indicators/kamaState.ts +34 -34
- package/src/engine/indicators/keltnerState.ts +49 -49
- package/src/engine/indicators/kstState.ts +42 -42
- package/src/engine/indicators/maState.ts +36 -36
- package/src/engine/indicators/macdState.ts +76 -76
- package/src/engine/indicators/mfiState.ts +27 -27
- package/src/engine/indicators/momState.ts +25 -25
- package/src/engine/indicators/obvState.ts +25 -25
- package/src/engine/indicators/parkinsonState.ts +28 -28
- package/src/engine/indicators/pivotState.ts +51 -51
- package/src/engine/indicators/pvtState.ts +25 -25
- package/src/engine/indicators/rocState.ts +27 -27
- package/src/engine/indicators/rsiState.ts +65 -65
- package/src/engine/indicators/sarState.ts +41 -41
- package/src/engine/indicators/scheduler.ts +1205 -1205
- package/src/engine/indicators/soa.ts +352 -352
- package/src/engine/indicators/stateComposer.ts +1262 -1262
- package/src/engine/indicators/stochState.ts +26 -26
- package/src/engine/indicators/structureState.ts +69 -69
- package/src/engine/indicators/supertrendState.ts +37 -37
- package/src/engine/indicators/temaState.ts +27 -27
- package/src/engine/indicators/trixState.ts +35 -35
- package/src/engine/indicators/vmaState.ts +27 -27
- package/src/engine/indicators/volumeProfileState.ts +63 -63
- package/src/engine/indicators/vwapState.ts +29 -29
- package/src/engine/indicators/wmaState.ts +27 -27
- package/src/engine/indicators/wmsrState.ts +25 -25
- package/src/engine/indicators/workerProtocol.ts +613 -613
- package/src/engine/indicators/zonesState.ts +47 -47
- package/src/engine/layout/pane.ts +161 -161
- package/src/engine/marker/registry.ts +265 -265
- package/src/engine/paneRenderer.ts +169 -169
- package/src/engine/renderers/Indicator/atr.ts +237 -237
- package/src/engine/renderers/Indicator/boll.ts +317 -317
- package/src/engine/renderers/Indicator/cci.ts +275 -275
- package/src/engine/renderers/Indicator/chaikinVol.ts +138 -138
- package/src/engine/renderers/Indicator/cmf.ts +137 -137
- package/src/engine/renderers/Indicator/dema.ts +136 -136
- package/src/engine/renderers/Indicator/donchian.ts +137 -137
- package/src/engine/renderers/Indicator/ene.ts +271 -271
- package/src/engine/renderers/Indicator/expma.ts +197 -197
- package/src/engine/renderers/Indicator/fastk.ts +316 -316
- package/src/engine/renderers/Indicator/fib.ts +141 -141
- package/src/engine/renderers/Indicator/hma.ts +136 -136
- package/src/engine/renderers/Indicator/hv.ts +124 -124
- package/src/engine/renderers/Indicator/ichimoku.ts +181 -181
- package/src/engine/renderers/Indicator/index.ts +241 -241
- package/src/engine/renderers/Indicator/indicatorData.ts +650 -650
- package/src/engine/renderers/Indicator/kama.ts +136 -136
- package/src/engine/renderers/Indicator/keltner.ts +137 -137
- package/src/engine/renderers/Indicator/kst.ts +302 -302
- package/src/engine/renderers/Indicator/ma.ts +200 -200
- package/src/engine/renderers/Indicator/macd.ts +477 -477
- package/src/engine/renderers/Indicator/macdLegend.ts +141 -141
- package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +272 -272
- package/src/engine/renderers/Indicator/mfi.ts +142 -142
- package/src/engine/renderers/Indicator/mom.ts +311 -311
- package/src/engine/renderers/Indicator/obv.ts +123 -123
- package/src/engine/renderers/Indicator/parkinson.ts +124 -124
- package/src/engine/renderers/Indicator/pivot.ts +131 -131
- package/src/engine/renderers/Indicator/pvt.ts +123 -123
- package/src/engine/renderers/Indicator/roc.ts +143 -143
- package/src/engine/renderers/Indicator/rsi.ts +390 -390
- package/src/engine/renderers/Indicator/sar.ts +113 -113
- package/src/engine/renderers/Indicator/scale/atr_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/cci_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/fastk_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/indicator_scale.ts +204 -204
- package/src/engine/renderers/Indicator/scale/kst_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/macd_scale.ts +22 -22
- package/src/engine/renderers/Indicator/scale/mom_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/rsi_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/stoch_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/volume_scale.ts +26 -26
- package/src/engine/renderers/Indicator/scale/wmsr_scale.ts +19 -19
- package/src/engine/renderers/Indicator/stoch.ts +359 -359
- package/src/engine/renderers/Indicator/structure.ts +126 -126
- package/src/engine/renderers/Indicator/subPaneConfig.ts +265 -265
- package/src/engine/renderers/Indicator/supertrend.ts +115 -115
- package/src/engine/renderers/Indicator/tema.ts +136 -136
- package/src/engine/renderers/Indicator/trix.ts +158 -158
- package/src/engine/renderers/Indicator/vma.ts +124 -124
- package/src/engine/renderers/Indicator/volumeProfile.ts +125 -125
- package/src/engine/renderers/Indicator/vwap.ts +123 -123
- package/src/engine/renderers/Indicator/wma.ts +136 -136
- package/src/engine/renderers/Indicator/wmsr.ts +328 -328
- package/src/engine/renderers/Indicator/zones.ts +104 -104
- package/src/engine/renderers/__tests__/boll.renderer.test.ts +314 -314
- package/src/engine/renderers/__tests__/ene.renderer.test.ts +305 -305
- package/src/engine/renderers/__tests__/expma.renderer.test.ts +279 -279
- package/src/engine/renderers/__tests__/ma.renderer.test.ts +426 -426
- package/src/engine/renderers/__tests__/mainIndicatorLegend.renderer.test.ts +502 -502
- package/src/engine/renderers/__tests__/yAxis.renderer.test.ts +173 -173
- package/src/engine/renderers/candle.ts +459 -459
- package/src/engine/renderers/crosshair.ts +69 -69
- package/src/engine/renderers/customMarkers.ts +162 -162
- package/src/engine/renderers/extremaMarkers.ts +246 -246
- package/src/engine/renderers/gridLines.ts +90 -90
- package/src/engine/renderers/lastPrice.ts +97 -97
- package/src/engine/renderers/paneTitle.ts +136 -136
- package/src/engine/renderers/subVolume.ts +236 -236
- package/src/engine/renderers/timeAxis.ts +121 -121
- package/src/engine/renderers/webgl/candleSurface.ts +955 -955
- package/src/engine/renderers/webgl/sharedWebGLSurface.ts +146 -146
- package/src/engine/renderers/yAxis.ts +105 -105
- package/src/engine/scale/__tests__/logFormula.spec.ts +148 -148
- package/src/engine/scale/logFormula.ts +130 -130
- package/src/engine/scale/price.ts +39 -39
- package/src/engine/scale/priceScale.ts +264 -264
- package/src/engine/subPaneManager.ts +427 -427
- package/src/engine/theme/colors.ts +642 -642
- package/src/engine/theme/fonts.ts +20 -20
- package/src/engine/utils/klineConfig.ts +49 -49
- package/src/engine/utils/tickCount.ts +11 -11
- package/src/engine/utils/tickPosition.ts +214 -214
- package/src/engine/utils/zoom.ts +83 -83
- package/src/engine/viewport/viewport.ts +67 -67
- package/src/index.ts +3 -3
- package/src/plugin/ConfigManager.ts +93 -93
- package/src/plugin/EventBus.ts +77 -77
- package/src/plugin/HookSystem.ts +106 -106
- package/src/plugin/PluginHost.ts +243 -243
- package/src/plugin/PluginRegistry.ts +92 -92
- package/src/plugin/StateStore.ts +73 -73
- package/src/plugin/index.ts +19 -19
- package/src/plugin/rendererPluginManager.ts +368 -368
- package/src/plugin/stateKeys.ts +8 -8
- package/src/plugin/types.ts +526 -526
- package/src/reactivity/index.ts +2 -2
- package/src/reactivity/signal.ts +119 -119
- package/src/semantic/controller.ts +251 -251
- package/src/semantic/drawShape.ts +260 -260
- package/src/semantic/index.ts +28 -28
- package/src/semantic/schema.json +256 -256
- package/src/semantic/types.ts +251 -251
- package/src/semantic/validator.ts +349 -349
- package/src/types/kLine.ts +13 -13
- package/src/types/price.ts +56 -56
- package/src/types/volumePrice.ts +33 -33
- package/src/utils/dateFormat.ts +208 -208
- package/src/utils/kLineDraw/axis.ts +562 -562
- package/src/utils/priceToY.ts +34 -34
- package/src/utils/volumePrice.ts +202 -202
- package/src/version.ts +1 -1
|
@@ -1,148 +1,148 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
toLog,
|
|
4
|
-
fromLog,
|
|
5
|
-
logFormulaForPriceRange,
|
|
6
|
-
convertPriceRangeToLog,
|
|
7
|
-
convertPriceRangeFromLog,
|
|
8
|
-
logFormulasAreSame,
|
|
9
|
-
type LogFormula,
|
|
10
|
-
} from '../logFormula'
|
|
11
|
-
|
|
12
|
-
describe('logFormula', () => {
|
|
13
|
-
const defaultFormula: LogFormula = { logicalOffset: 4, coordOffset: 0.0001 }
|
|
14
|
-
|
|
15
|
-
describe('toLog / fromLog', () => {
|
|
16
|
-
it('should round-trip for positive prices', () => {
|
|
17
|
-
const testPrices = [1, 10, 100, 0.1, 0.01, 50000, 0.0001]
|
|
18
|
-
for (const price of testPrices) {
|
|
19
|
-
const logical = toLog(price, defaultFormula)
|
|
20
|
-
const back = fromLog(logical, defaultFormula)
|
|
21
|
-
expect(back).toBeCloseTo(price, 10)
|
|
22
|
-
}
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('should round-trip for negative prices', () => {
|
|
26
|
-
const testPrices = [-1, -10, -100, -0.1, -0.01]
|
|
27
|
-
for (const price of testPrices) {
|
|
28
|
-
const logical = toLog(price, defaultFormula)
|
|
29
|
-
const back = fromLog(logical, defaultFormula)
|
|
30
|
-
expect(back).toBeCloseTo(price, 10)
|
|
31
|
-
}
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('should return 0 for zero', () => {
|
|
35
|
-
expect(toLog(0, defaultFormula)).toBe(0)
|
|
36
|
-
expect(fromLog(0, defaultFormula)).toBe(0)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('should handle very small prices with precision', () => {
|
|
40
|
-
const tinyPrice = 1e-9
|
|
41
|
-
const logical = toLog(tinyPrice, defaultFormula)
|
|
42
|
-
const back = fromLog(logical, defaultFormula)
|
|
43
|
-
expect(back).toBeCloseTo(tinyPrice, 10)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('should preserve sign', () => {
|
|
47
|
-
expect(toLog(100, defaultFormula)).toBeGreaterThan(0)
|
|
48
|
-
expect(toLog(-100, defaultFormula)).toBeLessThan(0)
|
|
49
|
-
})
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
describe('logFormulaForPriceRange', () => {
|
|
53
|
-
it('should return default formula for normal range', () => {
|
|
54
|
-
const range = { minPrice: 10, maxPrice: 100 }
|
|
55
|
-
const formula = logFormulaForPriceRange(range)
|
|
56
|
-
expect(formula.logicalOffset).toBe(4)
|
|
57
|
-
expect(formula.coordOffset).toBe(0.0001)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('should return default formula for range >= 1', () => {
|
|
61
|
-
const range = { minPrice: 0.5, maxPrice: 2.5 } // diff = 2
|
|
62
|
-
const formula = logFormulaForPriceRange(range)
|
|
63
|
-
expect(formula.logicalOffset).toBe(4)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('should increase offsets for small range (< 1)', () => {
|
|
67
|
-
const range = { minPrice: 0.001, maxPrice: 0.002 } // diff = 0.001 = 1e-3
|
|
68
|
-
const formula = logFormulaForPriceRange(range)
|
|
69
|
-
// diff = 0.001, digits = ceil(3) = 3
|
|
70
|
-
// logicalOffset = 4 + 3 = 7
|
|
71
|
-
expect(formula.logicalOffset).toBe(7)
|
|
72
|
-
expect(formula.coordOffset).toBe(1e-7)
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('should handle very small range', () => {
|
|
76
|
-
const range = { minPrice: 1e-9, maxPrice: 2e-9 }
|
|
77
|
-
const formula = logFormulaForPriceRange(range)
|
|
78
|
-
// diff = 1e-9, digits = ceil(9) = 9
|
|
79
|
-
// logicalOffset = 4 + 9 = 13
|
|
80
|
-
expect(formula.logicalOffset).toBe(13)
|
|
81
|
-
expect(formula.coordOffset).toBe(1e-13)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('should return default formula for null range', () => {
|
|
85
|
-
const formula = logFormulaForPriceRange(null)
|
|
86
|
-
expect(formula.logicalOffset).toBe(4)
|
|
87
|
-
expect(formula.coordOffset).toBe(0.0001)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('should return default formula for zero diff', () => {
|
|
91
|
-
const range = { minPrice: 100, maxPrice: 100 }
|
|
92
|
-
const formula = logFormulaForPriceRange(range)
|
|
93
|
-
expect(formula.logicalOffset).toBe(4)
|
|
94
|
-
})
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
describe('convertPriceRangeToLog / convertPriceRangeFromLog', () => {
|
|
98
|
-
it('should round-trip range conversion', () => {
|
|
99
|
-
const range = { minPrice: 10, maxPrice: 1000 }
|
|
100
|
-
const logRange = convertPriceRangeToLog(range, defaultFormula)
|
|
101
|
-
const back = convertPriceRangeFromLog(logRange, defaultFormula)
|
|
102
|
-
expect(back.minPrice).toBeCloseTo(range.minPrice, 10)
|
|
103
|
-
expect(back.maxPrice).toBeCloseTo(range.maxPrice, 10)
|
|
104
|
-
})
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
describe('logFormulasAreSame', () => {
|
|
108
|
-
it('should return true for identical formulas', () => {
|
|
109
|
-
const a: LogFormula = { logicalOffset: 4, coordOffset: 0.0001 }
|
|
110
|
-
const b: LogFormula = { logicalOffset: 4, coordOffset: 0.0001 }
|
|
111
|
-
expect(logFormulasAreSame(a, b)).toBe(true)
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it('should return false for different offsets', () => {
|
|
115
|
-
const a: LogFormula = { logicalOffset: 4, coordOffset: 0.0001 }
|
|
116
|
-
const b: LogFormula = { logicalOffset: 5, coordOffset: 0.0001 }
|
|
117
|
-
expect(logFormulasAreSame(a, b)).toBe(false)
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('should return false for different coordOffsets', () => {
|
|
121
|
-
const a: LogFormula = { logicalOffset: 4, coordOffset: 0.0001 }
|
|
122
|
-
const b: LogFormula = { logicalOffset: 4, coordOffset: 0.001 }
|
|
123
|
-
expect(logFormulasAreSame(a, b)).toBe(false)
|
|
124
|
-
})
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
describe('precision with dynamic formula', () => {
|
|
128
|
-
it('should maintain precision for crypto-scale prices', () => {
|
|
129
|
-
const range = { minPrice: 1e-9, maxPrice: 5e-9 }
|
|
130
|
-
const formula = logFormulaForPriceRange(range)
|
|
131
|
-
|
|
132
|
-
// Test that we can represent values in this range accurately
|
|
133
|
-
const testPrice = 2.5e-9
|
|
134
|
-
const logical = toLog(testPrice, formula)
|
|
135
|
-
const back = fromLog(logical, formula)
|
|
136
|
-
expect(back).toBeCloseTo(testPrice, 10)
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
it('should handle nano-scale prices', () => {
|
|
140
|
-
const range = { minPrice: 1e-12, maxPrice: 2e-12 }
|
|
141
|
-
const formula = logFormulaForPriceRange(range)
|
|
142
|
-
const testPrice = 1.5e-12
|
|
143
|
-
const logical = toLog(testPrice, formula)
|
|
144
|
-
const back = fromLog(logical, formula)
|
|
145
|
-
expect(back).toBeCloseTo(testPrice, 10)
|
|
146
|
-
})
|
|
147
|
-
})
|
|
148
|
-
})
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
toLog,
|
|
4
|
+
fromLog,
|
|
5
|
+
logFormulaForPriceRange,
|
|
6
|
+
convertPriceRangeToLog,
|
|
7
|
+
convertPriceRangeFromLog,
|
|
8
|
+
logFormulasAreSame,
|
|
9
|
+
type LogFormula,
|
|
10
|
+
} from '../logFormula'
|
|
11
|
+
|
|
12
|
+
describe('logFormula', () => {
|
|
13
|
+
const defaultFormula: LogFormula = { logicalOffset: 4, coordOffset: 0.0001 }
|
|
14
|
+
|
|
15
|
+
describe('toLog / fromLog', () => {
|
|
16
|
+
it('should round-trip for positive prices', () => {
|
|
17
|
+
const testPrices = [1, 10, 100, 0.1, 0.01, 50000, 0.0001]
|
|
18
|
+
for (const price of testPrices) {
|
|
19
|
+
const logical = toLog(price, defaultFormula)
|
|
20
|
+
const back = fromLog(logical, defaultFormula)
|
|
21
|
+
expect(back).toBeCloseTo(price, 10)
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('should round-trip for negative prices', () => {
|
|
26
|
+
const testPrices = [-1, -10, -100, -0.1, -0.01]
|
|
27
|
+
for (const price of testPrices) {
|
|
28
|
+
const logical = toLog(price, defaultFormula)
|
|
29
|
+
const back = fromLog(logical, defaultFormula)
|
|
30
|
+
expect(back).toBeCloseTo(price, 10)
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should return 0 for zero', () => {
|
|
35
|
+
expect(toLog(0, defaultFormula)).toBe(0)
|
|
36
|
+
expect(fromLog(0, defaultFormula)).toBe(0)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('should handle very small prices with precision', () => {
|
|
40
|
+
const tinyPrice = 1e-9
|
|
41
|
+
const logical = toLog(tinyPrice, defaultFormula)
|
|
42
|
+
const back = fromLog(logical, defaultFormula)
|
|
43
|
+
expect(back).toBeCloseTo(tinyPrice, 10)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('should preserve sign', () => {
|
|
47
|
+
expect(toLog(100, defaultFormula)).toBeGreaterThan(0)
|
|
48
|
+
expect(toLog(-100, defaultFormula)).toBeLessThan(0)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('logFormulaForPriceRange', () => {
|
|
53
|
+
it('should return default formula for normal range', () => {
|
|
54
|
+
const range = { minPrice: 10, maxPrice: 100 }
|
|
55
|
+
const formula = logFormulaForPriceRange(range)
|
|
56
|
+
expect(formula.logicalOffset).toBe(4)
|
|
57
|
+
expect(formula.coordOffset).toBe(0.0001)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('should return default formula for range >= 1', () => {
|
|
61
|
+
const range = { minPrice: 0.5, maxPrice: 2.5 } // diff = 2
|
|
62
|
+
const formula = logFormulaForPriceRange(range)
|
|
63
|
+
expect(formula.logicalOffset).toBe(4)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should increase offsets for small range (< 1)', () => {
|
|
67
|
+
const range = { minPrice: 0.001, maxPrice: 0.002 } // diff = 0.001 = 1e-3
|
|
68
|
+
const formula = logFormulaForPriceRange(range)
|
|
69
|
+
// diff = 0.001, digits = ceil(3) = 3
|
|
70
|
+
// logicalOffset = 4 + 3 = 7
|
|
71
|
+
expect(formula.logicalOffset).toBe(7)
|
|
72
|
+
expect(formula.coordOffset).toBe(1e-7)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('should handle very small range', () => {
|
|
76
|
+
const range = { minPrice: 1e-9, maxPrice: 2e-9 }
|
|
77
|
+
const formula = logFormulaForPriceRange(range)
|
|
78
|
+
// diff = 1e-9, digits = ceil(9) = 9
|
|
79
|
+
// logicalOffset = 4 + 9 = 13
|
|
80
|
+
expect(formula.logicalOffset).toBe(13)
|
|
81
|
+
expect(formula.coordOffset).toBe(1e-13)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('should return default formula for null range', () => {
|
|
85
|
+
const formula = logFormulaForPriceRange(null)
|
|
86
|
+
expect(formula.logicalOffset).toBe(4)
|
|
87
|
+
expect(formula.coordOffset).toBe(0.0001)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('should return default formula for zero diff', () => {
|
|
91
|
+
const range = { minPrice: 100, maxPrice: 100 }
|
|
92
|
+
const formula = logFormulaForPriceRange(range)
|
|
93
|
+
expect(formula.logicalOffset).toBe(4)
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe('convertPriceRangeToLog / convertPriceRangeFromLog', () => {
|
|
98
|
+
it('should round-trip range conversion', () => {
|
|
99
|
+
const range = { minPrice: 10, maxPrice: 1000 }
|
|
100
|
+
const logRange = convertPriceRangeToLog(range, defaultFormula)
|
|
101
|
+
const back = convertPriceRangeFromLog(logRange, defaultFormula)
|
|
102
|
+
expect(back.minPrice).toBeCloseTo(range.minPrice, 10)
|
|
103
|
+
expect(back.maxPrice).toBeCloseTo(range.maxPrice, 10)
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
describe('logFormulasAreSame', () => {
|
|
108
|
+
it('should return true for identical formulas', () => {
|
|
109
|
+
const a: LogFormula = { logicalOffset: 4, coordOffset: 0.0001 }
|
|
110
|
+
const b: LogFormula = { logicalOffset: 4, coordOffset: 0.0001 }
|
|
111
|
+
expect(logFormulasAreSame(a, b)).toBe(true)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('should return false for different offsets', () => {
|
|
115
|
+
const a: LogFormula = { logicalOffset: 4, coordOffset: 0.0001 }
|
|
116
|
+
const b: LogFormula = { logicalOffset: 5, coordOffset: 0.0001 }
|
|
117
|
+
expect(logFormulasAreSame(a, b)).toBe(false)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('should return false for different coordOffsets', () => {
|
|
121
|
+
const a: LogFormula = { logicalOffset: 4, coordOffset: 0.0001 }
|
|
122
|
+
const b: LogFormula = { logicalOffset: 4, coordOffset: 0.001 }
|
|
123
|
+
expect(logFormulasAreSame(a, b)).toBe(false)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
describe('precision with dynamic formula', () => {
|
|
128
|
+
it('should maintain precision for crypto-scale prices', () => {
|
|
129
|
+
const range = { minPrice: 1e-9, maxPrice: 5e-9 }
|
|
130
|
+
const formula = logFormulaForPriceRange(range)
|
|
131
|
+
|
|
132
|
+
// Test that we can represent values in this range accurately
|
|
133
|
+
const testPrice = 2.5e-9
|
|
134
|
+
const logical = toLog(testPrice, formula)
|
|
135
|
+
const back = fromLog(logical, formula)
|
|
136
|
+
expect(back).toBeCloseTo(testPrice, 10)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('should handle nano-scale prices', () => {
|
|
140
|
+
const range = { minPrice: 1e-12, maxPrice: 2e-12 }
|
|
141
|
+
const formula = logFormulaForPriceRange(range)
|
|
142
|
+
const testPrice = 1.5e-12
|
|
143
|
+
const logical = toLog(testPrice, formula)
|
|
144
|
+
const back = fromLog(logical, formula)
|
|
145
|
+
expect(back).toBeCloseTo(testPrice, 10)
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
})
|
|
@@ -1,130 +1,130 @@
|
|
|
1
|
-
import type { PriceRange } from './price'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* LogFormula 接口
|
|
5
|
-
* 用于对数变换的动态偏移,保证极小值下的精度
|
|
6
|
-
*
|
|
7
|
-
* - logicalOffset: 逻辑偏移,使输出值落在合理范围
|
|
8
|
-
* - coordOffset: 坐标偏移,避免 log10(0) 并提升精度
|
|
9
|
-
*/
|
|
10
|
-
export interface LogFormula {
|
|
11
|
-
logicalOffset: number
|
|
12
|
-
coordOffset: number
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** 默认 LogFormula */
|
|
16
|
-
const DEFAULT_FORMULA: LogFormula = {
|
|
17
|
-
logicalOffset: 4,
|
|
18
|
-
coordOffset: 0.0001,
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* 判断数值是否近似为零
|
|
23
|
-
*/
|
|
24
|
-
function isZero(value: number): boolean {
|
|
25
|
-
return Math.abs(value) < 1e-15
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* 真实价格 → log 逻辑空间
|
|
30
|
-
* 公式: sign(price) * (log10(|price| + coordOffset) + logicalOffset)
|
|
31
|
-
*
|
|
32
|
-
* @param price 真实价格(可为负数)
|
|
33
|
-
* @param f LogFormula
|
|
34
|
-
* @returns log 逻辑空间值
|
|
35
|
-
*/
|
|
36
|
-
export function toLog(price: number, f: LogFormula): number {
|
|
37
|
-
const m = Math.abs(price)
|
|
38
|
-
if (isZero(m)) {
|
|
39
|
-
return 0
|
|
40
|
-
}
|
|
41
|
-
const res = Math.log10(m + f.coordOffset) + f.logicalOffset
|
|
42
|
-
return price < 0 ? -res : res
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* log 逻辑空间 → 真实价格
|
|
47
|
-
* 公式: sign(logical) * (10^(|logical| - logicalOffset) - coordOffset)
|
|
48
|
-
*
|
|
49
|
-
* @param logical log 逻辑空间值
|
|
50
|
-
* @param f LogFormula
|
|
51
|
-
* @returns 真实价格
|
|
52
|
-
*/
|
|
53
|
-
export function fromLog(logical: number, f: LogFormula): number {
|
|
54
|
-
const m = Math.abs(logical)
|
|
55
|
-
if (isZero(m)) {
|
|
56
|
-
return 0
|
|
57
|
-
}
|
|
58
|
-
const res = Math.pow(10, m - f.logicalOffset) - f.coordOffset
|
|
59
|
-
return logical < 0 ? -res : res
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 根据价格范围动态计算 LogFormula
|
|
64
|
-
*
|
|
65
|
-
* 对于常规价格范围 (diff >= 1),使用默认值。
|
|
66
|
-
* 对于极小价格范围 (diff < 1),自动增大 logicalOffset 以维持精度。
|
|
67
|
-
*
|
|
68
|
-
* @param range 价格范围,可为 null
|
|
69
|
-
* @returns 最优的 LogFormula
|
|
70
|
-
*/
|
|
71
|
-
export function logFormulaForPriceRange(range: PriceRange | null): LogFormula {
|
|
72
|
-
if (range === null) {
|
|
73
|
-
return { ...DEFAULT_FORMULA }
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const diff = Math.abs(range.maxPrice - range.minPrice)
|
|
77
|
-
// 常规范围或无效范围,使用默认公式
|
|
78
|
-
if (diff >= 1 || diff < 1e-15) {
|
|
79
|
-
return { ...DEFAULT_FORMULA }
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// 极小范围:根据精度需求增大偏移
|
|
83
|
-
// 例如 diff = 0.0001 (1e-4),需要增加 4 位偏移
|
|
84
|
-
const digits = Math.ceil(Math.abs(Math.log10(diff)))
|
|
85
|
-
const logicalOffset = DEFAULT_FORMULA.logicalOffset + digits
|
|
86
|
-
const coordOffset = 1 / Math.pow(10, logicalOffset)
|
|
87
|
-
|
|
88
|
-
return { logicalOffset, coordOffset }
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* 范围转 log 空间
|
|
93
|
-
* 将价格的 min/max 都转换到 log 空间
|
|
94
|
-
*
|
|
95
|
-
* @param range 真实价格范围
|
|
96
|
-
* @param f LogFormula
|
|
97
|
-
* @returns log 空间的价格范围
|
|
98
|
-
*/
|
|
99
|
-
export function convertPriceRangeToLog(range: PriceRange, f: LogFormula): PriceRange {
|
|
100
|
-
return {
|
|
101
|
-
minPrice: toLog(range.minPrice, f),
|
|
102
|
-
maxPrice: toLog(range.maxPrice, f),
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* 范围从 log 空间转回
|
|
108
|
-
* 将 log 空间的 min/max 转换回真实价格
|
|
109
|
-
*
|
|
110
|
-
* @param range log 空间的价格范围
|
|
111
|
-
* @param f LogFormula
|
|
112
|
-
* @returns 真实价格范围
|
|
113
|
-
*/
|
|
114
|
-
export function convertPriceRangeFromLog(range: PriceRange, f: LogFormula): PriceRange {
|
|
115
|
-
return {
|
|
116
|
-
minPrice: fromLog(range.minPrice, f),
|
|
117
|
-
maxPrice: fromLog(range.maxPrice, f),
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* 判断两个 LogFormula 是否相同
|
|
123
|
-
*
|
|
124
|
-
* @param a LogFormula A
|
|
125
|
-
* @param b LogFormula B
|
|
126
|
-
* @returns 是否相同
|
|
127
|
-
*/
|
|
128
|
-
export function logFormulasAreSame(a: LogFormula, b: LogFormula): boolean {
|
|
129
|
-
return a.logicalOffset === b.logicalOffset && a.coordOffset === b.coordOffset
|
|
130
|
-
}
|
|
1
|
+
import type { PriceRange } from './price'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* LogFormula 接口
|
|
5
|
+
* 用于对数变换的动态偏移,保证极小值下的精度
|
|
6
|
+
*
|
|
7
|
+
* - logicalOffset: 逻辑偏移,使输出值落在合理范围
|
|
8
|
+
* - coordOffset: 坐标偏移,避免 log10(0) 并提升精度
|
|
9
|
+
*/
|
|
10
|
+
export interface LogFormula {
|
|
11
|
+
logicalOffset: number
|
|
12
|
+
coordOffset: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** 默认 LogFormula */
|
|
16
|
+
const DEFAULT_FORMULA: LogFormula = {
|
|
17
|
+
logicalOffset: 4,
|
|
18
|
+
coordOffset: 0.0001,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 判断数值是否近似为零
|
|
23
|
+
*/
|
|
24
|
+
function isZero(value: number): boolean {
|
|
25
|
+
return Math.abs(value) < 1e-15
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 真实价格 → log 逻辑空间
|
|
30
|
+
* 公式: sign(price) * (log10(|price| + coordOffset) + logicalOffset)
|
|
31
|
+
*
|
|
32
|
+
* @param price 真实价格(可为负数)
|
|
33
|
+
* @param f LogFormula
|
|
34
|
+
* @returns log 逻辑空间值
|
|
35
|
+
*/
|
|
36
|
+
export function toLog(price: number, f: LogFormula): number {
|
|
37
|
+
const m = Math.abs(price)
|
|
38
|
+
if (isZero(m)) {
|
|
39
|
+
return 0
|
|
40
|
+
}
|
|
41
|
+
const res = Math.log10(m + f.coordOffset) + f.logicalOffset
|
|
42
|
+
return price < 0 ? -res : res
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* log 逻辑空间 → 真实价格
|
|
47
|
+
* 公式: sign(logical) * (10^(|logical| - logicalOffset) - coordOffset)
|
|
48
|
+
*
|
|
49
|
+
* @param logical log 逻辑空间值
|
|
50
|
+
* @param f LogFormula
|
|
51
|
+
* @returns 真实价格
|
|
52
|
+
*/
|
|
53
|
+
export function fromLog(logical: number, f: LogFormula): number {
|
|
54
|
+
const m = Math.abs(logical)
|
|
55
|
+
if (isZero(m)) {
|
|
56
|
+
return 0
|
|
57
|
+
}
|
|
58
|
+
const res = Math.pow(10, m - f.logicalOffset) - f.coordOffset
|
|
59
|
+
return logical < 0 ? -res : res
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 根据价格范围动态计算 LogFormula
|
|
64
|
+
*
|
|
65
|
+
* 对于常规价格范围 (diff >= 1),使用默认值。
|
|
66
|
+
* 对于极小价格范围 (diff < 1),自动增大 logicalOffset 以维持精度。
|
|
67
|
+
*
|
|
68
|
+
* @param range 价格范围,可为 null
|
|
69
|
+
* @returns 最优的 LogFormula
|
|
70
|
+
*/
|
|
71
|
+
export function logFormulaForPriceRange(range: PriceRange | null): LogFormula {
|
|
72
|
+
if (range === null) {
|
|
73
|
+
return { ...DEFAULT_FORMULA }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const diff = Math.abs(range.maxPrice - range.minPrice)
|
|
77
|
+
// 常规范围或无效范围,使用默认公式
|
|
78
|
+
if (diff >= 1 || diff < 1e-15) {
|
|
79
|
+
return { ...DEFAULT_FORMULA }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 极小范围:根据精度需求增大偏移
|
|
83
|
+
// 例如 diff = 0.0001 (1e-4),需要增加 4 位偏移
|
|
84
|
+
const digits = Math.ceil(Math.abs(Math.log10(diff)))
|
|
85
|
+
const logicalOffset = DEFAULT_FORMULA.logicalOffset + digits
|
|
86
|
+
const coordOffset = 1 / Math.pow(10, logicalOffset)
|
|
87
|
+
|
|
88
|
+
return { logicalOffset, coordOffset }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 范围转 log 空间
|
|
93
|
+
* 将价格的 min/max 都转换到 log 空间
|
|
94
|
+
*
|
|
95
|
+
* @param range 真实价格范围
|
|
96
|
+
* @param f LogFormula
|
|
97
|
+
* @returns log 空间的价格范围
|
|
98
|
+
*/
|
|
99
|
+
export function convertPriceRangeToLog(range: PriceRange, f: LogFormula): PriceRange {
|
|
100
|
+
return {
|
|
101
|
+
minPrice: toLog(range.minPrice, f),
|
|
102
|
+
maxPrice: toLog(range.maxPrice, f),
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 范围从 log 空间转回
|
|
108
|
+
* 将 log 空间的 min/max 转换回真实价格
|
|
109
|
+
*
|
|
110
|
+
* @param range log 空间的价格范围
|
|
111
|
+
* @param f LogFormula
|
|
112
|
+
* @returns 真实价格范围
|
|
113
|
+
*/
|
|
114
|
+
export function convertPriceRangeFromLog(range: PriceRange, f: LogFormula): PriceRange {
|
|
115
|
+
return {
|
|
116
|
+
minPrice: fromLog(range.minPrice, f),
|
|
117
|
+
maxPrice: fromLog(range.maxPrice, f),
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 判断两个 LogFormula 是否相同
|
|
123
|
+
*
|
|
124
|
+
* @param a LogFormula A
|
|
125
|
+
* @param b LogFormula B
|
|
126
|
+
* @returns 是否相同
|
|
127
|
+
*/
|
|
128
|
+
export function logFormulasAreSame(a: LogFormula, b: LogFormula): boolean {
|
|
129
|
+
return a.logicalOffset === b.logicalOffset && a.coordOffset === b.coordOffset
|
|
130
|
+
}
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
// 价格坐标系:价格 <-> 绘图区 Y(逻辑像素)
|
|
2
|
-
|
|
3
|
-
export type PriceRange = { maxPrice: number; minPrice: number }
|
|
4
|
-
|
|
5
|
-
export function priceToY(
|
|
6
|
-
price: number,
|
|
7
|
-
maxPrice: number,
|
|
8
|
-
minPrice: number,
|
|
9
|
-
canvasHeight: number,
|
|
10
|
-
paddingTop: number,
|
|
11
|
-
paddingBottom: number,
|
|
12
|
-
): number {
|
|
13
|
-
const range = maxPrice - minPrice || 1
|
|
14
|
-
const ratio = (price - minPrice) / range
|
|
15
|
-
|
|
16
|
-
const viewHeight = Math.max(1, canvasHeight - paddingTop - paddingBottom)
|
|
17
|
-
return paddingTop + viewHeight * (1 - ratio)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* 将逻辑像素 y 反算回价格
|
|
22
|
-
* - y 是相对于绘图区顶部的逻辑像素坐标(不含额外 translate)
|
|
23
|
-
* - paddingTop/paddingBottom 需与 priceToY 使用一致
|
|
24
|
-
*/
|
|
25
|
-
export function yToPrice(
|
|
26
|
-
y: number,
|
|
27
|
-
maxPrice: number,
|
|
28
|
-
minPrice: number,
|
|
29
|
-
canvasHeight: number,
|
|
30
|
-
paddingTop: number,
|
|
31
|
-
paddingBottom: number,
|
|
32
|
-
): number {
|
|
33
|
-
const range = maxPrice - minPrice || 1
|
|
34
|
-
const viewHeight = Math.max(1, canvasHeight - paddingTop - paddingBottom)
|
|
35
|
-
const clampedY = Math.min(Math.max(y, paddingTop), paddingTop + viewHeight)
|
|
36
|
-
const ratio = 1 - (clampedY - paddingTop) / viewHeight
|
|
37
|
-
return minPrice + ratio * range
|
|
38
|
-
}
|
|
39
|
-
|
|
1
|
+
// 价格坐标系:价格 <-> 绘图区 Y(逻辑像素)
|
|
2
|
+
|
|
3
|
+
export type PriceRange = { maxPrice: number; minPrice: number }
|
|
4
|
+
|
|
5
|
+
export function priceToY(
|
|
6
|
+
price: number,
|
|
7
|
+
maxPrice: number,
|
|
8
|
+
minPrice: number,
|
|
9
|
+
canvasHeight: number,
|
|
10
|
+
paddingTop: number,
|
|
11
|
+
paddingBottom: number,
|
|
12
|
+
): number {
|
|
13
|
+
const range = maxPrice - minPrice || 1
|
|
14
|
+
const ratio = (price - minPrice) / range
|
|
15
|
+
|
|
16
|
+
const viewHeight = Math.max(1, canvasHeight - paddingTop - paddingBottom)
|
|
17
|
+
return paddingTop + viewHeight * (1 - ratio)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 将逻辑像素 y 反算回价格
|
|
22
|
+
* - y 是相对于绘图区顶部的逻辑像素坐标(不含额外 translate)
|
|
23
|
+
* - paddingTop/paddingBottom 需与 priceToY 使用一致
|
|
24
|
+
*/
|
|
25
|
+
export function yToPrice(
|
|
26
|
+
y: number,
|
|
27
|
+
maxPrice: number,
|
|
28
|
+
minPrice: number,
|
|
29
|
+
canvasHeight: number,
|
|
30
|
+
paddingTop: number,
|
|
31
|
+
paddingBottom: number,
|
|
32
|
+
): number {
|
|
33
|
+
const range = maxPrice - minPrice || 1
|
|
34
|
+
const viewHeight = Math.max(1, canvasHeight - paddingTop - paddingBottom)
|
|
35
|
+
const clampedY = Math.min(Math.max(y, paddingTop), paddingTop + viewHeight)
|
|
36
|
+
const ratio = 1 - (clampedY - paddingTop) / viewHeight
|
|
37
|
+
return minPrice + ratio * range
|
|
38
|
+
}
|
|
39
|
+
|