import isNumber from 'lodash-es/isNumber.js'
import size from 'lodash-es/size.js'
import each from 'lodash-es/each.js'
/**
* 計算各週期之ADX(Average Directional Index,動向指標,含+DI/-DI/DIcross)
*
* 自輸入索引1起算+DM、-DM與TR(True Range),經兩層Wilder's平滑後得+DI、-DI、DX與ADX
* 第二層平滑之ADX種子對應輸入索引2*len-1起算,其後逐根遞推;資料筆數n小於2*len+1時該期vs回空陣列
* 第一筆ADXslope因無前值可比故為0
*
* Unit Test: {@link https://github.com/yuda-lyu/w-data-trade/blob/master/test/unit-calcAdx.test.mjs Github}
* @function
* @param {Array} arr 輸入K線陣列,各元素需含time、High、Low、Close欄位
* @param {String} key 輸入計算所用數值欄位名稱字串,本指標固定讀取High/Low/Close欄位,key目前未被使用,僅為與其他指標介面一致而保留
* @param {Object} [opt={}] 輸入設定物件,預設{},本指標目前無可用設定鍵
* @returns {Promise} 回傳Promise,resolve為各期結果陣列,各元素為{period,len,vs},vs內各元素為{time,ADX,plusDI,minusDI,DIcross,ADXslope}
* @example
*
* let arr = [
* { time: '2020-01-01T00:00:00', Open: 100, High: 101, Low: 99, Close: 100 },
* { time: '2020-01-01T04:00:00', Open: 100, High: 103, Low: 99, Close: 102 },
* { time: '2020-01-01T08:00:00', Open: 102, High: 103, Low: 100, Close: 101 },
* { time: '2020-01-01T12:00:00', Open: 101, High: 106, Low: 100, Close: 105 },
* { time: '2020-01-01T16:00:00', Open: 105, High: 106, Low: 102, Close: 103 },
* { time: '2020-01-01T20:00:00', Open: 103, High: 108, Low: 102, Close: 107 },
* { time: '2020-01-02T00:00:00', Open: 107, High: 108, Low: 105, Close: 106 },
* ]
*
* calcAdx(arr, 'Close')
* .then((rs) => {
* console.log(rs[0])
* // => {
* // period: '12hr',
* // len: 3,
* // vs: [
* // { time: '2020-01-01T20:00:00', ADX: 100, plusDI: 29.23076923076923, minusDI: 0, DIcross: 29.23076923076923, ADXslope: 0 },
* // { time: '2020-01-02T00:00:00', ADX: 100, plusDI: 22.287390029325515, minusDI: 0, DIcross: 22.287390029325515, ADXslope: 0 }
* // ]
* // }
* })
*
*/
let calcAdx = (() => {
let kp = {
'12hr': 3,
'16hr': 4,
'20hr': 5,
'1day': 6, // 1 day = 6 * 4 hours
'2day': 12, // 2 days = 12 * 4 hours
'4day': 24, // 4 days = 24 * 4 hours
'7day': 42, // 7 days = 42 * 4 hours
'15day': 90, // 15 days = 90 * 4 hours
'30day': 180, // 30 days = 180 * 4 hours
}
let caAdx = (arr, len, opt = {}) => {
//check
if (!isNumber(len)) {
throw new Error(`len is not a number`)
}
//n
let n = size(arr)
//check, 需要2*len根(第一次平滑len + 第二次平滑ADX len)再加上i=1起算
if (n < 2 * len + 1) {
return []
}
let kTime = 'time'
let kHigh = 'High'
let kLow = 'Low'
let kClose = 'Close'
//計算每根的+DM, -DM, TR(從i=1開始)
let pdms = new Array(n).fill(0) // +DM
let ndms = new Array(n).fill(0) // -DM
let trs = new Array(n).fill(0) // TR
for (let i = 1; i < n; i++) {
let h = arr[i][kHigh]
let l = arr[i][kLow]
let cPrev = arr[i - 1][kClose]
let hPrev = arr[i - 1][kHigh]
let lPrev = arr[i - 1][kLow]
//check
if (!isNumber(h)) {
throw new Error(`invalid h[${h}]`)
}
if (!isNumber(l)) {
throw new Error(`invalid l[${l}]`)
}
if (!isNumber(cPrev)) {
throw new Error(`invalid cPrev[${cPrev}]`)
}
if (!isNumber(hPrev)) {
throw new Error(`invalid hPrev[${hPrev}]`)
}
if (!isNumber(lPrev)) {
throw new Error(`invalid lPrev[${lPrev}]`)
}
//+DM / -DM
let upMove = h - hPrev
let downMove = lPrev - l
if (upMove > downMove && upMove > 0) {
pdms[i] = upMove
}
if (downMove > upMove && downMove > 0) {
ndms[i] = downMove
}
//TR
let tr1 = Math.abs(h - l)
let tr2 = Math.abs(h - cPrev)
let tr3 = Math.abs(l - cPrev)
trs[i] = Math.max(tr1, tr2, tr3)
}
//第一次Wilder's平滑: 種子用SMA(1至len)
let sumPdm = 0
let sumNdm = 0
let sumTr = 0
for (let i = 1; i <= len; i++) {
sumPdm += pdms[i]
sumNdm += ndms[i]
sumTr += trs[i]
}
let smoothPdm = sumPdm / len
let smoothNdm = sumNdm / len
let smoothTr = sumTr / len
//計算+DI, -DI, DX序列(從index=len開始)
let dxVals = []
let diData = [] // 暫存{plusDI, minusDI}用於輸出
//index=len的第一筆
{
let plusDI = smoothTr !== 0 ? 100 * smoothPdm / smoothTr : 0
let minusDI = smoothTr !== 0 ? 100 * smoothNdm / smoothTr : 0
let diSum = plusDI + minusDI
let DX = diSum !== 0 ? 100 * Math.abs(plusDI - minusDI) / diSum : 0
dxVals.push(DX)
diData.push({ idx: len, plusDI, minusDI })
}
//index=len+1至n-1, 持續Wilder's平滑
for (let i = len + 1; i < n; i++) {
smoothPdm = (smoothPdm * (len - 1) + pdms[i]) / len
smoothNdm = (smoothNdm * (len - 1) + ndms[i]) / len
smoothTr = (smoothTr * (len - 1) + trs[i]) / len
let plusDI = smoothTr !== 0 ? 100 * smoothPdm / smoothTr : 0
let minusDI = smoothTr !== 0 ? 100 * smoothNdm / smoothTr : 0
let diSum = plusDI + minusDI
let DX = diSum !== 0 ? 100 * Math.abs(plusDI - minusDI) / diSum : 0
dxVals.push(DX)
diData.push({ idx: i, plusDI, minusDI })
}
//第二次Wilder's平滑: ADX = smooth(DX, len)
//種子: SMA of first len DX values
let sumDx = 0
for (let j = 0; j < len; j++) {
sumDx += dxVals[j]
}
let adxPrev = sumDx / len
//rs
let rs = []
//第一筆ADX(對應diData[len-1], arr index = len + len - 1 = 2*len - 1... 不對)
//dxVals[0]對應arr index=len, dxVals[len-1]對應arr index=2*len-1
//ADX種子用dxVals[0..len-1]的SMA, 對應arr index = 2*len-1
{
let d = diData[len - 1]
let ADX = adxPrev
rs.push({
time: arr[d.idx][kTime],
ADX,
plusDI: d.plusDI,
minusDI: d.minusDI,
DIcross: d.plusDI - d.minusDI,
ADXslope: 0, //第一筆無前值
})
}
//後續ADX
for (let j = len; j < dxVals.length; j++) {
let adx = (adxPrev * (len - 1) + dxVals[j]) / len
let d = diData[j]
rs.push({
time: arr[d.idx][kTime],
ADX: adx,
plusDI: d.plusDI,
minusDI: d.minusDI,
DIcross: d.plusDI - d.minusDI,
ADXslope: adx - adxPrev,
})
adxPrev = adx
}
// console.log('rs', rs)
return rs
}
let caAdxs = (arr, opt = {}) => {
//rrs
let rrs = []
each(kp, (len, period) => {
//caAdx
let rs = caAdx(arr, len, opt)
//push
rrs.push({
period,
len,
vs: rs,
})
// console.log('rrs', rrs)
})
return rrs
}
let calcAdx = async(arr, key, opt = {}) => {
// arr = [
// {"time":"2020-01-01T00:00:00","Open":7195,"High":7225.62,"Low":7145.01,"Close":7173.32,...},
// {"time":"2020-01-01T04:00:00","Open":7173.75,"High":7208.41,"Low":7165.1,"Close":7195.23,...},
// ...
// ]
//caAdxs
let rs = caAdxs(arr, opt)
return rs
}
return calcAdx
})()
export default calcAdx