'use strict'; const technicalindicators = require('technicalindicators'); const defaultOptions = { openKey: "open", closeKey: "close", lowKey: "low", highKey: "high", timestampISO8601Key: "timestamp", emaPeriod: 30, emaBuyRatio: 5e-3, emaSellRatio: 5e-3, macdFastPeriod: 5, macdSlowPeriod: 10, macdSignalPeriod: 7, williamsRPeriod: 20 }; function sliceToLength(arr, length) { return arr.slice(arr.length - length, arr.length); } function signal(candles, options = {}) { options = { ...defaultOptions, ...options }; candles = candles.map((candle) => ({ open: parseFloat(String(candle[options.openKey])), close: parseFloat(String(candle[options.closeKey])), low: parseFloat(String(candle[options.lowKey])), high: parseFloat(String(candle[options.highKey])), timestamp: String(candle[options.timestampISO8601Key]) })).sort((a, b) => a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0); const closes = candles.map((candle) => candle.close); let emas = technicalindicators.EMA.calculate({ values: closes, period: options.emaPeriod }); let macds = technicalindicators.MACD.calculate({ values: closes, fastPeriod: options.macdFastPeriod, slowPeriod: options.macdSlowPeriod, signalPeriod: options.macdSignalPeriod, SimpleMAOscillator: false, SimpleMASignal: false }); let williamsRs = technicalindicators.WilliamsR.calculate({ high: candles.map((candle) => candle.high), low: candles.map((candle) => candle.low), close: closes, period: options.williamsRPeriod }); const minLength = Math.min(candles.length, emas.length, macds.length, williamsRs.length); candles = sliceToLength(candles, minLength); emas = sliceToLength(emas, minLength); macds = sliceToLength(macds, minLength).map((macd, index) => ({ ...macd, index })); williamsRs = sliceToLength(williamsRs, minLength); for (const macd of macds.slice(1)) { const macdStart = [macd.index - 1, macds[macd.index - 1].MACD]; const macdEnd = [macd.index, macds[macd.index].MACD]; const signalStart = [macd.index - 1, macds[macd.index - 1].signal]; const signalEnd = [macd.index, macds[macd.index].signal]; const det = (macdEnd[0] - macdStart[0]) * (signalEnd[1] - signalStart[1]) - (signalEnd[0] - signalStart[0]) * (macdEnd[1] - macdStart[1]); if (det !== 0) { const lambda = ((signalEnd[1] - signalStart[1]) * (signalEnd[0] - macdStart[0]) + (signalStart[0] - signalEnd[0]) * (signalEnd[1] - macdStart[1])) / det; const gamma = ((macdStart[1] - macdEnd[1]) * (signalEnd[0] - macdStart[0]) + (macdEnd[0] - macdStart[0]) * (signalEnd[1] - macdStart[1])) / det; if (lambda > 0 && lambda < 1 && gamma > 0 && gamma < 1) { macd.isIntersecting = true; } } } const emaBuys = emas.map((ema) => ema - ema * options.emaBuyRatio); const emaSells = emas.map((ema) => ema + ema * options.emaSellRatio); const signals = {}; for (const { index } of macds.filter((macd) => macd.isIntersecting)) { if (williamsRs[index] >= -20 && candles[index].close >= emaSells[index]) { signals[index] = "sell"; } else if (williamsRs[index] <= -80 && candles[index].close <= emaBuys[index]) { signals[index] = "buy"; } } return macds.map((macd) => ({ open: candles[macd.index].open, close: candles[macd.index].close, low: candles[macd.index].low, high: candles[macd.index].high, timestamp: candles[macd.index].timestamp, ema: emas[macd.index], emaBuy: emaBuys[macd.index], emaSell: emaSells[macd.index], macd: macd.MACD, macdSignal: macd.signal, macdHistogram: macd.histogram, williamsR: williamsRs[macd.index], isIntersecting: macd.isIntersecting, signal: signals[macd.index] ?? "hold" })); } exports.signal = signal;