data/cl.data.js

/**
 * Copyright (c) 2018-present clchart Contributors.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

// 以下是ClData的实体定义
// 一般只用操作这个类就可以获取单个股票的所有数据
// 不支持多个股票的数据,对于列表来说,这里只保存ID列表,其他的数据由实际画图自行获取,

import {
  fromTradeTimeToIndex,
  fromIndexToTradeTime,
  checkZero,
  getSize,
  checkDayZero,
  checkDay5,
  updateStatic,
  findDateInDay,
  findIndexInMin,
  matchDayToWeek,
  matchDayToMon,
  getMinuteCount,
  transExrightMin,
  transExrightDay
} from './cl.data.tools'
import {
  FIELD_INFO,
  FIELD_DAY,
  FIELD_MIN,
  FIELD_TICK,
  FIELD_DAY5,
  FIELD_NOW,
  FIELD_ILINE,
  FIELD_ENOW
} from '../cl.data.def'
import {
  getDate,
  isEmptyArray,
  copyArrayOfDeep
} from '../util/cl.tool'
import {
  ClFormula
} from '../formula/cl.formula'
// 只保存一只股票的信息,当前日期,开收市时间

/**
 * Class representing ClData
 * data layer
 * @export
 * @class ClData
 */
export default class ClData {
  /**
   * Creates an instance of ClData.

   */
  constructor () {
    // this.formula = new ClFormula();
    this.static = {
      stktype: 1,
      volunit: 100,
      coinunit: 100,
      decimal: 2,
      before: 1000,
      stophigh: 1100,
      stoplow: 900
    }
  }

  // 只保存一只股票的信息,当前日期,开收市时间
  /**
   * init data
   * @param {Number} tradeDate
   * @param {Number} tradetime
   * @memberof ClData
   */
  initData (tradeDate, tradetime) {
    this.formula = new ClFormula()
    this.clearData()
    if (tradetime === undefined) {
      this.tradeTime = [{
        begin: 930,
        end: 1130
      },
      {
        begin: 1300,
        end: 1500
      }
      ]
    } else {
      this.tradeTime = tradetime
    }

    if (tradeDate === undefined) {
      this.tradeDate = getDate() // 得到当天的日期
    } else {
      this.tradeDate = tradeDate // 最近一个交易日期,并不一定等于今天的日期,比如节假日期间
    }
  }
  // 包含一个股票所有的数据,以便于以后做公式系统也使用这个数据定义
  /**
   * clear data
   * @memberof ClData
   */
  clearData () {
    this.InData = [] // 数据 json格式数据 {key:..,fields:.., value:[[],[]...]}
    this.OutData = [] // 专门用于获取数据时临时产生的数据
  }
  // 下面是设置数据的方法
  /**
   * set data
   * @param {String} key
   * @param {Object} fields
   * @param {Array} value
   * @memberof ClData
   */
  setData (key, fields, value) {
    if (value === undefined) value = []
    if (this.InData[key] === undefined) this.InData[key] = {}
    switch (key) {
      case 'DAY5':
        value = checkDay5(value, this.static.coinzoom, this.tradeDate, this.tradeTime)
        break
      case 'NOW':
      case 'ENOW':
        this.flushNowData(key, value)
        break
      case 'MIN':
      case 'DAY':
        value = checkDayZero(value, this.tradeDate)
        break
      case 'INFO':
        this.static = updateStatic(FIELD_INFO, value)
        break
    }
    // // 设置了CODE或者INFO后,把一些常用的数取出来放到static中
    // 仅仅接收以上和 MIN5 RIGHT 等原始数据,周月年和其他周期分钟线,全部通过计算获取
    this.InData[key] = {
      key,
      fields
    }
    this.InData[key].value = copyArrayOfDeep(value)
  }
  /**
   * flush tick
   * @param {Array} nowdata
   * @param {Array} fields
   * @memberof ClData
   */
  flushTick (nowdata, fields) {
    // if (this.static.stktype == 0) return ;
    if (getSize(this.InData['TICK']) < 1) {
      if (nowdata[fields.vol] > 0) {
        this.InData['TICK'] = {
          key: 'TICK',
          fields: FIELD_TICK,
          value: [nowdata[fields.time], nowdata[fields.close], nowdata[fields.vol]]
        }
      }
    } else {
      if (this.InData['TICK'].value[this.InData['TICK'].value.length - 1][FIELD_TICK.vol] < nowdata[fields.vol] ||
        this.InData['TICK'].value[this.InData['TICK'].value.length - 1][FIELD_TICK.close] !== nowdata[fields.close]) {
        // 成交量变化才生成tick,或收盘价不一样
        this.InData['TICK'].value.push([nowdata[fields.time], nowdata[fields.close], nowdata[fields.vol]])
      }
    }
  }
  /**
   * flush min data
   * @param {Array} nowdata
   * @param {Array} fields
   * @memberof ClData
   */
  flushMin (nowdata, fields) {
    if (this.InData['MIN'] === undefined) {
      this.InData['MIN'] = {
        key: 'MIN',
        fields: FIELD_MIN,
        value: [
          fromTradeTimeToIndex(nowdata[fields.time], this.tradeTime),
          nowdata[fields.open],
          nowdata[fields.high],
          nowdata[fields.low],
          nowdata[fields.close],
          nowdata[fields.vol],
          nowdata[fields.money]
        ]
      }
    } else {
      const index = fromTradeTimeToIndex(nowdata[fields.time], this.tradeTime)
      const checked = findIndexInMin(this.InData['MIN'], index)
      if (checked.status === 'old') {
        if (this.InData['MIN'].value[checked.index][fields.high] < nowdata[fields.close]) {
          this.InData['MIN'].value[checked.index][fields.high] = nowdata[fields.close]
        }
        if (this.InData['MIN'].value[checked.index][fields.low] > nowdata[fields.close]) {
          this.InData['MIN'].value[checked.index][fields.low] = nowdata[fields.close]
        }
        this.InData['MIN'].value[checked.index][fields.close] = nowdata[fields.close]
        this.InData['MIN'].value[checked.index][fields.vol] = nowdata[fields.vol]
        this.InData['MIN'].value[checked.index][fields.money] = nowdata[fields.money]
      } else if (checked.status === 'new') {
        this.InData['MIN'].value.push([index, nowdata[fields.close], nowdata[fields.close],
          nowdata[fields.close], nowdata[fields.close], nowdata[fields.vol], nowdata[fields.money]
        ])
      }
    }
  }
  // 当有新的NOW进来时,需要对TICK和当日MIN线进行更新,
  /**
   * flush now data
   * @param {String} key
   * @param {Array} nowdata
   * @memberof ClData
   */
  flushNowData (key, nowdata) {
    if (nowdata.length < 1) return
    let fields = FIELD_NOW
    if (key === 'ENOW') fields = FIELD_ENOW
    if (checkZero(nowdata, fields)) return

    // 先处理TICK
    this.flushTick(nowdata, fields)

    // 再处理Min
    this.flushMin(nowdata, fields)
  }

