UNPKG

@azteam/candlestick-chart

Version:

N/A

870 lines (853 loc) 35.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _lodash = _interopRequireDefault(require("lodash")); var _util = require("@azteam/util"); var _constant = require("./constant.js"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; } function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function roundDown(ts, period) { var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 25200; return Math.floor((ts + offset) / period) * period - offset; } var CandlestickChart = /*#__PURE__*/function () { function CandlestickChart() { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref$timezone = _ref.timezone, timezone = _ref$timezone === void 0 ? 'UTC' : _ref$timezone, _ref$pricePrecision = _ref.pricePrecision, pricePrecision = _ref$pricePrecision === void 0 ? 4 : _ref$pricePrecision, symbol = _ref.symbol; _classCallCheck(this, CandlestickChart); this.symbol = symbol; this.timezone = timezone; this.tzOffset = _constant.TZ_OFFSET_MAP[timezone] || 0; this.isInitChart = false; this.rolling = {}; this.chart = Object.values(_constant.TIME_CHART).reduce(function (result, value) { result[value.name] = []; return result; }, {}); this.pricePrecision = pricePrecision; } return _createClass(CandlestickChart, [{ key: "init", value: function init() { this.isInitChart = true; } }, { key: "convertTickId", value: function convertTickId(tick1minId) { var ts = tick1minId; var off = this.tzOffset; return { tick1minId: roundDown(ts, 60, off), tick5minId: roundDown(ts, 300, off), tick15minId: roundDown(ts, 900, off), tick30minId: roundDown(ts, 1800, off), tick60minId: roundDown(ts, 3600, off), tick4hourId: roundDown(ts, 14400, off), tick1dayId: roundDown(ts, 86400, off) }; } }, { key: "getTicks", value: function getTicks() { var _this = this; var position = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; var ticks = {}; var tfNames = ['1min', '5min', '15min', '30min', '60min', '4hour', '1day']; var baseArr = this.chart['1min']; var baseTick = baseArr[position]; if (!baseTick) return ticks; var setTicksLayer = function setTicksLayer(arr, idx, key) { ticks["tick".concat(key)] = arr[idx] || null; ticks["prevTick".concat(key)] = arr[idx + 1] || null; ticks["prev2Tick".concat(key)] = arr[idx + 2] || null; ticks["prev3Tick".concat(key)] = arr[idx + 3] || null; }; setTicksLayer(baseArr, position, '1min'); var ids = this.convertTickId(baseTick.id); var idMap = { '5min': ids.tick5minId, '15min': ids.tick15minId, '30min': ids.tick30minId, '60min': ids.tick60minId, '4hour': ids.tick4hourId, '1day': ids.tick1dayId }; var _loop = function _loop() { var key = tfNames[i]; var arr = _this.chart[key]; if (!arr || arr.length === 0) return 1; // continue var targetId = idMap[key]; var idx = -1; if (arr[0].id === targetId) idx = 0;else if (arr[1] && arr[1].id === targetId) idx = 1;else idx = arr.findIndex(function (x) { return x.id === targetId; }); if (idx !== -1) setTicksLayer(arr, idx, key); }; for (var i = 1; i < tfNames.length; i++) { if (_loop()) continue; } return ticks; } }, { key: "getChart", value: function getChart() { return this.chart; } }, { key: "detectHighVolatility", value: function detectHighVolatility(minutes) { var tf = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '1min'; var count = parseInt(minutes, 10); if (!count || isNaN(count)) { return { min_size: 0, max_size: 0, avg_size: 0, highest_price: 0, lowest_price: 0 }; } var baseArr = this.chart[tf]; if (!baseArr || baseArr.length === 0) { return { min_size: 0, max_size: 0, avg_size: 0, highest_price: 0, lowest_price: 0 }; } var limit = Math.min(baseArr.length, count); var minSize = null; var maxSize = null; var sumSize = 0; var validCount = 0; var minPrice = null; var maxPrice = null; for (var i = 0; i < limit; i++) { var candle = baseArr[i]; var size = candle.candle_size; if (size != null) { if (minSize === null || size < minSize) minSize = size; if (maxSize === null || size > maxSize) maxSize = size; sumSize += size; validCount++; } if (minPrice === null || candle.low < minPrice) minPrice = candle.low; if (maxPrice === null || candle.high > maxPrice) maxPrice = candle.high; } return { min_size: minSize !== null ? _lodash["default"].round(minSize, 4) : 0, max_size: maxSize !== null ? _lodash["default"].round(maxSize, 4) : 0, avg_size: validCount ? _lodash["default"].round(sumSize / validCount, 4) : 0, highest_price: maxPrice || 0, lowest_price: minPrice || 0 }; } }, { key: "predictMA5CrossMA10", value: function predictMA5CrossMA10() { var tf = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '1min'; var arr = this.chart[tf]; if (!arr || arr.length < 19) return null; var currentCandle = arr[0]; // Calculate Sum(C4..C8) var sum4to8 = 0; for (var i = 4; i <= 8; i++) { sum4to8 += arr[i].close; } // Calculate Sum(C0..C3) var sum0to3 = 0; for (var _i = 0; _i <= 3; _i++) { sum0to3 += arr[_i].close; } // Formula: C_next = Sum(C4..C8) - Sum(C0..C3) var nextPrice = sum4to8 - sum0to3; // Calculate SMA20 next to determine full straight var sum0to18 = 0; for (var _i2 = 0; _i2 <= 18; _i2++) { sum0to18 += arr[_i2].close; } var sma20Next = (nextPrice + sum0to18) / 20; var sma5Next = (nextPrice + sum0to3) / 5; // Determine new straight direction var isCrossingDown = currentCandle.close_sma_5 > currentCandle.close_sma_10; // Slightly adjust to resolve tie in sorting exactly at cross point var simulatedSma5 = isCrossingDown ? sma5Next - 0.000001 : sma5Next + 0.000001; var simulatedSma10 = sma5Next; var simulatedSma20 = sma20Next; var smaList = [{ key: 5, value: simulatedSma5 }, { key: 10, value: simulatedSma10 }, { key: 20, value: simulatedSma20 }]; smaList.sort(function (a, b) { return b.value - a.value; }); var direction = smaList.map(function (obj) { return obj.key; }).join('_'); var currentPrice = currentCandle.close; var percent = _lodash["default"].round((nextPrice - currentPrice) / currentPrice * 100, 4); return { next_price: _lodash["default"].round(nextPrice, this.pricePrecision), percent: percent, direction: direction }; } }, { key: "predictMA10CrossMA20", value: function predictMA10CrossMA20() { var tf = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '1min'; var arr = this.chart[tf]; if (!arr || arr.length < 19) return null; var currentCandle = arr[0]; // Calculate Sum(C9..C18) var sum9to18 = 0; for (var i = 9; i <= 18; i++) { sum9to18 += arr[i].close; } // Calculate Sum(C0..C8) var sum0to8 = 0; for (var _i3 = 0; _i3 <= 8; _i3++) { sum0to8 += arr[_i3].close; } // Formula: C_next = Sum(C9..C18) - Sum(C0..C8) var nextPrice = sum9to18 - sum0to8; // Need Sum(C0..C3) to calculate SMA5 var sum0to3 = 0; for (var _i4 = 0; _i4 <= 3; _i4++) { sum0to3 += arr[_i4].close; } var sma10Next = (nextPrice + sum0to8) / 10; var sma5Next = (nextPrice + sum0to3) / 5; var sma20Next = sma10Next; var isCrossingDown = currentCandle.close_sma_10 > currentCandle.close_sma_20; var simulatedSma10 = isCrossingDown ? sma10Next - 0.000001 : sma10Next + 0.000001; var simulatedSma5 = sma5Next; var simulatedSma20 = sma20Next; var smaList = [{ key: 5, value: simulatedSma5 }, { key: 10, value: simulatedSma10 }, { key: 20, value: simulatedSma20 }]; smaList.sort(function (a, b) { return b.value - a.value; }); var direction = smaList.map(function (obj) { return obj.key; }).join('_'); var currentPrice = currentCandle.close; var percent = _lodash["default"].round((nextPrice - currentPrice) / currentPrice * 100, 4); return { next_price: _lodash["default"].round(nextPrice, this.pricePrecision), percent: percent, direction: direction }; } }, { key: "predictMA5CrossMA20", value: function predictMA5CrossMA20() { var tf = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '1min'; var arr = this.chart[tf]; if (!arr || arr.length < 19) return null; var currentCandle = arr[0]; // Calculate Sum(C4..C18) var sum4to18 = 0; for (var i = 4; i <= 18; i++) { sum4to18 += arr[i].close; } // Calculate Sum(C0..C3) var sum0to3 = 0; for (var _i5 = 0; _i5 <= 3; _i5++) { sum0to3 += arr[_i5].close; } // Formula: C_next = (Sum(C4..C18) - 3 * Sum(C0..C3)) / 3 var nextPrice = (sum4to18 - 3 * sum0to3) / 3; // Need Sum(C0..C8) to calculate SMA10 var sum0to8 = 0; for (var _i6 = 0; _i6 <= 8; _i6++) { sum0to8 += arr[_i6].close; } var sma5Next = (nextPrice + sum0to3) / 5; var sma10Next = (nextPrice + sum0to8) / 10; var sma20Next = sma5Next; var isCrossingDown = currentCandle.close_sma_5 > currentCandle.close_sma_20; var simulatedSma5 = isCrossingDown ? sma5Next - 0.000001 : sma5Next + 0.000001; var simulatedSma10 = sma10Next; var simulatedSma20 = sma20Next; var smaList = [{ key: 5, value: simulatedSma5 }, { key: 10, value: simulatedSma10 }, { key: 20, value: simulatedSma20 }]; smaList.sort(function (a, b) { return b.value - a.value; }); var direction = smaList.map(function (obj) { return obj.key; }).join('_'); var currentPrice = currentCandle.close; var percent = _lodash["default"].round((nextPrice - currentPrice) / currentPrice * 100, 4); return { next_price: _lodash["default"].round(nextPrice, this.pricePrecision), percent: percent, direction: direction }; } }, { key: "calculateTickMA", value: function calculateTickMA(data, index, timeChart) { this._calculateBasicSmaAndMinMax(data, index, timeChart); this._calculateTrendAndStraight(data, index); this._calculateAboveBelowCounters(data, index); this._calculateEMA(data, index, 34, 'ema34'); this._calculateEMA(data, index, 89, 'ema89'); this._calculateRSI(data, index, 14); this._calculateMACD(data, index); this._calculateATR(data, index, 14); } }, { key: "_calculateBasicSmaAndMinMax", value: function _calculateBasicSmaAndMinMax(data, index, timeChart) { var tick = data[index]; var close = tick.close; var low = tick.low; var high = tick.high; if (!this.rolling[timeChart]) { this.rolling[timeChart] = { count: 0, sum20: 0, sum10: 0, sum5: 0, sqSum20: 0 }; } var roll = this.rolling[timeChart]; if (roll.count % 100 === 0 || roll.sum20 === 0 || index !== 0 || data.length <= index + 20) { // console.log(`[DEBUG] Resetting SMA/BB for ${timeChart} at index ${index}, length ${data.length}`); var s20 = 0, s10 = 0, s5 = 0, sq20 = 0; var mx20 = high, mx10 = high, mx5 = high; var mn20 = low, mn10 = low, mn5 = low; var limit = Math.min(data.length, index + 20); for (var i = index; i < limit; i++) { var t = data[i], v = t.close, h = t.high, l = t.low; s20 += v; sq20 += v * v; if (h > mx20) mx20 = h; if (l < mn20) mn20 = l; if (i < index + 10) { s10 += v; if (h > mx10) mx10 = h; if (l < mn10) mn10 = l; if (i < index + 5) { s5 += v; if (h > mx5) mx5 = h; if (l < mn5) mn5 = l; } } } if (data.length >= index + 20) { roll.sum20 = s20; roll.sum10 = s10; roll.sum5 = s5; roll.sqSum20 = sq20; } tick.max20 = mx20; tick.max10 = mx10; tick.max5 = mx5; tick.min20 = mn20; tick.min10 = mn10; tick.min5 = mn5; if (data.length <= index + 20) return; } else { var displaced20 = data[index + 20], displaced10 = data[index + 10], displaced5 = data[index + 5]; if (tick.isNewCandle) { roll.sum20 += close - (displaced20 ? displaced20.close : 0); roll.sum10 += close - (displaced10 ? displaced10.close : 0); roll.sum5 += close - (displaced5 ? displaced5.close : 0); roll.sqSum20 += close * close - (displaced20 ? displaced20.close * displaced20.close : 0); roll.count++; } else { var prevClose = tick.prevClose || close; var diff = close - prevClose; roll.sum20 += diff; roll.sum10 += diff; roll.sum5 += diff; roll.sqSum20 += close * close - prevClose * prevClose; } var _mx = high, _mx2 = high, _mx3 = high; var _mn = low, _mn2 = low, _mn3 = low; var _limit = Math.min(data.length, index + 20); for (var _i7 = index; _i7 < _limit; _i7++) { var _t = data[_i7]; if (_t.high > _mx) _mx = _t.high; if (_t.low < _mn) _mn = _t.low; if (_i7 < index + 10) { if (_t.high > _mx2) _mx2 = _t.high; if (_t.low < _mn2) _mn2 = _t.low; if (_i7 < index + 5) { if (_t.high > _mx3) _mx3 = _t.high; if (_t.low < _mn3) _mn3 = _t.low; } } } tick.max20 = _mx; tick.max10 = _mx2; tick.max5 = _mx3; tick.min20 = _mn; tick.min10 = _mn2; tick.min5 = _mn3; } var prec = this.pricePrecision; tick.bb_sma = _lodash["default"].round(roll.sum20 / 20, prec); tick.close_sma_5 = _lodash["default"].round(roll.sum5 / 5, prec); tick.close_sma_10 = _lodash["default"].round(roll.sum10 / 10, prec); tick.close_sma_20 = tick.bb_sma; tick.prevClose = close; delete tick.isNewCandle; } }, { key: "_calculateTrendAndStraight", value: function _calculateTrendAndStraight(data, index) { var current = data[index]; var prev2 = data[index + 2], prev1 = data[index + 1]; if (!prev2 || current.close_sma_5 === undefined) return; var determineTrend = function determineTrend(p2, p1, cur) { if (p2 < p1 && p1 < cur) return _constant.TREND.LONG; if (p2 > p1 && p1 > cur) return _constant.TREND.SHORT; return _constant.TREND.SIDEWAY; }; current.trend5 = determineTrend(prev2.close_sma_5, prev1.close_sma_5, current.close_sma_5); current.trend10 = determineTrend(prev2.close_sma_10, prev1.close_sma_10, current.close_sma_10); current.trend20 = determineTrend(prev2.close_sma_20, prev1.close_sma_20, current.close_sma_20); var smaList = [{ key: 5, value: current.close_sma_5 }, { key: 10, value: current.close_sma_10 }, { key: 20, value: current.close_sma_20 }]; smaList.sort(function (a, b) { return b.value - a.value; }); current.straight = smaList.map(function (obj) { return obj.key; }).join('_'); } }, { key: "_calculateAboveBelowCounters", value: function _calculateAboveBelowCounters(data, index) { if (data.length <= index + _constant.BB_DEVIATION) return; var current = data[index]; current.above_sma5 = current.above_sma10 = current.above_sma20 = 0; current.below_sma5 = current.below_sma10 = current.below_sma20 = 0; var flags = { a5: true, a10: true, a20: true, b5: true, b10: true, b20: true }; for (var i = index + 1; i < _constant.BB_DEVIATION + index; i += 1) { var tick = data[i]; if (flags.a5) tick.low > tick.close_sma_5 ? current.above_sma5++ : flags.a5 = false; if (flags.b5) tick.high < tick.close_sma_5 ? current.below_sma5++ : flags.b5 = false; if (flags.a10) tick.low > tick.close_sma_10 ? current.above_sma10++ : flags.a10 = false; if (flags.b10) tick.high < tick.close_sma_10 ? current.below_sma10++ : flags.b10 = false; if (flags.a20) tick.low > tick.close_sma_20 ? current.above_sma20++ : flags.a20 = false; if (flags.b20) tick.high < tick.close_sma_20 ? current.below_sma20++ : flags.b20 = false; if (!flags.a5 && !flags.a10 && !flags.a20 && !flags.b5 && !flags.b10 && !flags.b20) break; } } }, { key: "calculateProfitAndTimeTick", value: function calculateProfitAndTimeTick(data, index) { var tick = data[index]; tick.profit = _lodash["default"].round(tick.close - tick.open, 2); tick.profit_percent = (0, _util.getPercentDifference)(tick.close, tick.open); tick.max_profit_percent = (0, _util.getPercentDifference)(tick.high, tick.open); tick.max_loss_percent = (0, _util.getPercentDifference)(tick.low, tick.open); tick.candle_size = (0, _util.getPercentDifference)(tick.high, tick.low); tick.lower_wick_percent = (0, _util.getPercentDifference)(tick.close, tick.low); tick.upper_wick_percent = (0, _util.getPercentDifference)(tick.high, tick.close); tick.center_price = (tick.high + tick.low) / 2; tick.color = tick.open < tick.close ? _constant.CANDLE_COLOR.GREEN : _constant.CANDLE_COLOR.RED; } }, { key: "calculateBollingerBandsForTick", value: function calculateBollingerBandsForTick(data, index, timeChart) { if (data.length < index + _constant.BB_DEVIATION) return; this._calculateBasicSmaAndMinMax(data, index, timeChart); if (data.length < index + _constant.BB_DEVIATION) return; var tick = data[index], roll = this.rolling[timeChart]; var n = _constant.BB_DEVIATION, mean = roll.sum20 / n; var variance = roll.sqSum20 / n - mean * mean; var standardDeviation = Math.sqrt(Math.max(0, variance)); tick.bb_upper_band = _lodash["default"].round(tick.bb_sma + 2 * standardDeviation, this.pricePrecision); tick.bb_lower_band = _lodash["default"].round(tick.bb_sma - 2 * standardDeviation, this.pricePrecision); tick.bb_size = (0, _util.getPercentDifference)(tick.bb_upper_band, tick.bb_lower_band); tick.bb_width = _lodash["default"].round((tick.bb_upper_band - tick.bb_lower_band) / tick.bb_sma, 4); tick.bb_percent = _lodash["default"].round((tick.close - tick.bb_lower_band) / (tick.bb_upper_band - tick.bb_lower_band), 4) || 0; tick.bb_width_percentile = this.calculatePercentile(data, index, 'bb_width', 50); tick.bb_size_percentile = this.calculatePercentile(data, index, 'bb_size', 50); tick.bb_percent_percentile = this.calculatePercentile(data, index, 'bb_percent', 50); tick.pos = _constant.BB_POSITION.MIDDLE; if (tick.bb_size > 5) { var bb_upper_band = tick.bb_upper_band, bb_lower_band = tick.bb_lower_band, bb_sma = tick.bb_sma, close = tick.close; if (close > bb_upper_band) tick.pos = _constant.BB_POSITION.OUT_TOP;else if (close < bb_lower_band) tick.pos = _constant.BB_POSITION.OUT_BOTTOM;else if (close > (bb_upper_band + bb_sma) / 2) tick.pos = _constant.BB_POSITION.TOP;else if (close < (bb_lower_band + bb_sma) / 2) tick.pos = _constant.BB_POSITION.BOTTOM;else if (close > bb_sma) tick.pos = _constant.BB_POSITION.UPPER_MIDDLE;else if (close < bb_sma) tick.pos = _constant.BB_POSITION.LOWER_MIDDLE; } tick.time_at = tick.tick_id = tick.id; tick.is_bb_tick = true; } }, { key: "calculatePercentile", value: function calculatePercentile(data, index, field) { var lookback = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 50; if (data.length <= index + lookback) return null; var current = data[index][field]; if (current == null) return null; var smaller = 0, count = 0; for (var i = index + 1; i <= index + lookback; i++) { var _data$i; var val = (_data$i = data[i]) === null || _data$i === void 0 ? void 0 : _data$i[field]; if (val != null) { if (val <= current) smaller++; count++; } } return count ? Math.round(smaller / count * 100) : null; } }, { key: "_calculateEMA", value: function _calculateEMA(data, index, period, key) { var tick = data[index], prev = data[index + 1]; if (data.length <= index + period) return; var multiplier = 2 / (period + 1); if (prev && prev[key] !== undefined) { tick[key] = _lodash["default"].round((tick.close - prev[key]) * multiplier + prev[key], this.pricePrecision); } else { var sum = 0; for (var i = index; i < index + period; i++) sum += data[i].close; tick[key] = _lodash["default"].round(sum / period, this.pricePrecision); } } }, { key: "_calculateRSI", value: function _calculateRSI(data, index, period) { var tick = data[index], prev = data[index + 1]; if (data.length <= index + period + 2) return; var calculateInitialAvg = function calculateInitialAvg() { var totalGain = 0, totalLoss = 0; for (var i = index + period; i > index; i--) { var diff = data[i - 1].close - data[i].close; if (diff >= 0) totalGain += diff;else totalLoss -= diff; } return { avgGain: totalGain / period, avgLoss: totalLoss / period }; }; if (prev && prev.rsi_avg_gain !== undefined) { var diff = tick.close - prev.close; var currentGain = diff >= 0 ? diff : 0, currentLoss = diff < 0 ? -diff : 0; tick.rsi_avg_gain = (prev.rsi_avg_gain * (period - 1) + currentGain) / period; tick.rsi_avg_loss = (prev.rsi_avg_loss * (period - 1) + currentLoss) / period; } else { var _calculateInitialAvg = calculateInitialAvg(), avgGain = _calculateInitialAvg.avgGain, avgLoss = _calculateInitialAvg.avgLoss; tick.rsi_avg_gain = avgGain; tick.rsi_avg_loss = avgLoss; } if (tick.rsi_avg_loss === 0) tick.rsi = 100;else { var rs = tick.rsi_avg_gain / tick.rsi_avg_loss; tick.rsi = _lodash["default"].round(100 - 100 / (1 + rs), 2); } } }, { key: "_calculateMACD", value: function _calculateMACD(data, index) { var period = 9, tick = data[index], prev = data[index + 1]; if (data.length <= index + 26 + period) return; this._calculateEMA(data, index, 12, 'ema12'); this._calculateEMA(data, index, 26, 'ema26'); if (tick.ema12 === undefined || tick.ema26 === undefined) return; tick.macd_line = _lodash["default"].round(tick.ema12 - tick.ema26, this.pricePrecision); var multiplier = 2 / (period + 1); if (prev && prev.macd_signal !== undefined) { tick.macd_signal = _lodash["default"].round((tick.macd_line - prev.macd_signal) * multiplier + prev.macd_signal, this.pricePrecision); } else { var sum = 0; for (var i = index; i < index + period; i++) { if (data[i].macd_line === undefined) { this._calculateEMA(data, i, 12, 'ema12'); this._calculateEMA(data, i, 26, 'ema26'); data[i].macd_line = _lodash["default"].round(data[i].ema12 - data[i].ema26, this.pricePrecision); } sum += data[i].macd_line; } tick.macd_signal = _lodash["default"].round(sum / period, this.pricePrecision); } if (tick.macd_signal !== undefined) tick.macd_hist = _lodash["default"].round(tick.macd_line - tick.macd_signal, this.pricePrecision); } }, { key: "_calculateATR", value: function _calculateATR(data, index, period) { var tick = data[index], prev = data[index + 1]; if (data.length <= index + period + 1) return; // True Range = max(high - low, |high - prevClose|, |low - prevClose|) var prevClose = prev ? prev.close : tick.open; tick.tr = Math.max(tick.high - tick.low, Math.abs(tick.high - prevClose), Math.abs(tick.low - prevClose)); if (prev && prev.atr !== undefined) { // Wilder's smoothing: ATR = (prevATR * (period - 1) + TR) / period tick.atr = _lodash["default"].round((prev.atr * (period - 1) + tick.tr) / period, this.pricePrecision); } else { // Seed: SMA of first N true ranges var sumTR = tick.tr; for (var i = index + 1; i < index + period; i++) { var t = data[i], p = data[i + 1]; var pc = p ? p.close : t.open; sumTR += Math.max(t.high - t.low, Math.abs(t.high - pc), Math.abs(t.low - pc)); } tick.atr = _lodash["default"].round(sumTR / period, this.pricePrecision); } // ATR as percentage of close price tick.atr_percent = _lodash["default"].round(tick.atr / tick.close * 100, 4); } }, { key: "sumVolume", value: function sumVolume(ts, timeChart) { var blockTimes = function blockTimes(ts, timeChart) { var configMap = _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({}, _constant.TIME_CHART._1_DAY.name, { period: 14400, steps: [0, 1, 2, 3, 4, 5] }), _constant.TIME_CHART._4_HOUR.name, { period: 3600, steps: [0, 1, 2, 3] }), _constant.TIME_CHART._60_MIN.name, { period: 1800, steps: [0, 1] }), _constant.TIME_CHART._30_MIN.name, { period: 900, steps: [0, 1] }), _constant.TIME_CHART._15_MIN.name, { period: 300, steps: [0, 1, 2] }), _constant.TIME_CHART._5_MIN.name, { period: 60, steps: [0, 1, 2, 3, 4] }); var config = configMap[timeChart]; return config ? config.steps.map(function (x) { return ts + config.period * x; }) : [ts]; }; var blocks = blockTimes(ts, timeChart); var minTs = blocks[0], maxTs = blocks[blocks.length - 1]; var tfMap = _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({}, _constant.TIME_CHART._1_DAY.name, '4hour'), _constant.TIME_CHART._4_HOUR.name, '60min'), _constant.TIME_CHART._60_MIN.name, '30min'), _constant.TIME_CHART._30_MIN.name, '15min'), _constant.TIME_CHART._15_MIN.name, '5min'), _constant.TIME_CHART._5_MIN.name, '1min'); var chartArr = this.chart[tfMap[timeChart]]; if (!chartArr) return { vol: 0, amount: 0, taker_vol: 0, taker_amount: 0, count: 0 }; var v = 0, a = 0, tv = 0, ta = 0, c = 0; for (var i = 0; i < chartArr.length; i++) { var _t$time_at; var t = chartArr[i], tId = (_t$time_at = t.time_at) !== null && _t$time_at !== void 0 ? _t$time_at : t.id; if (tId < minTs) break; if (tId <= maxTs && blocks.includes(tId)) { v += t.vol; a += t.amount; tv += t.taker_vol; ta += t.taker_amount; c += t.count; } } return { vol: v, amount: a, taker_vol: tv, taker_amount: ta, count: c }; } }, { key: "addTickToChart", value: function addTickToChart(tick) { var timeChart = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '1min'; var isInitialized = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; var momentObj = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; var tickIds = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null; var chart = this.chart[timeChart]; var ids = tickIds || this.convertTickId(tick.id); var tickId = ids["tick".concat(timeChart, "Id") || "tick".concat(timeChart.toUpperCase(), "Id") || 0]; var tc = Object.values(_constant.TIME_CHART).find(function (x) { return x.name === timeChart; }); if (chart.length === 0) { chart.unshift(_objectSpread(_objectSpread({}, tick), {}, { id: tickId, chart_type: timeChart })); this.convertBBFirstTick(chart, timeChart); } else if (chart[0].id === tickId) { this._updateExistingTick(chart[0], tick, timeChart, tickId); } else if (tickId - chart[0].id === tc.period) { tick.isNewCandle = true; chart.unshift(_objectSpread(_objectSpread({}, tick), {}, { id: tickId, chart_type: timeChart })); this.convertBBFirstTick(chart, timeChart); } else { this._validateTickContinuity(chart, tickId, timeChart, tc.period); } if (chart.length > (tc.size || _constant.CHART_SIZE)) chart.pop(); this.calculateProfitAndTimeTick(chart, 0); this.calculateBollingerBandsForTick(chart, 0, timeChart); this._calculateTrendAndStraight(chart, 0); this._calculateAboveBelowCounters(chart, 0); this._calculateEMA(chart, 0, 34, 'ema34'); this._calculateEMA(chart, 0, 89, 'ema89'); this._calculateRSI(chart, 0, 14); this._calculateMACD(chart, 0); this._calculateATR(chart, 0, 14); if (timeChart === '1min' && !isInitialized) this._updateSubTicks(chart[0]); return chart[0]; } }, { key: "_updateSubTicks", value: function _updateSubTicks(candle1min) { var ticks = this.getTicks(); candle1min.ticks = { tick5min: ticks.tick5min ? _objectSpread({}, ticks.tick5min) : null, tick15min: ticks.tick15min ? _objectSpread({}, ticks.tick15min) : null, tick30min: ticks.tick30min ? _objectSpread({}, ticks.tick30min) : null, tick60min: ticks.tick60min ? _objectSpread({}, ticks.tick60min) : null, tick4hour: ticks.tick4hour ? _objectSpread({}, ticks.tick4hour) : null, tick1day: ticks.tick1day ? _objectSpread({}, ticks.tick1day) : null }; } }, { key: "_updateExistingTick", value: function _updateExistingTick(existingTick, tick, timeChart, tickId) { if (timeChart === '1min') existingTick.open = tick.open; existingTick.close = tick.close; if (existingTick.high < tick.high) existingTick.high = tick.high; if (existingTick.low > tick.low) existingTick.low = tick.low; if (timeChart !== '1min') { var aggregated = this.sumVolume(tickId, timeChart); if (aggregated.vol > 0) { Object.assign(existingTick, aggregated); } else { existingTick.vol += tick.vol; existingTick.amount += tick.amount; existingTick.taker_vol += tick.taker_vol; existingTick.taker_amount += tick.taker_amount; existingTick.count += tick.count; } } } }, { key: "_validateTickContinuity", value: function _validateTickContinuity(chart, tickId, timeChart, period) { if (!chart.some(function (t) { return t.id === tickId; })) { if (timeChart === '1min' && Math.abs(tickId - chart[0].id) !== period) { throw new Error("Add wrong tick ts [".concat(timeChart, ", ").concat(tickId, ", ").concat(chart[0].id, "]")); } } } }, { key: "convertBBFirstTick", value: function convertBBFirstTick(chart, timeChart) { this.calculateProfitAndTimeTick(chart, 0); this.calculateBollingerBandsForTick(chart, 0, timeChart); } }, { key: "addTickToAllChart", value: function addTickToAllChart(tick) { var _this2 = this; var ids = this.convertTickId(tick.id); Object.values(_constant.TIME_CHART).forEach(function (tc) { return _this2.addTickToChart(tick, tc.name, false, null, ids); }); } }]); }(); var _default = exports["default"] = CandlestickChart;