(bars: OHLCBar[])
| 72 | } |
| 73 | |
| 74 | function computeMACD(bars: OHLCBar[]): { |
| 75 | macd: { time: number; value: number }[]; |
| 76 | signal: { time: number; value: number }[]; |
| 77 | histogram: { time: number; value: number; color: string }[]; |
| 78 | } { |
| 79 | if (bars.length < 26) return { macd: [], signal: [], histogram: [] }; |
| 80 | const closes = bars.map(b => b.close); |
| 81 | const ema12 = computeEMA(closes, 12); |
| 82 | const ema26 = computeEMA(closes, 26); |
| 83 | const startIdx = 25; |
| 84 | const difValues: number[] = []; |
| 85 | const difSeries: { time: number; value: number }[] = []; |
| 86 | for (let i = startIdx; i < bars.length; i++) { |
| 87 | const dif = ema12[i] - ema26[i]; |
| 88 | difValues.push(dif); |
| 89 | difSeries.push({ time: bars[i].time, value: dif }); |
| 90 | } |
| 91 | const deaValues = computeEMA(difValues, 9); |
| 92 | const deaSeries: { time: number; value: number }[] = []; |
| 93 | const histSeries: { time: number; value: number; color: string }[] = []; |
| 94 | for (let i = 0; i < deaValues.length; i++) { |
| 95 | const time = difSeries[i].time; |
| 96 | const dif = difValues[i]; |
| 97 | const dea = deaValues[i]; |
| 98 | const bar = (dif - dea) * 2; |
| 99 | deaSeries.push({ time, value: dea }); |
| 100 | histSeries.push({ |
| 101 | time, |
| 102 | value: bar, |
| 103 | color: bar >= 0 |
| 104 | ? (bar >= (i > 0 ? (difValues[i - 1] - deaValues[i - 1]) * 2 : 0) ? "#22c55e" : "#22c55e88") |
| 105 | : (bar <= (i > 0 ? (difValues[i - 1] - deaValues[i - 1]) * 2 : 0) ? "#ff4444" : "#ff444488"), |
| 106 | }); |
| 107 | } |
| 108 | return { macd: difSeries, signal: deaSeries, histogram: histSeries }; |
| 109 | } |
| 110 | |
| 111 | function dedupByTime<T extends { time: number }>(data: T[]): T[] { |
| 112 | const map = new Map<number, T>(); |
no test coverage detected