bitprophet
Version:
Crypto trading platform for Binance that uses chat bots as its interface
549 lines (450 loc) • 17.6 kB
JavaScript
module.exports = {
isObject: function(object) {
return object !== undefined && object !== null && object.constructor == Object
},
ema: function(ticks, length, momentum = 100, arraySize = 1) {
//Read more: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_averages
var fromJsonObject = this.isObject(ticks[0])
if(ticks.length < length + momentum + arraySize) return []
var sma = 0
var currentValue = 0
for(var i = ticks.length - length - momentum - arraySize; i < ticks.length - momentum - arraySize; ++i) {
let value
if(fromJsonObject) value = parseFloat(ticks[i].close)
else value = parseFloat(ticks[i])
if(i == ticks.length - momentum - arraySize - 1) {
currentValue = value
}
else {
sma += value
}
}
sma /= length
var weightLastPrice = (2 / (length + 1))
var ema = (currentValue - sma) * weightLastPrice + sma
var emaArray = []
for(let i = 0; i < momentum + arraySize; ++i) {
var tickIndex = ticks.length - momentum - arraySize + i
let value
if(fromJsonObject) value = parseFloat(ticks[tickIndex].close)
else value = parseFloat(ticks[tickIndex])
ema = (value - ema) * weightLastPrice + ema
if(i > momentum - 1) emaArray.push(ema)
}
return emaArray
},
sma: function(ticks, length, arraySize = 1) {
//Read more: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_averages
var fromJsonObject = this.isObject(ticks[0])
if(ticks.length < length + arraySize - 1) return []
var averages = []
for(var i = 0; i < arraySize; ++i) {
var index = ticks.length - arraySize + i
var price
if(fromJsonObject) price = parseFloat(ticks[index].close)
else price = parseFloat(ticks[index])
for(var j = 1; j < length; ++j) {
if(fromJsonObject) price += parseFloat(ticks[index - length + j].close)
else price += parseFloat(ticks[index - length + j])
}
price = price / length
averages.push(price)
}
return averages
},
rsi: function(ticks, length, momentum, arraySize = 1) {
//Read more: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:relative_strength_index_rsi
var fromJsonObject = this.isObject(ticks[0])
if(ticks.length < (length + 1) + momentum + arraySize) return []
var gains = [], losses = []
var avgGain = 0, avgLoss = 0
var currentGain = 0, currentLoss = 0
for(var i = ticks.length - (length + 1) - momentum - arraySize; i < ticks.length - momentum - arraySize; ++i) {
var value, change
if(fromJsonObject) {
value = ticks[i].close
change = value - ticks[i - 1].close
}
else {
value = ticks[i]
change = value - ticks[i - 1]
}
if(i == ticks.length - momentum - 1) {
if(change > 0) currentGain = change
else if(change < 0) currentLoss = change * -1
}
else {
if(change > 0) gains.push(change)
else if(change < 0) losses.push(change * -1)
}
}
for(let i = 0; i < gains.length; ++i) {
avgGain += gains[i]
}
for(let i = 0; i < losses.length; ++i) {
avgLoss += losses[i]
}
//To build the momentum, we need the first rsis to be calculated using smas
var smaGains = avgGain / length
var smaLosses = avgLoss / length
var weightLastPrice = (2 / (length * 2 + 1))
var emaGains = (currentGain - smaGains) * weightLastPrice + smaGains
var emaLosses = (currentLoss - smaLosses) * weightLastPrice + smaLosses
var rsiArray = []
for(let i = 0; i < momentum + arraySize; ++i) {
var tickIndex = ticks.length - momentum - arraySize + i
let value, change
if(fromJsonObject) {
value = ticks[tickIndex].close
change = value - ticks[tickIndex - 1].close
}
else {
value = ticks[tickIndex]
change = value - ticks[tickIndex - 1]
}
if(change > 0) {
currentGain = change
currentLoss = 0
}
else if(change < 0) {
currentGain = 0
currentLoss = change * -1
}
else {
currentGain = 0
currentLoss = 0
}
emaGains = (currentGain - emaGains) * weightLastPrice + emaGains
emaLosses = (currentLoss - emaLosses) * weightLastPrice + emaLosses
if(i > momentum - 1) rsiArray.push(100 - 100 / (1 + emaGains / emaLosses))
}
return rsiArray
},
stochastic: function(ticks, length, arraySize = 1, smooth = 3) {
//Read more: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:stochastic_oscillator_fast_slow_and_full
var fromJsonObject = this.isObject(ticks[0])
if(ticks.length < length + arraySize + (smooth - 1)) return []
var stochasticsSharp = []
for(let i = 0; i < arraySize + (smooth - 1); ++i) {
var currentIndex = ticks.length - (arraySize + (smooth - 1)) + i
var currentClose, lowestLow, highestHigh
if(fromJsonObject) {
currentClose = ticks[currentIndex].close
lowestLow = ticks[currentIndex].low
highestHigh = ticks[currentIndex].high
}
else {
currentClose = ticks[currentIndex]
lowestLow = ticks[currentIndex]
highestHigh = ticks[currentIndex]
}
for(let j = 0; j < length; ++j) {
var lowest, highest
if(fromJsonObject) {
lowest = ticks[currentIndex - j - 1].low
highest = ticks[currentIndex - j - 1].high
}
else {
lowest = ticks[currentIndex - j - 1]
highest = ticks[currentIndex - j - 1]
}
if(lowest < lowestLow) lowestLow = lowest
if(highest > highestHigh) highestHigh = highest
}
stochasticsSharp.push((currentClose - lowestLow) / (highestHigh - lowestLow))
}
var stochastics = []
for(var i = smooth - 1; i < stochasticsSharp.length; ++i) {
var stoch = stochasticsSharp[i]
for(var j = 1; j < smooth; ++j) {
stoch += stochasticsSharp[i - j]
}
stoch /= smooth
stochastics.push(parseFloat(stoch * 100).toFixed(2))
}
return stochastics
},
bollingerBands: function(ticks, length, deviation = 2, arraySize = 1) {
//Read more: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:bollinger_bands
var fromJsonObject = this.isObject(ticks[0])
var bands = []
var movingAverages = this.sma(ticks, length, arraySize)
for(var i = 0; i < movingAverages.length; ++i) {
var movingAverage = movingAverages[i]
var sum = 0
for(var j = ticks.length - length; j < ticks.length; ++j) {
var close
if(fromJsonObject) close = parseFloat(ticks[j - movingAverages.length + i + 1].close)
else close = parseFloat(ticks[j - movingAverages.length + i + 1])
sum += Math.pow((close - movingAverage), 2)
}
deviation = Math.sqrt(sum / (length - 1)) * 2
bands.push([movingAverage - deviation, movingAverage, movingAverage + deviation])
}
return bands
},
ichimoku: function(ticks, conversionPeriods = 10, basePeriods = 30, laggingSpan2Periods = 60, displacement = 30, arraySize = 1) {
//Read more: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:ichimoku_cloud
function donchian(startIndex, length) {
var lowest = 0, highest = 0
for(var i = 0; i < length; ++i) {
var index = startIndex - length + i + 1
if(i == 0) {
lowest = parseFloat(ticks[index].low)
highest = parseFloat(ticks[index].high)
}
else {
var low = parseFloat(ticks[index].low)
if(low < lowest) lowest = low
var high = parseFloat(ticks[index].high)
if(high > highest) highest = high
}
}
return (lowest + highest) / 2.
}
var ichimokuArray = []
for(var i = 0; i < arraySize; ++i) {
var index = ticks.length - arraySize - 1 - displacement + i
var conversionLine = donchian(index, conversionPeriods)
var baseLine = donchian(index, basePeriods)
var leadLine1 = (conversionLine + baseLine) / 2.
var leadLine2 = donchian(index, laggingSpan2Periods)
ichimokuArray.push([conversionLine, baseLine, leadLine1, leadLine2])
}
return ichimokuArray
},
heikinAshi: function(ticks, arraySize = -1) {
//Heikin Ashi candlesticks are best for swing trading (not daytrading) and are used to identify market trends
//Read more: http://stockcharts.com/school/doku.php?id=chart_school:chart_analysis:heikin_ashi
//arraySize = -1, calculate Heikin Ashi for all ticks
//arraySize = n, where n >= 1, calculate Heikin Ashi for the last n ticks
var ha = []
var startingTick = arraySize == -1 ? 0 : ticks.length - arraySize
for(var i = startingTick; i < ticks.length; i++) {
var o = parseFloat(ticks[i].open)
var h = parseFloat(ticks[i].high)
var l = parseFloat(ticks[i].low)
var c = parseFloat(ticks[i].close)
var hac = (o + h + l + c) / 4
var hao = (o + c) / 2
var hah = i == startingTick ? h : Math.max(h, hao, hac)
var hal = i == startingTick ? l : Math.min(l, hao, hac)
ha.push({open: hao, high: hah, low: hal, close: hac})
}
return ha
},
macd: function(ticks, shortEma = 12, longEma = 26, signal = 9, arraySize = 1) {
//Read more: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_average_convergence_divergence_macd
/*
MACD Line: Fast EMA - Slow EMA
Signal Line: EMA of MACD Line
MACD Histogram: MACD Line - Signal Line
*/
var momentum = 100
var emaArraySize = arraySize + momentum + signal
var emaShort = this.ema(ticks, shortEma, momentum, emaArraySize)
var emaLong = this.ema(ticks, longEma, momentum, emaArraySize)
var macdLine = []
for(var i = 0; i < emaArraySize; ++i) {
macdLine.push(emaShort[emaShort.length - emaArraySize + i] - emaLong[emaLong.length - emaArraySize + i])
}
var signalLine = this.ema(macdLine, signal, momentum, arraySize)
var histogramArray = []
for(i = 0; i < arraySize; ++i) {
var histogram = macdLine[macdLine.length - arraySize + i] - signalLine[i]
histogramArray.push(histogram)
}
var macd = []
for(i = 0; i < arraySize; ++i) {
macd.push({line: macdLine[macdLine.length - arraySize + i], signal: signalLine[i], histogram: histogramArray[i]})
}
return macd
},
zigzag: function(ticks, deviation = 5, arraySize = -1) {
//Determines percent deviation in price changes, presenting frequency and volatility in deviation. Also helps determine trend reversals.
//Read more: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:zigzag
//arraySize = -1, calculate ZigZag for all ticks
//arraySize = n, where n >= 1, calculate the ZigZag for the last n ticks
var turningPoints = []
var basePrice = -1
var lastDeviation = 0
deviation /= 100.
var startingTick = arraySize == -1 ? 0 : ticks.length - arraySize
//Calculate all turning points that have a deviation equal or superior to the argument received
for(var i = startingTick; i < ticks.length; ++i) {
var close = parseFloat(ticks[i].close)
var high = parseFloat(ticks[i].high)
var low = parseFloat(ticks[i].low)
var positiveDeviation = high / basePrice - 1
var negativeDeviation = low / basePrice - 1
if(basePrice == -1) {
basePrice = close
lastDeviation = 0
turningPoints.push({timePeriod: i, value: close, deviation: lastDeviation})
continue
}
//Is it a positive turning point or is it higher than the last positive one?
if(positiveDeviation >= deviation || (positiveDeviation > 0 && lastDeviation > 0)) {
if(lastDeviation > 0) {
positiveDeviation += lastDeviation
turningPoints.pop()
}
turningPoints.push({timePeriod: i, value: high, deviation: positiveDeviation})
lastDeviation = positiveDeviation
basePrice = high
}
//Is it a positive turning point or is it lower than the last negative one?
else if(negativeDeviation <= -deviation || (negativeDeviation < 0 && lastDeviation < 0)) {
if(lastDeviation < 0) {
negativeDeviation += lastDeviation
turningPoints.pop()
}
turningPoints.push({timePeriod: i, value: low, deviation: negativeDeviation})
lastDeviation = negativeDeviation
basePrice = low
}
//Add always the last one as a turning point, just to make our life easier for the next calculation
else if(i == ticks.length - 1) {
if(positiveDeviation > 0) turningPoints.push({timePeriod: i, value: high, deviation: positiveDeviation})
else turningPoints.push({timePeriod: i, value: low, deviation: negativeDeviation})
}
}
var zigzag = []
//Add the turning points to the returning array, calculate the values between those turning points and add them as well
for(i = 0; i < turningPoints.length; ++i) {
var turningPoint = turningPoints[i]
zigzag.push({timePeriod: turningPoint.timePeriod, value: turningPoint.value, deviation: parseFloat((turningPoint.deviation * 100).toFixed(2)),
turningPoint: turningPoint.deviation > deviation || turningPoint.deviation < -deviation})
if(turningPoint.timePeriod >= ticks.length -1) continue
var nextTurningPoint = turningPoints[i + 1]
for(var j = turningPoint.timePeriod + 1; j < nextTurningPoint.timePeriod; ++j) {
var distanceToTP = j - turningPoint.timePeriod
var distanceTPs = nextTurningPoint.timePeriod - turningPoint.timePeriod
var value = turningPoint.value + (nextTurningPoint.value - turningPoint.value) / distanceTPs * distanceToTP
var currentDeviation = value / turningPoint.value
zigzag.push({timePeriod: j, value: value, deviation: parseFloat((currentDeviation * 100).toFixed(2)),
turningPoint: false})
}
}
return zigzag
},
waveTrend: function(ticks, channelLength, avgLength, arraySize = 1) {
//Read more: https://www.tradingview.com/script/2KE8wTuF-Indicator-WaveTrend-Oscillator-WT/
var momentum = 100
var hlc3 = []
for(var i = 0; i < ticks.length; ++i) {
hlc3.push((parseFloat(ticks[i].high) + parseFloat(ticks[i].low) + parseFloat(ticks[i].close)) / 3.)
}
var esa = this.ema(hlc3, channelLength, momentum, hlc3.length - momentum - channelLength)
var esaDiff = []
for(let i = 0; i < esa.length; ++i) {
esaDiff.push(Math.abs(hlc3[hlc3.length - esa.length + i] - esa[i]))
}
var d = this.ema(esaDiff, channelLength, momentum, esaDiff.length - momentum - channelLength)
var ci = []
for(let i = 0; i < d.length; ++i) {
ci.push((hlc3[hlc3.length - d.length + i] - esa[esa.length - d.length + i]) / (0.015 * d[i]))
}
var waveTrendArray = this.ema(ci, avgLength, momentum, arraySize)
return waveTrendArray
},
nextSupportBase: function(ticks, length, startBackRead = 1, confirmationsNeeded = 2, percentageBounce = 1.01) {
//Indicator based on Quickfingers Luc's technique: https://www.youtube.com/watch?v=vgcFe_XO_LQ
var referenceLowValue = this.lowestValue(ticks, startBackRead, 0)
var nextBase = referenceLowValue
var confirmations = -1
var indexNextBase = -1
for(var i = startBackRead; i < length; ++i) {
var index = ticks.length - 1 - i
var tickLow = ticks[index].low
if(tickLow <= nextBase) {
nextBase = tickLow
indexNextBase = index
confirmations = 0
continue
}
else if(confirmations >= 0 && tickLow > nextBase) {
confirmations++
if(confirmations >= confirmationsNeeded) {
var highestPoint = -1
for(var j = indexNextBase + 1; j < length; ++j) {
var tickHigh = ticks[j].high
if(tickHigh > highestPoint) highestPoint = tickHigh
}
if(highestPoint / nextBase > percentageBounce) break
else {
confirmations = 0
continue
}
}
}
}
if(confirmations != confirmationsNeeded) return null
return nextBase
},
measureMaxDiff: function(ticks, length, measureWicks = false) {
//Percentual difference between the high and low from the last length of ticks
var highValue = ticks[ticks.length - 1].close
var lowValue = highValue
for(var i = 0; i < length; ++i) {
var high, low
if(measureWicks) {
high = ticks[ticks.length - 1 - i].high
low = ticks[ticks.length - 1 - i].low
}
else {
var open = ticks[ticks.length - 1 - i].open
var close = ticks[ticks.length - 1 - i].close
if(open > close) {
high = open
low = close
}
else {
high = close
low = open
}
}
if(high > highValue) highValue = high
else if(low < lowValue) lowValue = low
}
return (highValue - lowValue) / lowValue * 100
},
highestValue: function(ticks, length, startBackRead = 1) {
//Returns the highest value from the last length of ticks
var highValue = ticks[ticks.length - 1 - startBackRead].high
for(var i = startBackRead; i < length; ++i) {
var high = ticks[ticks.length - 1 - i].high
if(high > highValue) highValue = high
}
return highValue
},
lowestValue: function(ticks, length, startBackRead = 1) {
//Returns the lowest value from the last length of ticks
var lowValue = ticks[ticks.length - 1 - startBackRead].low
for(var i = startBackRead; i < length; ++i) {
var low = ticks[ticks.length - 1 - i].low
if(low < lowValue) lowValue = low
}
return lowValue
},
average: function(values) {
//Returns the average of the input array
var avgValues = 0
for(var i = 0; i < values.length; ++i) {
avgValues += parseFloat(values[i])
}
return avgValues /= values.length
},
volume24h: function(ticks, intervalMin) {
//Returns the volume for the last 24h.
//intervalMin is the ticks' interval in minutes, so when using the interval 1h, intervalMin = 60
var volume24h = 0
for(var i = ticks.length - (24 * 60 / intervalMin); i < ticks.length; ++i) {
var high = parseFloat(ticks[i].high)
var low = parseFloat(ticks[i].low)
var avgPrice = (high - low) / 2. + low
volume24h += parseFloat(ticks[i].volume) * avgPrice
}
return volume24h
}
}