@tranding-custom-chart/financial 0.0.0-stage → 2.0.0

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 (53) hide show
  1. package/README.md +40 -2
  2. package/dist/adapter.d.ts +24 -0
  3. package/dist/adapter.d.ts.map +1 -0
  4. package/dist/adapter.js +47 -0
  5. package/dist/index.d.ts +21 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +20 -0
  8. package/dist/indicators/atr.d.ts +13 -0
  9. package/dist/indicators/atr.d.ts.map +1 -0
  10. package/dist/indicators/atr.js +40 -0
  11. package/dist/indicators/bollinger.d.ts +12 -0
  12. package/dist/indicators/bollinger.d.ts.map +1 -0
  13. package/dist/indicators/bollinger.js +34 -0
  14. package/dist/indicators/ema.d.ts +7 -0
  15. package/dist/indicators/ema.d.ts.map +1 -0
  16. package/dist/indicators/ema.js +26 -0
  17. package/dist/indicators/heikin-ashi.d.ts +14 -0
  18. package/dist/indicators/heikin-ashi.d.ts.map +1 -0
  19. package/dist/indicators/heikin-ashi.js +36 -0
  20. package/dist/indicators/kagi.d.ts +20 -0
  21. package/dist/indicators/kagi.d.ts.map +1 -0
  22. package/dist/indicators/kagi.js +122 -0
  23. package/dist/indicators/line-break.d.ts +18 -0
  24. package/dist/indicators/line-break.d.ts.map +1 -0
  25. package/dist/indicators/line-break.js +73 -0
  26. package/dist/indicators/macd.d.ts +13 -0
  27. package/dist/indicators/macd.d.ts.map +1 -0
  28. package/dist/indicators/macd.js +60 -0
  29. package/dist/indicators/renko.d.ts +19 -0
  30. package/dist/indicators/renko.d.ts.map +1 -0
  31. package/dist/indicators/renko.js +84 -0
  32. package/dist/indicators/rsi.d.ts +7 -0
  33. package/dist/indicators/rsi.d.ts.map +1 -0
  34. package/dist/indicators/rsi.js +47 -0
  35. package/dist/indicators/sma.d.ts +15 -0
  36. package/dist/indicators/sma.d.ts.map +1 -0
  37. package/dist/indicators/sma.js +37 -0
  38. package/dist/indicators/supertrend.d.ts +8 -0
  39. package/dist/indicators/supertrend.d.ts.map +1 -0
  40. package/dist/indicators/supertrend.js +88 -0
  41. package/dist/indicators/volume-profile.d.ts +10 -0
  42. package/dist/indicators/volume-profile.d.ts.map +1 -0
  43. package/dist/indicators/volume-profile.js +134 -0
  44. package/dist/indicators/vwap.d.ts +7 -0
  45. package/dist/indicators/vwap.d.ts.map +1 -0
  46. package/dist/indicators/vwap.js +33 -0
  47. package/dist/indicators/wma.d.ts +7 -0
  48. package/dist/indicators/wma.d.ts.map +1 -0
  49. package/dist/indicators/wma.js +21 -0
  50. package/dist/layout/pane-layout.d.ts +19 -0
  51. package/dist/layout/pane-layout.d.ts.map +1 -0
  52. package/dist/layout/pane-layout.js +39 -0
  53. package/package.json +33 -4
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @file packages/chart-financial/src/indicators/macd.ts
3
+ * Moving Average Convergence Divergence (MACD) calculation.
4
+ * Outputs MACD Line, Signal Line, and Histogram oscillator.
5
+ */
6
+ import { computeEMA } from './ema.js';
7
+ export function computeMACD(bars, fastPeriod = 12, slowPeriod = 26, signalPeriod = 9) {
8
+ const n = bars.length;
9
+ if (n === 0) {
10
+ return { macd: [], signal: [], histogram: [] };
11
+ }
12
+ const fastEMA = computeEMA(bars, fastPeriod);
13
+ const slowEMA = computeEMA(bars, slowPeriod);
14
+ const macdLine = new Array(n).fill(null);
15
+ for (let i = 0; i < n; i++) {
16
+ const f = fastEMA[i];
17
+ const s = slowEMA[i];
18
+ if (f !== null && f !== undefined && s !== null && s !== undefined) {
19
+ macdLine[i] = f - s;
20
+ }
21
+ }
22
+ // Compute Signal Line as EMA over valid MACD values
23
+ const signalLine = new Array(n).fill(null);
24
+ const validMacdIndices = [];
25
+ for (let i = 0; i < n; i++) {
26
+ if (macdLine[i] !== null) {
27
+ validMacdIndices.push(i);
28
+ }
29
+ }
30
+ if (validMacdIndices.length >= signalPeriod) {
31
+ // Synthetic bar series to reuse standard EMA logic
32
+ const dummyBars = validMacdIndices.map((idx) => ({
33
+ time: bars[idx].time,
34
+ open: macdLine[idx],
35
+ high: macdLine[idx],
36
+ low: macdLine[idx],
37
+ close: macdLine[idx],
38
+ volume: null,
39
+ }));
40
+ const signalValues = computeEMA(dummyBars, signalPeriod);
41
+ for (let k = 0; k < validMacdIndices.length; k++) {
42
+ const origIdx = validMacdIndices[k];
43
+ signalLine[origIdx] = signalValues[k];
44
+ }
45
+ }
46
+ // Compute Histogram: MACD Line - Signal Line
47
+ const histogram = new Array(n).fill(null);
48
+ for (let i = 0; i < n; i++) {
49
+ const m = macdLine[i];
50
+ const s = signalLine[i];
51
+ if (m !== null && m !== undefined && s !== null && s !== undefined) {
52
+ histogram[i] = m - s;
53
+ }
54
+ }
55
+ return {
56
+ macd: macdLine,
57
+ signal: signalLine,
58
+ histogram,
59
+ };
60
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @file packages/chart-financial/src/indicators/renko.ts
3
+ * Algorithmic Renko brick generation from OHLCV bar observations.
4
+ * Renko filters time and minor fluctuations, creating bricks of a fixed price size.
5
+ */
6
+ import type { OHLCVBar } from '@tranding-custom-chart/core';
7
+ export interface RenkoBrick extends OHLCVBar {
8
+ readonly isUp: boolean;
9
+ readonly originalIndex: number;
10
+ }
11
+ export interface RenkoOptions {
12
+ readonly brickSize?: number | undefined;
13
+ }
14
+ /**
15
+ * Computes Renko bricks from standard OHLCV bars.
16
+ * Each brick represents a fixed price movement (brickSize).
17
+ */
18
+ export declare function computeRenko(bars: readonly OHLCVBar[], options?: RenkoOptions): readonly RenkoBrick[];
19
+ //# sourceMappingURL=renko.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renko.d.ts","sourceRoot":"","sources":["../../src/indicators/renko.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAE5D,MAAM,WAAW,UAAW,SAAQ,QAAQ;IAC1C,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,SAAS,QAAQ,EAAE,EACzB,OAAO,GAAE,YAAiB,GACzB,SAAS,UAAU,EAAE,CA6EvB"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @file packages/chart-financial/src/indicators/renko.ts
3
+ * Algorithmic Renko brick generation from OHLCV bar observations.
4
+ * Renko filters time and minor fluctuations, creating bricks of a fixed price size.
5
+ */
6
+ /**
7
+ * Computes Renko bricks from standard OHLCV bars.
8
+ * Each brick represents a fixed price movement (brickSize).
9
+ */
10
+ export function computeRenko(bars, options = {}) {
11
+ if (bars.length === 0)
12
+ return [];
13
+ // Determine brick size
14
+ let brickSize = options.brickSize;
15
+ if (!brickSize || brickSize <= 0) {
16
+ // Dynamic default: 1% of the starting close or median range
17
+ const firstClose = bars[0]?.close ?? 100;
18
+ brickSize = Math.max(0.1, Number((firstClose * 0.005).toFixed(2)));
19
+ }
20
+ const bricks = [];
21
+ let lastBrickPrice = Math.floor(bars[0].close / brickSize) * brickSize;
22
+ let lastDirection = null;
23
+ for (let i = 0; i < bars.length; i++) {
24
+ const bar = bars[i];
25
+ const price = bar.close;
26
+ while (true) {
27
+ if (price >= lastBrickPrice + brickSize) {
28
+ // Up brick
29
+ const open = lastBrickPrice;
30
+ const close = lastBrickPrice + brickSize;
31
+ bricks.push({
32
+ time: bar.time,
33
+ open,
34
+ high: close,
35
+ low: open,
36
+ close,
37
+ volume: bar.volume ? Math.round(bar.volume / 2) : 0,
38
+ isUp: true,
39
+ originalIndex: i,
40
+ dateStr: bar.dateStr,
41
+ });
42
+ lastBrickPrice = close;
43
+ lastDirection = 'up';
44
+ }
45
+ else if (price <= lastBrickPrice - brickSize) {
46
+ // Down brick
47
+ const open = lastBrickPrice;
48
+ const close = lastBrickPrice - brickSize;
49
+ bricks.push({
50
+ time: bar.time,
51
+ open,
52
+ high: open,
53
+ low: close,
54
+ close,
55
+ volume: bar.volume ? Math.round(bar.volume / 2) : 0,
56
+ isUp: false,
57
+ originalIndex: i,
58
+ dateStr: bar.dateStr,
59
+ });
60
+ lastBrickPrice = close;
61
+ lastDirection = 'down';
62
+ }
63
+ else {
64
+ break;
65
+ }
66
+ }
67
+ }
68
+ // If no brick triggered (e.g. range was too narrow), create at least one baseline brick
69
+ if (bricks.length === 0 && bars.length > 0) {
70
+ const b = bars[0];
71
+ bricks.push({
72
+ time: b.time,
73
+ open: b.open,
74
+ high: Math.max(b.open, b.close),
75
+ low: Math.min(b.open, b.close),
76
+ close: b.close,
77
+ volume: b.volume ?? 0,
78
+ isUp: b.close >= b.open,
79
+ originalIndex: 0,
80
+ dateStr: b.dateStr,
81
+ });
82
+ }
83
+ return bricks;
84
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file packages/chart-financial/src/indicators/rsi.ts
3
+ * Relative Strength Index (RSI) using Wilder's smoothed moving average.
4
+ */
5
+ import type { OHLCVBar } from '@tranding-custom-chart/core';
6
+ export declare function computeRSI(bars: readonly OHLCVBar[], period?: number): (number | null)[];
7
+ //# sourceMappingURL=rsi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rsi.d.ts","sourceRoot":"","sources":["../../src/indicators/rsi.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAE5D,wBAAgB,UAAU,CACxB,IAAI,EAAE,SAAS,QAAQ,EAAE,EACzB,MAAM,SAAK,GACV,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAgDnB"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @file packages/chart-financial/src/indicators/rsi.ts
3
+ * Relative Strength Index (RSI) using Wilder's smoothed moving average.
4
+ */
5
+ export function computeRSI(bars, period = 14) {
6
+ if (period <= 0 || bars.length <= period) {
7
+ return bars.map(() => null);
8
+ }
9
+ const result = new Array(bars.length).fill(null);
10
+ // Compute initial average gain and loss over first 'period' price changes
11
+ let totalGain = 0;
12
+ let totalLoss = 0;
13
+ for (let i = 1; i <= period; i++) {
14
+ const change = bars[i].close - bars[i - 1].close;
15
+ if (change > 0) {
16
+ totalGain += change;
17
+ }
18
+ else {
19
+ totalLoss += Math.abs(change);
20
+ }
21
+ }
22
+ let avgGain = totalGain / period;
23
+ let avgLoss = totalLoss / period;
24
+ if (avgLoss === 0) {
25
+ result[period] = 100;
26
+ }
27
+ else {
28
+ const rs = avgGain / avgLoss;
29
+ result[period] = 100 - 100 / (1 + rs);
30
+ }
31
+ // Smooth subsequent values using Wilder's technique
32
+ for (let i = period + 1; i < bars.length; i++) {
33
+ const change = bars[i].close - bars[i - 1].close;
34
+ const gain = change > 0 ? change : 0;
35
+ const loss = change < 0 ? Math.abs(change) : 0;
36
+ avgGain = (avgGain * (period - 1) + gain) / period;
37
+ avgLoss = (avgLoss * (period - 1) + loss) / period;
38
+ if (avgLoss === 0) {
39
+ result[i] = 100;
40
+ }
41
+ else {
42
+ const rs = avgGain / avgLoss;
43
+ result[i] = 100 - 100 / (1 + rs);
44
+ }
45
+ }
46
+ return result;
47
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @file packages/chart-financial/src/indicators/sma.ts
3
+ * Simple Moving Average (SMA) computation with transparent warm-up and incremental updates.
4
+ */
5
+ import type { OHLCVBar } from '@tranding-custom-chart/core';
6
+ /**
7
+ * Computes SMA over close prices.
8
+ * Returns an array of equal length to bars, with `null` for the warm-up periods.
9
+ */
10
+ export declare function computeSMA(bars: readonly OHLCVBar[], period: number): (number | null)[];
11
+ /**
12
+ * Updates the latest trailing SMA value when the current live candle updates.
13
+ */
14
+ export declare function updateTrailingSMA(bars: readonly OHLCVBar[], period: number, updatedClose: number): number | null;
15
+ //# sourceMappingURL=sma.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sma.d.ts","sourceRoot":"","sources":["../../src/indicators/sma.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAE5D;;;GAGG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,SAAS,QAAQ,EAAE,EACzB,MAAM,EAAE,MAAM,GACb,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAqBnB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,SAAS,QAAQ,EAAE,EACzB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GACnB,MAAM,GAAG,IAAI,CAQf"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @file packages/chart-financial/src/indicators/sma.ts
3
+ * Simple Moving Average (SMA) computation with transparent warm-up and incremental updates.
4
+ */
5
+ /**
6
+ * Computes SMA over close prices.
7
+ * Returns an array of equal length to bars, with `null` for the warm-up periods.
8
+ */
9
+ export function computeSMA(bars, period) {
10
+ if (period <= 0 || bars.length < period) {
11
+ return bars.map(() => null);
12
+ }
13
+ const result = new Array(bars.length).fill(null);
14
+ let windowSum = 0;
15
+ for (let i = 0; i < bars.length; i++) {
16
+ windowSum += bars[i].close;
17
+ if (i >= period) {
18
+ windowSum -= bars[i - period].close;
19
+ }
20
+ if (i >= period - 1) {
21
+ result[i] = windowSum / period;
22
+ }
23
+ }
24
+ return result;
25
+ }
26
+ /**
27
+ * Updates the latest trailing SMA value when the current live candle updates.
28
+ */
29
+ export function updateTrailingSMA(bars, period, updatedClose) {
30
+ if (period <= 0 || bars.length < period)
31
+ return null;
32
+ let sum = updatedClose;
33
+ for (let i = 1; i < period; i++) {
34
+ sum += bars[bars.length - 1 - i].close;
35
+ }
36
+ return sum / period;
37
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @file packages/chart-financial/src/indicators/supertrend.ts
3
+ * Algorithmic Supertrend calculation engine.
4
+ * Computes ATR-based dynamic trailing stop bands and trend direction (1 = Bullish, -1 = Bearish).
5
+ */
6
+ import type { OHLCVBar, SupertrendResult, SupertrendOptions } from '@tranding-custom-chart/core';
7
+ export declare function computeSupertrend(bars: readonly OHLCVBar[], options?: SupertrendOptions): SupertrendResult;
8
+ //# sourceMappingURL=supertrend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supertrend.d.ts","sourceRoot":"","sources":["../../src/indicators/supertrend.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,QAAQ,EAER,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,6BAA6B,CAAC;AAGrC,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,SAAS,QAAQ,EAAE,EACzB,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,CAuFlB"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @file packages/chart-financial/src/indicators/supertrend.ts
3
+ * Algorithmic Supertrend calculation engine.
4
+ * Computes ATR-based dynamic trailing stop bands and trend direction (1 = Bullish, -1 = Bearish).
5
+ */
6
+ import { computeATR } from './atr.js';
7
+ export function computeSupertrend(bars, options = {}) {
8
+ const { period = 10, multiplier = 3.0 } = options;
9
+ if (bars.length === 0) {
10
+ return { points: [], period, multiplier };
11
+ }
12
+ const atrPoints = computeATR(bars, period);
13
+ const points = [];
14
+ let prevFinalUpper = 0;
15
+ let prevFinalLower = 0;
16
+ let prevDirection = 1;
17
+ for (let i = 0; i < bars.length; i++) {
18
+ const bar = bars[i];
19
+ const atr = atrPoints[i]?.atr ?? 0;
20
+ const hl2 = (bar.high + bar.low) / 2;
21
+ const basicUpper = hl2 + multiplier * atr;
22
+ const basicLower = hl2 - multiplier * atr;
23
+ let finalUpper = basicUpper;
24
+ let finalLower = basicLower;
25
+ if (i > 0) {
26
+ const prevBar = bars[i - 1];
27
+ // Final Upper Band
28
+ if (basicUpper < prevFinalUpper || prevBar.close > prevFinalUpper) {
29
+ finalUpper = basicUpper;
30
+ }
31
+ else {
32
+ finalUpper = prevFinalUpper;
33
+ }
34
+ // Final Lower Band
35
+ if (basicLower > prevFinalLower || prevBar.close < prevFinalLower) {
36
+ finalLower = basicLower;
37
+ }
38
+ else {
39
+ finalLower = prevFinalLower;
40
+ }
41
+ }
42
+ else {
43
+ prevFinalUpper = basicUpper;
44
+ prevFinalLower = basicLower;
45
+ }
46
+ // Determine Direction
47
+ let direction = prevDirection;
48
+ if (i === 0) {
49
+ direction = bar.close >= hl2 ? 1 : -1;
50
+ }
51
+ else {
52
+ if (prevDirection === 1) {
53
+ if (bar.close < finalLower) {
54
+ direction = -1;
55
+ }
56
+ else {
57
+ direction = 1;
58
+ }
59
+ }
60
+ else {
61
+ if (bar.close > finalUpper) {
62
+ direction = 1;
63
+ }
64
+ else {
65
+ direction = -1;
66
+ }
67
+ }
68
+ }
69
+ const isReversal = i > 0 && direction !== prevDirection;
70
+ const supertrendVal = direction === 1 ? finalLower : finalUpper;
71
+ points.push({
72
+ time: bar.time,
73
+ supertrend: supertrendVal,
74
+ direction,
75
+ upperBand: finalUpper,
76
+ lowerBand: finalLower,
77
+ isReversal,
78
+ });
79
+ prevFinalUpper = finalUpper;
80
+ prevFinalLower = finalLower;
81
+ prevDirection = direction;
82
+ }
83
+ return {
84
+ points,
85
+ period,
86
+ multiplier,
87
+ };
88
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @file packages/chart-financial/src/indicators/volume-profile.ts
3
+ * Algorithmic Visible Range Volume Profile (VRVP) calculation.
4
+ * Distributes traded volume into discrete price bins, identifying Point of Control (POC)
5
+ * and the 70% Value Area (VAH and VAL).
6
+ */
7
+ import type { OHLCVBar, VolumeProfileBin, VolumeProfileResult, VolumeProfileOptions } from '@tranding-custom-chart/core';
8
+ export type { VolumeProfileBin, VolumeProfileResult, VolumeProfileOptions };
9
+ export declare function computeVolumeProfile(bars: readonly OHLCVBar[], options?: VolumeProfileOptions): VolumeProfileResult;
10
+ //# sourceMappingURL=volume-profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"volume-profile.d.ts","sourceRoot":"","sources":["../../src/indicators/volume-profile.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,QAAQ,EACR,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,6BAA6B,CAAC;AAErC,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,CAAC;AAE5E,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,SAAS,QAAQ,EAAE,EACzB,OAAO,GAAE,oBAAyB,GACjC,mBAAmB,CAmJrB"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * @file packages/chart-financial/src/indicators/volume-profile.ts
3
+ * Algorithmic Visible Range Volume Profile (VRVP) calculation.
4
+ * Distributes traded volume into discrete price bins, identifying Point of Control (POC)
5
+ * and the 70% Value Area (VAH and VAL).
6
+ */
7
+ export function computeVolumeProfile(bars, options = {}) {
8
+ if (bars.length === 0) {
9
+ return {
10
+ bins: [],
11
+ pocPrice: 0,
12
+ pocVolume: 0,
13
+ vahPrice: 0,
14
+ valPrice: 0,
15
+ totalVolume: 0,
16
+ };
17
+ }
18
+ const { binCount = 24, valueAreaRatio = 0.7 } = options;
19
+ let minPrice = Infinity;
20
+ let maxPrice = -Infinity;
21
+ let overallVolume = 0;
22
+ for (const b of bars) {
23
+ if (b.low < minPrice)
24
+ minPrice = b.low;
25
+ if (b.high > maxPrice)
26
+ maxPrice = b.high;
27
+ overallVolume += b.volume ?? 0;
28
+ }
29
+ if (maxPrice <= minPrice || overallVolume === 0) {
30
+ return {
31
+ bins: [],
32
+ pocPrice: minPrice,
33
+ pocVolume: 0,
34
+ vahPrice: maxPrice,
35
+ valPrice: minPrice,
36
+ totalVolume: overallVolume,
37
+ };
38
+ }
39
+ const priceSpan = maxPrice - minPrice;
40
+ const binStep = priceSpan / binCount;
41
+ // Initialize bins
42
+ const rawBins = [];
43
+ for (let k = 0; k < binCount; k++) {
44
+ const low = minPrice + k * binStep;
45
+ const high = low + binStep;
46
+ rawBins.push({
47
+ priceLow: low,
48
+ priceHigh: high,
49
+ priceMid: (low + high) / 2,
50
+ buyVolume: 0,
51
+ sellVolume: 0,
52
+ totalVolume: 0,
53
+ });
54
+ }
55
+ // Allocate bar volume across bins
56
+ for (const b of bars) {
57
+ const vol = b.volume ?? 0;
58
+ if (vol <= 0)
59
+ continue;
60
+ const isUp = b.close >= b.open;
61
+ const barLow = b.low;
62
+ const barHigh = b.high;
63
+ // Find overlapping bins
64
+ const startBin = Math.max(0, Math.min(binCount - 1, Math.floor((barLow - minPrice) / binStep)));
65
+ const endBin = Math.max(0, Math.min(binCount - 1, Math.floor((barHigh - minPrice) / binStep)));
66
+ const binsTouched = endBin - startBin + 1;
67
+ const volPerBin = vol / binsTouched;
68
+ for (let idx = startBin; idx <= endBin; idx++) {
69
+ const target = rawBins[idx];
70
+ if (isUp) {
71
+ target.buyVolume += volPerBin;
72
+ }
73
+ else {
74
+ target.sellVolume += volPerBin;
75
+ }
76
+ target.totalVolume += volPerBin;
77
+ }
78
+ }
79
+ // Find Point of Control (POC)
80
+ let pocIdx = 0;
81
+ let maxBinVol = -1;
82
+ for (let k = 0; k < binCount; k++) {
83
+ if (rawBins[k].totalVolume > maxBinVol) {
84
+ maxBinVol = rawBins[k].totalVolume;
85
+ pocIdx = k;
86
+ }
87
+ }
88
+ const pocPrice = rawBins[pocIdx].priceMid;
89
+ const pocVolume = rawBins[pocIdx].totalVolume;
90
+ // Determine Value Area (70% total volume radiating outward from POC)
91
+ const targetVAVolume = overallVolume * valueAreaRatio;
92
+ let currentVAVolume = pocVolume;
93
+ let upIdx = pocIdx;
94
+ let downIdx = pocIdx;
95
+ while (currentVAVolume < targetVAVolume && (upIdx < binCount - 1 || downIdx > 0)) {
96
+ const nextUpVol = upIdx < binCount - 1 ? rawBins[upIdx + 1].totalVolume : -1;
97
+ const nextDownVol = downIdx > 0 ? rawBins[downIdx - 1].totalVolume : -1;
98
+ if (nextUpVol >= nextDownVol && upIdx < binCount - 1) {
99
+ upIdx++;
100
+ currentVAVolume += nextUpVol;
101
+ }
102
+ else if (downIdx > 0) {
103
+ downIdx--;
104
+ currentVAVolume += nextDownVol;
105
+ }
106
+ else if (upIdx < binCount - 1) {
107
+ upIdx++;
108
+ currentVAVolume += nextUpVol;
109
+ }
110
+ else {
111
+ break;
112
+ }
113
+ }
114
+ const valPrice = rawBins[downIdx].priceLow;
115
+ const vahPrice = rawBins[upIdx].priceHigh;
116
+ const resultBins = rawBins.map((rb, idx) => ({
117
+ priceLow: Number(rb.priceLow.toFixed(2)),
118
+ priceHigh: Number(rb.priceHigh.toFixed(2)),
119
+ priceMid: Number(rb.priceMid.toFixed(2)),
120
+ buyVolume: Math.round(rb.buyVolume),
121
+ sellVolume: Math.round(rb.sellVolume),
122
+ totalVolume: Math.round(rb.totalVolume),
123
+ isValueArea: idx >= downIdx && idx <= upIdx,
124
+ isPOC: idx === pocIdx,
125
+ }));
126
+ return {
127
+ bins: resultBins,
128
+ pocPrice: Number(pocPrice.toFixed(2)),
129
+ pocVolume: Math.round(pocVolume),
130
+ vahPrice: Number(vahPrice.toFixed(2)),
131
+ valPrice: Number(valPrice.toFixed(2)),
132
+ totalVolume: Math.round(overallVolume),
133
+ };
134
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file packages/chart-financial/src/indicators/vwap.ts
3
+ * Volume Weighted Average Price (VWAP) calculation with daily intraday session resets.
4
+ */
5
+ import type { OHLCVBar } from '@tranding-custom-chart/core';
6
+ export declare function computeVWAP(bars: readonly OHLCVBar[]): readonly (number | null)[];
7
+ //# sourceMappingURL=vwap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vwap.d.ts","sourceRoot":"","sources":["../../src/indicators/vwap.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAE5D,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,QAAQ,EAAE,GAAG,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAgCjF"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @file packages/chart-financial/src/indicators/vwap.ts
3
+ * Volume Weighted Average Price (VWAP) calculation with daily intraday session resets.
4
+ */
5
+ export function computeVWAP(bars) {
6
+ if (bars.length === 0)
7
+ return [];
8
+ const result = [];
9
+ let cumVolume = 0;
10
+ let cumTypicalPriceVolume = 0;
11
+ let lastSessionKey = '';
12
+ for (let i = 0; i < bars.length; i++) {
13
+ const bar = bars[i];
14
+ const vol = bar.volume ?? 0;
15
+ // Detect session reset by calendar day
16
+ const sessionKey = new Date(bar.time * 1000).toDateString();
17
+ if (sessionKey !== lastSessionKey) {
18
+ cumVolume = 0;
19
+ cumTypicalPriceVolume = 0;
20
+ lastSessionKey = sessionKey;
21
+ }
22
+ const typicalPrice = (bar.high + bar.low + bar.close) / 3;
23
+ cumVolume += vol;
24
+ cumTypicalPriceVolume += typicalPrice * vol;
25
+ if (cumVolume > 0) {
26
+ result.push(cumTypicalPriceVolume / cumVolume);
27
+ }
28
+ else {
29
+ result.push(typicalPrice);
30
+ }
31
+ }
32
+ return result;
33
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file packages/chart-financial/src/indicators/wma.ts
3
+ * Weighted Moving Average (WMA) computation with linear weights.
4
+ */
5
+ import type { OHLCVBar } from '@tranding-custom-chart/core';
6
+ export declare function computeWMA(bars: readonly OHLCVBar[], period: number): readonly (number | null)[];
7
+ //# sourceMappingURL=wma.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wma.d.ts","sourceRoot":"","sources":["../../src/indicators/wma.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAE5D,wBAAgB,UAAU,CACxB,IAAI,EAAE,SAAS,QAAQ,EAAE,EACzB,MAAM,EAAE,MAAM,GACb,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAmB5B"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @file packages/chart-financial/src/indicators/wma.ts
3
+ * Weighted Moving Average (WMA) computation with linear weights.
4
+ */
5
+ export function computeWMA(bars, period) {
6
+ if (period <= 0 || bars.length < period) {
7
+ return bars.map(() => null);
8
+ }
9
+ const result = new Array(bars.length).fill(null);
10
+ const weightDenominator = (period * (period + 1)) / 2;
11
+ for (let i = period - 1; i < bars.length; i++) {
12
+ let weightedSum = 0;
13
+ for (let k = 0; k < period; k++) {
14
+ const weight = period - k;
15
+ const barIndex = i - k;
16
+ weightedSum += bars[barIndex].close * weight;
17
+ }
18
+ result[i] = weightedSum / weightDenominator;
19
+ }
20
+ return result;
21
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @file packages/chart-financial/src/layout/pane-layout.ts
3
+ * Multi-pane vertical split layout manager (Price, Volume, Indicators).
4
+ */
5
+ import type { Dimension, Padding } from '@tranding-custom-chart/core';
6
+ export interface PaneDefinition {
7
+ readonly id: string;
8
+ readonly heightRatio: number;
9
+ readonly minHeight?: number | undefined;
10
+ }
11
+ export interface ComputedPaneLayout {
12
+ readonly id: string;
13
+ readonly top: number;
14
+ readonly bottom: number;
15
+ readonly height: number;
16
+ readonly padding: Padding;
17
+ }
18
+ export declare function computePaneLayouts(totalBounds: Dimension, padding: Padding, panes: readonly PaneDefinition[], paneGap?: number): readonly ComputedPaneLayout[];
19
+ //# sourceMappingURL=pane-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pane-layout.d.ts","sourceRoot":"","sources":["../../src/layout/pane-layout.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAEtE,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,SAAS,EACtB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,SAAS,cAAc,EAAE,EAChC,OAAO,SAAI,GACV,SAAS,kBAAkB,EAAE,CA0C/B"}