diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1535e13 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "deno.enable": true, + "deno.lint": true, + "deno.unstable": true +} \ No newline at end of file diff --git a/README.md b/README.md index 71a41a2..bb32cb0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# deno-talib -deno-talib port technical analysis of [https://github.com/anandanand84/technicalindicators](https://github.com/anandanand84/technicalindicators) +# deno-talib (WIP) +deno-talib port technical analysis of [https://github.com/anandanand84/technicalindicators](https://github.com/anandanand84/technicalindicators) diff --git a/index.ts b/index.ts index 89330ae..3db7fa6 100644 --- a/index.ts +++ b/index.ts @@ -1,80 +1,80 @@ -// export * from './lib/index.js'; -export function getAvailableIndicators () { - let AvailableIndicators = [] - AvailableIndicators.push('sma'); - AvailableIndicators.push('ema'); - AvailableIndicators.push('wma'); - AvailableIndicators.push('wema'); - AvailableIndicators.push('macd'); +export * from './lib/index.ts'; +export const getAvailableIndicators = () => { + const AvailableIndicators: string[] = [] + // AvailableIndicators.push('sma'); + // AvailableIndicators.push('ema'); + // AvailableIndicators.push('wma'); + // AvailableIndicators.push('wema'); + // AvailableIndicators.push('macd'); AvailableIndicators.push('rsi'); - AvailableIndicators.push('bollingerbands'); - AvailableIndicators.push('adx'); - AvailableIndicators.push('atr'); - AvailableIndicators.push('truerange'); - AvailableIndicators.push('roc'); - AvailableIndicators.push('kst'); - AvailableIndicators.push('psar'); - AvailableIndicators.push('stochastic'); - AvailableIndicators.push('williamsr'); - AvailableIndicators.push('adl'); - AvailableIndicators.push('obv'); - AvailableIndicators.push('trix'); + // AvailableIndicators.push('bollingerbands'); + // AvailableIndicators.push('adx'); + // AvailableIndicators.push('atr'); + // AvailableIndicators.push('truerange'); + // AvailableIndicators.push('roc'); + // AvailableIndicators.push('kst'); + // AvailableIndicators.push('psar'); + // AvailableIndicators.push('stochastic'); + // AvailableIndicators.push('williamsr'); + // AvailableIndicators.push('adl'); + // AvailableIndicators.push('obv'); + // AvailableIndicators.push('trix'); - AvailableIndicators.push('cci'); - AvailableIndicators.push('awesomeoscillator'); - AvailableIndicators.push('forceindex'); - AvailableIndicators.push('vwap'); - AvailableIndicators.push('volumeprofile'); - AvailableIndicators.push('renko'); - AvailableIndicators.push('heikinashi'); + // AvailableIndicators.push('cci'); + // AvailableIndicators.push('awesomeoscillator'); + // AvailableIndicators.push('forceindex'); + // AvailableIndicators.push('vwap'); + // AvailableIndicators.push('volumeprofile'); + // AvailableIndicators.push('renko'); + // AvailableIndicators.push('heikinashi'); - AvailableIndicators.push('stochasticrsi'); - AvailableIndicators.push('mfi'); + // AvailableIndicators.push('stochasticrsi'); + // AvailableIndicators.push('mfi'); - AvailableIndicators.push('averagegain'); - AvailableIndicators.push('averageloss'); - AvailableIndicators.push('highest'); - AvailableIndicators.push('lowest'); - AvailableIndicators.push('sum'); - AvailableIndicators.push('FixedSizeLinkedList'); - AvailableIndicators.push('sd'); - AvailableIndicators.push('bullish'); - AvailableIndicators.push('bearish'); - AvailableIndicators.push('abandonedbaby'); - AvailableIndicators.push('doji'); - AvailableIndicators.push('bearishengulfingpattern'); - AvailableIndicators.push('bullishengulfingpattern'); - AvailableIndicators.push('darkcloudcover'); - AvailableIndicators.push('downsidetasukigap'); - AvailableIndicators.push('dragonflydoji'); - AvailableIndicators.push('gravestonedoji'); - AvailableIndicators.push('bullishharami'); - AvailableIndicators.push('bearishharami'); - AvailableIndicators.push('bullishharamicross'); - AvailableIndicators.push('bearishharamicross'); - AvailableIndicators.push('eveningdojistar'); - AvailableIndicators.push('eveningstar'); - AvailableIndicators.push('morningdojistar'); - AvailableIndicators.push('morningstar'); - AvailableIndicators.push('bullishmarubozu'); - AvailableIndicators.push('bearishmarubozu'); - AvailableIndicators.push('piercingline'); - AvailableIndicators.push('bullishspinningtop'); - AvailableIndicators.push('bearishspinningtop'); - AvailableIndicators.push('threeblackcrows'); - AvailableIndicators.push('threewhitesoldiers'); - AvailableIndicators.push('bullishhammerstick'); - AvailableIndicators.push('bearishhammerstick'); - AvailableIndicators.push('bullishinvertedhammerstick'); - AvailableIndicators.push('bearishinvertedhammerstick'); - AvailableIndicators.push('hammerpattern'); - AvailableIndicators.push('hammerpatternunconfirmed'); - AvailableIndicators.push('hangingman'); - AvailableIndicators.push('hangingmanunconfirmed'); - AvailableIndicators.push('shootingstar'); - AvailableIndicators.push('shootingstarunconfirmed'); - AvailableIndicators.push('tweezertop'); - AvailableIndicators.push('tweezerbottom'); + // AvailableIndicators.push('averagegain'); + // AvailableIndicators.push('averageloss'); + // AvailableIndicators.push('highest'); + // AvailableIndicators.push('lowest'); + // AvailableIndicators.push('sum'); + // AvailableIndicators.push('FixedSizeLinkedList'); + // AvailableIndicators.push('sd'); + // AvailableIndicators.push('bullish'); + // AvailableIndicators.push('bearish'); + // AvailableIndicators.push('abandonedbaby'); + // AvailableIndicators.push('doji'); + // AvailableIndicators.push('bearishengulfingpattern'); + // AvailableIndicators.push('bullishengulfingpattern'); + // AvailableIndicators.push('darkcloudcover'); + // AvailableIndicators.push('downsidetasukigap'); + // AvailableIndicators.push('dragonflydoji'); + // AvailableIndicators.push('gravestonedoji'); + // AvailableIndicators.push('bullishharami'); + // AvailableIndicators.push('bearishharami'); + // AvailableIndicators.push('bullishharamicross'); + // AvailableIndicators.push('bearishharamicross'); + // AvailableIndicators.push('eveningdojistar'); + // AvailableIndicators.push('eveningstar'); + // AvailableIndicators.push('morningdojistar'); + // AvailableIndicators.push('morningstar'); + // AvailableIndicators.push('bullishmarubozu'); + // AvailableIndicators.push('bearishmarubozu'); + // AvailableIndicators.push('piercingline'); + // AvailableIndicators.push('bullishspinningtop'); + // AvailableIndicators.push('bearishspinningtop'); + // AvailableIndicators.push('threeblackcrows'); + // AvailableIndicators.push('threewhitesoldiers'); + // AvailableIndicators.push('bullishhammerstick'); + // AvailableIndicators.push('bearishhammerstick'); + // AvailableIndicators.push('bullishinvertedhammerstick'); + // AvailableIndicators.push('bearishinvertedhammerstick'); + // AvailableIndicators.push('hammerpattern'); + // AvailableIndicators.push('hammerpatternunconfirmed'); + // AvailableIndicators.push('hangingman'); + // AvailableIndicators.push('hangingmanunconfirmed'); + // AvailableIndicators.push('shootingstar'); + // AvailableIndicators.push('shootingstarunconfirmed'); + // AvailableIndicators.push('tweezertop'); + // AvailableIndicators.push('tweezerbottom'); // AvailableIndicators.push('predictPattern'); // AvailableIndicators.push('hasDoubleBottom'); @@ -83,16 +83,17 @@ export function getAvailableIndicators () { // AvailableIndicators.push('hasInverseHeadAndShoulder'); // AvailableIndicators.push('isTrendingUp'); // AvailableIndicators.push('isTrendingDown'); - AvailableIndicators.push('ichimokucloud'); + + // AvailableIndicators.push('ichimokucloud'); - AvailableIndicators.push('keltnerchannels'); - AvailableIndicators.push('chandelierexit'); - AvailableIndicators.push('crossup'); - AvailableIndicators.push('crossdown'); - AvailableIndicators.push('crossover'); + // AvailableIndicators.push('keltnerchannels'); + // AvailableIndicators.push('chandelierexit'); + // AvailableIndicators.push('crossup'); + // AvailableIndicators.push('crossdown'); + // AvailableIndicators.push('crossover'); return AvailableIndicators; }; -let AvailableIndicators = getAvailableIndicators(); +const AvailableIndicators = getAvailableIndicators(); console.log('AvailableIndicators', AvailableIndicators); export { AvailableIndicators } \ No newline at end of file diff --git a/lib/StockData.js b/lib/StockData.js new file mode 100644 index 0000000..645b7e5 --- /dev/null +++ b/lib/StockData.js @@ -0,0 +1,20 @@ +export default class StockData { + constructor(open, high, low, close, reversedInput) { + this.open = open; + this.high = high; + this.low = low; + this.close = close; + this.reversedInput = reversedInput; + } +} +export class CandleData {} +export class CandleList { + constructor() { + this.open = []; + this.high = []; + this.low = []; + this.close = []; + this.volume = []; + this.timestamp = []; + } +} diff --git a/lib/Utils/AverageGain.ts b/lib/Utils/AverageGain.ts new file mode 100644 index 0000000..8cabf41 --- /dev/null +++ b/lib/Utils/AverageGain.ts @@ -0,0 +1,68 @@ +// deno-lint-ignore-file +import { Indicator, IndicatorInput } from '../indicator/indicator.ts'; +export class AvgGainInput extends IndicatorInput { +} +export class AverageGain extends Indicator { +generator: Generator; +static calculate: (input: { reversedInput: any; values?: any[]|undefined; open?: any[]|undefined; high?: any[]|undefined; low?: any[]|undefined; close?: any[]|undefined; volume?: any[]|undefined; timestamp?: any[]|undefined; }) => any; + constructor(input: { period?: any; values?: any; format?: (v: any) => any; } | any) { + super(input); + let values = input.values; + let period = input.period; + let format = this.format; + this.generator = (function* (period) { + // @ts-ignore + var currentValue = yield; + var counter = 1; + var gainSum = 0; + var avgGain; + var gain; + var lastValue = currentValue; + // @ts-ignore + 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; + // @ts-ignore + currentValue = yield avgGain; + } + })(period); + this.generator.next(); + this.result = []; + values.forEach((tick: any) => { + var result = this.generator.next(tick); + if (result.value !== undefined) { + this.result.push(result.value); + } + }); + } + nextValue(price: any) { + return this.generator.next(price).value; + } + ; +} +AverageGain.calculate = averagegain; +export function averagegain(input: { reversedInput: any; values?: any[]; open?: any[]; high?: any[]; low?: any[]; close?: any[]; volume?: any[]; timestamp?: any[]; } | any) { + Indicator.reverseInputs(input); + var result = new AverageGain(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/Utils/AverageLoss.ts b/lib/Utils/AverageLoss.ts new file mode 100644 index 0000000..e9ce0fd --- /dev/null +++ b/lib/Utils/AverageLoss.ts @@ -0,0 +1,68 @@ +// deno-lint-ignore-file +import { Indicator, IndicatorInput } from '../indicator/indicator.ts'; +export class AvgLossInput extends IndicatorInput { +} +export class AverageLoss extends Indicator { +generator: Generator; +static calculate: (input: { reversedInput: any; values?: any[]|undefined; open?: any[]|undefined; high?: any[]|undefined; low?: any[]|undefined; close?: any[]|undefined; volume?: any[]|undefined; timestamp?: any[]|undefined; }) => any; + constructor(input: { period?: any; values?: any; format?: (v: any) => any; } | any) { + super(input); + let values = input.values; + let period = input.period; + let format = this.format; + this.generator = (function* (period) { + // @ts-ignore + var currentValue = yield; + var counter = 1; + var lossSum = 0; + var avgLoss; + var loss; + var lastValue = currentValue; + // @ts-ignore + 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; + // @ts-ignore + currentValue = yield avgLoss; + } + })(period); + this.generator.next(); + this.result = []; + values.forEach((tick: any) => { + var result = this.generator.next(tick); + if (result.value !== undefined) { + this.result.push(result.value); + } + }); + } + nextValue(price: any) { + return this.generator.next(price).value; + } + ; +} +AverageLoss.calculate = averageloss; +export function averageloss(input: { reversedInput: any; values?: any[]; open?: any[]; high?: any[]; low?: any[]; close?: any[]; volume?: any[]; timestamp?: any[]; } | any) { + Indicator.reverseInputs(input); + var result = new AverageLoss(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/Utils/CrossDown.ts b/lib/Utils/CrossDown.ts new file mode 100644 index 0000000..50a17b1 --- /dev/null +++ b/lib/Utils/CrossDown.ts @@ -0,0 +1,79 @@ +// @ts-nocheck +import { Indicator, IndicatorInput } from '../indicator/indicator.ts'; +export class CrossInput extends IndicatorInput { + constructor(lineA, lineB) { + super(); + this.lineA = lineA; + this.lineB = lineB; + } +} +export class CrossDown extends Indicator { + constructor(input) { + super(input); + this.lineA = input.lineA; + this.lineB = input.lineB; + var currentLineA = []; + var currentLineB = []; + const genFn = (function* () { + var current = yield; + var result = false; + while (true) { + currentLineA.unshift(current.valueA); + currentLineB.unshift(current.valueB); + result = current.valueA < current.valueB; + var pointer = 1; + while (result === true && currentLineA[pointer] <= currentLineB[pointer]) { + if (currentLineA[pointer] < currentLineB[pointer]) { + result = false; + } + else if (currentLineA[pointer] > currentLineB[pointer]) { + result = true; + } + else if (currentLineA[pointer] === currentLineB[pointer]) { + pointer += 1; + } + } + if (result === true) { + currentLineA = [current.valueA]; + currentLineB = [current.valueB]; + } + current = yield result; + } + }); + this.generator = genFn(); + this.generator.next(); + this.result = []; + this.lineA.forEach((value, index) => { + var result = this.generator.next({ + valueA: this.lineA[index], + valueB: this.lineB[index] + }); + if (result.value !== undefined) { + this.result.push(result.value); + } + }); + } + static reverseInputs(input) { + if (input.reversedInput) { + input.lineA ? input.lineA.reverse() : undefined; + input.lineB ? input.lineB.reverse() : undefined; + } + } + nextValue(valueA, valueB) { + return this.generator.next({ + valueA: valueA, + valueB: valueB + }).value; + } + ; +} +CrossDown.calculate = crossDown; +export function crossDown(input) { + Indicator.reverseInputs(input); + var result = new CrossDown(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} diff --git a/lib/Utils/CrossOver.ts b/lib/Utils/CrossOver.ts new file mode 100644 index 0000000..be43dca --- /dev/null +++ b/lib/Utils/CrossOver.ts @@ -0,0 +1,63 @@ +// @ts-nocheck +import { Indicator, IndicatorInput } from '../indicator/indicator.ts'; +import { CrossUp } from './CrossUp.ts'; +import { CrossDown } from './CrossDown.ts'; +export class CrossInput extends IndicatorInput { + constructor(lineA, lineB) { + super(); + this.lineA = lineA; + this.lineB = lineB; + } +} +export class CrossOver extends Indicator { + constructor(input) { + super(input); + var crossUp = new CrossUp({ lineA: input.lineA, lineB: input.lineB }); + var crossDown = new CrossDown({ lineA: input.lineA, lineB: input.lineB }); + const genFn = (function* () { + var current = yield; + var result = false; + var first = true; + while (true) { + var nextUp = crossUp.nextValue(current.valueA, current.valueB); + var nextDown = crossDown.nextValue(current.valueA, current.valueB); + result = nextUp || nextDown; + if (first) + result = false; + first = false; + current = yield result; + } + }); + this.generator = genFn(); + this.generator.next(); + var resultA = crossUp.getResult(); + var resultB = crossDown.getResult(); + this.result = resultA.map((a, index) => { + if (index === 0) + return false; + return !!(a || resultB[index]); + }); + } + static reverseInputs(input) { + if (input.reversedInput) { + input.lineA ? input.lineA.reverse() : undefined; + input.lineB ? input.lineB.reverse() : undefined; + } + } + nextValue(valueA, valueB) { + return this.generator.next({ + valueA: valueA, + valueB: valueB + }).value; + } +} +CrossOver.calculate = crossOver; +export function crossOver(input) { + Indicator.reverseInputs(input); + var result = new CrossOver(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} diff --git a/lib/Utils/CrossUp.ts b/lib/Utils/CrossUp.ts new file mode 100644 index 0000000..d449966 --- /dev/null +++ b/lib/Utils/CrossUp.ts @@ -0,0 +1,79 @@ +// @ts-nocheck +import { Indicator, IndicatorInput } from '../indicator/indicator.ts'; +export class CrossInput extends IndicatorInput { + constructor(lineA, lineB) { + super(); + this.lineA = lineA; + this.lineB = lineB; + } +} +export class CrossUp extends Indicator { + constructor(input) { + super(input); + this.lineA = input.lineA; + this.lineB = input.lineB; + var currentLineA = []; + var currentLineB = []; + const genFn = (function* () { + var current = yield; + var result = false; + while (true) { + currentLineA.unshift(current.valueA); + currentLineB.unshift(current.valueB); + result = current.valueA > current.valueB; + var pointer = 1; + while (result === true && currentLineA[pointer] >= currentLineB[pointer]) { + if (currentLineA[pointer] > currentLineB[pointer]) { + result = false; + } + else if (currentLineA[pointer] < currentLineB[pointer]) { + result = true; + } + else if (currentLineA[pointer] === currentLineB[pointer]) { + pointer += 1; + } + } + if (result === true) { + currentLineA = [current.valueA]; + currentLineB = [current.valueB]; + } + current = yield result; + } + }); + this.generator = genFn(); + this.generator.next(); + this.result = []; + this.lineA.forEach((value, index) => { + var result = this.generator.next({ + valueA: this.lineA[index], + valueB: this.lineB[index] + }); + if (result.value !== undefined) { + this.result.push(result.value); + } + }); + } + static reverseInputs(input) { + if (input.reversedInput) { + input.lineA ? input.lineA.reverse() : undefined; + input.lineB ? input.lineB.reverse() : undefined; + } + } + nextValue(valueA, valueB) { + return this.generator.next({ + valueA: valueA, + valueB: valueB + }).value; + } + ; +} +CrossUp.calculate = crossUp; +export function crossUp(input) { + Indicator.reverseInputs(input); + var result = new CrossUp(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} diff --git a/lib/Utils/FixedSizeLinkedList.ts b/lib/Utils/FixedSizeLinkedList.ts new file mode 100644 index 0000000..7c923b4 --- /dev/null +++ b/lib/Utils/FixedSizeLinkedList.ts @@ -0,0 +1,90 @@ +/** + * Created by AAravindan on 5/7/16. + */ +import { LinkedList } from './LinkedList.ts'; +export default class FixedSizeLinkedList extends LinkedList { +size: any; +maintainHigh: undefined; +maintainLow: undefined; +maintainSum: undefined; +totalPushed: number; +periodHigh: number; +periodLow: number; +periodSum: number; +_push: (data: any) => void; +lastShift: any; + constructor(size: any, maintainHigh?: undefined, maintainLow?: undefined, maintainSum?: undefined) { + 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: number) { + 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; + } + } + } +} diff --git a/lib/Utils/Highest.js b/lib/Utils/Highest.js new file mode 100644 index 0000000..7bf6688 --- /dev/null +++ b/lib/Utils/Highest.js @@ -0,0 +1,51 @@ +// @ts-nocheck +import { Indicator, IndicatorInput } from '../indicator/indicator.ts'; +import FixedSizedLinkedList from './FixedSizeLinkedList.ts'; +export class HighestInput extends IndicatorInput { +} +export class Highest extends Indicator { + constructor(input) { + super(input); + var values = input.values; + var period = input.period; + this.result = []; + var periodList = new FixedSizedLinkedList(period, true, false, false); + this.generator = (function* () { + var result; + var tick; + var high; + tick = yield; + while (true) { + periodList.push(tick); + if (periodList.totalPushed >= period) { + high = periodList.periodHigh; + } + tick = yield high; + } + })(); + this.generator.next(); + values.forEach((value, index) => { + var result = this.generator.next(value); + if (result.value != undefined) { + this.result.push(result.value); + } + }); + } + ; + nextValue(price) { + var result = this.generator.next(price); + if (result.value != undefined) { + return result.value; + } + } +} +Highest.calculate = highest; +export function highest(input) { + Indicator.reverseInputs(input); + var result = new Highest(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} \ No newline at end of file diff --git a/lib/Utils/LinkedList.ts b/lib/Utils/LinkedList.ts new file mode 100644 index 0000000..d1e2bed --- /dev/null +++ b/lib/Utils/LinkedList.ts @@ -0,0 +1,153 @@ +// deno-lint-ignore-file +class Item { +next: any; +prev: any; +data: any; + constructor(data: any, prev: any, next?: any) { + this.next = next; + if (next) + next.prev = this; + this.prev = prev; + if (prev) + prev.next = this; + this.data = data; + } +} +export class LinkedList { +_length: number; +_head: any; +_tail: any; +_current: any; +_next: any; + 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: number) { + 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; + } + 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; + } + return head.data; + } + unshift(data: any) { + 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; + // 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; + 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; + } + } +} diff --git a/lib/Utils/Lowest.js b/lib/Utils/Lowest.js new file mode 100644 index 0000000..603927f --- /dev/null +++ b/lib/Utils/Lowest.js @@ -0,0 +1,53 @@ +// @ts-nocheck +import { Indicator, IndicatorInput } from '../indicator/indicator.ts'; +import FixedSizedLinkedList from './FixedSizeLinkedList.ts'; +export class LowestInput extends IndicatorInput { +} +export class Lowest extends Indicator { + constructor(input) { + super(input); + var values = input.values; + var period = input.period; + this.result = []; + var periodList = new FixedSizedLinkedList(period, false, true, false); + this.generator = (function* () { + var result; + var tick; + var high; + tick = yield; + while (true) { + periodList.push(tick); + if (periodList.totalPushed >= period) { + high = periodList.periodLow; + } + tick = yield high; + } + })(); + this.generator.next(); + values.forEach((value, index) => { + var result = this.generator.next(value); + if (result.value != undefined) { + this.result.push(result.value); + } + }); + } + ; + nextValue(price) { + var result = this.generator.next(price); + if (result.value != undefined) { + return result.value; + } + } + ; +} +Lowest.calculate = lowest; +export function lowest(input) { + Indicator.reverseInputs(input); + var result = new Lowest(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/Utils/NumberFormatter.ts b/lib/Utils/NumberFormatter.ts new file mode 100644 index 0000000..ce6f180 --- /dev/null +++ b/lib/Utils/NumberFormatter.ts @@ -0,0 +1,8 @@ +import { getConfig } from '../config.ts'; +export function format(v: number) { + let precision = getConfig('precision'); + if (precision) { + return parseFloat(v.toPrecision(precision)); + } + return v; +} diff --git a/lib/Utils/SD.js b/lib/Utils/SD.js new file mode 100644 index 0000000..7f5b3b3 --- /dev/null +++ b/lib/Utils/SD.js @@ -0,0 +1,64 @@ +// @ts-nocheck +import { IndicatorInput, Indicator } from '../indicator/indicator.ts'; +import { SMA } from '../moving_averages/SMA.ts'; +import LinkedList from '../Utils/FixedSizeLinkedList.ts'; +/** + * Created by AAravindan on 5/7/16. + */ +"use strict"; +export class SDInput extends IndicatorInput { +} +; +export class SD extends Indicator { + constructor(input) { + super(input); + var period = input.period; + var priceArray = input.values; + var sma = new SMA({ period: period, values: [], format: (v) => { return v; } }); + this.result = []; + this.generator = (function* () { + var tick; + var mean; + var currentSet = new LinkedList(period); + ; + tick = yield; + var sd; + while (true) { + currentSet.push(tick); + mean = sma.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; +export function sd(input) { + Indicator.reverseInputs(input); + var result = new SD(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/Utils/Sum.js b/lib/Utils/Sum.js new file mode 100644 index 0000000..c4872f3 --- /dev/null +++ b/lib/Utils/Sum.js @@ -0,0 +1,53 @@ +// @ts-nocheck +import { Indicator, IndicatorInput } from '../indicator/indicator.ts'; +import FixedSizedLinkedList from './FixedSizeLinkedList.ts'; +export class SumInput extends IndicatorInput { +} +export class Sum extends Indicator { + constructor(input) { + super(input); + var values = input.values; + var period = input.period; + this.result = []; + var periodList = new FixedSizedLinkedList(period, false, false, true); + this.generator = (function* () { + var result; + var tick; + var high; + tick = yield; + while (true) { + periodList.push(tick); + if (periodList.totalPushed >= period) { + high = periodList.periodSum; + } + tick = yield high; + } + })(); + this.generator.next(); + values.forEach((value, index) => { + var result = this.generator.next(value); + if (result.value != undefined) { + this.result.push(result.value); + } + }); + } + ; + nextValue(price) { + var result = this.generator.next(price); + if (result.value != undefined) { + return result.value; + } + } + ; +} +Sum.calculate = sum; +export function sum(input) { + Indicator.reverseInputs(input); + var result = new Sum(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/candlestick/AbandonedBaby.js b/lib/candlestick/AbandonedBaby.js new file mode 100644 index 0000000..35fcd98 --- /dev/null +++ b/lib/candlestick/AbandonedBaby.js @@ -0,0 +1,38 @@ +import CandlestickFinder from './CandlestickFinder'; +import Doji from './Doji'; +export default class AbandonedBaby extends CandlestickFinder { + constructor() { + super(); + this.name = 'AbandonedBaby'; + this.requiredCount = 3; + } + logic(data) { + let firstdaysOpen = data.open[0]; + let firstdaysClose = data.close[0]; + let firstdaysHigh = data.high[0]; + let firstdaysLow = data.low[0]; + let seconddaysOpen = data.open[1]; + let seconddaysClose = data.close[1]; + let seconddaysHigh = data.high[1]; + let seconddaysLow = data.low[1]; + let thirddaysOpen = data.open[2]; + let thirddaysClose = data.close[2]; + let thirddaysHigh = data.high[2]; + let thirddaysLow = data.low[2]; + let isFirstBearish = firstdaysClose < firstdaysOpen; + let dojiExists = new Doji().hasPattern({ + "open": [seconddaysOpen], + "close": [seconddaysClose], + "high": [seconddaysHigh], + "low": [seconddaysLow] + }); + let gapExists = ((seconddaysHigh < firstdaysLow) && + (thirddaysLow > seconddaysHigh) && + (thirddaysClose > thirddaysOpen)); + let isThirdBullish = (thirddaysHigh < firstdaysOpen); + return (isFirstBearish && dojiExists && gapExists && isThirdBullish); + } +} +export function abandonedbaby(data) { + return new AbandonedBaby().hasPattern(data); +} diff --git a/lib/candlestick/Bearish.js b/lib/candlestick/Bearish.js new file mode 100644 index 0000000..22556ed --- /dev/null +++ b/lib/candlestick/Bearish.js @@ -0,0 +1,45 @@ +import BearishEngulfingPattern from './BearishEngulfingPattern'; +import BearishHarami from './BearishHarami'; +import BearishHaramiCross from './BearishHaramiCross'; +import EveningDojiStar from './EveningDojiStar'; +import EveningStar from './EveningStar'; +import BearishMarubozu from './BearishMarubozu'; +import ThreeBlackCrows from './ThreeBlackCrows'; +import BearishHammerStick from './BearishHammerStick'; +import BearishInvertedHammerStick from './BearishInvertedHammerStick'; +import HangingMan from './HangingMan'; +import HangingManUnconfirmed from './HangingManUnconfirmed'; +import ShootingStar from './ShootingStar'; +import ShootingStarUnconfirmed from './ShootingStarUnconfirmed'; +import TweezerTop from './TweezerTop'; +import CandlestickFinder from './CandlestickFinder'; +let bearishPatterns = [ + new BearishEngulfingPattern(), + new BearishHarami(), + new BearishHaramiCross(), + new EveningDojiStar(), + new EveningStar(), + new BearishMarubozu(), + new ThreeBlackCrows(), + new BearishHammerStick(), + new BearishInvertedHammerStick(), + new HangingMan(), + new HangingManUnconfirmed(), + new ShootingStar(), + new ShootingStarUnconfirmed(), + new TweezerTop() +]; +export default class BearishPatterns extends CandlestickFinder { + constructor() { + super(); + this.name = 'Bearish Candlesticks'; + } + hasPattern(data) { + return bearishPatterns.reduce(function (state, pattern) { + return state || pattern.hasPattern(data); + }, false); + } +} +export function bearish(data) { + return new BearishPatterns().hasPattern(data); +} diff --git a/lib/candlestick/BearishEngulfingPattern.js b/lib/candlestick/BearishEngulfingPattern.js new file mode 100644 index 0000000..a7a62af --- /dev/null +++ b/lib/candlestick/BearishEngulfingPattern.js @@ -0,0 +1,26 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class BearishEngulfingPattern extends CandlestickFinder { + constructor() { + super(); + this.name = 'BearishEngulfingPattern'; + this.requiredCount = 2; + } + logic(data) { + let firstdaysOpen = data.open[0]; + let firstdaysClose = data.close[0]; + let firstdaysHigh = data.high[0]; + let firstdaysLow = data.low[0]; + let seconddaysOpen = data.open[1]; + let seconddaysClose = data.close[1]; + let seconddaysHigh = data.high[1]; + let seconddaysLow = data.low[1]; + let isBearishEngulfing = ((firstdaysClose > firstdaysOpen) && + (firstdaysOpen < seconddaysOpen) && + (firstdaysClose < seconddaysOpen) && + (firstdaysOpen > seconddaysClose)); + return (isBearishEngulfing); + } +} +export function bearishengulfingpattern(data) { + return new BearishEngulfingPattern().hasPattern(data); +} diff --git a/lib/candlestick/BearishHammerStick.js b/lib/candlestick/BearishHammerStick.js new file mode 100644 index 0000000..7c432cd --- /dev/null +++ b/lib/candlestick/BearishHammerStick.js @@ -0,0 +1,21 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class BearishHammerStick extends CandlestickFinder { + constructor() { + super(); + this.name = 'BearishHammerStick'; + this.requiredCount = 1; + } + logic(data) { + let daysOpen = data.open[0]; + let daysClose = data.close[0]; + let daysHigh = data.high[0]; + let daysLow = data.low[0]; + let isBearishHammer = daysOpen > daysClose; + isBearishHammer = isBearishHammer && this.approximateEqual(daysOpen, daysHigh); + isBearishHammer = isBearishHammer && (daysOpen - daysClose) <= 2 * (daysClose - daysLow); + return isBearishHammer; + } +} +export function bearishhammerstick(data) { + return new BearishHammerStick().hasPattern(data); +} diff --git a/lib/candlestick/BearishHarami.js b/lib/candlestick/BearishHarami.js new file mode 100644 index 0000000..dff4158 --- /dev/null +++ b/lib/candlestick/BearishHarami.js @@ -0,0 +1,27 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class BearishHarami extends CandlestickFinder { + constructor() { + super(); + this.requiredCount = 2; + this.name = 'BearishHarami'; + } + logic(data) { + let firstdaysOpen = data.open[0]; + let firstdaysClose = data.close[0]; + let firstdaysHigh = data.high[0]; + let firstdaysLow = data.low[0]; + let seconddaysOpen = data.open[1]; + let seconddaysClose = data.close[1]; + let seconddaysHigh = data.high[1]; + let seconddaysLow = data.low[1]; + let isBearishHaramiPattern = ((firstdaysOpen < seconddaysOpen) && + (firstdaysClose > seconddaysOpen) && + (firstdaysClose > seconddaysClose) && + (firstdaysOpen < seconddaysLow) && + (firstdaysHigh > seconddaysHigh)); + return (isBearishHaramiPattern); + } +} +export function bearishharami(data) { + return new BearishHarami().hasPattern(data); +} diff --git a/lib/candlestick/BearishHaramiCross.js b/lib/candlestick/BearishHaramiCross.js new file mode 100644 index 0000000..bf4a3f5 --- /dev/null +++ b/lib/candlestick/BearishHaramiCross.js @@ -0,0 +1,28 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class BearishHaramiCross extends CandlestickFinder { + constructor() { + super(); + this.requiredCount = 2; + this.name = 'BearishHaramiCross'; + } + logic(data) { + let firstdaysOpen = data.open[0]; + let firstdaysClose = data.close[0]; + let firstdaysHigh = data.high[0]; + let firstdaysLow = data.low[0]; + let seconddaysOpen = data.open[1]; + let seconddaysClose = data.close[1]; + let seconddaysHigh = data.high[1]; + let seconddaysLow = data.low[1]; + let isBearishHaramiCrossPattern = ((firstdaysOpen < seconddaysOpen) && + (firstdaysClose > seconddaysOpen) && + (firstdaysClose > seconddaysClose) && + (firstdaysOpen < seconddaysLow) && + (firstdaysHigh > seconddaysHigh)); + let isSecondDayDoji = this.approximateEqual(seconddaysOpen, seconddaysClose); + return (isBearishHaramiCrossPattern && isSecondDayDoji); + } +} +export function bearishharamicross(data) { + return new BearishHaramiCross().hasPattern(data); +} diff --git a/lib/candlestick/BearishInvertedHammerStick.js b/lib/candlestick/BearishInvertedHammerStick.js new file mode 100644 index 0000000..5791c2c --- /dev/null +++ b/lib/candlestick/BearishInvertedHammerStick.js @@ -0,0 +1,21 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class BearishInvertedHammerStick extends CandlestickFinder { + constructor() { + super(); + this.name = 'BearishInvertedHammerStick'; + this.requiredCount = 1; + } + logic(data) { + let daysOpen = data.open[0]; + let daysClose = data.close[0]; + let daysHigh = data.high[0]; + let daysLow = data.low[0]; + let isBearishInvertedHammer = daysOpen > daysClose; + isBearishInvertedHammer = isBearishInvertedHammer && this.approximateEqual(daysClose, daysLow); + isBearishInvertedHammer = isBearishInvertedHammer && (daysOpen - daysClose) <= 2 * (daysHigh - daysOpen); + return isBearishInvertedHammer; + } +} +export function bearishinvertedhammerstick(data) { + return new BearishInvertedHammerStick().hasPattern(data); +} diff --git a/lib/candlestick/BearishMarubozu.js b/lib/candlestick/BearishMarubozu.js new file mode 100644 index 0000000..2819f68 --- /dev/null +++ b/lib/candlestick/BearishMarubozu.js @@ -0,0 +1,22 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class BearishMarubozu extends CandlestickFinder { + constructor() { + super(); + this.name = 'BearishMarubozu'; + this.requiredCount = 1; + } + logic(data) { + let daysOpen = data.open[0]; + let daysClose = data.close[0]; + let daysHigh = data.high[0]; + let daysLow = data.low[0]; + let isBearishMarbozu = this.approximateEqual(daysOpen, daysHigh) && + this.approximateEqual(daysLow, daysClose) && + daysOpen > daysClose && + daysOpen > daysLow; + return (isBearishMarbozu); + } +} +export function bearishmarubozu(data) { + return new BearishMarubozu().hasPattern(data); +} diff --git a/lib/candlestick/BearishSpinningTop.js b/lib/candlestick/BearishSpinningTop.js new file mode 100644 index 0000000..43b0544 --- /dev/null +++ b/lib/candlestick/BearishSpinningTop.js @@ -0,0 +1,23 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class BearishSpinningTop extends CandlestickFinder { + constructor() { + super(); + this.name = 'BearishSpinningTop'; + this.requiredCount = 1; + } + logic(data) { + let daysOpen = data.open[0]; + let daysClose = data.close[0]; + let daysHigh = data.high[0]; + let daysLow = data.low[0]; + let bodyLength = Math.abs(daysClose - daysOpen); + let upperShadowLength = Math.abs(daysHigh - daysOpen); + let lowerShadowLength = Math.abs(daysHigh - daysLow); + let isBearishSpinningTop = bodyLength < upperShadowLength && + bodyLength < lowerShadowLength; + return isBearishSpinningTop; + } +} +export function bearishspinningtop(data) { + return new BearishSpinningTop().hasPattern(data); +} diff --git a/lib/candlestick/Bullish.js b/lib/candlestick/Bullish.js new file mode 100644 index 0000000..48bf523 --- /dev/null +++ b/lib/candlestick/Bullish.js @@ -0,0 +1,46 @@ +import MorningStar from './MorningStar'; +import BullishEngulfingPattern from './BullishEngulfingPattern'; +import BullishHarami from './BullishHarami'; +import BullishHaramiCross from './BullishHaramiCross'; +import MorningDojiStar from './MorningDojiStar'; +import DownsideTasukiGap from './DownsideTasukiGap'; +import BullishMarubozu from './BullishMarubozu'; +import PiercingLine from './PiercingLine'; +import ThreeWhiteSoldiers from './ThreeWhiteSoldiers'; +import BullishHammerStick from './BullishHammerStick'; +import BullishInvertedHammerStick from './BullishInvertedHammerStick'; +import HammerPattern from './HammerPattern'; +import HammerPatternUnconfirmed from './HammerPatternUnconfirmed'; +import CandlestickFinder from './CandlestickFinder'; +import TweezerBottom from './TweezerBottom'; +let bullishPatterns = [ + new BullishEngulfingPattern(), + new DownsideTasukiGap(), + new BullishHarami(), + new BullishHaramiCross(), + new MorningDojiStar(), + new MorningStar(), + new BullishMarubozu(), + new PiercingLine(), + new ThreeWhiteSoldiers(), + new BullishHammerStick(), + new BullishInvertedHammerStick(), + new HammerPattern(), + new HammerPatternUnconfirmed(), + new TweezerBottom() +]; +export default class BullishPatterns extends CandlestickFinder { + constructor() { + super(); + this.name = 'Bullish Candlesticks'; + } + hasPattern(data) { + return bullishPatterns.reduce(function (state, pattern) { + let result = pattern.hasPattern(data); + return state || result; + }, false); + } +} +export function bullish(data) { + return new BullishPatterns().hasPattern(data); +} diff --git a/lib/candlestick/BullishEngulfingPattern.js b/lib/candlestick/BullishEngulfingPattern.js new file mode 100644 index 0000000..a8f95a7 --- /dev/null +++ b/lib/candlestick/BullishEngulfingPattern.js @@ -0,0 +1,26 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class BullishEngulfingPattern extends CandlestickFinder { + constructor() { + super(); + this.name = 'BullishEngulfingPattern'; + this.requiredCount = 2; + } + logic(data) { + let firstdaysOpen = data.open[0]; + let firstdaysClose = data.close[0]; + let firstdaysHigh = data.high[0]; + let firstdaysLow = data.low[0]; + let seconddaysOpen = data.open[1]; + let seconddaysClose = data.close[1]; + let seconddaysHigh = data.high[1]; + let seconddaysLow = data.low[1]; + let isBullishEngulfing = ((firstdaysClose < firstdaysOpen) && + (firstdaysOpen > seconddaysOpen) && + (firstdaysClose > seconddaysOpen) && + (firstdaysOpen < seconddaysClose)); + return (isBullishEngulfing); + } +} +export function bullishengulfingpattern(data) { + return new BullishEngulfingPattern().hasPattern(data); +} diff --git a/lib/candlestick/BullishHammerStick.js b/lib/candlestick/BullishHammerStick.js new file mode 100644 index 0000000..d13b983 --- /dev/null +++ b/lib/candlestick/BullishHammerStick.js @@ -0,0 +1,21 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class BullishHammerStick extends CandlestickFinder { + constructor() { + super(); + this.name = 'BullishHammerStick'; + this.requiredCount = 1; + } + logic(data) { + let daysOpen = data.open[0]; + let daysClose = data.close[0]; + let daysHigh = data.high[0]; + let daysLow = data.low[0]; + let isBullishHammer = daysClose > daysOpen; + isBullishHammer = isBullishHammer && this.approximateEqual(daysClose, daysHigh); + isBullishHammer = isBullishHammer && (daysClose - daysOpen) <= 2 * (daysOpen - daysLow); + return isBullishHammer; + } +} +export function bullishhammerstick(data) { + return new BullishHammerStick().hasPattern(data); +} diff --git a/lib/candlestick/BullishHarami.js b/lib/candlestick/BullishHarami.js new file mode 100644 index 0000000..6ce2b4a --- /dev/null +++ b/lib/candlestick/BullishHarami.js @@ -0,0 +1,27 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class BullishHarami extends CandlestickFinder { + constructor() { + super(); + this.requiredCount = 2; + this.name = "BullishHarami"; + } + logic(data) { + let firstdaysOpen = data.open[0]; + let firstdaysClose = data.close[0]; + let firstdaysHigh = data.high[0]; + let firstdaysLow = data.low[0]; + let seconddaysOpen = data.open[1]; + let seconddaysClose = data.close[1]; + let seconddaysHigh = data.high[1]; + let seconddaysLow = data.low[1]; + let isBullishHaramiPattern = ((firstdaysOpen > seconddaysOpen) && + (firstdaysClose < seconddaysOpen) && + (firstdaysClose < seconddaysClose) && + (firstdaysOpen > seconddaysLow) && + (firstdaysHigh > seconddaysHigh)); + return (isBullishHaramiPattern); + } +} +export function bullishharami(data) { + return new BullishHarami().hasPattern(data); +} diff --git a/lib/candlestick/BullishHaramiCross.js b/lib/candlestick/BullishHaramiCross.js new file mode 100644 index 0000000..d5305cf --- /dev/null +++ b/lib/candlestick/BullishHaramiCross.js @@ -0,0 +1,28 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class BullishHaramiCross extends CandlestickFinder { + constructor() { + super(); + this.requiredCount = 2; + this.name = 'BullishHaramiCross'; + } + logic(data) { + let firstdaysOpen = data.open[0]; + let firstdaysClose = data.close[0]; + let firstdaysHigh = data.high[0]; + let firstdaysLow = data.low[0]; + let seconddaysOpen = data.open[1]; + let seconddaysClose = data.close[1]; + let seconddaysHigh = data.high[1]; + let seconddaysLow = data.low[1]; + let isBullishHaramiCrossPattern = ((firstdaysOpen > seconddaysOpen) && + (firstdaysClose < seconddaysOpen) && + (firstdaysClose < seconddaysClose) && + (firstdaysOpen > seconddaysLow) && + (firstdaysHigh > seconddaysHigh)); + let isSecondDayDoji = this.approximateEqual(seconddaysOpen, seconddaysClose); + return (isBullishHaramiCrossPattern && isSecondDayDoji); + } +} +export function bullishharamicross(data) { + return new BullishHaramiCross().hasPattern(data); +} diff --git a/lib/candlestick/BullishInvertedHammerStick.js b/lib/candlestick/BullishInvertedHammerStick.js new file mode 100644 index 0000000..46955e1 --- /dev/null +++ b/lib/candlestick/BullishInvertedHammerStick.js @@ -0,0 +1,21 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class BullishInvertedHammerStick extends CandlestickFinder { + constructor() { + super(); + this.name = 'BullishInvertedHammerStick'; + this.requiredCount = 1; + } + logic(data) { + let daysOpen = data.open[0]; + let daysClose = data.close[0]; + let daysHigh = data.high[0]; + let daysLow = data.low[0]; + let isBullishInvertedHammer = daysClose > daysOpen; + isBullishInvertedHammer = isBullishInvertedHammer && this.approximateEqual(daysOpen, daysLow); + isBullishInvertedHammer = isBullishInvertedHammer && (daysClose - daysOpen) <= 2 * (daysHigh - daysClose); + return isBullishInvertedHammer; + } +} +export function bullishinvertedhammerstick(data) { + return new BullishInvertedHammerStick().hasPattern(data); +} diff --git a/lib/candlestick/BullishMarubozu.js b/lib/candlestick/BullishMarubozu.js new file mode 100644 index 0000000..a2ba19c --- /dev/null +++ b/lib/candlestick/BullishMarubozu.js @@ -0,0 +1,22 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class BullishMarubozu extends CandlestickFinder { + constructor() { + super(); + this.name = 'BullishMarubozu'; + this.requiredCount = 1; + } + logic(data) { + let daysOpen = data.open[0]; + let daysClose = data.close[0]; + let daysHigh = data.high[0]; + let daysLow = data.low[0]; + let isBullishMarbozu = this.approximateEqual(daysClose, daysHigh) && + this.approximateEqual(daysLow, daysOpen) && + daysOpen < daysClose && + daysOpen < daysHigh; + return (isBullishMarbozu); + } +} +export function bullishmarubozu(data) { + return new BullishMarubozu().hasPattern(data); +} diff --git a/lib/candlestick/BullishSpinningTop.js b/lib/candlestick/BullishSpinningTop.js new file mode 100644 index 0000000..e60eee4 --- /dev/null +++ b/lib/candlestick/BullishSpinningTop.js @@ -0,0 +1,23 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class BullishSpinningTop extends CandlestickFinder { + constructor() { + super(); + this.name = 'BullishSpinningTop'; + this.requiredCount = 1; + } + logic(data) { + let daysOpen = data.open[0]; + let daysClose = data.close[0]; + let daysHigh = data.high[0]; + let daysLow = data.low[0]; + let bodyLength = Math.abs(daysClose - daysOpen); + let upperShadowLength = Math.abs(daysHigh - daysClose); + let lowerShadowLength = Math.abs(daysOpen - daysLow); + let isBullishSpinningTop = bodyLength < upperShadowLength && + bodyLength < lowerShadowLength; + return isBullishSpinningTop; + } +} +export function bullishspinningtop(data) { + return new BullishSpinningTop().hasPattern(data); +} diff --git a/lib/candlestick/CandlestickFinder.js b/lib/candlestick/CandlestickFinder.js new file mode 100644 index 0000000..ee04593 --- /dev/null +++ b/lib/candlestick/CandlestickFinder.js @@ -0,0 +1,93 @@ +export default class CandlestickFinder { + constructor() { + // if (new.target === Abstract) { + // throw new TypeError("Abstract class"); + // } + } + approximateEqual(a, b) { + let left = parseFloat(Math.abs(a - b).toPrecision(4)) * 1; + let right = parseFloat((a * 0.001).toPrecision(4)) * 1; + return left <= right; + } + logic(data) { + throw "this has to be implemented"; + } + getAllPatternIndex(data) { + if (data.close.length < this.requiredCount) { + console.warn('Data count less than data required for the strategy ', this.name); + return []; + } + if (data.reversedInput) { + data.open.reverse(); + data.high.reverse(); + data.low.reverse(); + data.close.reverse(); + } + let strategyFn = this.logic; + return this._generateDataForCandleStick(data) + .map((current, index) => { + return strategyFn.call(this, current) ? index : undefined; + }).filter((hasIndex) => { + return hasIndex; + }); + } + hasPattern(data) { + if (data.close.length < this.requiredCount) { + console.warn('Data count less than data required for the strategy ', this.name); + return false; + } + if (data.reversedInput) { + data.open.reverse(); + data.high.reverse(); + data.low.reverse(); + data.close.reverse(); + } + let strategyFn = this.logic; + return strategyFn.call(this, this._getLastDataForCandleStick(data)); + } + _getLastDataForCandleStick(data) { + let requiredCount = this.requiredCount; + if (data.close.length === requiredCount) { + return data; + } + else { + let returnVal = { + open: [], + high: [], + low: [], + close: [] + }; + let i = 0; + let index = data.close.length - requiredCount; + while (i < requiredCount) { + returnVal.open.push(data.open[index + i]); + returnVal.high.push(data.high[index + i]); + returnVal.low.push(data.low[index + i]); + returnVal.close.push(data.close[index + i]); + i++; + } + return returnVal; + } + } + _generateDataForCandleStick(data) { + let requiredCount = this.requiredCount; + let generatedData = data.close.map(function (currentData, index) { + let i = 0; + let returnVal = { + open: [], + high: [], + low: [], + close: [] + }; + while (i < requiredCount) { + returnVal.open.push(data.open[index + i]); + returnVal.high.push(data.high[index + i]); + returnVal.low.push(data.low[index + i]); + returnVal.close.push(data.close[index + i]); + i++; + } + return returnVal; + }).filter((val, index) => { return (index <= (data.close.length - requiredCount)); }); + return generatedData; + } +} diff --git a/lib/candlestick/DarkCloudCover.js b/lib/candlestick/DarkCloudCover.js new file mode 100644 index 0000000..77342ba --- /dev/null +++ b/lib/candlestick/DarkCloudCover.js @@ -0,0 +1,28 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class DarkCloudCover extends CandlestickFinder { + constructor() { + super(); + this.name = 'DarkCloudCover'; + this.requiredCount = 2; + } + logic(data) { + let firstdaysOpen = data.open[0]; + let firstdaysClose = data.close[0]; + let firstdaysHigh = data.high[0]; + let firstdaysLow = data.low[0]; + let seconddaysOpen = data.open[1]; + let seconddaysClose = data.close[1]; + let seconddaysHigh = data.high[1]; + let seconddaysLow = data.low[1]; + let firstdayMidpoint = ((firstdaysClose + firstdaysOpen) / 2); + let isFirstBullish = firstdaysClose > firstdaysOpen; + let isSecondBearish = seconddaysClose < seconddaysOpen; + let isDarkCloudPattern = ((seconddaysOpen > firstdaysHigh) && + (seconddaysClose < firstdayMidpoint) && + (seconddaysClose > firstdaysOpen)); + return (isFirstBullish && isSecondBearish && isDarkCloudPattern); + } +} +export function darkcloudcover(data) { + return new DarkCloudCover().hasPattern(data); +} diff --git a/lib/candlestick/Doji.js b/lib/candlestick/Doji.js new file mode 100644 index 0000000..2f584c3 --- /dev/null +++ b/lib/candlestick/Doji.js @@ -0,0 +1,21 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class Doji extends CandlestickFinder { + constructor() { + super(); + this.name = 'Doji'; + this.requiredCount = 1; + } + logic(data) { + let daysOpen = data.open[0]; + let daysClose = data.close[0]; + let daysHigh = data.high[0]; + let daysLow = data.low[0]; + let isOpenEqualsClose = this.approximateEqual(daysOpen, daysClose); + let isHighEqualsOpen = isOpenEqualsClose && this.approximateEqual(daysOpen, daysHigh); + let isLowEqualsClose = isOpenEqualsClose && this.approximateEqual(daysClose, daysLow); + return (isOpenEqualsClose && isHighEqualsOpen == isLowEqualsClose); + } +} +export function doji(data) { + return new Doji().hasPattern(data); +} diff --git a/lib/candlestick/Doji.js.map b/lib/candlestick/Doji.js.map new file mode 100644 index 0000000..5b12337 --- /dev/null +++ b/lib/candlestick/Doji.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Doji.js","sourceRoot":"","sources":["../../src/candlestick/Doji.ts"],"names":[],"mappings":"AACA,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AAEpD,MAAM,CAAC,OAAO,WAAY,SAAQ,iBAAiB;IAC/C;QACI,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC;QACnB,IAAI,CAAC,aAAa,GAAI,CAAC,CAAC;IAC5B,CAAC;IACD,KAAK,CAAE,IAAc;QACjB,IAAI,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACtD,CAAC;CACJ;AAED,MAAM,eAAe,IAAc;IACjC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AACrC,CAAC"} \ No newline at end of file diff --git a/lib/candlestick/DownsideTasukiGap.js b/lib/candlestick/DownsideTasukiGap.js new file mode 100644 index 0000000..7f61b5c --- /dev/null +++ b/lib/candlestick/DownsideTasukiGap.js @@ -0,0 +1,34 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class DownsideTasukiGap extends CandlestickFinder { + constructor() { + super(); + this.requiredCount = 3; + this.name = 'DownsideTasukiGap'; + } + logic(data) { + let firstdaysOpen = data.open[0]; + let firstdaysClose = data.close[0]; + let firstdaysHigh = data.high[0]; + let firstdaysLow = data.low[0]; + let seconddaysOpen = data.open[1]; + let seconddaysClose = data.close[1]; + let seconddaysHigh = data.high[1]; + let seconddaysLow = data.low[1]; + let thirddaysOpen = data.open[2]; + let thirddaysClose = data.close[2]; + let thirddaysHigh = data.high[2]; + let thirddaysLow = data.low[2]; + let isFirstBearish = firstdaysClose < firstdaysOpen; + let isSecondBearish = seconddaysClose < seconddaysOpen; + let isThirdBullish = thirddaysClose > thirddaysOpen; + let isFirstGapExists = seconddaysHigh < firstdaysLow; + let isDownsideTasukiGap = ((seconddaysOpen > thirddaysOpen) && + (seconddaysClose < thirddaysOpen) && + (thirddaysClose > seconddaysOpen) && + (thirddaysClose < firstdaysClose)); + return (isFirstBearish && isSecondBearish && isThirdBullish && isFirstGapExists && isDownsideTasukiGap); + } +} +export function downsidetasukigap(data) { + return new DownsideTasukiGap().hasPattern(data); +} diff --git a/lib/candlestick/DragonFlyDoji.js b/lib/candlestick/DragonFlyDoji.js new file mode 100644 index 0000000..67c2ac7 --- /dev/null +++ b/lib/candlestick/DragonFlyDoji.js @@ -0,0 +1,21 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class DragonFlyDoji extends CandlestickFinder { + constructor() { + super(); + this.requiredCount = 1; + this.name = 'DragonFlyDoji'; + } + logic(data) { + let daysOpen = data.open[0]; + let daysClose = data.close[0]; + let daysHigh = data.high[0]; + let daysLow = data.low[0]; + let isOpenEqualsClose = this.approximateEqual(daysOpen, daysClose); + let isHighEqualsOpen = isOpenEqualsClose && this.approximateEqual(daysOpen, daysHigh); + let isLowEqualsClose = isOpenEqualsClose && this.approximateEqual(daysClose, daysLow); + return (isOpenEqualsClose && isHighEqualsOpen && !isLowEqualsClose); + } +} +export function dragonflydoji(data) { + return new DragonFlyDoji().hasPattern(data); +} diff --git a/lib/candlestick/EveningDojiStar.js b/lib/candlestick/EveningDojiStar.js new file mode 100644 index 0000000..60565b3 --- /dev/null +++ b/lib/candlestick/EveningDojiStar.js @@ -0,0 +1,41 @@ +import Doji from './Doji'; +import CandlestickFinder from './CandlestickFinder'; +export default class EveningDojiStar extends CandlestickFinder { + constructor() { + super(); + this.name = 'EveningDojiStar'; + this.requiredCount = 3; + } + logic(data) { + let firstdaysOpen = data.open[0]; + let firstdaysClose = data.close[0]; + let firstdaysHigh = data.high[0]; + let firstdaysLow = data.low[0]; + let seconddaysOpen = data.open[1]; + let seconddaysClose = data.close[1]; + let seconddaysHigh = data.high[1]; + let seconddaysLow = data.low[1]; + let thirddaysOpen = data.open[2]; + let thirddaysClose = data.close[2]; + let thirddaysHigh = data.high[2]; + let thirddaysLow = data.low[2]; + let firstdaysMidpoint = ((firstdaysOpen + firstdaysClose) / 2); + let isFirstBullish = firstdaysClose > firstdaysOpen; + let dojiExists = new Doji().hasPattern({ + "open": [seconddaysOpen], + "close": [seconddaysClose], + "high": [seconddaysHigh], + "low": [seconddaysLow] + }); + let isThirdBearish = thirddaysOpen > thirddaysClose; + let gapExists = ((seconddaysHigh > firstdaysHigh) && + (seconddaysLow > firstdaysHigh) && + (thirddaysOpen < seconddaysLow) && + (seconddaysClose > thirddaysOpen)); + let doesCloseBelowFirstMidpoint = thirddaysClose < firstdaysMidpoint; + return (isFirstBullish && dojiExists && gapExists && isThirdBearish && doesCloseBelowFirstMidpoint); + } +} +export function eveningdojistar(data) { + return new EveningDojiStar().hasPattern(data); +} diff --git a/lib/candlestick/EveningStar.js b/lib/candlestick/EveningStar.js new file mode 100644 index 0000000..79fbd7d --- /dev/null +++ b/lib/candlestick/EveningStar.js @@ -0,0 +1,36 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class EveningStar extends CandlestickFinder { + constructor() { + super(); + this.name = 'EveningStar'; + this.requiredCount = 3; + } + logic(data) { + let firstdaysOpen = data.open[0]; + let firstdaysClose = data.close[0]; + let firstdaysHigh = data.high[0]; + let firstdaysLow = data.low[0]; + let seconddaysOpen = data.open[1]; + let seconddaysClose = data.close[1]; + let seconddaysHigh = data.high[1]; + let seconddaysLow = data.low[1]; + let thirddaysOpen = data.open[2]; + let thirddaysClose = data.close[2]; + let thirddaysHigh = data.high[2]; + let thirddaysLow = data.low[2]; + let firstdaysMidpoint = ((firstdaysOpen + firstdaysClose) / 2); + let isFirstBullish = firstdaysClose > firstdaysOpen; + let isSmallBodyExists = ((firstdaysHigh < seconddaysLow) && + (firstdaysHigh < seconddaysHigh)); + let isThirdBearish = thirddaysOpen > thirddaysClose; + let gapExists = ((seconddaysHigh > firstdaysHigh) && + (seconddaysLow > firstdaysHigh) && + (thirddaysOpen < seconddaysLow) && + (seconddaysClose > thirddaysOpen)); + let doesCloseBelowFirstMidpoint = thirddaysClose < firstdaysMidpoint; + return (isFirstBullish && isSmallBodyExists && gapExists && isThirdBearish && doesCloseBelowFirstMidpoint); + } +} +export function eveningstar(data) { + return new EveningStar().hasPattern(data); +} diff --git a/lib/candlestick/GraveStoneDoji.js b/lib/candlestick/GraveStoneDoji.js new file mode 100644 index 0000000..35c1b3f --- /dev/null +++ b/lib/candlestick/GraveStoneDoji.js @@ -0,0 +1,21 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class GraveStoneDoji extends CandlestickFinder { + constructor() { + super(); + this.requiredCount = 1; + this.name = 'GraveStoneDoji'; + } + logic(data) { + let daysOpen = data.open[0]; + let daysClose = data.close[0]; + let daysHigh = data.high[0]; + let daysLow = data.low[0]; + let isOpenEqualsClose = this.approximateEqual(daysOpen, daysClose); + let isHighEqualsOpen = isOpenEqualsClose && this.approximateEqual(daysOpen, daysHigh); + let isLowEqualsClose = isOpenEqualsClose && this.approximateEqual(daysClose, daysLow); + return (isOpenEqualsClose && isLowEqualsClose && !isHighEqualsOpen); + } +} +export function gravestonedoji(data) { + return new GraveStoneDoji().hasPattern(data); +} diff --git a/lib/candlestick/HammerPattern.js b/lib/candlestick/HammerPattern.js new file mode 100644 index 0000000..374b635 --- /dev/null +++ b/lib/candlestick/HammerPattern.js @@ -0,0 +1,63 @@ +import CandlestickFinder from './CandlestickFinder'; +import { averageloss } from '../Utils/AverageLoss'; +import { averagegain } from '../Utils/AverageGain'; +import { bearishhammerstick } from './BearishHammerStick'; +import { bearishinvertedhammerstick } from './BearishInvertedHammerStick'; +import { bullishhammerstick } from './BullishHammerStick'; +import { bullishinvertedhammerstick } from './BullishInvertedHammerStick'; +export default class HammerPattern extends CandlestickFinder { + constructor() { + super(); + this.name = 'HammerPattern'; + this.requiredCount = 5; + } + logic(data) { + let isPattern = this.downwardTrend(data); + isPattern = isPattern && this.includesHammer(data); + isPattern = isPattern && this.hasConfirmation(data); + return isPattern; + } + downwardTrend(data, confirm = true) { + let end = confirm ? 3 : 4; + // Analyze trends in closing prices of the first three or four candlesticks + let gains = averagegain({ values: data.close.slice(0, end), period: end - 1 }); + let losses = averageloss({ values: data.close.slice(0, end), period: end - 1 }); + // Downward trend, so more losses than gains + return losses > gains; + } + includesHammer(data, confirm = true) { + let start = confirm ? 3 : 4; + let end = confirm ? 4 : undefined; + let possibleHammerData = { + open: data.open.slice(start, end), + close: data.close.slice(start, end), + low: data.low.slice(start, end), + high: data.high.slice(start, end), + }; + let isPattern = bearishhammerstick(possibleHammerData); + isPattern = isPattern || bearishinvertedhammerstick(possibleHammerData); + isPattern = isPattern || bullishhammerstick(possibleHammerData); + isPattern = isPattern || bullishinvertedhammerstick(possibleHammerData); + return isPattern; + } + hasConfirmation(data) { + let possibleHammer = { + open: data.open[3], + close: data.close[3], + low: data.low[3], + high: data.high[3], + }; + let possibleConfirmation = { + open: data.open[4], + close: data.close[4], + low: data.low[4], + high: data.high[4], + }; + // Confirmation candlestick is bullish + let isPattern = possibleConfirmation.open < possibleConfirmation.close; + return isPattern && possibleHammer.close < possibleConfirmation.close; + } +} +export function hammerpattern(data) { + return new HammerPattern().hasPattern(data); +} diff --git a/lib/candlestick/HammerPatternUnconfirmed.js b/lib/candlestick/HammerPatternUnconfirmed.js new file mode 100644 index 0000000..3deab68 --- /dev/null +++ b/lib/candlestick/HammerPatternUnconfirmed.js @@ -0,0 +1,15 @@ +import HammerPattern from './HammerPattern'; +export default class HammerPatternUnconfirmed extends HammerPattern { + constructor() { + super(); + this.name = 'HammerPatternUnconfirmed'; + } + logic(data) { + let isPattern = this.downwardTrend(data, false); + isPattern = isPattern && this.includesHammer(data, false); + return isPattern; + } +} +export function hammerpatternunconfirmed(data) { + return new HammerPatternUnconfirmed().hasPattern(data); +} diff --git a/lib/candlestick/HangingMan.js b/lib/candlestick/HangingMan.js new file mode 100644 index 0000000..a02e443 --- /dev/null +++ b/lib/candlestick/HangingMan.js @@ -0,0 +1,59 @@ +import CandlestickFinder from './CandlestickFinder'; +import { averageloss } from '../Utils/AverageLoss'; +import { averagegain } from '../Utils/AverageGain'; +import { bearishhammerstick } from './BearishHammerStick'; +import { bullishhammerstick } from './BullishHammerStick'; +export default class HangingMan extends CandlestickFinder { + constructor() { + super(); + this.name = 'HangingMan'; + this.requiredCount = 5; + } + logic(data) { + let isPattern = this.upwardTrend(data); + isPattern = isPattern && this.includesHammer(data); + isPattern = isPattern && this.hasConfirmation(data); + return isPattern; + } + upwardTrend(data, confirm = true) { + let end = confirm ? 3 : 4; + // Analyze trends in closing prices of the first three or four candlesticks + let gains = averagegain({ values: data.close.slice(0, end), period: end - 1 }); + let losses = averageloss({ values: data.close.slice(0, end), period: end - 1 }); + // Upward trend, so more gains than losses + return gains > losses; + } + includesHammer(data, confirm = true) { + let start = confirm ? 3 : 4; + let end = confirm ? 4 : undefined; + let possibleHammerData = { + open: data.open.slice(start, end), + close: data.close.slice(start, end), + low: data.low.slice(start, end), + high: data.high.slice(start, end), + }; + let isPattern = bearishhammerstick(possibleHammerData); + isPattern = isPattern || bullishhammerstick(possibleHammerData); + return isPattern; + } + hasConfirmation(data) { + let possibleHammer = { + open: data.open[3], + close: data.close[3], + low: data.low[3], + high: data.high[3], + }; + let possibleConfirmation = { + open: data.open[4], + close: data.close[4], + low: data.low[4], + high: data.high[4], + }; + // Confirmation candlestick is bearish + let isPattern = possibleConfirmation.open > possibleConfirmation.close; + return isPattern && possibleHammer.close > possibleConfirmation.close; + } +} +export function hangingman(data) { + return new HangingMan().hasPattern(data); +} diff --git a/lib/candlestick/HangingManUnconfirmed.js b/lib/candlestick/HangingManUnconfirmed.js new file mode 100644 index 0000000..6f3c20a --- /dev/null +++ b/lib/candlestick/HangingManUnconfirmed.js @@ -0,0 +1,15 @@ +import HangingMan from './HangingMan'; +export default class HangingManUnconfirmed extends HangingMan { + constructor() { + super(); + this.name = 'HangingManUnconfirmed'; + } + logic(data) { + let isPattern = this.upwardTrend(data, false); + isPattern = isPattern && this.includesHammer(data, false); + return isPattern; + } +} +export function hangingmanunconfirmed(data) { + return new HangingManUnconfirmed().hasPattern(data); +} diff --git a/lib/candlestick/MorningDojiStar.js b/lib/candlestick/MorningDojiStar.js new file mode 100644 index 0000000..a72cdbf --- /dev/null +++ b/lib/candlestick/MorningDojiStar.js @@ -0,0 +1,42 @@ +import Doji from './Doji'; +import CandlestickFinder from './CandlestickFinder'; +export default class MorningDojiStar extends CandlestickFinder { + constructor() { + super(); + this.name = 'MorningDojiStar'; + this.requiredCount = 3; + } + logic(data) { + let firstdaysOpen = data.open[0]; + let firstdaysClose = data.close[0]; + let firstdaysHigh = data.high[0]; + let firstdaysLow = data.low[0]; + let seconddaysOpen = data.open[1]; + let seconddaysClose = data.close[1]; + let seconddaysHigh = data.high[1]; + let seconddaysLow = data.low[1]; + let thirddaysOpen = data.open[2]; + let thirddaysClose = data.close[2]; + let thirddaysHigh = data.high[2]; + let thirddaysLow = data.low[2]; + let firstdaysMidpoint = ((firstdaysOpen + firstdaysClose) / 2); + let isFirstBearish = firstdaysClose < firstdaysOpen; + let dojiExists = new Doji().hasPattern({ + "open": [seconddaysOpen], + "close": [seconddaysClose], + "high": [seconddaysHigh], + "low": [seconddaysLow] + }); + let isThirdBullish = thirddaysOpen < thirddaysClose; + let gapExists = ((seconddaysHigh < firstdaysLow) && + (seconddaysLow < firstdaysLow) && + (thirddaysOpen > seconddaysHigh) && + (seconddaysClose < thirddaysOpen)); + let doesCloseAboveFirstMidpoint = thirddaysClose > firstdaysMidpoint; + return (isFirstBearish && dojiExists && isThirdBullish && gapExists && + doesCloseAboveFirstMidpoint); + } +} +export function morningdojistar(data) { + return new MorningDojiStar().hasPattern(data); +} diff --git a/lib/candlestick/MorningStar.js b/lib/candlestick/MorningStar.js new file mode 100644 index 0000000..bef661b --- /dev/null +++ b/lib/candlestick/MorningStar.js @@ -0,0 +1,36 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class MorningStar extends CandlestickFinder { + constructor() { + super(); + this.name = 'MorningStar'; + this.requiredCount = 3; + } + logic(data) { + let firstdaysOpen = data.open[0]; + let firstdaysClose = data.close[0]; + let firstdaysHigh = data.high[0]; + let firstdaysLow = data.low[0]; + let seconddaysOpen = data.open[1]; + let seconddaysClose = data.close[1]; + let seconddaysHigh = data.high[1]; + let seconddaysLow = data.low[1]; + let thirddaysOpen = data.open[2]; + let thirddaysClose = data.close[2]; + let thirddaysHigh = data.high[2]; + let thirddaysLow = data.low[2]; + let firstdaysMidpoint = ((firstdaysOpen + firstdaysClose) / 2); + let isFirstBearish = firstdaysClose < firstdaysOpen; + let isSmallBodyExists = ((firstdaysLow > seconddaysLow) && + (firstdaysLow > seconddaysHigh)); + let isThirdBullish = thirddaysOpen < thirddaysClose; + let gapExists = ((seconddaysHigh < firstdaysLow) && + (seconddaysLow < firstdaysLow) && + (thirddaysOpen > seconddaysHigh) && + (seconddaysClose < thirddaysOpen)); + let doesCloseAboveFirstMidpoint = thirddaysClose > firstdaysMidpoint; + return (isFirstBearish && isSmallBodyExists && gapExists && isThirdBullish && doesCloseAboveFirstMidpoint); + } +} +export function morningstar(data) { + return new MorningStar().hasPattern(data); +} diff --git a/lib/candlestick/PiercingLine.js b/lib/candlestick/PiercingLine.js new file mode 100644 index 0000000..415f8ae --- /dev/null +++ b/lib/candlestick/PiercingLine.js @@ -0,0 +1,28 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class PiercingLine extends CandlestickFinder { + constructor() { + super(); + this.requiredCount = 2; + this.name = 'PiercingLine'; + } + logic(data) { + let firstdaysOpen = data.open[0]; + let firstdaysClose = data.close[0]; + let firstdaysHigh = data.high[0]; + let firstdaysLow = data.low[0]; + let seconddaysOpen = data.open[1]; + let seconddaysClose = data.close[1]; + let seconddaysHigh = data.high[1]; + let seconddaysLow = data.low[1]; + let firstdaysMidpoint = ((firstdaysOpen + firstdaysClose) / 2); + let isDowntrend = seconddaysLow < firstdaysLow; + let isFirstBearish = firstdaysClose < firstdaysOpen; + let isSecondBullish = seconddaysClose > seconddaysOpen; + let isPiercingLinePattern = ((firstdaysLow > seconddaysOpen) && + (seconddaysClose > firstdaysMidpoint)); + return (isDowntrend && isFirstBearish && isPiercingLinePattern && isSecondBullish); + } +} +export function piercingline(data) { + return new PiercingLine().hasPattern(data); +} diff --git a/lib/candlestick/ShootingStar.js b/lib/candlestick/ShootingStar.js new file mode 100644 index 0000000..af655e1 --- /dev/null +++ b/lib/candlestick/ShootingStar.js @@ -0,0 +1,59 @@ +import CandlestickFinder from './CandlestickFinder'; +import { averageloss } from '../Utils/AverageLoss'; +import { averagegain } from '../Utils/AverageGain'; +import { bearishinvertedhammerstick } from './BearishInvertedHammerStick'; +import { bullishinvertedhammerstick } from './BullishInvertedHammerStick'; +export default class ShootingStar extends CandlestickFinder { + constructor() { + super(); + this.name = 'ShootingStar'; + this.requiredCount = 5; + } + logic(data) { + let isPattern = this.upwardTrend(data); + isPattern = isPattern && this.includesHammer(data); + isPattern = isPattern && this.hasConfirmation(data); + return isPattern; + } + upwardTrend(data, confirm = true) { + let end = confirm ? 3 : 4; + // Analyze trends in closing prices of the first three or four candlesticks + let gains = averagegain({ values: data.close.slice(0, end), period: end - 1 }); + let losses = averageloss({ values: data.close.slice(0, end), period: end - 1 }); + // Upward trend, so more gains than losses + return gains > losses; + } + includesHammer(data, confirm = true) { + let start = confirm ? 3 : 4; + let end = confirm ? 4 : undefined; + let possibleHammerData = { + open: data.open.slice(start, end), + close: data.close.slice(start, end), + low: data.low.slice(start, end), + high: data.high.slice(start, end), + }; + let isPattern = bearishinvertedhammerstick(possibleHammerData); + isPattern = isPattern || bullishinvertedhammerstick(possibleHammerData); + return isPattern; + } + hasConfirmation(data) { + let possibleHammer = { + open: data.open[3], + close: data.close[3], + low: data.low[3], + high: data.high[3], + }; + let possibleConfirmation = { + open: data.open[4], + close: data.close[4], + low: data.low[4], + high: data.high[4], + }; + // Confirmation candlestick is bearish + let isPattern = possibleConfirmation.open > possibleConfirmation.close; + return isPattern && possibleHammer.close > possibleConfirmation.close; + } +} +export function shootingstar(data) { + return new ShootingStar().hasPattern(data); +} diff --git a/lib/candlestick/ShootingStarUnconfirmed.js b/lib/candlestick/ShootingStarUnconfirmed.js new file mode 100644 index 0000000..5cd246b --- /dev/null +++ b/lib/candlestick/ShootingStarUnconfirmed.js @@ -0,0 +1,15 @@ +import ShootingStar from './ShootingStar'; +export default class ShootingStarUnconfirmed extends ShootingStar { + constructor() { + super(); + this.name = 'ShootingStarUnconfirmed'; + } + logic(data) { + let isPattern = this.upwardTrend(data, false); + isPattern = isPattern && this.includesHammer(data, false); + return isPattern; + } +} +export function shootingstarunconfirmed(data) { + return new ShootingStarUnconfirmed().hasPattern(data); +} diff --git a/lib/candlestick/ThreeBlackCrows.js b/lib/candlestick/ThreeBlackCrows.js new file mode 100644 index 0000000..3b29a51 --- /dev/null +++ b/lib/candlestick/ThreeBlackCrows.js @@ -0,0 +1,35 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class ThreeBlackCrows extends CandlestickFinder { + constructor() { + super(); + this.name = 'ThreeBlackCrows'; + this.requiredCount = 3; + } + logic(data) { + let firstdaysOpen = data.open[0]; + let firstdaysClose = data.close[0]; + let firstdaysHigh = data.high[0]; + let firstdaysLow = data.low[0]; + let seconddaysOpen = data.open[1]; + let seconddaysClose = data.close[1]; + let seconddaysHigh = data.high[1]; + let seconddaysLow = data.low[1]; + let thirddaysOpen = data.open[2]; + let thirddaysClose = data.close[2]; + let thirddaysHigh = data.high[2]; + let thirddaysLow = data.low[2]; + let isDownTrend = firstdaysLow > seconddaysLow && + seconddaysLow > thirddaysLow; + let isAllBearish = firstdaysOpen > firstdaysClose && + seconddaysOpen > seconddaysClose && + thirddaysOpen > thirddaysClose; + let doesOpenWithinPreviousBody = firstdaysOpen > seconddaysOpen && + seconddaysOpen > firstdaysClose && + seconddaysOpen > thirddaysOpen && + thirddaysOpen > seconddaysClose; + return (isDownTrend && isAllBearish && doesOpenWithinPreviousBody); + } +} +export function threeblackcrows(data) { + return new ThreeBlackCrows().hasPattern(data); +} diff --git a/lib/candlestick/ThreeWhiteSoldiers.js b/lib/candlestick/ThreeWhiteSoldiers.js new file mode 100644 index 0000000..0dfc4e5 --- /dev/null +++ b/lib/candlestick/ThreeWhiteSoldiers.js @@ -0,0 +1,35 @@ +import CandlestickFinder from './CandlestickFinder'; +export default class ThreeWhiteSoldiers extends CandlestickFinder { + constructor() { + super(); + this.name = 'ThreeWhiteSoldiers'; + this.requiredCount = 3; + } + logic(data) { + let firstdaysOpen = data.open[0]; + let firstdaysClose = data.close[0]; + let firstdaysHigh = data.high[0]; + let firstdaysLow = data.low[0]; + let seconddaysOpen = data.open[1]; + let seconddaysClose = data.close[1]; + let seconddaysHigh = data.high[1]; + let seconddaysLow = data.low[1]; + let thirddaysOpen = data.open[2]; + let thirddaysClose = data.close[2]; + let thirddaysHigh = data.high[2]; + let thirddaysLow = data.low[2]; + let isUpTrend = seconddaysHigh > firstdaysHigh && + thirddaysHigh > seconddaysHigh; + let isAllBullish = firstdaysOpen < firstdaysClose && + seconddaysOpen < seconddaysClose && + thirddaysOpen < thirddaysClose; + let doesOpenWithinPreviousBody = firstdaysClose > seconddaysOpen && + seconddaysOpen < firstdaysHigh && + seconddaysHigh > thirddaysOpen && + thirddaysOpen < seconddaysClose; + return (isUpTrend && isAllBullish && doesOpenWithinPreviousBody); + } +} +export function threewhitesoldiers(data) { + return new ThreeWhiteSoldiers().hasPattern(data); +} diff --git a/lib/candlestick/TweezerBottom.js b/lib/candlestick/TweezerBottom.js new file mode 100644 index 0000000..96451f3 --- /dev/null +++ b/lib/candlestick/TweezerBottom.js @@ -0,0 +1,23 @@ +import CandlestickFinder from './CandlestickFinder'; +import { averageloss } from '../Utils/AverageLoss'; +import { averagegain } from '../Utils/AverageGain'; +export default class TweezerBottom extends CandlestickFinder { + constructor() { + super(); + this.name = 'TweezerBottom'; + this.requiredCount = 5; + } + logic(data) { + return this.downwardTrend(data) && data.low[3] == data.low[4]; + } + downwardTrend(data) { + // Analyze trends in closing prices of the first three or four candlesticks + let gains = averagegain({ values: data.close.slice(0, 3), period: 2 }); + let losses = averageloss({ values: data.close.slice(0, 3), period: 2 }); + // Downward trend, so more losses than gains + return losses > gains; + } +} +export function tweezerbottom(data) { + return new TweezerBottom().hasPattern(data); +} diff --git a/lib/candlestick/TweezerTop.js b/lib/candlestick/TweezerTop.js new file mode 100644 index 0000000..068b3b3 --- /dev/null +++ b/lib/candlestick/TweezerTop.js @@ -0,0 +1,23 @@ +import CandlestickFinder from './CandlestickFinder'; +import { averageloss } from '../Utils/AverageLoss'; +import { averagegain } from '../Utils/AverageGain'; +export default class TweezerTop extends CandlestickFinder { + constructor() { + super(); + this.name = 'TweezerTop'; + this.requiredCount = 5; + } + logic(data) { + return this.upwardTrend(data) && data.high[3] == data.high[4]; + } + upwardTrend(data) { + // Analyze trends in closing prices of the first three or four candlesticks + let gains = averagegain({ values: data.close.slice(0, 3), period: 2 }); + let losses = averageloss({ values: data.close.slice(0, 3), period: 2 }); + // Upward trend, so more gains than losses + return gains > losses; + } +} +export function tweezertop(data) { + return new TweezerTop().hasPattern(data); +} diff --git a/lib/chart_types/HeikinAshi.js b/lib/chart_types/HeikinAshi.js new file mode 100644 index 0000000..b51c74b --- /dev/null +++ b/lib/chart_types/HeikinAshi.js @@ -0,0 +1,101 @@ +import { CandleList } from '../StockData'; +/** + * Created by AAravindan on 5/4/16. + */ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +export class HeikinAshiInput extends IndicatorInput { +} +export class HeikinAshi extends Indicator { + constructor(input) { + super(input); + var format = this.format; + this.result = new CandleList(); + let lastOpen = null; + let lastHigh = 0; + let lastLow = Infinity; + let lastClose = 0; + let lastVolume = 0; + let lastTimestamp = 0; + this.generator = (function* () { + let candleData = yield; + let calculated = null; + while (true) { + if (lastOpen === null) { + lastOpen = (candleData.close + candleData.open) / 2; + lastHigh = candleData.high; + lastLow = candleData.low; + lastClose = (candleData.close + candleData.open + candleData.high + candleData.low) / 4; + lastVolume = (candleData.volume || 0); + lastTimestamp = (candleData.timestamp || 0); + calculated = { + open: lastOpen, + high: lastHigh, + low: lastLow, + close: lastClose, + volume: candleData.volume || 0, + timestamp: (candleData.timestamp || 0) + }; + } + else { + let newClose = (candleData.close + candleData.open + candleData.high + candleData.low) / 4; + let newOpen = (lastOpen + lastClose) / 2; + let newHigh = Math.max(newOpen, newClose, candleData.high); + let newLow = Math.min(candleData.low, newOpen, newClose); + calculated = { + close: newClose, + open: newOpen, + high: newHigh, + low: newLow, + volume: (candleData.volume || 0), + timestamp: (candleData.timestamp || 0) + }; + lastClose = newClose; + lastOpen = newOpen; + lastHigh = newHigh; + lastLow = newLow; + } + candleData = yield calculated; + } + })(); + this.generator.next(); + input.low.forEach((tick, index) => { + var result = this.generator.next({ + open: input.open[index], + high: input.high[index], + low: input.low[index], + close: input.close[index], + volume: input.volume ? input.volume[index] : input.volume, + timestamp: input.timestamp ? input.timestamp[index] : input.timestamp + }); + if (result.value) { + this.result.open.push(result.value.open); + this.result.high.push(result.value.high); + this.result.low.push(result.value.low); + this.result.close.push(result.value.close); + this.result.volume.push(result.value.volume); + this.result.timestamp.push(result.value.timestamp); + } + }); + } + nextValue(price) { + var result = this.generator.next(price).value; + return result; + } + ; +} +HeikinAshi.calculate = heikinashi; +export function heikinashi(input) { + Indicator.reverseInputs(input); + var result = new HeikinAshi(input).result; + if (input.reversedInput) { + result.open.reverse(); + result.high.reverse(); + result.low.reverse(); + result.close.reverse(); + result.volume.reverse(); + result.timestamp.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/chart_types/Renko.js b/lib/chart_types/Renko.js new file mode 100644 index 0000000..e17e28c --- /dev/null +++ b/lib/chart_types/Renko.js @@ -0,0 +1,114 @@ +import { CandleList } from '../StockData'; +import { atr } from '../directionalmovement/ATR'; +/** + * Created by AAravindan on 5/4/16. + */ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +export class RenkoInput extends IndicatorInput { +} +class Renko extends Indicator { + constructor(input) { + super(input); + var format = this.format; + let useATR = input.useATR; + let brickSize = input.brickSize || 0; + if (useATR) { + let atrResult = atr(Object.assign({}, input)); + brickSize = atrResult[atrResult.length - 1]; + } + this.result = new CandleList(); + ; + if (brickSize === 0) { + console.error('Not enough data to calculate brickSize for renko when using ATR'); + return; + } + let lastOpen = 0; + let lastHigh = 0; + let lastLow = Infinity; + let lastClose = 0; + let lastVolume = 0; + let lastTimestamp = 0; + this.generator = (function* () { + let candleData = yield; + while (true) { + //Calculating first bar + if (lastOpen === 0) { + lastOpen = candleData.close; + lastHigh = candleData.high; + lastLow = candleData.low; + lastClose = candleData.close; + lastVolume = candleData.volume; + lastTimestamp = candleData.timestamp; + candleData = yield; + continue; + } + let absoluteMovementFromClose = Math.abs(candleData.close - lastClose); + let absoluteMovementFromOpen = Math.abs(candleData.close - lastOpen); + if ((absoluteMovementFromClose >= brickSize) && (absoluteMovementFromOpen >= brickSize)) { + let reference = absoluteMovementFromClose > absoluteMovementFromOpen ? lastOpen : lastClose; + let calculated = { + open: reference, + high: lastHigh > candleData.high ? lastHigh : candleData.high, + low: lastLow < candleData.Low ? lastLow : candleData.low, + close: reference > candleData.close ? (reference - brickSize) : (reference + brickSize), + volume: lastVolume + candleData.volume, + timestamp: candleData.timestamp + }; + lastOpen = calculated.open; + lastHigh = calculated.close; + lastLow = calculated.close; + lastClose = calculated.close; + lastVolume = 0; + candleData = yield calculated; + } + else { + lastHigh = lastHigh > candleData.high ? lastHigh : candleData.high; + lastLow = lastLow < candleData.Low ? lastLow : candleData.low; + lastVolume = lastVolume + candleData.volume; + lastTimestamp = candleData.timestamp; + candleData = yield; + } + } + })(); + this.generator.next(); + input.low.forEach((tick, index) => { + var result = this.generator.next({ + open: input.open[index], + high: input.high[index], + low: input.low[index], + close: input.close[index], + volume: input.volume[index], + timestamp: input.timestamp[index] + }); + if (result.value) { + this.result.open.push(result.value.open); + this.result.high.push(result.value.high); + this.result.low.push(result.value.low); + this.result.close.push(result.value.close); + this.result.volume.push(result.value.volume); + this.result.timestamp.push(result.value.timestamp); + } + }); + } + nextValue(price) { + console.error('Cannot calculate next value on Renko, Every value has to be recomputed for every change, use calcualte method'); + return null; + } + ; +} +Renko.calculate = renko; +export function renko(input) { + Indicator.reverseInputs(input); + var result = new Renko(input).result; + if (input.reversedInput) { + result.open.reverse(); + result.high.reverse(); + result.low.reverse(); + result.close.reverse(); + result.volume.reverse(); + result.timestamp.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/chart_types/TypicalPrice.js b/lib/chart_types/TypicalPrice.js new file mode 100644 index 0000000..ba86e0f --- /dev/null +++ b/lib/chart_types/TypicalPrice.js @@ -0,0 +1,43 @@ +/** + * Created by AAravindan on 5/4/16. + */ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +export class TypicalPriceInput extends IndicatorInput { +} +export class TypicalPrice extends Indicator { + constructor(input) { + super(input); + this.result = []; + this.generator = (function* () { + let priceInput = yield; + while (true) { + priceInput = yield (priceInput.high + priceInput.low + priceInput.close) / 3; + } + })(); + this.generator.next(); + input.low.forEach((tick, index) => { + var result = this.generator.next({ + high: input.high[index], + low: input.low[index], + close: input.close[index], + }); + this.result.push(result.value); + }); + } + nextValue(price) { + var result = this.generator.next(price).value; + return result; + } + ; +} +TypicalPrice.calculate = typicalprice; +export function typicalprice(input) { + Indicator.reverseInputs(input); + var result = new TypicalPrice(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/config.ts b/lib/config.ts new file mode 100644 index 0000000..a3c234d --- /dev/null +++ b/lib/config.ts @@ -0,0 +1,7 @@ +let config:any = {}; +export function setConfig(key: string, value: string) { + config[key] = value; +} +export function getConfig(key: string) { + return config[key]; +} diff --git a/lib/directionalmovement/ADX.js b/lib/directionalmovement/ADX.js new file mode 100644 index 0000000..a4e8ee8 --- /dev/null +++ b/lib/directionalmovement/ADX.js @@ -0,0 +1,105 @@ +import { WilderSmoothing } from '../moving_averages/WilderSmoothing'; +import { Indicator, IndicatorInput } from '../indicator/indicator'; +import { MDM } from './MinusDM'; +import { PDM } from './PlusDM'; +import { TrueRange } from './TrueRange'; +import { WEMA } from '../moving_averages/WEMA'; +export class ADXInput extends IndicatorInput { +} +; +export class ADXOutput extends IndicatorInput { +} +; +export 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; +export function adx(input) { + Indicator.reverseInputs(input); + var result = new ADX(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/directionalmovement/ATR.js b/lib/directionalmovement/ATR.js new file mode 100644 index 0000000..7a5c9e8 --- /dev/null +++ b/lib/directionalmovement/ATR.js @@ -0,0 +1,76 @@ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +/** + * Created by AAravindan on 5/8/16. + */ +"use strict"; +import { WEMA } from '../moving_averages/WEMA'; +import { TrueRange } from './TrueRange'; +export class ATRInput extends IndicatorInput { +} +; +export 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 = 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.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; +export function atr(input) { + Indicator.reverseInputs(input); + var result = new ATR(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/directionalmovement/MinusDM.js b/lib/directionalmovement/MinusDM.js new file mode 100644 index 0000000..615daea --- /dev/null +++ b/lib/directionalmovement/MinusDM.js @@ -0,0 +1,58 @@ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +/** + * Created by AAravindan on 5/8/16. + */ +"use strict"; +export class MDMInput extends IndicatorInput { +} +; +export 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; + } + ; +} diff --git a/lib/directionalmovement/PlusDM.js b/lib/directionalmovement/PlusDM.js new file mode 100644 index 0000000..3feb36c --- /dev/null +++ b/lib/directionalmovement/PlusDM.js @@ -0,0 +1,57 @@ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +/** + * Created by AAravindan on 5/8/16. + */ +export class PDMInput extends IndicatorInput { +} +; +export 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; + } + ; +} diff --git a/lib/directionalmovement/TrueRange.js b/lib/directionalmovement/TrueRange.js new file mode 100644 index 0000000..aa96f79 --- /dev/null +++ b/lib/directionalmovement/TrueRange.js @@ -0,0 +1,67 @@ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +/** + * Created by AAravindan on 5/8/16. + */ +/** + * Created by AAravindan on 5/8/16. + */ +"use strict"; +export class TrueRangeInput extends IndicatorInput { +} +; +export 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; +export function truerange(input) { + Indicator.reverseInputs(input); + var result = new TrueRange(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/drawingtools/fibonacci.js b/lib/drawingtools/fibonacci.js new file mode 100644 index 0000000..ae3309d --- /dev/null +++ b/lib/drawingtools/fibonacci.js @@ -0,0 +1,29 @@ +/** + * Calcaultes the fibonacci retracements for given start and end points + * + * If calculating for up trend start should be low and end should be high and vice versa + * + * returns an array of retracements level containing [0 , 23.6, 38.2, 50, 61.8, 78.6, 100, 127.2, 161.8, 261.8, 423.6] + * + * @export + * @param {number} start + * @param {number} end + * @returns {number[]} + */ +export function fibonacciretracement(start, end) { + let levels = [0, 23.6, 38.2, 50, 61.8, 78.6, 100, 127.2, 161.8, 261.8, 423.6]; + let retracements; + if (start < end) { + retracements = levels.map(function (level) { + let calculated = end - Math.abs(start - end) * (level) / 100; + return calculated > 0 ? calculated : 0; + }); + } + else { + retracements = levels.map(function (level) { + let calculated = end + Math.abs(start - end) * (level) / 100; + return calculated > 0 ? calculated : 0; + }); + } + return retracements; +} diff --git a/lib/ichimoku/IchimokuCloud.js b/lib/ichimoku/IchimokuCloud.js new file mode 100644 index 0000000..51df6c4 --- /dev/null +++ b/lib/ichimoku/IchimokuCloud.js @@ -0,0 +1,96 @@ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +import LinkedList from '../Utils/FixedSizeLinkedList'; +export class IchimokuCloudInput extends IndicatorInput { + constructor() { + super(...arguments); + this.conversionPeriod = 9; + this.basePeriod = 26; + this.spanPeriod = 52; + this.displacement = 26; + } +} +export class IchimokuCloudOutput { +} +export class IchimokuCloud extends Indicator { + constructor(input) { + super(input); + this.result = []; + var defaults = { + conversionPeriod: 9, + basePeriod: 26, + spanPeriod: 52, + displacement: 26 + }; + var params = Object.assign({}, defaults, input); + var currentConversionData = new LinkedList(params.conversionPeriod * 2, true, true, false); + var currentBaseData = new LinkedList(params.basePeriod * 2, true, true, false); + var currenSpanData = new LinkedList(params.spanPeriod * 2, true, true, false); + this.generator = (function* () { + let result; + let tick; + let period = Math.max(params.conversionPeriod, params.basePeriod, params.spanPeriod, params.displacement); + let periodCounter = 1; + tick = yield; + while (true) { + // Keep a list of lows/highs for the max period + currentConversionData.push(tick.high); + currentConversionData.push(tick.low); + currentBaseData.push(tick.high); + currentBaseData.push(tick.low); + currenSpanData.push(tick.high); + currenSpanData.push(tick.low); + if (periodCounter < period) { + periodCounter++; + } + else { + // Tenkan-sen (ConversionLine): (9-period high + 9-period low)/2)) + let conversionLine = (currentConversionData.periodHigh + currentConversionData.periodLow) / 2; + // Kijun-sen (Base Line): (26-period high + 26-period low)/2)) + let baseLine = (currentBaseData.periodHigh + currentBaseData.periodLow) / 2; + // Senkou Span A (Leading Span A): (Conversion Line + Base Line)/2)) + let spanA = (conversionLine + baseLine) / 2; + // Senkou Span B (Leading Span B): (52-period high + 52-period low)/2)) + let spanB = (currenSpanData.periodHigh + currenSpanData.periodLow) / 2; + // Senkou Span A / Senkou Span B offset by 26 periods + // if(spanCounter < params.displacement) { + // spanCounter++ + // } else { + // spanA = spanAs.shift() + // spanB = spanBs.shift() + // } + result = { + conversion: conversionLine, + base: baseLine, + spanA: spanA, + spanB: spanB + }; + } + tick = yield result; + } + })(); + this.generator.next(); + input.low.forEach((tick, index) => { + var result = this.generator.next({ + high: input.high[index], + low: input.low[index], + }); + if (result.value) { + this.result.push(result.value); + } + }); + } + nextValue(price) { + return this.generator.next(price).value; + } +} +IchimokuCloud.calculate = ichimokucloud; +export function ichimokucloud(input) { + Indicator.reverseInputs(input); + var result = new IchimokuCloud(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 0000000..478393d --- /dev/null +++ b/lib/index.ts @@ -0,0 +1,90 @@ +// import FixedSizeLinkedList from "./Utils/FixedSizeLinkedList"; +// export { CandleData, CandleList } from "./StockData"; +// export { sma, SMA } from './moving_averages/SMA'; +// export { ema, EMA } from './moving_averages/EMA'; +// export { wma, WMA } from './moving_averages/WMA'; +// export { wema, WEMA } from './moving_averages/WEMA'; +// export { macd, MACD } from './moving_averages/MACD'; +export { rsi, RSI } from './oscillators/RSI.ts'; +// export { bollingerbands, BollingerBands } from './volatility/BollingerBands'; +// export { adx, ADX } from './directionalmovement/ADX'; +// export { atr, ATR } from './directionalmovement/ATR'; +// export { truerange, TrueRange } from './directionalmovement/TrueRange'; +// export { roc, ROC } from './momentum/ROC'; +// export { kst, KST } from './momentum/KST'; +// export { psar, PSAR } from './momentum/PSAR'; +// export { stochastic, Stochastic } from './momentum/Stochastic'; +// export { williamsr, WilliamsR } from './momentum/WilliamsR'; +// export { adl, ADL } from './volume/ADL'; +// export { obv, OBV } from './volume/OBV'; +// export { trix, TRIX } from './momentum/TRIX'; +// export { forceindex, ForceIndex } from './volume/ForceIndex'; +// export { cci, CCI } from './oscillators/CCI'; +// export { awesomeoscillator, AwesomeOscillator } from './oscillators/AwesomeOscillator'; +// export { vwap, VWAP } from './volume/VWAP'; +// export { volumeprofile, VolumeProfile } from './volume/VolumeProfile'; +// export { mfi, MFI } from './volume/MFI'; +// export { stochasticrsi, StochasticRSI } from './momentum/StochasticRSI'; +// export { averagegain, AverageGain } from './Utils/AverageGain'; +// export { averageloss, AverageLoss } from './Utils/AverageLoss'; +// export { sd, SD } from './Utils/SD'; +// export { highest, Highest } from './Utils/Highest'; +// export { lowest, Lowest } from './Utils/Lowest'; +// export { sum, Sum } from './Utils/Sum'; +// export { FixedSizeLinkedList }; +// export { renko } from './chart_types/Renko'; +// export { HeikinAshi, heikinashi } from './chart_types/HeikinAshi'; +// export { bullish } from './candlestick/Bullish'; +// export { bearish } from './candlestick/Bearish'; +// export { abandonedbaby } from './candlestick/AbandonedBaby'; +// export { doji } from './candlestick/Doji'; +// export { bearishengulfingpattern } from './candlestick/BearishEngulfingPattern'; +// export { bullishengulfingpattern } from './candlestick/BullishEngulfingPattern'; +// export { darkcloudcover } from './candlestick/DarkCloudCover'; +// export { downsidetasukigap } from './candlestick/DownsideTasukiGap'; +// export { dragonflydoji } from './candlestick/DragonFlyDoji'; +// export { gravestonedoji } from './candlestick/GraveStoneDoji'; +// export { bullishharami } from './candlestick/BullishHarami'; +// export { bearishharami } from './candlestick/BearishHarami'; +// export { bullishharamicross } from './candlestick/BullishHaramiCross'; +// export { bearishharamicross } from './candlestick/BearishHaramiCross'; +// export { eveningdojistar } from './candlestick/EveningDojiStar'; +// export { eveningstar } from './candlestick/EveningStar'; +// export { morningdojistar } from './candlestick/MorningDojiStar'; +// export { morningstar } from './candlestick/MorningStar'; +// export { bullishmarubozu } from './candlestick/BullishMarubozu'; +// export { bearishmarubozu } from './candlestick/BearishMarubozu'; +// export { piercingline } from './candlestick/PiercingLine'; +// export { bullishspinningtop } from './candlestick/BullishSpinningTop'; +// export { bearishspinningtop } from './candlestick/BearishSpinningTop'; +// export { threeblackcrows } from './candlestick/ThreeBlackCrows'; +// export { threewhitesoldiers } from './candlestick/ThreeWhiteSoldiers'; +// export { bullishhammerstick } from './candlestick/BullishHammerStick'; +// export { bearishhammerstick } from './candlestick/BearishHammerStick'; +// export { bullishinvertedhammerstick } from './candlestick/BullishInvertedHammerStick'; +// export { bearishinvertedhammerstick } from './candlestick/BearishInvertedHammerStick'; +// export { hammerpattern } from './candlestick/HammerPattern'; +// export { hammerpatternunconfirmed } from './candlestick/HammerPatternUnconfirmed'; +// export { hangingman } from './candlestick/HangingMan'; +// export { hangingmanunconfirmed } from './candlestick/HangingManUnconfirmed'; +// export { shootingstar } from './candlestick/ShootingStar'; +// export { shootingstarunconfirmed } from './candlestick/ShootingStarUnconfirmed'; +// export { tweezertop } from './candlestick/TweezerTop'; +// export { tweezerbottom } from './candlestick/TweezerBottom'; +// export { fibonacciretracement } from './drawingtools/fibonacci'; + +// export { predictPattern, PatternDetector } from './patterndetection/patterndetection'; +// export { AvailablePatterns } from './patterndetection/patterndetection'; +// export { hasDoubleBottom} from './patterndetection/patterndetection'; +// export { hasDoubleTop } from './patterndetection/patterndetection'; +// export { hasHeadAndShoulder} from './patterndetection/patterndetection'; +// export { hasInverseHeadAndShoulder } from './patterndetection/patterndetection'; +// export { isTrendingUp} from './patterndetection/patterndetection'; +// export { isTrendingDown } from './patterndetection/patterndetection'; + +// export { ichimokucloud, IchimokuCloud } from './ichimoku/IchimokuCloud'; +// export { keltnerchannels, KeltnerChannels, KeltnerChannelsInput, KeltnerChannelsOutput } from './volatility/KeltnerChannels'; +// export { chandelierexit, ChandelierExit, ChandelierExitInput, ChandelierExitOutput } from './volatility/ChandelierExit'; +// export { crossUp, CrossUp } from './Utils/CrossUp'; +// export { crossDown, CrossDown } from './Utils/CrossDown'; +// export { setConfig, getConfig } from './config'; diff --git a/lib/indicator/indicator.ts b/lib/indicator/indicator.ts new file mode 100644 index 0000000..c7264e2 --- /dev/null +++ b/lib/indicator/indicator.ts @@ -0,0 +1,26 @@ +import { format as nf } from '../Utils/NumberFormatter.ts'; +export class IndicatorInput { +} +export class AllInputs { +} +export class Indicator { + format: any; + result: any; + constructor(input: { format: (v: any) => any; }) { + this.format = input.format || nf; + } + static reverseInputs(input: { reversedInput: any; values: any[]; open: any[]; high: any[]; low: any[]; close: any[]; volume: any[]; timestamp: any[]; }) { + 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; + } +} diff --git a/lib/momentum/KST.js b/lib/momentum/KST.js new file mode 100644 index 0000000..0c85a75 --- /dev/null +++ b/lib/momentum/KST.js @@ -0,0 +1,87 @@ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +import { SMA } from '../moving_averages/SMA'; +import { ROC } from './ROC'; +export class KSTInput extends IndicatorInput { +} +export class KSTOutput { +} +export 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; +export function kst(input) { + Indicator.reverseInputs(input); + var result = new KST(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/momentum/PSAR.js b/lib/momentum/PSAR.js new file mode 100644 index 0000000..0a05a41 --- /dev/null +++ b/lib/momentum/PSAR.js @@ -0,0 +1,114 @@ +import { IndicatorInput, Indicator } from '../indicator/indicator'; +"use strict"; +/* + 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. + ---------------------------------------------------------------------------------------- +*/ +export class PSARInput extends IndicatorInput { +} +; +export 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; +export function psar(input) { + Indicator.reverseInputs(input); + var result = new PSAR(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/momentum/ROC.js b/lib/momentum/ROC.js new file mode 100644 index 0000000..ae7b73a --- /dev/null +++ b/lib/momentum/ROC.js @@ -0,0 +1,55 @@ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +import LinkedList from '../Utils/FixedSizeLinkedList'; +export class ROCInput extends IndicatorInput { +} +export 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 LinkedList(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; +; +export function roc(input) { + Indicator.reverseInputs(input); + var result = new ROC(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/momentum/Stochastic.1.js b/lib/momentum/Stochastic.1.js new file mode 100644 index 0000000..8e5712c --- /dev/null +++ b/lib/momentum/Stochastic.1.js @@ -0,0 +1,93 @@ +import { IndicatorInput, Indicator } from '../indicator/indicator'; +/** + * Created by AAravindan on 5/10/16. + */ +"use strict"; +import LinkedList from '../Utils/FixedSizeLinkedList'; +import { SMA } from '../moving_averages/SMA'; +export class StochasticInput extends IndicatorInput { +} +; +export class StochasticOutput { +} +; +export 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 LinkedList(period, true, false); + let pastLowPeriods = new LinkedList(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 are same for the entire period; Bug fix for + d = dSma.nextValue(k); + tick = yield { + k: format(k), + d: (d !== undefined) ? format(d) : undefined + }; + } + })(); + 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(input) { + let nextResult = this.generator.next(input); + if (nextResult.value !== undefined) + return nextResult.value; + } + ; +} +Stochastic.calculate = stochastic; +export function stochastic(input) { + Indicator.reverseInputs(input); + var result = new Stochastic(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; +//# sourceMappingURL=Stochastic.1.js.map \ No newline at end of file diff --git a/lib/momentum/Stochastic.js b/lib/momentum/Stochastic.js new file mode 100644 index 0000000..a2fab54 --- /dev/null +++ b/lib/momentum/Stochastic.js @@ -0,0 +1,92 @@ +import { IndicatorInput, Indicator } from '../indicator/indicator'; +/** + * Created by AAravindan on 5/10/16. + */ +"use strict"; +import LinkedList from '../Utils/FixedSizeLinkedList'; +import { SMA } from '../moving_averages/SMA'; +export class StochasticInput extends IndicatorInput { +} +; +export class StochasticOutput { +} +; +export 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 LinkedList(period, true, false); + let pastLowPeriods = new LinkedList(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 are same for the entire period; Bug fix for + d = dSma.nextValue(k); + tick = yield { + k: format(k), + d: (d !== undefined) ? format(d) : undefined + }; + } + })(); + 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(input) { + let nextResult = this.generator.next(input); + if (nextResult.value !== undefined) + return nextResult.value; + } + ; +} +Stochastic.calculate = stochastic; +export function stochastic(input) { + Indicator.reverseInputs(input); + var result = new Stochastic(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/momentum/StochasticRSI.js b/lib/momentum/StochasticRSI.js new file mode 100644 index 0000000..8869331 --- /dev/null +++ b/lib/momentum/StochasticRSI.js @@ -0,0 +1,80 @@ +import { IndicatorInput, Indicator } from '../indicator/indicator'; +/** + * Created by AAravindan on 5/10/16. + */ +"use strict"; +import { SMA } from '../moving_averages/SMA'; +import { RSI } from '../oscillators/RSI'; +import { Stochastic } from '../momentum/Stochastic'; +export class StochasticRsiInput extends IndicatorInput { +} +; +export class StochasticRSIOutput { +} +; +export class StochasticRSI extends Indicator { + constructor(input) { + super(input); + let closes = input.values; + let rsiPeriod = input.rsiPeriod; + let stochasticPeriod = input.stochasticPeriod; + let kPeriod = input.kPeriod; + let dPeriod = input.dPeriod; + let format = this.format; + this.result = []; + this.generator = (function* () { + let index = 1; + let rsi = new RSI({ period: rsiPeriod, values: [] }); + let stochastic = new Stochastic({ period: stochasticPeriod, high: [], low: [], close: [], signalPeriod: kPeriod }); + let dSma = new SMA({ + period: dPeriod, + values: [], + format: (v) => { return v; } + }); + let lastRSI, stochasticRSI, d, result; + var tick = yield; + while (true) { + lastRSI = rsi.nextValue(tick); + if (lastRSI !== undefined) { + var stochasticInput = { high: lastRSI, low: lastRSI, close: lastRSI }; + stochasticRSI = stochastic.nextValue(stochasticInput); + if (stochasticRSI !== undefined && stochasticRSI.d !== undefined) { + d = dSma.nextValue(stochasticRSI.d); + if (d !== undefined) + result = { + stochRSI: stochasticRSI.k, + k: stochasticRSI.d, + d: d + }; + } + } + tick = yield result; + } + })(); + this.generator.next(); + closes.forEach((tick, index) => { + var result = this.generator.next(tick); + if (result.value !== undefined) { + this.result.push(result.value); + } + }); + } + ; + nextValue(input) { + let nextResult = this.generator.next(input); + if (nextResult.value !== undefined) + return nextResult.value; + } + ; +} +StochasticRSI.calculate = stochasticrsi; +export function stochasticrsi(input) { + Indicator.reverseInputs(input); + var result = new StochasticRSI(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/momentum/TRIX.js b/lib/momentum/TRIX.js new file mode 100644 index 0000000..826bbc5 --- /dev/null +++ b/lib/momentum/TRIX.js @@ -0,0 +1,57 @@ +/** + * Created by AAravindan on 5/9/16. + */ +"use strict"; +import { ROC } from './ROC.js'; +import { EMA } from '../moving_averages/EMA.js'; +import { Indicator, IndicatorInput } from '../indicator/indicator'; +export class TRIXInput extends IndicatorInput { +} +; +export class TRIX extends Indicator { + constructor(input) { + super(input); + let priceArray = input.values; + let period = input.period; + let format = this.format; + let ema = new EMA({ period: period, values: [], format: (v) => { return v; } }); + let emaOfema = new EMA({ period: period, values: [], format: (v) => { return v; } }); + let emaOfemaOfema = new EMA({ period: period, values: [], format: (v) => { return v; } }); + let trixROC = new ROC({ period: 1, values: [], format: (v) => { return v; } }); + this.result = []; + this.generator = (function* () { + let tick = yield; + while (true) { + let initialema = ema.nextValue(tick); + let smoothedResult = initialema ? emaOfema.nextValue(initialema) : undefined; + let doubleSmoothedResult = smoothedResult ? emaOfemaOfema.nextValue(smoothedResult) : undefined; + let result = doubleSmoothedResult ? trixROC.nextValue(doubleSmoothedResult) : undefined; + tick = yield result ? format(result) : undefined; + } + })(); + 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; + } + ; +} +TRIX.calculate = trix; +export function trix(input) { + Indicator.reverseInputs(input); + var result = new TRIX(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/momentum/WilliamsR.js b/lib/momentum/WilliamsR.js new file mode 100644 index 0000000..96806a3 --- /dev/null +++ b/lib/momentum/WilliamsR.js @@ -0,0 +1,74 @@ +import { IndicatorInput, Indicator } from '../indicator/indicator'; +import LinkedList from '../Utils/FixedSizeLinkedList'; +export class WilliamsRInput extends IndicatorInput { +} +; +export class WilliamsR extends Indicator { + constructor(input) { + super(input); + let lows = input.low; + let highs = input.high; + let closes = input.close; + let period = input.period; + let format = this.format; + if (!((lows.length === highs.length) && (highs.length === closes.length))) { + throw ('Inputs(low,high, close) not of equal size'); + } + this.result = []; + //%R = (Highest High - Close)/(Highest High - Lowest Low) * -100 + //Lowest Low = lowest low for the look-back period + //Highest High = highest high for the look-back period + //%R is multiplied by -100 correct the inversion and move the decimal. + this.generator = (function* () { + let index = 1; + let pastHighPeriods = new LinkedList(period, true, false); + let pastLowPeriods = new LinkedList(period, false, true); + let periodLow; + let periodHigh; + var tick = yield; + let williamsR; + while (true) { + pastHighPeriods.push(tick.high); + pastLowPeriods.push(tick.low); + if (index < period) { + index++; + tick = yield; + continue; + } + periodLow = pastLowPeriods.periodLow; + periodHigh = pastHighPeriods.periodHigh; + williamsR = format((periodHigh - tick.close) / (periodHigh - periodLow) * -100); + tick = yield williamsR; + } + })(); + this.generator.next(); + lows.forEach((low, 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) { + var nextResult = this.generator.next(price); + if (nextResult.value != undefined) + return this.format(nextResult.value); + } + ; +} +WilliamsR.calculate = williamsr; +export function williamsr(input) { + Indicator.reverseInputs(input); + var result = new WilliamsR(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/moving_averages/EMA.txt b/lib/moving_averages/EMA.txt new file mode 100644 index 0000000..0564821 --- /dev/null +++ b/lib/moving_averages/EMA.txt @@ -0,0 +1,54 @@ +import { Indicator } from '../indicator/indicator.ts'; +import { SMA } from './SMA.ts'; +export class EMA extends Indicator { + constructor(input) { + super(input); + var period = input.period; + var priceArray = input.values; + var exponent = (2 / (period + 1)); + var sma; + this.result = []; + sma = 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.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; +export function ema(input) { + Indicator.reverseInputs(input); + var result = new EMA(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} diff --git a/lib/moving_averages/MACD.txt b/lib/moving_averages/MACD.txt new file mode 100644 index 0000000..caa54ab --- /dev/null +++ b/lib/moving_averages/MACD.txt @@ -0,0 +1,79 @@ +/** + * Created by AAravindan on 5/4/16. + */ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +import { SMA } from './SMA'; +import { EMA } from './EMA'; +export class MACDInput extends IndicatorInput { + constructor(values) { + super(); + this.values = values; + this.SimpleMAOscillator = true; + this.SimpleMASignal = true; + } +} +export class MACDOutput { +} +export 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; +export function macd(input) { + Indicator.reverseInputs(input); + var result = new MACD(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/moving_averages/SMA.ts b/lib/moving_averages/SMA.ts new file mode 100644 index 0000000..6de1d40 --- /dev/null +++ b/lib/moving_averages/SMA.ts @@ -0,0 +1,78 @@ +// deno-lint-ignore-file +//STEP 1. Import Necessary indicator or rather last step +import { Indicator, IndicatorInput } from '../indicator/indicator.ts'; +import { LinkedList } from '../Utils/LinkedList.ts'; +//STEP 2. Create the input for the indicator, mandatory should be in the constructor +export class MAInput extends IndicatorInput { +period: any; +values: any; + constructor(period: any, values: any) { + super(); + this.period = period; + this.values = values; + } +} +//STEP3. Add class based syntax with export +interface Input { period?: any; values?: any; format?: (v: any) => any; } + +export class SMA extends Indicator { +period: any; +price: any; +generator: Generator; +static calculate: (input: { reversedInput: any; values?: any[]|undefined; open?: any[]|undefined; high?: any[]|undefined; low?: any[]|undefined; close?: any[]|undefined; volume?: any[]|undefined; timestamp?: any[]|undefined; }) => any; + + constructor(input: Input| any) { + super(input); + this.period = input.period; + this.price = input.values; + var genFn = (function* (period: number) { + var list = new LinkedList(); + var sum = 0; + var counter = 1; + // @ts-ignore + 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); + } + // @ts-ignore + current = yield result; + } + }); + this.generator = genFn(this.period); + this.generator.next(); + this.result = []; + this.price.forEach((tick: any) => { + var result = this.generator.next(tick); + if (result.value !== undefined) { + this.result.push(this.format(result.value)); + } + }); + } + nextValue(price: any) { + var result = this.generator.next(price).value; + if (result != undefined) + return this.format(result); + } + ; +} +SMA.calculate = sma; +export function sma(input: { reversedInput: any; values?: any[]; open?: any[]; high?: any[]; low?: any[]; close?: any[]; volume?: any[]; timestamp?: any[]; } | any) { + Indicator.reverseInputs(input); + var result = new SMA(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +}; +//STEP 6. Run the tests diff --git a/lib/moving_averages/WEMA.txt b/lib/moving_averages/WEMA.txt new file mode 100644 index 0000000..2a5c84e --- /dev/null +++ b/lib/moving_averages/WEMA.txt @@ -0,0 +1,54 @@ +import { Indicator } from '../indicator/indicator'; +import { SMA } from './SMA'; +export class WEMA extends Indicator { + constructor(input) { + super(input); + var period = input.period; + var priceArray = input.values; + var exponent = 1 / period; + var sma; + this.result = []; + sma = 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.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; +export function wema(input) { + Indicator.reverseInputs(input); + var result = new WEMA(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} diff --git a/lib/moving_averages/WMA.txt b/lib/moving_averages/WMA.txt new file mode 100644 index 0000000..7be3cec --- /dev/null +++ b/lib/moving_averages/WMA.txt @@ -0,0 +1,55 @@ +"use strict"; +import { Indicator } from '../indicator/indicator'; +import { LinkedList } from '../Utils/LinkedList'; +export 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; +; +export function wma(input) { + Indicator.reverseInputs(input); + var result = new WMA(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} diff --git a/lib/moving_averages/WilderSmoothing.txt b/lib/moving_averages/WilderSmoothing.txt new file mode 100644 index 0000000..bb2321b --- /dev/null +++ b/lib/moving_averages/WilderSmoothing.txt @@ -0,0 +1,60 @@ +import { Indicator } from '../indicator/indicator'; +import { LinkedList } from '../Utils/LinkedList'; +//STEP3. Add class based syntax with export +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; +export 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 diff --git a/lib/oscillators/AwesomeOscillator.ts b/lib/oscillators/AwesomeOscillator.ts new file mode 100644 index 0000000..63d444e --- /dev/null +++ b/lib/oscillators/AwesomeOscillator.ts @@ -0,0 +1,68 @@ +// deno-lint-ignore-file +import { Indicator, IndicatorInput } from '../indicator/indicator.ts'; +import { SMA } from '../moving_averages/SMA.ts'; +export class AwesomeOscillatorInput extends IndicatorInput { +} +export class AwesomeOscillator extends Indicator { +generator: Generator; +static calculate: (input: { reversedInput: any; values?: any[]|undefined; open?: any[]|undefined; high?: any[]|undefined; low?: any[]|undefined; close?: any[]|undefined; volume?: any[]|undefined; timestamp?: any[]|undefined; }) => any; + constructor(input: { high?: any; low?: any; fastPeriod?: any; slowPeriod?: any; format?: (v: any) => any; } | any) { + super(input); + var highs = input.high; + var lows = input.low; + var fastPeriod = input.fastPeriod; + var slowPeriod = input.slowPeriod; + var slowSMA = new SMA({ values: [], period: slowPeriod }); + var fastSMA = new SMA({ values: [], period: fastPeriod }); + this.result = []; + this.generator = (function* () { + var result; + var tick; + var medianPrice; + var slowSmaValue; + var fastSmaValue; + // @ts-ignore + tick = yield; + while (true) { + medianPrice = (tick.high + tick.low) / 2; + slowSmaValue = slowSMA.nextValue(medianPrice); + fastSmaValue = fastSMA.nextValue(medianPrice); + if (slowSmaValue !== undefined && fastSmaValue !== undefined) { + result = fastSmaValue - slowSmaValue; + } + // @ts-ignore + tick = yield result; + } + })(); + this.generator.next(); + highs.forEach((tickHigh: any, index: string|number) => { + var tickInput = { + high: tickHigh, + low: lows[index], + }; + var result = this.generator.next(tickInput); + if (result.value != undefined) { + this.result.push(this.format(result.value)); + } + }); + } + ; + nextValue(price: any) { + var result = this.generator.next(price); + if (result.value != undefined) { + return this.format(result.value); + } + } + ; +} +AwesomeOscillator.calculate = awesomeoscillator; +export function awesomeoscillator(input: { reversedInput: any; values?: any[]; open?: any[]; high?: any[]; low?: any[]; close?: any[]; volume?: any[]; timestamp?: any[]; } | any) { + Indicator.reverseInputs(input); + var result = new AwesomeOscillator(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/oscillators/CCI.ts b/lib/oscillators/CCI.ts new file mode 100644 index 0000000..6fe3b36 --- /dev/null +++ b/lib/oscillators/CCI.ts @@ -0,0 +1,78 @@ +// deno-lint-ignore-file +import { Indicator, IndicatorInput } from '../indicator/indicator.ts'; +import { SMA } from '../moving_averages/SMA.ts'; +import LinkedList from '../Utils/FixedSizeLinkedList.ts'; +export class CCIInput extends IndicatorInput {}; +export class CCI extends Indicator { +generator: Generator; +static calculate: (input: any) => any; + constructor(input: { low?: any; high?: any; close?: any; period?: any; format?: (v: any) => any; } | any) { + super(input); + var lows = input.low; + var highs = input.high; + var closes = input.close; + var period = input.period; + var format = this.format; + let constant = .015; + var currentTpSet = new LinkedList(period); + ; + var tpSMACalculator = new SMA({ period: period, values: [], format: (v: any) => { return v; } }); + if (!((lows.length === highs.length) && (highs.length === closes.length))) { + throw ('Inputs(low,high, close) not of equal size'); + } + this.result = []; + this.generator = (function* () { + // @ts-ignore + var tick = yield; + while (true) { + let tp = (tick.high + tick.low + tick.close) / 3; + currentTpSet.push(tp); + let smaTp = tpSMACalculator.nextValue(tp); + let meanDeviation = null; + let cci; + let sum = 0; + if (smaTp != undefined) { + //First, subtract the most recent 20-period average of the typical price from each period's typical price. + //Second, take the absolute values of these numbers. + //Third,sum the absolute values. + for (let x of currentTpSet.iterator()) { + sum = sum + (Math.abs(x - smaTp)); + } + //Fourth, divide by the total number of periods (20). + meanDeviation = sum / period; + cci = (tp - smaTp) / (constant * meanDeviation); + } + // @ts-ignore + tick = yield cci; + } + })(); + this.generator.next(); + lows.forEach((_: any, index: string|number) => { + 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: any) { + let result = this.generator.next(price).value; + if (result != undefined) { + return result; + } + } +} +CCI.calculate = cci; +export function cci(input: { reversedInput: any; values?: any[]; open?: any[]; high?: any[]; low?: any[]; close?: any[]; volume?: any[]; timestamp?: any[]; } | any) { + Indicator.reverseInputs(input); + var result = new CCI(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/oscillators/RSI.ts b/lib/oscillators/RSI.ts new file mode 100644 index 0000000..0fd24db --- /dev/null +++ b/lib/oscillators/RSI.ts @@ -0,0 +1,70 @@ +// deno-lint-ignore-file +/** + * Created by AAravindan on 5/5/16. + */ +import { Indicator, IndicatorInput } from '../indicator/indicator.ts'; +import { AverageGain } from '../Utils/AverageGain.ts'; +import { AverageLoss } from '../Utils/AverageLoss.ts'; +export class RSIInput extends IndicatorInput { +} +export class RSI extends Indicator { +generator: Generator; +static calculate: (input: { reversedInput: any; values?: any[]|undefined; open?: any[]|undefined; high?: any[]|undefined; low?: any[]|undefined; close?: any[]|undefined; volume?: any[]|undefined; timestamp?: any[]|undefined; }) => any; + constructor(input: { period?: any; values?: any; format?: (v: any) => any; } | any) { + 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) { + // @ts-ignore + 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++; + // @ts-ignore + current = yield currentRSI; + } + })(period); + this.generator.next(); + this.result = []; + values.forEach((tick: any) => { + var result = this.generator.next(tick); + if (result.value !== undefined) { + this.result.push(result.value); + } + }); + } + ; + nextValue(price: any) { + return this.generator.next(price).value; + } + ; +} +RSI.calculate = rsi; +export function rsi(input: { reversedInput: any; values?: any[]; open?: any[]; high?: any[]; low?: any[]; close?: any[]; volume?: any[]; timestamp?: any[]; } | any) { + Indicator.reverseInputs(input); + var result = new RSI(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/patterndetection/patterndetection.js b/lib/patterndetection/patterndetection.js new file mode 100644 index 0000000..0de4e69 --- /dev/null +++ b/lib/patterndetection/patterndetection.js @@ -0,0 +1,165 @@ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +import { Indicator, IndicatorInput } from '../indicator/indicator'; +// import * as tf from '@tensorflow/tfjs'; +var isNodeEnvironment = false; +var model; +var oneHotMap = ['IHS', 'TU', 'DB', 'HS', 'TD', 'DT']; +var tf; +try { + isNodeEnvironment = Object.prototype.toString.call(global.process) === '[object process]'; +} +catch (e) { } +export class PatternDetectorInput extends IndicatorInput { + constructor(values) { + super(); + this.values = values; + } +} +export var AvailablePatterns; +(function (AvailablePatterns) { + AvailablePatterns[AvailablePatterns["IHS"] = 0] = "IHS"; + AvailablePatterns[AvailablePatterns["TU"] = 1] = "TU"; + AvailablePatterns[AvailablePatterns["DB"] = 2] = "DB"; + AvailablePatterns[AvailablePatterns["HS"] = 3] = "HS"; + AvailablePatterns[AvailablePatterns["TD"] = 4] = "TD"; + AvailablePatterns[AvailablePatterns["DT"] = 5] = "DT"; +})(AvailablePatterns || (AvailablePatterns = {})); +function interpolateArray(data, fitCount) { + var linearInterpolate = function (before, after, atPoint) { + return before + (after - before) * atPoint; + }; + var newData = new Array(); + var springFactor = new Number((data.length - 1) / (fitCount - 1)); + newData[0] = data[0]; // for new allocation + for (var i = 1; i < fitCount - 1; i++) { + var tmp = i * springFactor; + var before = new Number(Math.floor(tmp)).toFixed(); + var after = new Number(Math.ceil(tmp)).toFixed(); + var atPoint = tmp - before; + newData[i] = linearInterpolate(data[before], data[after], atPoint); + } + newData[fitCount - 1] = data[data.length - 1]; // for new allocation + return newData; +} +; +function l2Normalize(arr) { + var sum = arr.reduce((cum, value) => { return cum + (value * value); }, 0); + var norm = Math.sqrt(sum); + return arr.map((v) => v / norm); +} +export class PatternDetectorOutput { +} +var modelLoaded = false; +var laodingModel = false; +var loadingPromise; +function loadModel() { + return __awaiter(this, void 0, void 0, function* () { + if (modelLoaded) + return Promise.resolve(true); + if (laodingModel) + return loadingPromise; + laodingModel = true; + loadingPromise = new Promise(function (resolve, reject) { + return __awaiter(this, void 0, void 0, function* () { + if (isNodeEnvironment) { + tf = require('@tensorflow/tfjs'); + console.log('Nodejs Environment detected '); + var tfnode = require('@tensorflow/tfjs-node'); + var modelPath = require('path').resolve(__dirname, '../tf_model/model.json'); + model = yield tf.loadModel(tfnode.io.fileSystem(modelPath)); + } + else { + if (typeof window.tf == "undefined") { + modelLoaded = false; + laodingModel = false; + console.log('Tensorflow js not imported, pattern detection may not work'); + resolve(); + return; + } + tf = window.tf; + console.log('Browser Environment detected ', tf); + console.log('Loading model ....'); + model = yield tf.loadModel('/tf_model/model.json'); + modelLoaded = true; + laodingModel = false; + setTimeout(resolve, 1000); + console.log('Loaded model'); + return; + } + modelLoaded = true; + laodingModel = false; + resolve(); + return; + }); + }); + yield loadingPromise; + return; + }); +} +loadModel(); +export function predictPattern(input) { + return __awaiter(this, void 0, void 0, function* () { + yield loadModel(); + if (input.values.length < 300) { + console.warn('Pattern detector requires atleast 300 data points for a reliable prediction, received just ', input.values.length); + } + Indicator.reverseInputs(input); + var values = input.values; + var output = yield model.predict(tf.tensor2d([l2Normalize(interpolateArray(values, 400))])); + var index = tf.argMax(output, 1).get(0); + Indicator.reverseInputs(input); + return { patternId: index, pattern: oneHotMap[index], probability: output.get(0, 4) * 100 }; + }); +} +export function hasDoubleBottom(input) { + return __awaiter(this, void 0, void 0, function* () { + var result = yield predictPattern(input); + return (result.patternId === AvailablePatterns.DB); + }); +} +export function hasDoubleTop(input) { + return __awaiter(this, void 0, void 0, function* () { + var result = yield predictPattern(input); + return (result.patternId === AvailablePatterns.DT); + }); +} +export function hasHeadAndShoulder(input) { + return __awaiter(this, void 0, void 0, function* () { + var result = yield predictPattern(input); + return (result.patternId === AvailablePatterns.HS); + }); +} +export function hasInverseHeadAndShoulder(input) { + return __awaiter(this, void 0, void 0, function* () { + var result = yield predictPattern(input); + return (result.patternId === AvailablePatterns.IHS); + }); +} +export function isTrendingUp(input) { + return __awaiter(this, void 0, void 0, function* () { + var result = yield predictPattern(input); + return (result.patternId === AvailablePatterns.TU); + }); +} +export function isTrendingDown(input) { + return __awaiter(this, void 0, void 0, function* () { + var result = yield predictPattern(input); + return (result.patternId === AvailablePatterns.TD); + }); +} +export class PatternDetector extends Indicator { +} +PatternDetector.predictPattern = predictPattern; +PatternDetector.hasDoubleBottom = hasDoubleBottom; +PatternDetector.hasDoubleTop = hasDoubleTop; +PatternDetector.hasHeadAndShoulder = hasHeadAndShoulder; +PatternDetector.hasInverseHeadAndShoulder = hasInverseHeadAndShoulder; +PatternDetector.isTrendingUp = isTrendingUp; +PatternDetector.isTrendingDown = isTrendingDown; diff --git a/lib/volatility/BollingerBands.js b/lib/volatility/BollingerBands.js new file mode 100644 index 0000000..42b930f --- /dev/null +++ b/lib/volatility/BollingerBands.js @@ -0,0 +1,69 @@ +"use strict"; +import { Indicator, IndicatorInput } from '../indicator/indicator'; +import { SMA } from '../moving_averages/SMA'; +import { SD } from '../Utils/SD'; +export class BollingerBandsInput extends IndicatorInput { +} +; +export class BollingerBandsOutput extends IndicatorInput { +} +; +export 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, sd; + this.result = []; + sma = new SMA({ period: period, values: [], format: (v) => { return v; } }); + sd = 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.nextValue(tick); + calcsd = sd.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; +export function bollingerbands(input) { + Indicator.reverseInputs(input); + var result = new BollingerBands(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/volatility/ChandelierExit.js b/lib/volatility/ChandelierExit.js new file mode 100644 index 0000000..665ba59 --- /dev/null +++ b/lib/volatility/ChandelierExit.js @@ -0,0 +1,73 @@ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +import { ATR } from '../directionalmovement/ATR'; +import LinkedList from '../Utils/FixedSizeLinkedList'; +export class ChandelierExitInput extends IndicatorInput { + constructor() { + super(...arguments); + this.period = 22; + this.multiplier = 3; + } +} +export class ChandelierExitOutput extends IndicatorInput { +} +; +export class ChandelierExit extends Indicator { + constructor(input) { + super(input); + var highs = input.high; + var lows = input.low; + var closes = input.close; + this.result = []; + var atrProducer = new ATR({ period: input.period, high: [], low: [], close: [], format: (v) => { return v; } }); + var dataCollector = new LinkedList(input.period * 2, true, true, false); + this.generator = (function* () { + var result; + var tick = yield; + var atr; + while (true) { + var { high, low } = tick; + dataCollector.push(high); + dataCollector.push(low); + atr = atrProducer.nextValue(tick); + if ((dataCollector.totalPushed >= (2 * input.period)) && atr != undefined) { + result = { + exitLong: dataCollector.periodHigh - atr * input.multiplier, + exitShort: dataCollector.periodLow + atr * input.multiplier + }; + } + tick = yield result; + } + })(); + this.generator.next(); + highs.forEach((tickHigh, index) => { + var tickInput = { + high: tickHigh, + low: lows[index], + close: closes[index], + }; + var result = this.generator.next(tickInput); + if (result.value != undefined) { + this.result.push(result.value); + } + }); + } + ; + nextValue(price) { + var result = this.generator.next(price); + if (result.value != undefined) { + return result.value; + } + } + ; +} +ChandelierExit.calculate = chandelierexit; +export function chandelierexit(input) { + Indicator.reverseInputs(input); + var result = new ChandelierExit(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/volatility/KeltnerChannels.js b/lib/volatility/KeltnerChannels.js new file mode 100644 index 0000000..cf24e55 --- /dev/null +++ b/lib/volatility/KeltnerChannels.js @@ -0,0 +1,76 @@ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +import { SMA } from '../moving_averages/SMA'; +import { EMA } from '../moving_averages/EMA'; +import { ATR } from '../directionalmovement/ATR'; +export class KeltnerChannelsInput extends IndicatorInput { + constructor() { + super(...arguments); + this.maPeriod = 20; + this.atrPeriod = 10; + this.useSMA = false; + this.multiplier = 1; + } +} +export class KeltnerChannelsOutput extends IndicatorInput { +} +; +export class KeltnerChannels extends Indicator { + constructor(input) { + super(input); + var maType = input.useSMA ? SMA : EMA; + var maProducer = new maType({ period: input.maPeriod, values: [], format: (v) => { return v; } }); + var atrProducer = new ATR({ period: input.atrPeriod, high: [], low: [], close: [], format: (v) => { return v; } }); + var tick; + this.result = []; + this.generator = (function* () { + var KeltnerChannelsOutput; + var result; + tick = yield; + while (true) { + var { close } = tick; + var ma = maProducer.nextValue(close); + var atr = atrProducer.nextValue(tick); + if (ma != undefined && atr != undefined) { + result = { + middle: ma, + upper: ma + (input.multiplier * (atr)), + lower: ma - (input.multiplier * (atr)) + }; + } + tick = yield result; + } + })(); + this.generator.next(); + var highs = input.high; + highs.forEach((tickHigh, index) => { + var tickInput = { + high: tickHigh, + low: input.low[index], + close: input.close[index], + }; + var result = this.generator.next(tickInput); + if (result.value != undefined) { + this.result.push(result.value); + } + }); + } + ; + nextValue(price) { + var result = this.generator.next(price); + if (result.value != undefined) { + return result.value; + } + } + ; +} +KeltnerChannels.calculate = keltnerchannels; +export function keltnerchannels(input) { + Indicator.reverseInputs(input); + var result = new KeltnerChannels(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/volume/ADL.js b/lib/volume/ADL.js new file mode 100644 index 0000000..c5fc04c --- /dev/null +++ b/lib/volume/ADL.js @@ -0,0 +1,60 @@ +/** + * Created by AAravindan on 5/17/16. + */ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +export class ADLInput extends IndicatorInput { +} +export class ADL extends Indicator { + constructor(input) { + super(input); + var highs = input.high; + var lows = input.low; + var closes = input.close; + var volumes = input.volume; + if (!((lows.length === highs.length) && (highs.length === closes.length) && (highs.length === volumes.length))) { + throw ('Inputs(low,high, close, volumes) not of equal size'); + } + this.result = []; + this.generator = (function* () { + var result = 0; + var tick; + tick = yield; + while (true) { + let moneyFlowMultiplier = ((tick.close - tick.low) - (tick.high - tick.close)) / (tick.high - tick.low); + moneyFlowMultiplier = isNaN(moneyFlowMultiplier) ? 1 : moneyFlowMultiplier; + let moneyFlowVolume = moneyFlowMultiplier * tick.volume; + result = result + moneyFlowVolume; + tick = yield Math.round(result); + } + })(); + this.generator.next(); + highs.forEach((tickHigh, index) => { + var tickInput = { + high: tickHigh, + low: lows[index], + close: closes[index], + volume: volumes[index] + }; + var result = this.generator.next(tickInput); + if (result.value != undefined) { + this.result.push(result.value); + } + }); + } + ; + nextValue(price) { + return this.generator.next(price).value; + } + ; +} +ADL.calculate = adl; +export function adl(input) { + Indicator.reverseInputs(input); + var result = new ADL(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/volume/ForceIndex.js b/lib/volume/ForceIndex.js new file mode 100644 index 0000000..abac138 --- /dev/null +++ b/lib/volume/ForceIndex.js @@ -0,0 +1,62 @@ +import { EMA } from '../moving_averages/EMA'; +import { Indicator, IndicatorInput } from '../indicator/indicator'; +export class ForceIndexInput extends IndicatorInput { + constructor() { + super(...arguments); + this.period = 1; + } +} +; +export class ForceIndex extends Indicator { + constructor(input) { + super(input); + var closes = input.close; + var volumes = input.volume; + var period = input.period || 1; + if (!((volumes.length === closes.length))) { + throw ('Inputs(volume, close) not of equal size'); + } + let emaForceIndex = new EMA({ values: [], period: period }); + this.result = []; + this.generator = (function* () { + var previousTick = yield; + var tick = yield; + let forceIndex; + while (true) { + forceIndex = (tick.close - previousTick.close) * tick.volume; + previousTick = tick; + tick = yield emaForceIndex.nextValue(forceIndex); + } + })(); + this.generator.next(); + volumes.forEach((tick, index) => { + var result = this.generator.next({ + close: closes[index], + volume: volumes[index] + }); + if (result.value != undefined) { + this.result.push(result.value); + } + }); + } + ; + ; + nextValue(price) { + let result = this.generator.next(price).value; + if (result != undefined) { + return result; + } + } + ; +} +ForceIndex.calculate = forceindex; +export function forceindex(input) { + Indicator.reverseInputs(input); + var result = new ForceIndex(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/volume/MFI.js b/lib/volume/MFI.js new file mode 100644 index 0000000..0a0c87f --- /dev/null +++ b/lib/volume/MFI.js @@ -0,0 +1,91 @@ +/** + * Created by AAravindan on 5/17/16. + */ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +import { TypicalPrice } from '../chart_types/TypicalPrice'; +import FixedSizeLinkedList from '../Utils/FixedSizeLinkedList'; +export class MFIInput extends IndicatorInput { +} +export class MFI extends Indicator { + constructor(input) { + super(input); + var highs = input.high; + var lows = input.low; + var closes = input.close; + var volumes = input.volume; + var period = input.period; + var typicalPrice = new TypicalPrice({ low: [], high: [], close: [] }); + var positiveFlow = new FixedSizeLinkedList(period, false, false, true); + var negativeFlow = new FixedSizeLinkedList(period, false, false, true); + if (!((lows.length === highs.length) && (highs.length === closes.length) && (highs.length === volumes.length))) { + throw ('Inputs(low,high, close, volumes) not of equal size'); + } + this.result = []; + this.generator = (function* () { + var result; + var tick; + var lastClose; + var positiveFlowForPeriod; + var rawMoneyFlow = 0; + var moneyFlowRatio; + var negativeFlowForPeriod; + let typicalPriceValue = null; + let prevousTypicalPrice = null; + tick = yield; + lastClose = tick.close; //Fist value + tick = yield; + while (true) { + var { high, low, close, volume } = tick; + var positionMoney = 0; + var negativeMoney = 0; + typicalPriceValue = typicalPrice.nextValue({ high, low, close }); + rawMoneyFlow = typicalPriceValue * volume; + if ((typicalPriceValue != null) && (prevousTypicalPrice != null)) { + typicalPriceValue > prevousTypicalPrice ? positionMoney = rawMoneyFlow : negativeMoney = rawMoneyFlow; + positiveFlow.push(positionMoney); + negativeFlow.push(negativeMoney); + positiveFlowForPeriod = positiveFlow.periodSum; + negativeFlowForPeriod = negativeFlow.periodSum; + if ((positiveFlow.totalPushed >= period) && (positiveFlow.totalPushed >= period)) { + moneyFlowRatio = positiveFlowForPeriod / negativeFlowForPeriod; + result = 100 - 100 / (1 + moneyFlowRatio); + } + } + prevousTypicalPrice = typicalPriceValue; + tick = yield result; + } + })(); + this.generator.next(); + highs.forEach((tickHigh, index) => { + var tickInput = { + high: tickHigh, + low: lows[index], + close: closes[index], + volume: volumes[index] + }; + var result = this.generator.next(tickInput); + if (result.value != undefined) { + this.result.push(parseFloat(result.value.toFixed(2))); + } + }); + } + ; + nextValue(price) { + var result = this.generator.next(price); + if (result.value != undefined) { + return (parseFloat(result.value.toFixed(2))); + } + } + ; +} +MFI.calculate = mfi; +export function mfi(input) { + Indicator.reverseInputs(input); + var result = new MFI(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/volume/OBV.js b/lib/volume/OBV.js new file mode 100644 index 0000000..ae3ceb2 --- /dev/null +++ b/lib/volume/OBV.js @@ -0,0 +1,61 @@ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +/** + * Created by AAravindan on 5/17/16. + */ +"use strict"; +export class OBVInput extends IndicatorInput { +} +export class OBV extends Indicator { + constructor(input) { + super(input); + var closes = input.close; + var volumes = input.volume; + this.result = []; + this.generator = (function* () { + var result = 0; + var tick; + var lastClose; + tick = yield; + if (tick.close && (typeof tick.close === 'number')) { + lastClose = tick.close; + tick = yield; + } + while (true) { + if (lastClose < tick.close) { + result = result + tick.volume; + } + else if (tick.close < lastClose) { + result = result - tick.volume; + } + lastClose = tick.close; + tick = yield result; + } + })(); + this.generator.next(); + closes.forEach((close, index) => { + let tickInput = { + close: closes[index], + volume: volumes[index] + }; + let result = this.generator.next(tickInput); + if (result.value != undefined) { + this.result.push(result.value); + } + }); + } + nextValue(price) { + return this.generator.next(price).value; + } + ; +} +OBV.calculate = obv; +export function obv(input) { + Indicator.reverseInputs(input); + var result = new OBV(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/volume/VWAP.js b/lib/volume/VWAP.js new file mode 100644 index 0000000..18a3c01 --- /dev/null +++ b/lib/volume/VWAP.js @@ -0,0 +1,63 @@ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +export class VWAPInput extends IndicatorInput { +} +; +export class VWAP extends Indicator { + constructor(input) { + super(input); + var lows = input.low; + var highs = input.high; + var closes = input.close; + var volumes = input.volume; + var format = this.format; + if (!((lows.length === highs.length) && (highs.length === closes.length))) { + throw ('Inputs(low,high, close) not of equal size'); + } + this.result = []; + this.generator = (function* () { + var tick = yield; + let cumulativeTotal = 0; + let cumulativeVolume = 0; + while (true) { + let typicalPrice = (tick.high + tick.low + tick.close) / 3; + let total = tick.volume * typicalPrice; + cumulativeTotal = cumulativeTotal + total; + cumulativeVolume = cumulativeVolume + tick.volume; + tick = yield cumulativeTotal / cumulativeVolume; + ; + } + })(); + this.generator.next(); + lows.forEach((tick, index) => { + var result = this.generator.next({ + high: highs[index], + low: lows[index], + close: closes[index], + volume: volumes[index] + }); + if (result.value != undefined) { + this.result.push(result.value); + } + }); + } + ; + ; + nextValue(price) { + let result = this.generator.next(price).value; + if (result != undefined) { + return result; + } + } + ; +} +VWAP.calculate = vwap; +export function vwap(input) { + Indicator.reverseInputs(input); + var result = new VWAP(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +; diff --git a/lib/volume/VolumeProfile.js b/lib/volume/VolumeProfile.js new file mode 100644 index 0000000..0b90ce0 --- /dev/null +++ b/lib/volume/VolumeProfile.js @@ -0,0 +1,70 @@ +import { Indicator, IndicatorInput } from '../indicator/indicator'; +export class VolumeProfileInput extends IndicatorInput { +} +export class VolumeProfileOutput { +} +export function priceFallsBetweenBarRange(low, high, low1, high1) { + return (low <= low1 && high >= low1) || (low1 <= low && high1 >= low); +} +export class VolumeProfile extends Indicator { + constructor(input) { + super(input); + var highs = input.high; + var lows = input.low; + var closes = input.close; + var opens = input.open; + var volumes = input.volume; + var bars = input.noOfBars; + if (!((lows.length === highs.length) && (highs.length === closes.length) && (highs.length === volumes.length))) { + throw ('Inputs(low,high, close, volumes) not of equal size'); + } + this.result = []; + var max = Math.max(...highs, ...lows, ...closes, ...opens); + var min = Math.min(...highs, ...lows, ...closes, ...opens); + var barRange = (max - min) / bars; + var lastEnd = min; + for (let i = 0; i < bars; i++) { + let rangeStart = lastEnd; + let rangeEnd = rangeStart + barRange; + lastEnd = rangeEnd; + let bullishVolume = 0; + let bearishVolume = 0; + let totalVolume = 0; + for (let priceBar = 0; priceBar < highs.length; priceBar++) { + let priceBarStart = lows[priceBar]; + let priceBarEnd = highs[priceBar]; + let priceBarOpen = opens[priceBar]; + let priceBarClose = closes[priceBar]; + let priceBarVolume = volumes[priceBar]; + if (priceFallsBetweenBarRange(rangeStart, rangeEnd, priceBarStart, priceBarEnd)) { + totalVolume = totalVolume + priceBarVolume; + if (priceBarOpen > priceBarClose) { + bearishVolume = bearishVolume + priceBarVolume; + } + else { + bullishVolume = bullishVolume + priceBarVolume; + } + } + } + this.result.push({ + rangeStart, rangeEnd, bullishVolume, bearishVolume, totalVolume + }); + } + } + ; + nextValue(price) { + throw ('Next value not supported for volume profile'); + } + ; +} +VolumeProfile.calculate = volumeprofile; +export function volumeprofile(input) { + Indicator.reverseInputs(input); + var result = new VolumeProfile(input).result; + if (input.reversedInput) { + result.reverse(); + } + Indicator.reverseInputs(input); + return result; +} +;