UNPKG

mytt-ts

Version:

通达信、同花顺、文华麦语言等指标公式的 TypeScript 实现

842 lines (839 loc) 22.5 kB
'use strict'; // MyTT TypeScript版本 - 移植自Python版本 // 用于技术分析指标计算 // ------------------- 0级:核心工具函数 ------------------- /** * 四舍五入到指定小数位 */ function RD(n, decimals = 3) { return Number(Math.round(Number(n + 'e' + decimals)) + 'e-' + decimals); } /** * 返回序列倒数第N个值 */ function RET(s, n = 1) { return s[s.length - n]; } /** * 返回绝对值 */ function ABS(s) { return s.map(Math.abs); } /** * 自然对数 */ function LN(s) { return s.map(Math.log); } /** * 求幂 */ function POW(s, n) { return s.map(x => Math.pow(x, n)); } /** * 平方根 */ function SQRT(s) { return s.map(Math.sqrt); } /** * 正弦 */ function SIN(s) { return s.map(Math.sin); } /** * 余弦 */ function COS(s) { return s.map(Math.cos); } /** * 正切 */ function TAN(s) { return s.map(Math.tan); } /** * 序列最大值 */ function MAX(s1, s2) { return s1.map((v, i) => Math.max(v, s2[i])); } /** * 序列最小值 */ function MIN(s1, s2) { return s1.map((v, i) => Math.min(v, s2[i])); } /** * 条件判断 */ function IF(condition, a, b) { const aIsNumber = typeof a === 'number'; const bIsNumber = typeof b === 'number'; return condition.map((v, i) => { const aValue = aIsNumber ? a : a[i]; const bValue = bIsNumber ? b : b[i]; return v ? aValue : bValue; }); } /** * 序列位移 */ function REF(s, n = 1) { const result = new Array(s.length).fill(NaN); for (let i = n; i < s.length; i++) { result[i] = s[i - n]; } return result; } /** * 计算差值 */ function DIFF(s, n = 1) { const result = new Array(s.length).fill(NaN); for (let i = n; i < s.length; i++) { result[i] = s[i] - s[i - n]; } return result; } /** * 标准差 */ function STD(s, n) { const result = new Array(s.length).fill(NaN); for (let i = n - 1; i < s.length; i++) { const slice = s.slice(i - n + 1, i + 1); const mean = slice.reduce((a, b) => a + b, 0) / n; const sum = slice.reduce((a, b) => a + Math.pow(b - mean, 2), 0); result[i] = Math.sqrt(sum / n); } return result; } /** * 对序列求N天累计和 */ function SUM(s, n) { const result = new Array(s.length).fill(NaN); if (n <= 0) { let sum = 0; return s.map(v => sum += v); } for (let i = 0; i < s.length; i++) { if (i < n - 1) continue; result[i] = s.slice(i - n + 1, i + 1).reduce((a, b) => a + b, 0); } return result; } /** * 返回序列最后的值组成常量序列 */ function CONST(s) { const lastValue = s[s.length - 1]; return new Array(s.length).fill(lastValue); } /** * 求N周期内最高值 */ function HHV(s, n) { const result = new Array(s.length).fill(NaN); for (let i = 0; i < s.length; i++) { if (i < n - 1) continue; result[i] = Math.max(...s.slice(i - n + 1, i + 1)); } return result; } /** * 求N周期内最低值 */ function LLV(s, n) { const result = new Array(s.length).fill(NaN); for (let i = 0; i < s.length; i++) { if (i < n - 1) continue; result[i] = Math.min(...s.slice(i - n + 1, i + 1)); } return result; } /** * 求N周期内最高值到当前周期数 */ function HHVBARS(s, n) { const result = new Array(s.length).fill(NaN); for (let i = n - 1; i < s.length; i++) { const slice = s.slice(i - n + 1, i + 1); const max = Math.max(...slice); result[i] = slice.length - 1 - slice.lastIndexOf(max); } return result; } /** * 求N周期内最低值到当前周期数 */ function LLVBARS(s, n) { const result = new Array(s.length).fill(NaN); for (let i = n - 1; i < s.length; i++) { const slice = s.slice(i - n + 1, i + 1); const min = Math.min(...slice); result[i] = slice.length - 1 - slice.lastIndexOf(min); } return result; } /** * 简单移动平均 */ function MA(s, n) { const result = new Array(s.length).fill(NaN); for (let i = n - 1; i < s.length; i++) { result[i] = s.slice(i - n + 1, i + 1).reduce((a, b) => a + b, 0) / n; } return result; } /** * 指数移动平均 */ function EMA(s, n) { const alpha = 2 / (n + 1); const result = new Array(s.length).fill(NaN); result[0] = s[0]; for (let i = 1; i < s.length; i++) { result[i] = alpha * s[i] + (1 - alpha) * result[i - 1]; } return result; } /** * 中国式的SMA */ function SMA(s, n, m = 1) { const alpha = m / n; const result = new Array(s.length).fill(NaN); result[0] = s[0]; for (let i = 1; i < s.length; i++) { result[i] = alpha * s[i] + (1 - alpha) * result[i - 1]; } return result; } /** * 加权移动平均 */ function WMA(s, n) { const result = new Array(s.length).fill(NaN); const weights = Array.from({ length: n }, (_, i) => i + 1); const weightSum = weights.reduce((a, b) => a + b, 0); for (let i = n - 1; i < s.length; i++) { let sum = 0; for (let j = 0; j < n; j++) { sum += s[i - j] * (n - j); } result[i] = sum / weightSum; } return result; } /** * 动态移动平均 */ function DMA(s, a) { const result = new Array(s.length).fill(NaN); result[0] = s[0]; if (typeof a === 'number') { for (let i = 1; i < s.length; i++) { result[i] = a * s[i] + (1 - a) * result[i - 1]; } } else { for (let i = 1; i < s.length; i++) { const alpha = isNaN(a[i]) ? 1.0 : a[i]; result[i] = alpha * s[i] + (1 - alpha) * result[i - 1]; } } return result; } /** * 平均绝对偏差 */ function AVEDEV(s, n) { const result = new Array(s.length).fill(NaN); for (let i = n - 1; i < s.length; i++) { const slice = s.slice(i - n + 1, i + 1); const mean = slice.reduce((a, b) => a + b, 0) / n; result[i] = slice.reduce((a, b) => a + Math.abs(b - mean), 0) / n; } return result; } // --- 1级:应用层函数 --- /** * 统计N日内满足条件的天数 */ function COUNT(s, n) { return SUM(s.map(v => v ? 1 : 0), n); } /** * 判断N日内是否都满足条件 */ function EVERY(s, n) { const result = new Array(s.length).fill(false); for (let i = n - 1; i < s.length; i++) { let allTrue = true; for (let j = 0; j < n; j++) { if (!s[i - j]) { allTrue = false; break; } } result[i] = allTrue; } return result; } /** * N日内是否存在满足条件的情况 */ function EXIST(s, n) { const result = new Array(s.length).fill(false); for (let i = n - 1; i < s.length; i++) { let exists = false; for (let j = 0; j < n; j++) { if (s[i - j]) { exists = true; break; } } result[i] = exists; } return result; } /** * 条件过滤器 */ function FILTER(s, n) { const result = [...s]; for (let i = 0; i < result.length; i++) { if (result[i]) { for (let j = 1; j <= n && i + j < result.length; j++) { result[i + j] = 0; } } } return result; } /** * 上一次条件成立到当前的周期 */ function BARSLAST(s) { const result = new Array(s.length).fill(0); let count = 0; for (let i = 0; i < s.length; i++) { if (s[i]) { count = 0; } else { count++; } result[i] = count; } return result; } /** * 统计连续满足条件的周期数 */ function BARSLASTCOUNT(s) { const result = new Array(s.length).fill(0); let count = 0; for (let i = 0; i < s.length; i++) { if (s[i]) { count++; } else { count = 0; } result[i] = count; } return result; } /** * N周期内第一次条件成立到现在的周期数 */ function BARSSINCEN(s, n) { const result = new Array(s.length).fill(0); for (let i = 0; i < s.length; i++) { if (i < n - 1) continue; const slice = s.slice(i - n + 1, i + 1); const firstTrueIndex = slice.findIndex(v => v); result[i] = firstTrueIndex === -1 ? 0 : n - 1 - firstTrueIndex; } return result; } /** * 判断向上金叉穿越 */ function CROSS(s1, s2) { const result = new Array(s1.length).fill(false); for (let i = 1; i < s1.length; i++) { result[i] = s1[i] > s2[i] && s1[i - 1] <= s2[i - 1]; } return result; } /** * 两条线维持一定周期后交叉 */ function LONGCROSS(s1, s2, n) { const result = new Array(s1.length).fill(false); for (let i = n; i < s1.length; i++) { if (s1[i] <= s2[i]) continue; let allLess = true; for (let j = 1; j <= n; j++) { if (s1[i - j] >= s2[i - j]) { allLess = false; break; } } result[i] = allLess; } return result; } /** * 取条件成立时的值 */ function VALUEWHEN(condition, value) { const result = new Array(condition.length).fill(NaN); let lastValue = NaN; for (let i = 0; i < condition.length; i++) { if (condition[i]) { lastValue = value[i]; } result[i] = lastValue; } return result; } /** * 判断值是否在两个值之间 * 支持 A<S<B 或 A>S>B 两种情况 */ function BETWEEN(s, a, b) { return s.map((v, i) => { const av = a[i]; const bv = b[i]; return (av < v && v < bv) || (av > v && v > bv); }); } /** * 当前最高价是近多少周期内最高价的最大值 */ function TOPRANGE(s) { const result = new Array(s.length).fill(0); for (let i = 1; i < s.length; i++) { let count = 0; for (let j = i - 1; j >= 0; j--) { if (s[j] >= s[i]) break; count++; } result[i] = count; } return result; } /** * 当前最低价是近多少周期内最低价的最小值 */ function LOWRANGE(s) { const result = new Array(s.length).fill(0); for (let i = 1; i < s.length; i++) { let count = 0; for (let j = i - 1; j >= 0; j--) { if (s[j] <= s[i]) break; count++; } result[i] = count; } return result; } // --- 2级:技术指标函数 --- /** * MACD指标 */ function MACD(close, short = 12, long = 26, m = 9) { const DIF = EMA(close, short).map((v, i) => v - EMA(close, long)[i]); const DEA = EMA(DIF, m); const MACD = DIF.map((v, i) => (v - DEA[i]) * 2); return [DIF.map(v => RD(v)), DEA.map(v => RD(v)), MACD.map(v => RD(v))]; } /** * KDJ指标 */ function KDJ(close, high, low, n = 9, m1 = 3, m2 = 3) { const RSV = close.map((v, i) => { const llv = LLV(low, n)[i]; const hhv = HHV(high, n)[i]; return ((v - llv) / (hhv - llv)) * 100; }); const K = SMA(RSV, m1 * 2 - 1); const D = SMA(K, m2 * 2 - 1); const J = K.map((v, i) => 3 * v - 2 * D[i]); return [K, D, J]; } /** * RSI指标 */ function RSI(close, n = 24) { const DIF = close.map((v, i) => i === 0 ? 0 : v - close[i - 1]); const maxDIF = DIF.map(v => Math.max(v, 0)); const absDIF = DIF.map(Math.abs); return SMA(maxDIF, n).map((v, i) => RD(v / SMA(absDIF, n)[i] * 100)); } /** * W&R 威廉指标 */ function WR(close, high, low, n = 10, n1 = 6) { const WR = close.map((v, i) => { const hhv = HHV(high, n)[i]; const llv = LLV(low, n)[i]; return ((hhv - v) / (hhv - llv)) * 100; }); const WR1 = close.map((v, i) => { const hhv = HHV(high, n1)[i]; const llv = LLV(low, n1)[i]; return ((hhv - v) / (hhv - llv)) * 100; }); return [WR.map(v => RD(v)), WR1.map(v => RD(v))]; } /** * BIAS乖离率 */ function BIAS(close, l1 = 6, l2 = 12, l3 = 24) { const MA1 = MA(close, l1); const MA2 = MA(close, l2); const MA3 = MA(close, l3); const BIAS1 = close.map((v, i) => ((v - MA1[i]) / MA1[i]) * 100); const BIAS2 = close.map((v, i) => ((v - MA2[i]) / MA2[i]) * 100); const BIAS3 = close.map((v, i) => ((v - MA3[i]) / MA3[i]) * 100); return [BIAS1.map(v => RD(v)), BIAS2.map(v => RD(v)), BIAS3.map(v => RD(v))]; } /** * BOLL指标,布林带 */ function BOLL(close, n = 20, p = 2) { const MID = MA(close, n); const std = STD(close, n); const UPPER = MID.map((v, i) => v + std[i] * p); const LOWER = MID.map((v, i) => v - std[i] * p); return [UPPER.map(v => RD(v)), MID.map(v => RD(v)), LOWER.map(v => RD(v))]; } /** * PSY心理线指标 */ function PSY(close, n = 12, m = 6) { // 计算上涨天数 const upDays = close.map((v, i) => i === 0 ? 0 : (v > close[i - 1] ? 1 : 0)); // 计算N日内上涨天数的比率 const PSY = SUM(upDays, n).map(v => (v / n) * 100); // 计算PSY的M日移动平均 const PSYMA = MA(PSY, m); return [PSY.map(v => RD(v)), PSYMA.map(v => RD(v))]; } /** * CCI顺势指标 */ function CCI(close, high, low, n = 14) { const TP = close.map((v, i) => (high[i] + low[i] + v) / 3); const MA_TP = MA(TP, n); const AVEDEV_TP = AVEDEV(TP, n); return TP.map((v, i) => (v - MA_TP[i]) / (0.015 * AVEDEV_TP[i])); } /** * ATR真实波动N日平均值 */ function ATR(close, high, low, n = 20) { const TR = close.map((v, i) => { if (i === 0) return high[i] - low[i]; return Math.max(Math.max(high[i] - low[i], Math.abs(close[i - 1] - high[i])), Math.abs(close[i - 1] - low[i])); }); return MA(TR, n); } /** * BBI多空指标 */ function BBI(close, m1 = 3, m2 = 6, m3 = 12, m4 = 20) { return close.map((_, i) => { const ma1 = MA(close, m1)[i]; const ma2 = MA(close, m2)[i]; const ma3 = MA(close, m3)[i]; const ma4 = MA(close, m4)[i]; return (ma1 + ma2 + ma3 + ma4) / 4; }); } /** * DMI动向指标 */ function DMI(close, high, low, m1 = 14, m2 = 6) { const TR = close.map((v, i) => { if (i === 0) return high[i] - low[i]; return Math.max(Math.max(high[i] - low[i], Math.abs(high[i] - close[i - 1])), Math.abs(low[i] - close[i - 1])); }); const HD = high.map((v, i) => i === 0 ? 0 : v - high[i - 1]); const LD = low.map((v, i) => i === 0 ? 0 : low[i - 1] - v); const DMP = SUM(HD.map((v, i) => v > 0 && v > LD[i] ? v : 0), m1); const DMM = SUM(LD.map((v, i) => v > 0 && v > HD[i] ? v : 0), m1); const TR_SUM = SUM(TR, m1); const PDI = DMP.map((v, i) => (v * 100) / TR_SUM[i]); const MDI = DMM.map((v, i) => (v * 100) / TR_SUM[i]); const ADX = MA(PDI.map((v, i) => Math.abs(MDI[i] - v) / (MDI[i] + v) * 100), m2); const ADXR = ADX.map((v, i) => i < m2 ? v : (v + ADX[i - m2]) / 2); return [PDI, MDI, ADX, ADXR]; } /** * 唐安奇通道(海龟)交易指标 */ function TAQ(high, low, n) { const UP = HHV(high, n); const DOWN = LLV(low, n); const MID = UP.map((v, i) => (v + DOWN[i]) / 2); return [UP, MID, DOWN]; } /** * 肯特纳交易通道 */ function KTN(close, high, low, n = 20, m = 10) { const MID = EMA(high.map((v, i) => (v + low[i] + close[i]) / 3), n); const ATRN = ATR(close, high, low, m); const UPPER = MID.map((v, i) => v + 2 * ATRN[i]); const LOWER = MID.map((v, i) => v - 2 * ATRN[i]); return [UPPER, MID, LOWER]; } /** * 三重指数平滑平均线 */ function TRIX(close, m1 = 12, m2 = 20) { const TR = EMA(EMA(EMA(close, m1), m1), m1); const TRIX = TR.map((v, i) => i === 0 ? 0 : ((v - TR[i - 1]) / TR[i - 1]) * 100); const TRMA = MA(TRIX, m2); return [TRIX, TRMA]; } /** * VR容量比率 */ function VR(close, vol, m1 = 26) { const LC = REF(close, 1); const UP_VOL = vol.map((v, i) => close[i] > LC[i] ? v : 0); const DOWN_VOL = vol.map((v, i) => close[i] <= LC[i] ? v : 0); return SUM(UP_VOL, m1).map((v, i) => (v / SUM(DOWN_VOL, m1)[i]) * 100); } /** * CR价格动量指标 */ function CR(close, high, low, n = 20) { const MID = REF(high.map((v, i) => (v + low[i] + close[i]) / 3), 1); const UP = high.map((v, i) => Math.max(0, v - MID[i])); const DOWN = low.map((v, i) => Math.max(0, MID[i] - v)); return SUM(UP, n).map((v, i) => (v / SUM(DOWN, n)[i]) * 100); } /** * 简易波指标 */ function EMV(high, low, vol, n = 14, m = 9) { const VOLUME = MA(vol, n).map((v, i) => v / vol[i]); const MID = high.map((v, i) => 100 * (v + low[i] - (i === 0 ? v + low[i] : high[i - 1] + low[i - 1])) / (v + low[i])); const EMV = MA(MID.map((v, i) => v * VOLUME[i] * (high[i] - low[i]) / MA(high.map((h, j) => h - low[j]), n)[i]), n); const MAEMV = MA(EMV, m); return [EMV, MAEMV]; } /** * 区间震荡线 */ function DPO(close, m1 = 20, m2 = 10, m3 = 6) { const DPO = close.map((v, i) => { const maValue = MA(close, m1)[i]; const refValue = i >= m2 ? MA(close, m1)[i - m2] : maValue; return v - refValue; }); const MADPO = MA(DPO, m3); return [DPO, MADPO]; } /** * BRAR-ARBR 情绪指标 */ function BRAR(open, close, high, low, m1 = 26) { const AR = SUM(high.map((v, i) => v - open[i]), m1).map((v, i) => (v / SUM(open.map((o, j) => o - low[j]), m1)[i]) * 100); const BR = SUM(high.map((v, i) => Math.max(0, v - REF(close, 1)[i])), m1).map((v, i) => (v / SUM(REF(close, 1).map((c, j) => Math.max(0, c - low[j])), m1)[i]) * 100); return [AR, BR]; } /** * 平行线差指标 */ function DFMA(close, n1 = 10, n2 = 50, m = 10) { const DIF = MA(close, n1).map((v, i) => v - MA(close, n2)[i]); const DIFMA = MA(DIF, m); return [DIF, DIFMA]; } /** * 动量指标 */ function MTM(close, n = 12, m = 6) { const MTM = close.map((v, i) => i < n ? 0 : v - close[i - n]); const MTMMA = MA(MTM, m); return [MTM, MTMMA]; } /** * 梅斯线 */ function MASS(high, low, n1 = 9, n2 = 25, m = 6) { const MASS = SUM(MA(high.map((v, i) => v - low[i]), n1).map((v, i) => v / MA(MA(high.map((h, j) => h - low[j]), n1), n1)[i]), n2); const MA_MASS = MA(MASS, m); return [MASS, MA_MASS]; } /** * 变动率指标 */ function ROC(close, n = 12, m = 6) { const ROC = close.map((v, i) => i < n ? 0 : ((v - close[i - n]) / close[i - n]) * 100); const MAROC = MA(ROC, m); return [ROC, MAROC]; } /** * EMA指数平均数指标 */ function EXPMA(close, n1 = 12, n2 = 50) { return [EMA(close, n1), EMA(close, n2)]; } /** * 能量指标 */ function OBV(close, vol) { const result = new Array(close.length).fill(0); result[0] = vol[0]; for (let i = 1; i < close.length; i++) { if (close[i] > close[i - 1]) { result[i] = result[i - 1] + vol[i]; } else if (close[i] < close[i - 1]) { result[i] = result[i - 1] - vol[i]; } else { result[i] = result[i - 1]; } } return result.map(v => v / 10000); } /** * MFI资金流量指标 */ function MFI(close, high, low, vol, n = 14) { const TYP = high.map((v, i) => (v + low[i] + close[i]) / 3); const V1 = SUM(TYP.map((v, i) => i === 0 ? 0 : v > TYP[i - 1] ? v * vol[i] : 0), n).map((v, i) => v / SUM(TYP.map((t, j) => j === 0 ? 0 : t < TYP[j - 1] ? t * vol[j] : 0), n)[i]); return V1.map(v => 100 - (100 / (1 + v))); } /** * 振动升降指标 */ function ASI(open, close, high, low, m1 = 26, m2 = 10) { const LC = REF(close, 1); const AA = high.map((v, i) => Math.abs(v - LC[i])); const BB = low.map((v, i) => Math.abs(v - LC[i])); const CC = high.map((v, i) => Math.abs(v - (i === 0 ? low[i] : low[i - 1]))); const DD = LC.map((v, i) => Math.abs(v - (i === 0 ? open[i] : open[i - 1]))); const R = AA.map((v, i) => { if (v > BB[i] && v > CC[i]) { return v + BB[i] / 2 + DD[i] / 4; } else if (BB[i] > CC[i] && BB[i] > v) { return BB[i] + v / 2 + DD[i] / 4; } else { return CC[i] + DD[i] / 4; } }); const X = close.map((v, i) => v - LC[i] + (v - open[i]) / 2 + LC[i] - (i === 0 ? open[i] : open[i - 1])); const SI = X.map((v, i) => 16 * v / R[i] * Math.max(AA[i], BB[i])); const ASI = SUM(SI, m1); const ASIT = MA(ASI, m2); return [ASI, ASIT]; } /** * 薛斯通道II */ function XSII(close, high, low, n = 102, m = 7) { const AA = MA(high.map((v, i) => (2 * close[i] + v + low[i]) / 4), 5); const TD1 = AA.map(v => v * n / 100); const TD2 = AA.map(v => v * (200 - n) / 100); const CC = close.map((v, i) => Math.abs((2 * v + high[i] + low[i]) / 4 - MA(close, 20)[i]) / MA(close, 20)[i]); const DD = DMA(close, CC); const TD3 = DD.map(v => v * (1 + m / 100)); const TD4 = DD.map(v => v * (1 - m / 100)); return [TD1, TD2, TD3, TD4]; } exports.ABS = ABS; exports.ASI = ASI; exports.ATR = ATR; exports.AVEDEV = AVEDEV; exports.BARSLAST = BARSLAST; exports.BARSLASTCOUNT = BARSLASTCOUNT; exports.BARSSINCEN = BARSSINCEN; exports.BBI = BBI; exports.BETWEEN = BETWEEN; exports.BIAS = BIAS; exports.BOLL = BOLL; exports.BRAR = BRAR; exports.CCI = CCI; exports.CONST = CONST; exports.COS = COS; exports.COUNT = COUNT; exports.CR = CR; exports.CROSS = CROSS; exports.DFMA = DFMA; exports.DIFF = DIFF; exports.DMA = DMA; exports.DMI = DMI; exports.DPO = DPO; exports.EMA = EMA; exports.EMV = EMV; exports.EVERY = EVERY; exports.EXIST = EXIST; exports.EXPMA = EXPMA; exports.FILTER = FILTER; exports.HHV = HHV; exports.HHVBARS = HHVBARS; exports.IF = IF; exports.KDJ = KDJ; exports.KTN = KTN; exports.LLV = LLV; exports.LLVBARS = LLVBARS; exports.LN = LN; exports.LONGCROSS = LONGCROSS; exports.LOWRANGE = LOWRANGE; exports.MA = MA; exports.MACD = MACD; exports.MASS = MASS; exports.MAX = MAX; exports.MFI = MFI; exports.MIN = MIN; exports.MTM = MTM; exports.OBV = OBV; exports.POW = POW; exports.PSY = PSY; exports.RD = RD; exports.REF = REF; exports.RET = RET; exports.ROC = ROC; exports.RSI = RSI; exports.SIN = SIN; exports.SMA = SMA; exports.SQRT = SQRT; exports.STD = STD; exports.SUM = SUM; exports.TAN = TAN; exports.TAQ = TAQ; exports.TOPRANGE = TOPRANGE; exports.TRIX = TRIX; exports.VALUEWHEN = VALUEWHEN; exports.VR = VR; exports.WMA = WMA; exports.WR = WR; exports.XSII = XSII;