@azteam/candlestick-chart
Version:
N/A
870 lines (853 loc) • 35.5 kB
JavaScript
"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;