  // 下面是获取数据的方法,先从OutData获取,没有数据就从InData数据中获取

  /**
   * get data
   * @param {String} key
   * @param {String} rightMode
   * @return {Array}
   * @memberof ClData
   */
  getData (key, rightMode) {
    switch (key) {
      case 'DAY':
        this.OutData['DAY'] = {
          key,
          fields: FIELD_DAY
        }
        this.OutData['DAY'].value = this.mergeDay(this.InData['DAY'], this.InData['NOW'], rightMode)
        // 对原始数据不做变更
        break
      case 'WEEK':
        this.OutData['WEEK'] = {
          key,
          fields: FIELD_DAY
        }
        this.OutData['WEEK'].value = this.mergeWeek(this.InData['DAY'], this.InData['NOW'], rightMode)
        // 每次都从日线计算生成 -- 避免除权数据无法正确显示的错误
        break
      case 'MON':
        this.OutData['MON'] = {
          key,
          fields: FIELD_DAY
        }
        this.OutData['MON'].value = this.mergeMon(this.InData['DAY'], this.InData['NOW'], rightMode)
        // 每次都从日线计算生成 -- 避免除权数据无法正确显示的错误
        break
      case 'DAY5':
        this.OutData['DAY5'] = {
          key,
          fields: FIELD_DAY5
        }
        this.OutData['DAY5'].value = this.mergeDay5(this.InData['DAY5'], this.InData['MIN'])
        // 每次都从日线计算生成
        break
      case 'M5':
      case 'M15':
      case 'M30':
      case 'M60':
        this.OutData[key] = {
          key,
          fields: FIELD_DAY
        }
        this.OutData[key].value = this.makeMinute(key, this.InData[key], this.InData['MIN'], rightMode)
        break
      case 'MIN':
        this.OutData[key] = {
          key,
          fields: FIELD_MIN
        }
        this.OutData[key].value = this.updateMinute(this.InData[key])
        break
    }
    // 先找Out中的数据,没有就找In的数据
    return this.OutData[key] ? this.OutData[key] : this.InData[key]
  }
  /**
   * update minute
   * @param {Object} source
   * @return {Array}
   * @memberof ClData
   */
  updateMinute (source) {
    let out = copyArrayOfDeep(source.value)

    let allmoney
    for (let k = 0; k < out.length; k++) {
      if (this.static.stktype === 0) {
        if (k === 0) {
          allmoney = out[k][FIELD_MIN.vol] * out[k][FIELD_MIN.close]
        } else {
          allmoney += (out[k][FIELD_MIN.vol] - out[k - 1][FIELD_MIN.vol]) * out[k][FIELD_MIN.close]
        }
        out[k][FIELD_MIN.allmoney] = allmoney
      } else {
        // value[k][fields.allmoney] = value[k][fields.money];
      }
    }
    return out
  }
  /**
   * merge day
   * @param {Object} source
   * @param {Array} now
   * @param {String} rightMode
   * @return {Array}
   * @memberof ClData
   */
  mergeDay (source, now, rightMode) {
    let out = copyArrayOfDeep(source.value)
    if (now !== undefined && !checkZero(now.value, now.fields)) {
      const checked = findDateInDay(source, getDate(now.value[now.fields.time]))
      if (checked.finded) {
        out[checked.index] = [
          getDate(now.value[now.fields.time]),
          now.value[now.fields.open],
          now.value[now.fields.high],
          now.value[now.fields.low],
          now.value[now.fields.close],
          now.value[now.fields.vol],
          now.value[now.fields.money]
        ]
      } else {
        out.push([
          getDate(now.value[now.fields.time]),
          now.value[now.fields.open],
          now.value[now.fields.high],
          now.value[now.fields.low],
          now.value[now.fields.close],
          now.value[now.fields.vol],
          now.value[now.fields.money]
        ])
      }
    }
    if (this.InData['RIGHT'] && rightMode !== 'none') {
      out = transExrightDay(out, this.InData['RIGHT'].value, rightMode,
        0, out.length - 1)
    }
    // this.config.start,this.config.stop);

    return out
  }
  /**
   * merge week data
   * @param {Object} source
   * @param {Array} now
   * @param {String} rightmode
   * @return {Array}
   * @memberof ClData
   */
  mergeWeek (source, now, rightmode) {
    const out = this.mergeDay(source, now, rightmode)
    return matchDayToWeek(out)
    // 合并周线
  }
  /**
   * merge month data
   * @param {Object} source
   * @param {Array} now
   * @param {String} rightmode
   * @return {Array}
   * @memberof ClData
   */
  mergeMon (source, now, rightmode) {
    const out = this.mergeDay(source, now, rightmode)
    return matchDayToMon(out)
    // 合并月线
  }
  /**
   * merge 5 day data
   * @param {Object} source
   * @param {Array} min
   * @return {Array}
   * @memberof ClData
   */
  mergeDay5 (source, min) {
    let out = []

    if (source !== undefined && !isEmptyArray(source.value)) {
      out = copyArrayOfDeep(source.value)
      const lastDate = getDate(source.value[source.value.length - 1][source.fields.time])
      if (lastDate === this.tradeDate) {
        return out
      }
    }
    if (min === undefined || isEmptyArray(min.value)) {
      return out
    }
    const daymins = getMinuteCount(this.tradeTime) * 4
    let money
    for (let k = 0; k < min.value.length; k++) {
      if (this.static.stktype === 0) {
        if (k === 0) {
          money = min.value[k][min.fields.vol] * min.value[k][min.fields.close]
        } else {
          money += (min.value[k][min.fields.vol] - min.value[k - 1][min.fields.vol]) * min.value[k][min.fields.close]
        }
      } else {
        money = min.value[k][min.fields.money]
      }
      out.push([
        fromIndexToTradeTime(min.value[k][min.fields.idx], this.tradeTime, this.tradeDate),
        min.value[k][min.fields.close],
        k === 0 ? min.value[k][min.fields.vol] : min.value[k][min.fields.vol] - min.value[k - 1][min.fields.vol],
        daymins + min.value[k][min.fields.idx],
        min.value[k][min.fields.vol],
        money
      ])
    }
    return out
  }
  // source历史分钟线 nowmin当日分钟线
  /**
   * make minute data
   * @param {String} outkey
   * @param {Object} source
   * @param {Array} nowmin
   * @param {String} rightMode
   * @return {Array}
   * @memberof ClData
   */
  makeMinute (outkey, source, nowmin, rightMode) {
    let out = []
    if (source !== undefined && !isEmptyArray(source.value)) {
      out = copyArrayOfDeep(source.value)
      out = transExrightMin(out, this.static.coinzoom, this.InData['RIGHT'].value, rightMode,
        // this.config.start,this.config.stop);
        0, out.length - 1)

      const lastDate = getDate(source.value[source.value.length - 1][source.fields.time])
      if (lastDate === this.tradeDate) {
        // 已经是最新的数据了
        return out
      }
    }
    // 没有原始数据或者未收市,需要把当日的nowmin合并
    if (nowmin !== undefined && !isEmptyArray(nowmin.value)) {
      let offset = 5
      if (outkey === 'M15') offset = 15
      if (outkey === 'M30') offset = 30
      if (outkey === 'M60') offset = 60
      out = this.mergeNowMinToMin(out, nowmin, offset) // 当日的分钟线转成分钟线,索引转时间的问题
    }
    // out = matchMinToMinute(out, outkey);
    return out
  }
  /**
   * merge now's min data to min data
   * @param {Object} source
   * @param {Array} min
   * @param {Number} offset
   * @return {Array}
   * @memberof ClData
   */
  mergeNowMinToMin (source, min, offset) {
    const curMin = []
    let sumVol = 0
    let sumMoney = 0

    let hasData = false
    let stopIdx = 4

    for (let k = 0; k < min.value.length; k++) {
      const curIndex = min.value[k][min.fields.idx]
      if (curIndex < 0) continue
      if (curIndex > stopIdx) {
        if (hasData) {
          curMin[min.fields.vol] = min.value[k][min.fields.vol] - sumVol
          curMin[min.fields.money] = min.value[k][min.fields.money] - sumMoney
          sumVol = min.value[k][min.fields.vol]
          sumMoney = min.value[k][min.fields.money]

          curMin[min.fields.time] = fromIndexToTradeTime(stopIdx, this.tradeTime, this.tradeDate)
          source.push(copyArrayOfDeep(curMin))
        }
        stopIdx = (Math.floor(curIndex / offset) + 1) * offset - 1
        curMin[min.fields.open] = min.value[k][min.fields.open]
        curMin[min.fields.high] = min.value[k][min.fields.high]
        curMin[min.fields.low] = min.value[k][min.fields.low]
        curMin[min.fields.close] = min.value[k][min.fields.close]
        hasData = true
      } else { // curIndex 在0-5之间
        if (hasData) {
          curMin[min.fields.high] = curMin[min.fields.high] > min.value[k][min.fields.high]
            ? curMin[min.fields.high] : min.value[k][min.fields.high]
          curMin[min.fields.low] = curMin[min.fields.low] < min.value[k][min.fields.low] ||
            min.value[k][min.fields.low] === 0
            ? curMin[min.fields.low] : min.value[k][min.fields.low]
          curMin[min.fields.close] = min.value[k][min.fields.close]
        } else {
          curMin[min.fields.open] = min.value[k][min.fields.open]
          curMin[min.fields.high] = min.value[k][min.fields.high]
          curMin[min.fields.low] = min.value[k][min.fields.low]
          curMin[min.fields.close] = min.value[k][min.fields.close]
          hasData = true
        }
      }
    } // for i
    if (hasData) {
      curMin[min.fields.vol] = min.value[min.value.length - 1][min.fields.vol] - sumVol
      curMin[min.fields.money] = min.value[min.value.length - 1][min.fields.money] - sumMoney
      curMin[min.fields.time] = fromIndexToTradeTime(stopIdx, this.tradeTime, this.tradeDate)
      source.push(copyArrayOfDeep(curMin))
    }
    return source
  }

  //  以下为公式系统,自由计算的定义

  /**
   * make lien data
   * @param {Object} source
   * @param {String} outkey
   * @param {String} formula
   * @return {Array}
   * @memberof ClData
   */
  makeLineData (source, outkey, formula) {
    const value = this.formula.runSingleStock(source, formula)
    if (this.OutData[outkey] === undefined) {
      this.OutData[outkey] = {
        outkey,
        fields: FIELD_ILINE,
        value
      }
    } else {
      this.OutData[outkey].value = value
      // 返回的值都是真实的值,不用再除单位了,具体显示几个小数点,由坐标的类别来定,
    }
    return this.OutData[outkey]
  }
} // end.