@gabriel3615/ta_analysis
Version:
stock ta analysis
588 lines (587 loc) • 20 kB
JavaScript
/**
* 计算简化版技术指标 - 新增
*/
export function calculateTechnicalIndicators(data) {
// 确保有足够的数据
if (data.length < 30) {
return {
macdSignal: '数据不足',
rsiLevel: 50,
bollingerStatus: '数据不足',
technicalSignal: '中性',
trendStatus: '数据不足',
};
}
const closes = data.map(d => d.close);
const highs = data.map(d => d.high);
const lows = data.map(d => d.low);
const currentPrice = closes[closes.length - 1];
// 计算完整的MACD序列
const macdData = calculateFullMACD(closes);
const macdLine = macdData.macdLine[macdData.macdLine.length - 1];
const signalLine = macdData.signalLine[macdData.signalLine.length - 1];
const previousMacdLine = macdData.macdLine[macdData.macdLine.length - 2];
const previousSignalLine = macdData.signalLine[macdData.signalLine.length - 2];
// 计算RSI
const rsi = calculateStandardRSI(closes, 14);
// 计算布林带
const sma20 = calculateSMA(closes, 20);
const stdDev = calculateStdDev(closes, 20);
const upperBand = sma20 + stdDev * 2;
const lowerBand = sma20 - stdDev * 2;
// 趋势识别
const trendStatus = identifyTrend(closes);
// 确定MACD信号
let macdSignal = '';
const isMacdCrossOver = macdLine > signalLine && previousMacdLine <= previousSignalLine;
const isMacdCrossUnder = macdLine < signalLine && previousMacdLine >= previousSignalLine;
if (isMacdCrossOver && macdLine > 0) {
macdSignal = '新金叉向上';
}
else if (isMacdCrossOver && macdLine <= 0) {
macdSignal = '新金叉但在0轴下';
}
else if (isMacdCrossUnder && macdLine > 0) {
macdSignal = '新死叉但在0轴上';
}
else if (isMacdCrossUnder && macdLine <= 0) {
macdSignal = '新死叉向下';
}
else if (macdLine > signalLine && macdLine > previousMacdLine) {
macdSignal = '金叉并继续向上';
}
else if (macdLine > signalLine && macdLine <= previousMacdLine) {
macdSignal = '金叉但有回落迹象';
}
else if (macdLine < signalLine && macdLine > previousMacdLine) {
macdSignal = '死叉但有反弹迹象';
}
else {
macdSignal = '死叉并继续向下';
}
// 确定布林带状态
let bollingerStatus = '';
if (currentPrice > upperBand) {
bollingerStatus = '突破上轨';
}
else if (currentPrice < lowerBand) {
bollingerStatus = '突破下轨';
}
else if (currentPrice > sma20) {
bollingerStatus = '运行于上轨道';
}
else {
bollingerStatus = '运行于下轨道';
}
// 计算布林带宽度 - 可用于判断波动性
const bandWidth = ((upperBand - lowerBand) / sma20) * 100;
const isBandSqueeze = bandWidth < 3.5; // 带宽较窄,可能即将爆发
// 综合技术信号
let buySignals = 0;
let sellSignals = 0;
// MACD信号计分
if (macdSignal.includes('新金叉向上'))
buySignals += 3;
else if (macdSignal.includes('新金叉但在0轴下'))
buySignals += 2;
else if (macdSignal.includes('金叉并继续向上'))
buySignals += 2;
else if (macdSignal.includes('金叉但有回落迹象'))
buySignals += 1;
else if (macdSignal.includes('新死叉向下'))
sellSignals += 3;
else if (macdSignal.includes('新死叉但在0轴上'))
sellSignals += 2;
else if (macdSignal.includes('死叉并继续向下'))
sellSignals += 2;
else if (macdSignal.includes('死叉但有反弹迹象'))
sellSignals += 1;
// RSI信号计分
if (rsi < 30)
buySignals += 2;
else if (rsi < 40)
buySignals += 1;
else if (rsi > 70)
sellSignals += 2;
else if (rsi > 60)
sellSignals += 1;
// 布林带信号计分
if (bollingerStatus === '突破下轨')
buySignals += 2;
else if (bollingerStatus === '运行于下轨道')
buySignals += 1;
else if (bollingerStatus === '突破上轨')
sellSignals += 2;
else if (bollingerStatus === '运行于上轨道')
sellSignals += 1;
// 趋势因素计分
if (trendStatus === '强势上涨趋势')
buySignals += 2;
else if (trendStatus === '中期反弹')
buySignals += 1;
else if (trendStatus === '强势下跌趋势')
sellSignals += 2;
else if (trendStatus === '中期回调')
sellSignals += 1;
// 布林带挤压可能代表即将爆发的行情
if (isBandSqueeze && macdLine > signalLine)
buySignals += 1;
else if (isBandSqueeze && macdLine < signalLine)
sellSignals += 1;
// 确定综合信号
let technicalSignal = '';
if (buySignals >= 6)
technicalSignal = '强买入';
else if (buySignals > sellSignals + 2)
technicalSignal = '买入';
else if (sellSignals >= 6)
technicalSignal = '强卖出';
else if (sellSignals > buySignals + 2)
technicalSignal = '卖出';
else
technicalSignal = '中性';
return {
macdSignal,
rsiLevel: parseFloat(rsi.toFixed(2)),
bollingerStatus,
technicalSignal,
trendStatus,
};
}
// 辅助函数: 计算完整的MACD序列
export function calculateFullMACD(prices) {
const ema12Values = [];
const ema26Values = [];
const macdValues = [];
// 计算所有EMA12和EMA26值
let ema12 = prices[0];
let ema26 = prices[0];
for (let i = 0; i < prices.length; i++) {
ema12 = prices[i] * (2 / 13) + ema12 * (1 - 2 / 13);
ema26 = prices[i] * (2 / 27) + ema26 * (1 - 2 / 27);
ema12Values.push(ema12);
ema26Values.push(ema26);
macdValues.push(ema12 - ema26);
}
// 计算信号线(MACD的9周期EMA)
const signalValues = [];
// 初始化信号线
if (macdValues.length >= 9) {
let signal = macdValues.slice(0, 9).reduce((sum, val) => sum + val, 0) / 9;
for (let i = 0; i < 9; i++) {
signalValues.push(signal); // 前9个值使用相同的初始值
}
// 计算剩余的信号线值
for (let i = 9; i < macdValues.length; i++) {
signal = macdValues[i] * (2 / 10) + signal * (1 - 2 / 10);
signalValues.push(signal);
}
}
else {
// 数据不足时填充
for (let i = 0; i < macdValues.length; i++) {
signalValues.push(macdValues[i]);
}
}
return {
macdLine: macdValues,
signalLine: signalValues,
};
}
// 辅助函数: 计算标准的RSI
export function calculateStandardRSI(prices, period) {
if (prices.length <= period)
return 50;
let avgGain = 0;
let avgLoss = 0;
// 计算首个RSI的平均涨跌幅
for (let i = 1; i <= period; i++) {
const change = prices[i] - prices[i - 1];
if (change >= 0) {
avgGain += change;
}
else {
avgLoss -= change;
}
}
avgGain /= period;
avgLoss /= period;
// 使用Wilder平滑技术计算后续RSI
for (let i = period + 1; i < prices.length; i++) {
const change = prices[i] - prices[i - 1];
if (change >= 0) {
avgGain = (avgGain * (period - 1) + change) / period;
avgLoss = (avgLoss * (period - 1)) / period;
}
else {
avgGain = (avgGain * (period - 1)) / period;
avgLoss = (avgLoss * (period - 1) - change) / period;
}
}
if (avgLoss === 0)
return 100;
const rs = avgGain / avgLoss;
return 100 - 100 / (1 + rs);
}
// 辅助函数: 趋势识别
export function identifyTrend(prices) {
if (prices.length < 60)
return '数据不足';
const sma5 = calculateSMA(prices, 5);
const sma10 = calculateSMA(prices, 10);
const sma20 = calculateSMA(prices, 20);
const sma60 = calculateSMA(prices, 60);
// 计算价格与各均线的关系
const priceVsSma5 = prices[prices.length - 1] > sma5;
const priceVsSma10 = prices[prices.length - 1] > sma10;
const priceVsSma20 = prices[prices.length - 1] > sma20;
const priceVsSma60 = prices[prices.length - 1] > sma60;
// 计算均线间的关系
const sma5VsSma10 = sma5 > sma10;
const sma10VsSma20 = sma10 > sma20;
const sma20VsSma60 = sma20 > sma60;
// 判断多周期趋势
if (priceVsSma5 &&
priceVsSma10 &&
priceVsSma20 &&
priceVsSma60 &&
sma5VsSma10 &&
sma10VsSma20 &&
sma20VsSma60) {
return '强势上涨趋势';
}
else if (!priceVsSma5 &&
!priceVsSma10 &&
!priceVsSma20 &&
!priceVsSma60 &&
!sma5VsSma10 &&
!sma10VsSma20 &&
!sma20VsSma60) {
return '强势下跌趋势';
}
else if (priceVsSma5 && priceVsSma10 && priceVsSma20 && !priceVsSma60) {
return '中期反弹';
}
else if (!priceVsSma5 && !priceVsSma10 && !priceVsSma20 && priceVsSma60) {
return '中期回调';
}
else if (priceVsSma5 && priceVsSma10 && !priceVsSma20 && !priceVsSma60) {
return '短期反弹';
}
else if (!priceVsSma5 && !priceVsSma10 && priceVsSma20 && priceVsSma60) {
return '短期回调';
}
else {
return '震荡整理';
}
}
// 辅助函数 - 计算SMA
export function calculateSMA(data, period) {
const slice = data.slice(-period);
if (slice.length === 0)
return 0;
return slice.reduce((sum, price) => sum + price, 0) / slice.length;
}
// 辅助函数 - 计算标准差
export function calculateStdDev(data, period) {
const slice = data.slice(-period);
const mean = slice.reduce((sum, val) => sum + val, 0) / slice.length;
const squaredDiffs = slice.map(val => Math.pow(val - mean, 2));
const variance = squaredDiffs.reduce((sum, val) => sum + val, 0) / slice.length;
return Math.sqrt(variance);
}
/**
* 计算成交量力量指标
*/
export function calculateVolumeForce(data, lookbackPeriod) {
if (data.length < lookbackPeriod) {
return 0;
}
const recentData = data.slice(-lookbackPeriod);
// 计算上涨日的平均成交量和下跌日的平均成交量
let upVolume = 0;
let upDays = 0;
let downVolume = 0;
let downDays = 0;
for (let i = 1; i < recentData.length; i++) {
const priceChange = recentData[i].close - recentData[i - 1].close;
const volume = recentData[i].volume;
if (priceChange > 0) {
upVolume += volume;
upDays++;
}
else if (priceChange < 0) {
downVolume += volume;
downDays++;
}
}
const avgUpVolume = upDays > 0 ? upVolume / upDays : 0;
const avgDownVolume = downDays > 0 ? downVolume / downDays : 0;
// 防止除以零
if (avgUpVolume + avgDownVolume === 0)
return 0;
// 计算成交量力量 (-100 到 100)
const volumeForce = (100 * (avgUpVolume - avgDownVolume)) / (avgUpVolume + avgDownVolume);
return volumeForce;
}
/**
* 计算资金流指标 (Money Flow Index, MFI)
*/
export function calculateMoneyFlowIndex(data, period) {
if (data.length <= period) {
return 50; // 默认返回中性值
}
const typicalPrices = [];
const moneyFlow = [];
// 计算典型价格和资金流
for (let i = 0; i < data.length; i++) {
const { high, low, close, volume } = data[i];
const typicalPrice = (high + low + close) / 3;
typicalPrices.push(typicalPrice);
moneyFlow.push(typicalPrice * volume);
}
// 计算资金流比率
let positiveFlow = 0;
let negativeFlow = 0;
for (let i = data.length - period; i < data.length; i++) {
if (i > 0) {
if (typicalPrices[i] > typicalPrices[i - 1]) {
positiveFlow += moneyFlow[i];
}
else if (typicalPrices[i] < typicalPrices[i - 1]) {
negativeFlow += moneyFlow[i];
}
}
}
// 防止除以零
if (negativeFlow === 0)
return 100;
if (positiveFlow === 0)
return 0;
const moneyFlowRatio = positiveFlow / negativeFlow;
const mfi = 100 - 100 / (1 + moneyFlowRatio);
return mfi;
}
/**
* 计算蔡金摆动指标 (Chaikin Oscillator)
*
* 这个函数执行归一化处理,使返回值在合理范围内。
* 原因:A/D线可能会累积到非常大的值,导致蔡金摆动指标也非常大。
*/
export function calculateChaikinOscillator(adLine) {
if (adLine.length < 10) {
return 0;
}
// 仅使用最近100个数据点计算蔡金摆动指标,避免历史累积值过大
const recentADLine = adLine.slice(-100);
// 根据最大绝对值对参与计算的A/D线进行归一化
const maxAbsValue = Math.max(...recentADLine.map(v => Math.abs(v)));
const normalizedADLine = maxAbsValue === 0 ? recentADLine : recentADLine.map(v => v / maxAbsValue);
// 计算3日和10日EMA
const ema3 = calculateEMA(normalizedADLine, 3);
const ema10 = calculateEMA(normalizedADLine, 10);
// 蔡金摆动指标是两个EMA的差值
// 将差值乘以5使其处于典型的范围内(通常在-5到+5之间)
return (ema3 - ema10) * 5;
}
/**
* 计算指数移动平均线 (EMA)
*/
function calculateEMA(data, period) {
if (data.length < period) {
return data[data.length - 1];
}
const alpha = 2 / (period + 1);
const recentData = data.slice(-period * 2);
// 初始EMA为第一个值
let ema = recentData[0];
// 计算EMA
for (let i = 1; i < recentData.length; i++) {
ema = alpha * recentData[i] + (1 - alpha) * ema;
}
return ema;
}
/**
* 计算积累分布线 (A/D Line)
*/
export function calculateADLine(data) {
const adLine = [];
let ad = 0;
for (let i = 0; i < data.length; i++) {
const { high, low, close, volume } = data[i];
// 防止除以零
if (high === low) {
adLine.push(ad);
continue;
}
// 计算货币流量乘数 (Money Flow Multiplier)
const mfm = (close - low - (high - close)) / (high - low);
// 计算货币流量量 (Money Flow Volume)
const mfv = mfm * volume;
// 更新积累分布线
ad += mfv;
adLine.push(ad);
}
return adLine;
}
/**
* 计算能量潮指标 (OBV)
*
* 注意:OBV是累积型指标,随着数据增多会累积到很大的数值。
* 在使用OBV时,应主要关注其变化趋势而非绝对值的大小。
*/
export function calculateOBV(data) {
const obv = [0]; // 初始OBV值设为0
for (let i = 1; i < data.length; i++) {
const currentClose = data[i].close;
const previousClose = data[i - 1].close;
const currentVolume = data[i].volume;
if (currentClose > previousClose) {
// 收盘价上涨,加上当日成交量
obv.push(obv[i - 1] + currentVolume);
}
else if (currentClose < previousClose) {
// 收盘价下跌,减去当日成交量
obv.push(obv[i - 1] - currentVolume);
}
else {
// 收盘价不变,OBV保持不变
obv.push(obv[i - 1]);
}
}
return obv;
}
/**
* 计算返回率序列
*/
export function calculateReturns(prices) {
const returns = [];
for (let i = 1; i < prices.length; i++) {
returns.push(prices[i] / prices[i - 1] - 1);
}
return returns;
}
/**
* 计算标准差
*/
export function calculateStandardDeviation(data) {
if (!data || data.length === 0)
return 0;
const mean = data.reduce((sum, value) => sum + value, 0) / data.length;
const squaredDiffs = data.map(value => Math.pow(value - mean, 2));
const variance = squaredDiffs.reduce((sum, value) => sum + value, 0) / data.length;
return Math.sqrt(Math.max(variance, 0));
}
/**
* 计算最近的ATR值
*/
export function calculateATR(data, period) {
const trValues = [];
// 计算真实范围值
for (let i = 1; i < data.length; i++) {
const high = data[i].high;
const low = data[i].low;
const prevClose = data[i - 1].close;
const tr1 = high - low;
const tr2 = Math.abs(high - prevClose);
const tr3 = Math.abs(low - prevClose);
const tr = Math.max(tr1, tr2, tr3);
trValues.push(tr);
}
// 计算ATR
if (trValues.length === 0)
return 0;
if (trValues.length < period) {
return trValues.reduce((sum, tr) => sum + tr, 0) / trValues.length;
}
const recentTRs = trValues.slice(-period);
return recentTRs.reduce((sum, tr) => sum + tr, 0) / period;
}
/**
* 计算完整的ATR序列
*/
export function calculateATRSeries(data, period) {
const trValues = [];
const atrValues = [];
// 计算真实范围值
for (let i = 1; i < data.length; i++) {
const high = data[i].high;
const low = data[i].low;
const prevClose = data[i - 1].close;
const tr1 = high - low;
const tr2 = Math.abs(high - prevClose);
const tr3 = Math.abs(low - prevClose);
const tr = Math.max(tr1, tr2, tr3);
trValues.push(tr);
}
// 计算初始ATR
if (trValues.length < period) {
const initialATR = trValues.reduce((sum, tr) => sum + tr, 0) / trValues.length;
atrValues.push(initialATR);
return atrValues;
}
// 计算第一个ATR
let atr = trValues.slice(0, period).reduce((sum, tr) => sum + tr, 0) / period;
atrValues.push(atr);
// 计算其余ATR (Wilder's smoothing)
for (let i = period; i < trValues.length; i++) {
atr = (atr * (period - 1) + trValues[i]) / period;
atrValues.push(atr);
}
return atrValues;
}
export function rollingMin(prices, window) {
const result = [];
for (let i = 0; i < prices.length; i++) {
const start = Math.max(0, i - window + 1);
const end = i + 1;
const windowSlice = prices.slice(start, end);
result.push(Math.min(...windowSlice));
}
return result;
}
export function rollingMax(prices, window) {
const result = [];
for (let i = 0; i < prices.length; i++) {
const start = Math.max(0, i - window + 1);
const end = i + 1;
const windowSlice = prices.slice(start, end);
result.push(Math.max(...windowSlice));
}
return result;
}
export function percentChange(prices) {
const changes = [];
for (let i = 1; i < prices.length; i++) {
const change = ((prices[i] - prices[i - 1]) / prices[i - 1]) * 100;
changes.push(change);
}
return changes;
}
/**
* 计算数据序列的线性回归斜率
*
* 注意:对于大值序列(如OBV和A/D Line),在计算斜率前应进行归一化处理。
* 该函数默认应用在已经归一化的数据上。
*/
export function calculateSlope(prices) {
const n = prices.length;
const x = Array.from({ length: n }, (_, i) => i + 1);
const y = prices;
const sumX = x.reduce((a, b) => a + b, 0);
const sumY = y.reduce((a, b) => a + b, 0);
const sumXY = x.reduce((sum, xi, i) => sum + xi * y[i], 0);
const sumX2 = x.reduce((sum, xi) => sum + xi * xi, 0);
return (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
}
// 辅助函数 - 根据累积百分比找到对应价格
export function findPriceAtCumulativePercentage(cumulativeDistribution, targetPercentage) {
// 查找最接近目标累积百分比的项
for (let i = 0; i < cumulativeDistribution.length; i++) {
if (cumulativeDistribution[i].cumulativePercentage >= targetPercentage) {
return cumulativeDistribution[i].price;
}
}
// 如果没有找到,返回最后一项的价格
return cumulativeDistribution[cumulativeDistribution.length - 1].price;
}