import 'dart:math'; import '../entity/index.dart'; class DataUtil { static calculate(List dataList, [List maDayList = const [5, 10, 20], int n = 20, k = 2]) { /// calculate main state calcMA(dataList, maDayList); calcBOLL(dataList, n, k); calcSAR(dataList); /// calculate secondary state calcVolumeMA(dataList); calcKDJ(dataList); calcMACD(dataList); calcRSI(dataList); calcWR(dataList); calcCCI(dataList); } static calcMA(List dataList, List maDayList) { List ma = List.filled(maDayList.length, 0); if (dataList.isNotEmpty) { for (int i = 0; i < dataList.length; i++) { KLineEntity entity = dataList[i]; final closePrice = entity.close; entity.maValueList = List.filled(maDayList.length, 0); for (int j = 0; j < maDayList.length; j++) { ma[j] += closePrice; if (i == maDayList[j] - 1) { entity.maValueList?[j] = ma[j] / maDayList[j]; } else if (i >= maDayList[j]) { ma[j] -= dataList[i - maDayList[j]].close; entity.maValueList?[j] = ma[j] / maDayList[j]; } } } } } static void calcSAR(List dataList) { const List params = [2, 2, 20]; //calcParams default final startAf = params[0] / 100; final step = params[1] / 100; final maxAf = params[2] / 100; // Acceleration factor double af = startAf; // Extreme point double ep = -100; // Determine trend direction — false: downtrend bool isIncreasing = false; double sar = 0; for (int i = 0; i < dataList.length; ++i) { // the previous period SAR final preSar = sar; final high = dataList[i].high; final low = dataList[i].low; if (isIncreasing) { // Uptrend if (ep == -100 || ep < high) { // Reinitialize parameters ep = high; af = min(af + step, maxAf); } sar = preSar + af * (ep - preSar); final lowMin = min(dataList[max(1, i) - 1].low, low); if (sar > dataList[i].low) { sar = ep; // Reinitialize parameters af = startAf; ep = -100; isIncreasing = !isIncreasing; } else if (sar > lowMin) { sar = lowMin; } } else { if (ep == -100 || ep > low) { // Reinitialize parameters ep = low; af = min(af + step, maxAf); } sar = preSar + af * (ep - preSar); final highMax = max(dataList[max(1, i) - 1].high, high); if (sar < dataList[i].high) { sar = ep; // Reinitialize parameters af = 0; ep = -100; isIncreasing = !isIncreasing; } else if (sar < highMax) { sar = highMax; } } dataList[i].sar = sar; } } static void calcBOLL(List dataList, int n, int k) { _calcBOLLMA(n, dataList); for (int i = 0; i < dataList.length; i++) { KLineEntity entity = dataList[i]; if (i >= n) { double md = 0; for (int j = i - n + 1; j <= i; j++) { double c = dataList[j].close; double m = entity.BOLLMA!; double value = c - m; md += value * value; } md = md / (n - 1); md = sqrt(md); entity.mb = entity.BOLLMA!; entity.up = entity.mb! + k * md; entity.dn = entity.mb! - k * md; } } } static void _calcBOLLMA(int day, List dataList) { double ma = 0; for (int i = 0; i < dataList.length; i++) { KLineEntity entity = dataList[i]; ma += entity.close; if (i == day - 1) { entity.BOLLMA = ma / day; } else if (i >= day) { ma -= dataList[i - day].close; entity.BOLLMA = ma / day; } else { entity.BOLLMA = null; } } } static void calcMACD(List dataList) { double ema12 = 0; double ema26 = 0; double dif = 0; double dea = 0; double macd = 0; for (int i = 0; i < dataList.length; i++) { KLineEntity entity = dataList[i]; final closePrice = entity.close; if (i == 0) { ema12 = closePrice; ema26 = closePrice; } else { // EMA(12) = 前一日EMA(12) X 11/13 + 今日收盘价 X 2/13 ema12 = ema12 * 11 / 13 + closePrice * 2 / 13; // EMA(26) = 前一日EMA(26) X 25/27 + 今日收盘价 X 2/27 ema26 = ema26 * 25 / 27 + closePrice * 2 / 27; } // DIF = EMA(12) - EMA(26) 。 // 今日DEA = (前一日DEA X 8/10 + 今日DIF X 2/10) // 用(DIF-DEA)*2即为MACD柱状图。 dif = ema12 - ema26; dea = dea * 8 / 10 + dif * 2 / 10; macd = (dif - dea) * 2; entity.dif = dif; entity.dea = dea; entity.macd = macd; } } static void calcVolumeMA(List dataList) { double volumeMa5 = 0; double volumeMa10 = 0; for (int i = 0; i < dataList.length; i++) { KLineEntity entry = dataList[i]; volumeMa5 += entry.vol; volumeMa10 += entry.vol; if (i == 4) { entry.MA5Volume = (volumeMa5 / 5); } else if (i > 4) { volumeMa5 -= dataList[i - 5].vol; entry.MA5Volume = volumeMa5 / 5; } else { entry.MA5Volume = 0; } if (i == 9) { entry.MA10Volume = volumeMa10 / 10; } else if (i > 9) { volumeMa10 -= dataList[i - 10].vol; entry.MA10Volume = volumeMa10 / 10; } else { entry.MA10Volume = 0; } } } static void calcRSI(List dataList) { double? rsi; double rsiABSEma = 0; double rsiMaxEma = 0; for (int i = 0; i < dataList.length; i++) { KLineEntity entity = dataList[i]; final double closePrice = entity.close; if (i == 0) { rsi = 0; rsiABSEma = 0; rsiMaxEma = 0; } else { double rMax = max(0, closePrice - dataList[i - 1].close.toDouble()); double rAbs = (closePrice - dataList[i - 1].close.toDouble()).abs(); rsiMaxEma = (rMax + (14 - 1) * rsiMaxEma) / 14; rsiABSEma = (rAbs + (14 - 1) * rsiABSEma) / 14; rsi = (rsiMaxEma / rsiABSEma) * 100; } if (i < 13) rsi = null; if (rsi != null && rsi.isNaN) rsi = null; entity.rsi = rsi; } } static void calcKDJ(List dataList) { var preK = 50.0; var preD = 50.0; final tmp = dataList.first; tmp.k = preK; tmp.d = preD; tmp.j = 50.0; for (int i = 1; i < dataList.length; i++) { final entity = dataList[i]; final n = max(0, i - 8); var low = entity.low; var high = entity.high; for (int j = n; j < i; j++) { final t = dataList[j]; if (t.low < low) { low = t.low; } if (t.high > high) { high = t.high; } } final cur = entity.close; var rsv = (cur - low) * 100.0 / (high - low); rsv = rsv.isNaN ? 0 : rsv; final k = (2 * preK + rsv) / 3.0; final d = (2 * preD + k) / 3.0; final j = 3 * k - 2 * d; preK = k; preD = d; entity.k = k; entity.d = d; entity.j = j; } } static void calcWR(List dataList) { double r; for (int i = 0; i < dataList.length; i++) { KLineEntity entity = dataList[i]; int startIndex = i - 14; if (startIndex < 0) { startIndex = 0; } double max14 = double.minPositive; double min14 = double.maxFinite; for (int index = startIndex; index <= i; index++) { max14 = max(max14, dataList[index].high); min14 = min(min14, dataList[index].low); } if (i < 13) { entity.r = -10; } else { r = -100 * (max14 - dataList[i].close) / (max14 - min14); if (r.isNaN) { entity.r = null; } else { entity.r = r; } } } } static void calcCCI(List dataList) { final size = dataList.length; final count = 14; for (int i = 0; i < size; i++) { final kline = dataList[i]; final tp = (kline.high + kline.low + kline.close) / 3; final start = max(0, i - count + 1); var amount = 0.0; var len = 0; for (int n = start; n <= i; n++) { amount += (dataList[n].high + dataList[n].low + dataList[n].close) / 3; len++; } final ma = amount / len; amount = 0.0; for (int n = start; n <= i; n++) { amount += (ma - (dataList[n].high + dataList[n].low + dataList[n].close) / 3) .abs(); } final md = amount / len; kline.cci = ((tp - ma) / 0.015 / md); if (kline.cci!.isNaN) { kline.cci = 0.0; } } } }