ma_crossover/events/data_managed_candles.js

'use strict'

const _isFinite = require('lodash/isFinite')
const _reverse = require('lodash/reverse')

/**
 * If the instance has internal indicators, they are either seeded with the
 * initial candle dataset or updated with new candles as they arrive. The
 * candle dataset is saved on the instance state for order generation.
 *
 * Indicator values are calculated, and if they have crossed the configured
 * atomic order is submitted, and the `'exec:stop`' event is emitted to
 * stop execution and trigger teardown.
 *
 * @memberOf module:MACrossover
 * @listens AOHost~dataManagedCandles
 * @param {AOInstance} instance - AO instance
 * @param {object[]} candles - incoming candles
 * @param {EventMetaInformation} meta - source channel information
 * @returns {Promise} p - resolves on completion
 */
const onDataManagedCandles = async (instance = {}, candles, meta) => {
  const { state = {}, h = {} } = instance
  const { args = {}, longIndicator, shortIndicator } = state
  const { symbol, long, short } = args
  const { debug, updateState, emitSelf, emit } = h
  const { chanFilter } = meta
  const { key } = chanFilter
  const chanDetails = key.split(':')
  const chanTF = chanDetails[1]
  const chanSymbol = chanDetails[2]

  if (symbol !== chanSymbol) {
    return
  }

  let indicatorsUpdated = false
  const [lastCandle] = candles

  if (chanTF === long.candleTimeFrame) {
    indicatorsUpdated = true

    if (longIndicator.l() === 0) {
      debug('seeding long indicator with %d candle prices', candles.length)
      const orderedCandles = _reverse(candles)

      orderedCandles.forEach((candle) => {
        longIndicator.add(candle[long.candlePrice])
      })
    } else {
      const price = lastCandle[long.candlePrice]
      debug('updating long indicator with candle price %f [%j]', price, lastCandle)

      if (!state.lastCandleLong) {
        longIndicator.add(price)
      } else if (state.lastCandleLong.mts === lastCandle.mts) {
        longIndicator.update(price)
      } else {
        longIndicator.add(price)
      }
    }

    await updateState(instance, { lastCandleLong: lastCandle })
  }

  if (chanTF === short.candleTimeFrame) {
    indicatorsUpdated = true

    if (shortIndicator.l() === 0) {
      debug('seeding short indicator with %d candle prices', candles.length)
      const orderedCandles = _reverse(candles)

      orderedCandles.forEach((candle) => {
        shortIndicator.add(candle[short.candlePrice])
      })
    } else {
      const price = lastCandle[short.candlePrice]
      debug('updating short indicator with candle price %f [%j]', price, lastCandle)

      if (!state.lastCandleShort) {
        shortIndicator.add(price)
      } else if (state.lastCandleShort.mts === lastCandle.mts) {
        shortIndicator.update(price)
      } else {
        shortIndicator.add(price)
      }
    }

    await updateState(instance, { lastCandleShort: lastCandle })
  }

  if (indicatorsUpdated) {
    const longV = longIndicator.v()
    const shortV = shortIndicator.v()

    if (_isFinite(shortV) && _isFinite(longV) && (
      shortIndicator.crossed(longV)
    )) {
      await emitSelf('submit_order')
      await emit('exec:stop')
    }
  }
}

module.exports = onDataManagedCandles