@sunrise1002/tats
Version:
Techincal Indicators written in javascript
1,550 lines (1,496 loc) • 155 kB
JavaScript
/* APP */
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
class Item {
constructor(data, prev, next) {
this.next = next;
if (next)
next.prev = this;
this.prev = prev;
if (prev)
prev.next = this;
this.data = data;
}
}
class LinkedList {
constructor() {
this._length = 0;
}
get head() {
return this._head && this._head.data;
}
get tail() {
return this._tail && this._tail.data;
}
get current() {
return this._current && this._current.data;
}
get length() {
return this._length;
}
push(data) {
this._tail = new Item(data, this._tail);
if (this._length === 0) {
this._head = this._tail;
this._current = this._head;
this._next = this._head;
}
this._length++;
}
pop() {
var tail = this._tail;
if (this._length === 0) {
return;
}
this._length--;
if (this._length === 0) {
this._head = this._tail = this._current = this._next = undefined;
return tail.data;
}
this._tail = tail.prev;
this._tail.next = undefined;
if (this._current === tail) {
this._current = this._tail;
this._next = undefined;
}
// Fix: Clear reference to prevent memory leak
tail.prev = undefined;
return tail.data;
}
shift() {
var head = this._head;
if (this._length === 0) {
return;
}
this._length--;
if (this._length === 0) {
this._head = this._tail = this._current = this._next = undefined;
return head.data;
}
this._head = this._head.next;
if (this._current === head) {
this._current = this._head;
this._next = this._current.next;
}
// Fix: Clear reference to prevent memory leak
head.next = undefined;
return head.data;
}
unshift(data) {
this._head = new Item(data, undefined, this._head);
if (this._length === 0) {
this._tail = this._head;
this._next = this._head;
}
this._length++;
}
unshiftCurrent() {
var current = this._current;
if (current === this._head || this._length < 2) {
return current && current.data;
}
// remove
if (current === this._tail) {
this._tail = current.prev;
this._tail.next = undefined;
this._current = this._tail;
}
else {
current.next.prev = current.prev;
current.prev.next = current.next;
this._current = current.prev;
}
this._next = this._current.next;
// Clear old references before reusing the node
const oldPrev = current.prev;
current.prev = undefined;
// unshift
current.next = this._head;
current.prev = undefined;
this._head.prev = current;
this._head = current;
return current.data;
}
removeCurrent() {
var current = this._current;
if (this._length === 0) {
return;
}
this._length--;
if (this._length === 0) {
this._head = this._tail = this._current = this._next = undefined;
return current.data;
}
if (current === this._tail) {
this._tail = current.prev;
this._tail.next = undefined;
this._current = this._tail;
}
else if (current === this._head) {
this._head = current.next;
this._head.prev = undefined;
this._current = this._head;
}
else {
current.next.prev = current.prev;
current.prev.next = current.next;
this._current = current.prev;
}
this._next = this._current.next;
// Fix: Clear references to prevent memory leak
current.next = undefined;
current.prev = undefined;
return current.data;
}
resetCursor() {
this._current = this._next = this._head;
return this;
}
next() {
var next = this._next;
if (next !== undefined) {
this._next = next.next;
this._current = next;
return next.data;
}
}
}
/**
* Created by AAravindan on 5/7/16.
*/
class FixedSizeLinkedList extends LinkedList {
constructor(size, maintainHigh, maintainLow, maintainSum) {
super();
this.size = size;
this.maintainHigh = maintainHigh;
this.maintainLow = maintainLow;
this.maintainSum = maintainSum;
this.totalPushed = 0;
this.periodHigh = 0;
this.periodLow = Infinity;
this.periodSum = 0;
if (!size || typeof size !== 'number') {
throw ('Size required and should be a number.');
}
this._push = this.push;
this.push = function (data) {
this.add(data);
this.totalPushed++;
};
}
add(data) {
if (this.length === this.size) {
this.lastShift = this.shift();
this._push(data);
//TODO: FInd a better way
if (this.maintainHigh)
if (this.lastShift == this.periodHigh)
this.calculatePeriodHigh();
if (this.maintainLow)
if (this.lastShift == this.periodLow)
this.calculatePeriodLow();
if (this.maintainSum) {
this.periodSum = this.periodSum - this.lastShift;
}
}
else {
this._push(data);
}
//TODO: FInd a better way
if (this.maintainHigh)
if (this.periodHigh <= data)
(this.periodHigh = data);
if (this.maintainLow)
if (this.periodLow >= data)
(this.periodLow = data);
if (this.maintainSum) {
this.periodSum = this.periodSum + data;
}
}
*iterator() {
this.resetCursor();
while (this.next()) {
yield this.current;
}
}
calculatePeriodHigh() {
this.resetCursor();
if (this.next())
this.periodHigh = this.current;
while (this.next()) {
if (this.periodHigh <= this.current) {
this.periodHigh = this.current;
}
}
}
calculatePeriodLow() {
this.resetCursor();
if (this.next())
this.periodLow = this.current;
while (this.next()) {
if (this.periodLow >= this.current) {
this.periodLow = this.current;
}
}
}
}
class StockData {
constructor(open, high, low, close, reversedInput) {
this.open = open;
this.high = high;
this.low = low;
this.close = close;
this.reversedInput = reversedInput;
}
}
class CandleData {
}
class CandleList {
constructor() {
this.open = [];
this.high = [];
this.low = [];
this.close = [];
this.volume = [];
this.timestamp = [];
}
}
let config = {};
function setConfig(key, value) {
config[key] = value;
}
function getConfig(key) {
return config[key];
}
function format(v) {
let precision = getConfig('precision');
if (precision) {
return parseFloat(v.toPrecision(precision));
}
return v;
}
class IndicatorInput {
}
class Indicator {
constructor(input) {
this.format = input.format || format;
}
static reverseInputs(input) {
if (input.reversedInput) {
input.values ? input.values.reverse() : undefined;
input.open ? input.open.reverse() : undefined;
input.high ? input.high.reverse() : undefined;
input.low ? input.low.reverse() : undefined;
input.close ? input.close.reverse() : undefined;
input.volume ? input.volume.reverse() : undefined;
input.timestamp ? input.timestamp.reverse() : undefined;
}
}
getResult() {
return this.result;
}
}
//STEP 1. Import Necessary indicator or rather last step
//STEP 2. Create the input for the indicator, mandatory should be in the constructor
//STEP3. Add class based syntax with export
class SMA extends Indicator {
constructor(input) {
super(input);
this.period = input.period;
this.price = input.values;
var genFn = (function* (period) {
var list = new LinkedList();
var sum = 0;
var counter = 1;
var current = yield;
var result;
list.push(0);
while (true) {
if (counter < period) {
counter++;
list.push(current);
sum = sum + current;
}
else {
sum = sum - list.shift() + current;
result = ((sum) / period);
list.push(current);
}
current = yield result;
}
});
this.generator = genFn(this.period);
this.generator.next();
this.result = [];
this.price.forEach((tick) => {
var result = this.generator.next(tick);
if (result.value !== undefined) {
this.result.push(this.format(result.value));
}
});
}
nextValue(price) {
var result = this.generator.next(price).value;
if (result != undefined)
return this.format(result);
}
;
}
SMA.calculate = sma;
function sma(input) {
Indicator.reverseInputs(input);
var result = new SMA(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
//STEP 6. Run the tests
class EMA extends Indicator {
constructor(input) {
super(input);
var period = input.period;
var priceArray = input.values;
var exponent = (2 / (period + 1));
var sma$$1;
this.result = [];
sma$$1 = new SMA({ period: period, values: [] });
var genFn = (function* () {
var tick = yield;
var prevEma;
while (true) {
if (prevEma !== undefined && tick !== undefined) {
prevEma = ((tick - prevEma) * exponent) + prevEma;
tick = yield prevEma;
}
else {
tick = yield;
prevEma = sma$$1.nextValue(tick);
if (prevEma)
tick = yield prevEma;
}
}
});
this.generator = genFn();
this.generator.next();
this.generator.next();
priceArray.forEach((tick) => {
var result = this.generator.next(tick);
if (result.value != undefined) {
this.result.push(this.format(result.value));
}
});
}
nextValue(price) {
var result = this.generator.next(price).value;
if (result != undefined)
return this.format(result);
}
;
}
EMA.calculate = ema;
function ema(input) {
Indicator.reverseInputs(input);
var result = new EMA(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
class WMA extends Indicator {
constructor(input) {
super(input);
var period = input.period;
var priceArray = input.values;
this.result = [];
this.generator = (function* () {
let data = new LinkedList();
let denominator = period * (period + 1) / 2;
while (true) {
if ((data.length) < period) {
data.push(yield);
}
else {
data.resetCursor();
let result = 0;
for (let i = 1; i <= period; i++) {
result = result + (data.next() * i / (denominator));
}
var next = yield result;
data.shift();
data.push(next);
}
}
})();
this.generator.next();
priceArray.forEach((tick, index) => {
var result = this.generator.next(tick);
if (result.value != undefined) {
this.result.push(this.format(result.value));
}
});
}
//STEP 5. REMOVE GET RESULT FUNCTION
nextValue(price) {
var result = this.generator.next(price).value;
if (result != undefined)
return this.format(result);
}
;
}
WMA.calculate = wma;
function wma(input) {
Indicator.reverseInputs(input);
var result = new WMA(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
class WEMA extends Indicator {
constructor(input) {
super(input);
var period = input.period;
var priceArray = input.values;
var exponent = 1 / period;
var sma$$1;
this.result = [];
sma$$1 = new SMA({ period: period, values: [] });
var genFn = (function* () {
var tick = yield;
var prevEma;
while (true) {
if (prevEma !== undefined && tick !== undefined) {
prevEma = ((tick - prevEma) * exponent) + prevEma;
tick = yield prevEma;
}
else {
tick = yield;
prevEma = sma$$1.nextValue(tick);
if (prevEma !== undefined)
tick = yield prevEma;
}
}
});
this.generator = genFn();
this.generator.next();
this.generator.next();
priceArray.forEach((tick) => {
var result = this.generator.next(tick);
if (result.value != undefined) {
this.result.push(this.format(result.value));
}
});
}
nextValue(price) {
var result = this.generator.next(price).value;
if (result != undefined)
return this.format(result);
}
;
}
WEMA.calculate = wema;
function wema(input) {
Indicator.reverseInputs(input);
var result = new WEMA(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
/**
* Created by AAravindan on 5/4/16.
*/
class MACD extends Indicator {
constructor(input) {
super(input);
var oscillatorMAtype = input.SimpleMAOscillator ? SMA : EMA;
var signalMAtype = input.SimpleMASignal ? SMA : EMA;
var fastMAProducer = new oscillatorMAtype({ period: input.fastPeriod, values: [], format: (v) => { return v; } });
var slowMAProducer = new oscillatorMAtype({ period: input.slowPeriod, values: [], format: (v) => { return v; } });
var signalMAProducer = new signalMAtype({ period: input.signalPeriod, values: [], format: (v) => { return v; } });
var format = this.format;
this.result = [];
this.generator = (function* () {
var index = 0;
var tick;
var MACD, signal, histogram, fast, slow;
while (true) {
if (index < input.slowPeriod) {
tick = yield;
fast = fastMAProducer.nextValue(tick);
slow = slowMAProducer.nextValue(tick);
index++;
continue;
}
if (fast && slow) { //Just for typescript to be happy
MACD = fast - slow;
signal = signalMAProducer.nextValue(MACD);
}
histogram = MACD - signal;
tick = yield ({
//fast : fast,
//slow : slow,
MACD: format(MACD),
signal: signal ? format(signal) : undefined,
histogram: isNaN(histogram) ? undefined : format(histogram)
});
fast = fastMAProducer.nextValue(tick);
slow = slowMAProducer.nextValue(tick);
}
})();
this.generator.next();
input.values.forEach((tick) => {
var result = this.generator.next(tick);
if (result.value != undefined) {
this.result.push(result.value);
}
});
}
nextValue(price) {
var result = this.generator.next(price).value;
return result;
}
;
}
MACD.calculate = macd;
function macd(input) {
Indicator.reverseInputs(input);
var result = new MACD(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
class AverageGain extends Indicator {
constructor(input) {
super(input);
let values = input.values;
let period = input.period;
let format = this.format;
this.generator = (function* (period) {
var currentValue = yield;
var counter = 1;
var gainSum = 0;
var avgGain;
var gain;
var lastValue = currentValue;
currentValue = yield;
while (true) {
gain = currentValue - lastValue;
gain = gain > 0 ? gain : 0;
if (gain > 0) {
gainSum = gainSum + gain;
}
if (counter < period) {
counter++;
}
else if (avgGain === undefined) {
avgGain = gainSum / period;
}
else {
avgGain = ((avgGain * (period - 1)) + gain) / period;
}
lastValue = currentValue;
avgGain = (avgGain !== undefined) ? format(avgGain) : undefined;
currentValue = yield avgGain;
}
})(period);
this.generator.next();
this.result = [];
values.forEach((tick) => {
var result = this.generator.next(tick);
if (result.value !== undefined) {
this.result.push(result.value);
}
});
}
nextValue(price) {
return this.generator.next(price).value;
}
;
}
AverageGain.calculate = averagegain;
function averagegain(input) {
Indicator.reverseInputs(input);
var result = new AverageGain(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
class AverageLoss extends Indicator {
constructor(input) {
super(input);
let values = input.values;
let period = input.period;
let format = this.format;
this.generator = (function* (period) {
var currentValue = yield;
var counter = 1;
var lossSum = 0;
var avgLoss;
var loss;
var lastValue = currentValue;
currentValue = yield;
while (true) {
loss = lastValue - currentValue;
loss = loss > 0 ? loss : 0;
if (loss > 0) {
lossSum = lossSum + loss;
}
if (counter < period) {
counter++;
}
else if (avgLoss === undefined) {
avgLoss = lossSum / period;
}
else {
avgLoss = ((avgLoss * (period - 1)) + loss) / period;
}
lastValue = currentValue;
avgLoss = (avgLoss !== undefined) ? format(avgLoss) : undefined;
currentValue = yield avgLoss;
}
})(period);
this.generator.next();
this.result = [];
values.forEach((tick) => {
var result = this.generator.next(tick);
if (result.value !== undefined) {
this.result.push(result.value);
}
});
}
nextValue(price) {
return this.generator.next(price).value;
}
;
}
AverageLoss.calculate = averageloss;
function averageloss(input) {
Indicator.reverseInputs(input);
var result = new AverageLoss(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
/**
* Created by AAravindan on 5/5/16.
*/
class RSI extends Indicator {
constructor(input) {
super(input);
var period = input.period;
var values = input.values;
var GainProvider = new AverageGain({ period: period, values: [] });
var LossProvider = new AverageLoss({ period: period, values: [] });
let count = 1;
this.generator = (function* (period) {
var current = yield;
var lastAvgGain, lastAvgLoss, RS, currentRSI;
while (true) {
lastAvgGain = GainProvider.nextValue(current);
lastAvgLoss = LossProvider.nextValue(current);
if ((lastAvgGain !== undefined) && (lastAvgLoss !== undefined)) {
if (lastAvgLoss === 0) {
currentRSI = 100;
}
else if (lastAvgGain === 0) {
currentRSI = 0;
}
else {
RS = lastAvgGain / lastAvgLoss;
RS = isNaN(RS) ? 0 : RS;
currentRSI = parseFloat((100 - (100 / (1 + RS))).toFixed(2));
}
}
count++;
current = yield currentRSI;
}
})(period);
this.generator.next();
this.result = [];
values.forEach((tick) => {
var result = this.generator.next(tick);
if (result.value !== undefined) {
this.result.push(result.value);
}
});
}
;
nextValue(price) {
return this.generator.next(price).value;
}
;
}
RSI.calculate = rsi;
function rsi(input) {
Indicator.reverseInputs(input);
var result = new RSI(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
class SD extends Indicator {
constructor(input) {
super(input);
var period = input.period;
var priceArray = input.values;
var sma$$1 = new SMA({ period: period, values: [], format: (v) => { return v; } });
this.result = [];
this.generator = (function* () {
var tick;
var mean;
var currentSet = new FixedSizeLinkedList(period);
tick = yield;
var sd;
while (true) {
currentSet.push(tick);
mean = sma$$1.nextValue(tick);
if (mean) {
let sum = 0;
for (let x of currentSet.iterator()) {
sum = sum + (Math.pow((x - mean), 2));
}
sd = Math.sqrt(sum / (period));
}
tick = yield sd;
}
})();
this.generator.next();
priceArray.forEach((tick) => {
var result = this.generator.next(tick);
if (result.value != undefined) {
this.result.push(this.format(result.value));
}
});
}
nextValue(price) {
var nextResult = this.generator.next(price);
if (nextResult.value != undefined)
return this.format(nextResult.value);
}
;
}
SD.calculate = sd;
function sd(input) {
Indicator.reverseInputs(input);
var result = new SD(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
class BollingerBands extends Indicator {
constructor(input) {
super(input);
var period = input.period;
var priceArray = input.values;
var stdDev = input.stdDev;
var format = this.format;
var sma$$1, sd$$1;
this.result = [];
sma$$1 = new SMA({ period: period, values: [], format: (v) => { return v; } });
sd$$1 = new SD({ period: period, values: [], format: (v) => { return v; } });
this.generator = (function* () {
var result;
var tick;
var calcSMA;
var calcsd;
tick = yield;
while (true) {
calcSMA = sma$$1.nextValue(tick);
calcsd = sd$$1.nextValue(tick);
if (calcSMA) {
let middle = format(calcSMA);
let upper = format(calcSMA + (calcsd * stdDev));
let lower = format(calcSMA - (calcsd * stdDev));
let pb = format((tick - lower) / (upper - lower));
result = {
middle: middle,
upper: upper,
lower: lower,
pb: pb
};
}
tick = yield result;
}
})();
this.generator.next();
priceArray.forEach((tick) => {
var result = this.generator.next(tick);
if (result.value != undefined) {
this.result.push(result.value);
}
});
}
nextValue(price) {
return this.generator.next(price).value;
}
;
}
BollingerBands.calculate = bollingerbands;
function bollingerbands(input) {
Indicator.reverseInputs(input);
var result = new BollingerBands(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
//STEP3. Add class based syntax with export
class WilderSmoothing extends Indicator {
constructor(input) {
super(input);
this.period = input.period;
this.price = input.values;
var genFn = (function* (period) {
var list = new LinkedList();
var sum = 0;
var counter = 1;
var current = yield;
var result = 0;
while (true) {
if (counter < period) {
counter++;
sum = sum + current;
result = undefined;
}
else if (counter == period) {
counter++;
sum = sum + current;
result = sum;
}
else {
result = result - (result / period) + current;
}
current = yield result;
}
});
this.generator = genFn(this.period);
this.generator.next();
this.result = [];
this.price.forEach((tick) => {
var result = this.generator.next(tick);
if (result.value != undefined) {
this.result.push(this.format(result.value));
}
});
}
nextValue(price) {
var result = this.generator.next(price).value;
if (result != undefined)
return this.format(result);
}
;
}
WilderSmoothing.calculate = wildersmoothing;
function wildersmoothing(input) {
Indicator.reverseInputs(input);
var result = new WilderSmoothing(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
//STEP 6. Run the tests
class MDM extends Indicator {
constructor(input) {
super(input);
var lows = input.low;
var highs = input.high;
var format = this.format;
if (lows.length != highs.length) {
throw ('Inputs(low,high) not of equal size');
}
this.result = [];
this.generator = (function* () {
var minusDm;
var current = yield;
var last;
while (true) {
if (last) {
let upMove = (current.high - last.high);
let downMove = (last.low - current.low);
minusDm = format((downMove > upMove && downMove > 0) ? downMove : 0);
}
last = current;
current = yield minusDm;
}
})();
this.generator.next();
lows.forEach((tick, index) => {
var result = this.generator.next({
high: highs[index],
low: lows[index]
});
if (result.value !== undefined)
this.result.push(result.value);
});
}
;
static calculate(input) {
Indicator.reverseInputs(input);
var result = new MDM(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
;
nextValue(price) {
return this.generator.next(price).value;
}
;
}
/**
* Created by AAravindan on 5/8/16.
*/
class PDM extends Indicator {
constructor(input) {
super(input);
var lows = input.low;
var highs = input.high;
var format = this.format;
if (lows.length != highs.length) {
throw ('Inputs(low,high) not of equal size');
}
this.result = [];
this.generator = (function* () {
var plusDm;
var current = yield;
var last;
while (true) {
if (last) {
let upMove = (current.high - last.high);
let downMove = (last.low - current.low);
plusDm = format((upMove > downMove && upMove > 0) ? upMove : 0);
}
last = current;
current = yield plusDm;
}
})();
this.generator.next();
lows.forEach((tick, index) => {
var result = this.generator.next({
high: highs[index],
low: lows[index]
});
if (result.value !== undefined)
this.result.push(result.value);
});
}
;
static calculate(input) {
Indicator.reverseInputs(input);
var result = new PDM(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
;
nextValue(price) {
return this.generator.next(price).value;
}
;
}
class TrueRange extends Indicator {
constructor(input) {
super(input);
var lows = input.low;
var highs = input.high;
var closes = input.close;
var format = this.format;
if (lows.length != highs.length) {
throw ('Inputs(low,high) not of equal size');
}
this.result = [];
this.generator = (function* () {
var current = yield;
var previousClose, result;
while (true) {
if (previousClose === undefined) {
previousClose = current.close;
current = yield result;
}
result = Math.max(current.high - current.low, isNaN(Math.abs(current.high - previousClose)) ? 0 : Math.abs(current.high - previousClose), isNaN(Math.abs(current.low - previousClose)) ? 0 : Math.abs(current.low - previousClose));
previousClose = current.close;
if (result != undefined) {
result = format(result);
}
current = yield result;
}
})();
this.generator.next();
lows.forEach((tick, index) => {
var result = this.generator.next({
high: highs[index],
low: lows[index],
close: closes[index]
});
if (result.value != undefined) {
this.result.push(result.value);
}
});
}
;
nextValue(price) {
return this.generator.next(price).value;
}
;
}
TrueRange.calculate = truerange;
function truerange(input) {
Indicator.reverseInputs(input);
var result = new TrueRange(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
class ADXOutput extends IndicatorInput {
}
class ADX extends Indicator {
constructor(input) {
super(input);
var lows = input.low;
var highs = input.high;
var closes = input.close;
var period = input.period;
var format = this.format;
var plusDM = new PDM({
high: [],
low: []
});
var minusDM = new MDM({
high: [],
low: []
});
var emaPDM = new WilderSmoothing({ period: period, values: [], format: (v) => { return v; } });
var emaMDM = new WilderSmoothing({ period: period, values: [], format: (v) => { return v; } });
var emaTR = new WilderSmoothing({ period: period, values: [], format: (v) => { return v; } });
var emaDX = new WEMA({ period: period, values: [], format: (v) => { return v; } });
var tr = new TrueRange({
low: [],
high: [],
close: [],
});
if (!((lows.length === highs.length) && (highs.length === closes.length))) {
throw ('Inputs(low,high, close) not of equal size');
}
this.result = [];
ADXOutput;
this.generator = (function* () {
var tick = yield;
var index = 0;
var lastATR, lastAPDM, lastAMDM, lastPDI, lastMDI, lastDX, smoothedDX;
lastATR = 0;
lastAPDM = 0;
lastAMDM = 0;
while (true) {
let calcTr = tr.nextValue(tick);
let calcPDM = plusDM.nextValue(tick);
let calcMDM = minusDM.nextValue(tick);
if (calcTr === undefined) {
tick = yield;
continue;
}
let lastATR = emaTR.nextValue(calcTr);
let lastAPDM = emaPDM.nextValue(calcPDM);
let lastAMDM = emaMDM.nextValue(calcMDM);
if ((lastATR != undefined) && (lastAPDM != undefined) && (lastAMDM != undefined)) {
lastPDI = (lastAPDM) * 100 / lastATR;
lastMDI = (lastAMDM) * 100 / lastATR;
let diDiff = Math.abs(lastPDI - lastMDI);
let diSum = (lastPDI + lastMDI);
lastDX = (diDiff / diSum) * 100;
smoothedDX = emaDX.nextValue(lastDX);
// console.log(tick.high.toFixed(2), tick.low.toFixed(2), tick.close.toFixed(2) , calcTr.toFixed(2), calcPDM.toFixed(2), calcMDM.toFixed(2), lastATR.toFixed(2), lastAPDM.toFixed(2), lastAMDM.toFixed(2), lastPDI.toFixed(2), lastMDI.toFixed(2), diDiff.toFixed(2), diSum.toFixed(2), lastDX.toFixed(2));
}
tick = yield { adx: smoothedDX, pdi: lastPDI, mdi: lastMDI };
}
})();
this.generator.next();
lows.forEach((tick, index) => {
var result = this.generator.next({
high: highs[index],
low: lows[index],
close: closes[index]
});
if (result.value != undefined && result.value.adx != undefined) {
this.result.push({ adx: format(result.value.adx), pdi: format(result.value.pdi), mdi: format(result.value.mdi) });
}
});
}
;
;
nextValue(price) {
let result = this.generator.next(price).value;
if (result != undefined && result.adx != undefined) {
return { adx: this.format(result.adx), pdi: this.format(result.pdi), mdi: this.format(result.mdi) };
}
}
;
}
ADX.calculate = adx;
function adx(input) {
Indicator.reverseInputs(input);
var result = new ADX(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
class ATR extends Indicator {
constructor(input) {
super(input);
var lows = input.low;
var highs = input.high;
var closes = input.close;
var period = input.period;
var format = this.format;
if (!((lows.length === highs.length) && (highs.length === closes.length))) {
throw ('Inputs(low,high, close) not of equal size');
}
var trueRange = new TrueRange({
low: [],
high: [],
close: []
});
var wema$$1 = new WEMA({ period: period, values: [], format: (v) => { return v; } });
this.result = [];
this.generator = (function* () {
var tick = yield;
var avgTrueRange, trange;
while (true) {
trange = trueRange.nextValue({
low: tick.low,
high: tick.high,
close: tick.close
});
if (trange === undefined) {
avgTrueRange = undefined;
}
else {
avgTrueRange = wema$$1.nextValue(trange);
}
tick = yield avgTrueRange;
}
})();
this.generator.next();
lows.forEach((tick, index) => {
var result = this.generator.next({
high: highs[index],
low: lows[index],
close: closes[index]
});
if (result.value !== undefined) {
this.result.push(format(result.value));
}
});
}
;
nextValue(price) {
return this.generator.next(price).value;
}
;
}
ATR.calculate = atr;
function atr(input) {
Indicator.reverseInputs(input);
var result = new ATR(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
class ROC extends Indicator {
constructor(input) {
super(input);
var period = input.period;
var priceArray = input.values;
this.result = [];
this.generator = (function* () {
let index = 1;
var pastPeriods = new FixedSizeLinkedList(period);
var tick = yield;
var roc;
while (true) {
pastPeriods.push(tick);
if (index < period) {
index++;
}
else {
roc = ((tick - pastPeriods.lastShift) / (pastPeriods.lastShift)) * 100;
}
tick = yield roc;
}
})();
this.generator.next();
priceArray.forEach((tick) => {
var result = this.generator.next(tick);
if (result.value != undefined && (!isNaN(result.value))) {
this.result.push(this.format(result.value));
}
});
}
nextValue(price) {
var nextResult = this.generator.next(price);
if (nextResult.value != undefined && (!isNaN(nextResult.value))) {
return this.format(nextResult.value);
}
}
;
}
ROC.calculate = roc;
function roc(input) {
Indicator.reverseInputs(input);
var result = new ROC(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
class KST extends Indicator {
constructor(input) {
super(input);
let priceArray = input.values;
let rocPer1 = input.ROCPer1;
let rocPer2 = input.ROCPer2;
let rocPer3 = input.ROCPer3;
let rocPer4 = input.ROCPer4;
let smaPer1 = input.SMAROCPer1;
let smaPer2 = input.SMAROCPer2;
let smaPer3 = input.SMAROCPer3;
let smaPer4 = input.SMAROCPer4;
let signalPeriod = input.signalPeriod;
let roc1 = new ROC({ period: rocPer1, values: [] });
let roc2 = new ROC({ period: rocPer2, values: [] });
let roc3 = new ROC({ period: rocPer3, values: [] });
let roc4 = new ROC({ period: rocPer4, values: [] });
let sma1 = new SMA({ period: smaPer1, values: [], format: (v) => { return v; } });
let sma2 = new SMA({ period: smaPer2, values: [], format: (v) => { return v; } });
let sma3 = new SMA({ period: smaPer3, values: [], format: (v) => { return v; } });
let sma4 = new SMA({ period: smaPer4, values: [], format: (v) => { return v; } });
let signalSMA = new SMA({ period: signalPeriod, values: [], format: (v) => { return v; } });
var format = this.format;
this.result = [];
let firstResult = Math.max(rocPer1 + smaPer1, rocPer2 + smaPer2, rocPer3 + smaPer3, rocPer4 + smaPer4);
this.generator = (function* () {
let index = 1;
let tick = yield;
let kst;
let RCMA1, RCMA2, RCMA3, RCMA4, signal, result;
while (true) {
let roc1Result = roc1.nextValue(tick);
let roc2Result = roc2.nextValue(tick);
let roc3Result = roc3.nextValue(tick);
let roc4Result = roc4.nextValue(tick);
RCMA1 = (roc1Result !== undefined) ? sma1.nextValue(roc1Result) : undefined;
RCMA2 = (roc2Result !== undefined) ? sma2.nextValue(roc2Result) : undefined;
RCMA3 = (roc3Result !== undefined) ? sma3.nextValue(roc3Result) : undefined;
RCMA4 = (roc4Result !== undefined) ? sma4.nextValue(roc4Result) : undefined;
if (index < firstResult) {
index++;
}
else {
kst = (RCMA1 * 1) + (RCMA2 * 2) + (RCMA3 * 3) + (RCMA4 * 4);
}
signal = (kst !== undefined) ? signalSMA.nextValue(kst) : undefined;
result = kst !== undefined ? {
kst: format(kst),
signal: signal ? format(signal) : undefined
} : undefined;
tick = yield result;
}
})();
this.generator.next();
priceArray.forEach((tick) => {
let result = this.generator.next(tick);
if (result.value != undefined) {
this.result.push(result.value);
}
});
}
;
nextValue(price) {
let nextResult = this.generator.next(price);
if (nextResult.value != undefined)
return nextResult.value;
}
;
}
KST.calculate = kst;
function kst(input) {
Indicator.reverseInputs(input);
var result = new KST(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
/*
There seems to be a few interpretations of the rules for this regarding which prices.
I mean the english from which periods are included. The wording does seem to
introduce some discrepancy so maybe that is why. I want to put the author's
own description here to reassess this later.
----------------------------------------------------------------------------------------
For the first day of entry the SAR is the previous Significant Point
If long the SP is the lowest price reached while in the previous short trade
If short the SP is the highest price reached while in the previous long trade
If long:
Find the difference between the highest price made while in the trade and the SAR for today.
Multiple the difference by the AF and ADD the result to today's SAR to obtain the SAR for tomorrow.
Use 0.02 for the first AF and increase it by 0.02 on every day that a new high for the trade is made.
If a new high is not made continue to use the AF as last increased. Do not increase the AF above .20
Never move the SAR for tomorrow ABOVE the previous day's LOW or today's LOW.
If the SAR is calculated to be ABOVE the previous day's LOW or today's LOW then use the lower low between today and the previous day as the new SAR.
Make the next day's calculations based on this SAR.
If short:
Find the difference between the lowest price made while in the trade and the SAR for today.
Multiple the difference by the AF and SUBTRACT the result to today's SAR to obtain the SAR for tomorrow.
Use 0.02 for the first AF and increase it by 0.02 on every day that a new high for the trade is made.
If a new high is not made continue to use the AF as last increased. Do not increase the AF above .20
Never move the SAR for tomorrow BELOW the previous day's HIGH or today's HIGH.
If the SAR is calculated to be BELOW the previous day's HIGH or today's HIGH then use the higher high between today and the previous day as the new SAR. Make the next day's calculations based on this SAR.
----------------------------------------------------------------------------------------
*/
class PSAR extends Indicator {
constructor(input) {
super(input);
let highs = input.high || [];
let lows = input.low || [];
var genFn = function* (step, max) {
let curr, extreme, sar, furthest;
let up = true;
let accel = step;
let prev = yield;
while (true) {
if (curr) {
sar = sar + accel * (extreme - sar);
if (up) {
sar = Math.min(sar, furthest.low, prev.low);
if (curr.high > extreme) {
extreme = curr.high;
accel = Math.min(accel + step, max);
}
}
else {
sar = Math.max(sar, furthest.high, prev.high);
if (curr.low < extreme) {
extreme = curr.low;
accel = Math.min(accel + step, max);
}
}
if ((up && curr.low < sar) || (!up && curr.high > sar)) {
accel = step;
sar = extreme;
up = !up;
extreme = !up ? curr.low : curr.high;
}
}
else {
// Randomly setup start values? What is the trend on first tick??
sar = prev.low;
extreme = prev.high;
}
furthest = prev;
if (curr)
prev = curr;
curr = yield sar;
}
};
this.result = [];
this.generator = genFn(input.step, input.max);
this.generator.next();
lows.forEach((tick, index) => {
var result = this.generator.next({
high: highs[index],
low: lows[index],
});
if (result.value !== undefined) {
this.result.push(result.value);
}
});
}
;
nextValue(input) {
let nextResult = this.generator.next(input);
if (nextResult.value !== undefined)
return nextResult.value;
}
;
}
PSAR.calculate = psar;
function psar(input) {
Indicator.reverseInputs(input);
var result = new PSAR(input).result;
if (input.reversedInput) {
result.reverse();
}
Indicator.reverseInputs(input);
return result;
}
class Stochastic extends Indicator {
constructor(input) {
super(input);
let lows = input.low;
let highs = input.high;
let closes = input.close;
let period = input.period;
let signalPeriod = input.signalPeriod;
let format = this.format;
if (!((lows.length === highs.length) && (highs.length === closes.length))) {
throw ('Inputs(low,high, close) not of equal size');
}
this.result = [];
//%K = (Current Close - Lowest Low)/(Highest High - Lowest Low) * 100
//%D = 3-day SMA of %K
//
//Lowest Low = lowest low for the look-back period
//Highest High = highest high for the look-back period
//%K is multiplied by 100 to move the decimal point two places
this.generator = (function* () {
let index = 1;
let pastHighPeriods = new FixedSizeLinkedList(period, true, false);
let pastLowPeriods = new FixedSizeLinkedList(period, false, true);
let dSma = new SMA({
period: signalPeriod,
values: [],
format: (v) => { return v; }
});
let k, d;
var tick = yield;
while (true) {
pastHighPeriods.push(tick.high);
pastLowPeriods.push(tick.low);
if (index < period) {
index++;
tick = yield;
continue;
}
let periodLow = pastLowPeriods.periodLow;
k = (tick.close - periodLow) / (pastHighPeriods.periodHigh - periodLow) * 100;
k = isNaN(k) ? 0 : k; //This happens when the close, high and low a