UNPKG

@gabriel3615/ta_analysis

Version:

stock ta analysis

590 lines (589 loc) 25.6 kB
import { calculateATR, calculateATRSeries, calculateReturns, calculateSMA, calculateStandardDeviation, } from '../../../util/taUtil.js'; import { volatilityConfig } from './volatilityConfig.js'; import { executeVolumeAnalysis, } from './volumeVolatilityAnalysis.js'; /** * 基于ATR百分比和布林带宽度判断波动率状态 */ function determineVolatilityRegime(atrPercent, bbWidth) { // 根据配置阈值判断: // - low/medium 需同时满足 ATR% 与 布林带宽度上限 // - high 当任一指标低于 high 阈值(但未满足 medium)即归为 high // - extreme 为更高波动状态(两项均超过 high 阈值) if (atrPercent < volatilityConfig.regime.low.atrPercentMax && bbWidth < volatilityConfig.regime.low.bbWidthMax) { return 'low'; } else if (atrPercent < volatilityConfig.regime.medium.atrPercentMax && bbWidth < volatilityConfig.regime.medium.bbWidthMax) { return 'medium'; } else if (atrPercent < volatilityConfig.regime.high.atrPercentMax || bbWidth < volatilityConfig.regime.high.bbWidthMax) { return 'high'; } else { return 'extreme'; } } /** * 计算最大回撤 */ function calculateMaxDrawdown(prices) { let maxDrawdown = 0; let peak = prices[0]; for (let i = 1; i < prices.length; i++) { if (prices[i] > peak) { peak = prices[i]; } else { const drawdown = ((peak - prices[i]) / peak) * 100; maxDrawdown = Math.max(maxDrawdown, drawdown); } } return maxDrawdown; } /** * 计算下行偏差(只考虑负收益) */ function calculateDownsideDeviation(returns) { const negativeReturns = returns.filter(r => r < 0); if (negativeReturns.length === 0) return 0; const squaredNegativeReturns = negativeReturns.map(r => r * r); const avgSquaredNegativeReturn = squaredNegativeReturns.reduce((sum, r) => sum + r, 0) / negativeReturns.length; return Math.sqrt(avgSquaredNegativeReturn); } /** * 确定波动率趋势描述 */ function determineVolatilityTrend(atrValues, bbWidth) { // 取最近的ATR值进行比较 const recentATRs = atrValues.slice(-20); const currentATR = recentATRs[recentATRs.length - 1]; const fiveDaysAgoATR = recentATRs[recentATRs.length - 5] || recentATRs[0]; // 计算5天变化百分比(防止除零) const denom = Math.abs(fiveDaysAgoATR) < 1e-8 ? 1e-8 : fiveDaysAgoATR; const fiveDayChange = ((currentATR - fiveDaysAgoATR) / denom) * 100; // 根据波动率变化和布林带宽度判断趋势 if (fiveDayChange > volatilityConfig.trend.fiveDayIncreaseFast) { return '波动率快速增加,可能预示着价格剧烈波动'; } else if (fiveDayChange > volatilityConfig.trend.fiveDayIncrease) { return '波动率稳步增加,市场不确定性上升'; } else if (fiveDayChange < volatilityConfig.trend.fiveDayDecreaseFast) { return '波动率显著下降,价格可能进入盘整阶段'; } else if (fiveDayChange < volatilityConfig.trend.fiveDayDecrease) { return '波动率逐渐下降,市场趋于稳定'; } else if (bbWidth < volatilityConfig.trend.bbSqueezeWidth) { return '波动率处于极低水平,可能即将爆发行情'; } else { return '波动率保持相对稳定'; } } /** * 计算夏普比率 */ function calculateSharpeRatio(returns) { const annualFactor = volatilityConfig.sharpe.annualFactor; // 交易日 const riskFreeRate = volatilityConfig.sharpe.riskFreeRateAnnual / annualFactor; // 计算超额收益 const excessReturns = returns.map(r => r - riskFreeRate); // 计算超额收益的平均值 const meanExcessReturn = excessReturns.reduce((sum, r) => sum + r, 0) / excessReturns.length; // 计算标准差 const stdDev = calculateStandardDeviation(returns); if (stdDev === 0) return 0; // 年化超额收益和标准差 const annualizedMeanExcessReturn = meanExcessReturn * annualFactor; const annualizedStdDev = stdDev * Math.sqrt(annualFactor); return annualizedMeanExcessReturn / annualizedStdDev; } /** * 计算平均价格范围 */ function calculateAverageRange(data, groupSize) { if (data.length < groupSize || groupSize <= 0) return 0; let totalRangePercent = 0; let groupCount = 0; for (let i = 0; i < data.length; i += groupSize) { if (i + groupSize <= data.length) { const group = data.slice(i, i + groupSize); const highInGroup = Math.max(...group.map(c => c.high)); const lowInGroup = Math.min(...group.map(c => c.low)); const denom = Math.abs(lowInGroup) < 1e-8 ? 1e-8 : lowInGroup; const rangePercent = ((highInGroup - lowInGroup) / denom) * 100; totalRangePercent += rangePercent; groupCount++; } } return groupCount > 0 ? totalRangePercent / groupCount : 0; } /** * 计算值在数组中的百分位 */ function calculatePercentile(value, array) { const sorted = [...array].sort((a, b) => a - b); const position = sorted.findIndex(item => item >= value); if (position === -1) return 100; return (position / sorted.length) * 100; } /** * 计算综合波动率分析,包括多项波动率指标 * @param data 历史K线数据 * @param lookbackPeriod 回溯期(默认为20个交易日) */ export function calculateVolatilityAnalysis(data, lookbackPeriod = volatilityConfig.periods.defaultLookback) { if (data.length < Math.max(lookbackPeriod, 30)) { throw new Error('数据不足以进行有效的波动率分析'); } // 提取价格数据 const closes = data.map(d => d.close); const highs = data.map(d => d.high); const lows = data.map(d => d.low); const recentCloses = closes.slice(-lookbackPeriod); // 1. 计算历史波动率 (标准差法) const returns = calculateReturns(closes); const recentReturns = returns.slice(-lookbackPeriod); const historicalVolatility = (calculateStandardDeviation(recentReturns) || 0) * Math.sqrt(252); // 年化 // 2. 计算布林带宽度 (相对于价格的百分比) const sma20 = calculateSMA(closes, volatilityConfig.periods.smaShort); const stdDev = calculateStandardDeviation(closes.slice(-volatilityConfig.periods.smaShort)); const upperBand = sma20 + stdDev * 2; const lowerBand = sma20 - stdDev * 2; const denomBB = Math.abs(sma20) < 1e-8 ? 1e-8 : sma20; const bollingerBandWidth = ((upperBand - lowerBand) / denomBB) * 100; // 3. 计算平均真实范围 (ATR) const atr = calculateATR(data, volatilityConfig.periods.atr); const lastClose = closes[closes.length - 1]; const denomClose = Math.abs(lastClose) < 1e-8 ? 1e-8 : lastClose; const atrPercent = (atr / denomClose) * 100; // 4. 判断波动率状态 const volatilityRegime = determineVolatilityRegime(atrPercent, bollingerBandWidth); // 5. 波动率趋势判断 const atrValues = calculateATRSeries(data, volatilityConfig.periods.atr); const prevIdx = Math.max(0, atrValues.length - 5); const isVolatilityIncreasing = atrValues[atrValues.length - 1] > atrValues[prevIdx]; // 6. 计算波动率百分位 const longTermATRs = calculateATRSeries(data, volatilityConfig.periods.atr); const volatilityPercentile = calculatePercentile(atrValues[atrValues.length - 1], longTermATRs); // 7. 确定波动率趋势描述 const volatilityTrend = determineVolatilityTrend(atrValues, bollingerBandWidth); // 8. 计算各时间段的价格波动范围 const recentRanges = { daily: calculateAverageRange(data.slice(-5), 1), weekly: calculateAverageRange(data.slice(-20), 5), monthly: calculateAverageRange(data, 20), }; // 9. 计算风险度量 const riskMetrics = { maxDrawdown: calculateMaxDrawdown(closes), downsideDeviation: calculateDownsideDeviation(returns), sharpeRatio: returns.length >= 60 ? calculateSharpeRatio(returns) : undefined, }; return { historicalVolatility, bollingerBandWidth, atr, atrPercent, volatilityRegime, isVolatilityIncreasing, volatilityPercentile, volatilityTrend, recentRanges, riskMetrics, }; } /** * 格式化波动率分析结果为易读的字符串 */ export function formatVolatilityAnalysis(analysis) { let result = '=== 波动率分析 ===\n\n'; result += `历史波动率: ${analysis.historicalVolatility.toFixed(2)}%(年化)\n`; result += `ATR: ${analysis.atr.toFixed(4)}${analysis.atrPercent.toFixed(2)}%)\n`; result += `布林带宽度: ${analysis.bollingerBandWidth.toFixed(2)}%\n`; result += `波动率状态: ${translateVolatilityRegime(analysis.volatilityRegime)}\n`; result += `波动率趋势: ${analysis.volatilityTrend}\n`; result += `波动率百分位: ${analysis.volatilityPercentile.toFixed(2)}%\n\n`; result += '平均波动范围:\n'; result += ` 日: ${analysis.recentRanges.daily.toFixed(2)}%\n`; result += ` 周: ${analysis.recentRanges.weekly.toFixed(2)}%\n`; result += ` 月: ${analysis.recentRanges.monthly.toFixed(2)}%\n\n`; result += '风险指标:\n'; result += ` 最大回撤: ${analysis.riskMetrics.maxDrawdown.toFixed(2)}%\n`; result += ` 下行偏差: ${analysis.riskMetrics.downsideDeviation.toFixed(4)}\n`; if (analysis.riskMetrics.sharpeRatio !== undefined) { result += ` 夏普比率: ${analysis.riskMetrics.sharpeRatio.toFixed(2)}\n`; } return result; } /** * 增强底部检测的波动率分析函数 * @param data 历史K线数据 * @param lookbackPeriod 回溯期 */ export function calculateEnhancedVolatilityAnalysis(data, lookbackPeriod = 20) { // 首先获取基础波动率分析结果 const baseAnalysis = calculateVolatilityAnalysis(data, lookbackPeriod); // 计算价格位置信息 const pricePosition = calculatePricePosition(data); // 分析波动率渐变特征 const volatilityTransition = analyzeVolatilityTransition(data); // 检测底部反转信号 const bottomSignals = detectBottomSignals(data, baseAnalysis); // 整合所有分析结果 return { ...baseAnalysis, pricePosition, volatilityTransition, bottomSignals, }; } /** * 计算价格相对于历史高低点和均线的位置 * * 注意: * relativeToHigh - 表示价格在52周区间中的位置百分比(0=低点,100=高点) * relativeToLow - 表示距离低点的百分比(越大表示越接近低点) */ function calculatePricePosition(data) { const closes = data.map(d => d.close); const currentPrice = closes[closes.length - 1]; // 计算52周(约250个交易日)高低点 const yearData = data.slice(-Math.min(volatilityConfig.pricePosition.yearDays, data.length)); const yearHigh = Math.max(...yearData.map(d => d.high)); const yearLow = Math.min(...yearData.map(d => d.low)); // 计算区间中的相对位置百分比(0=低点,100=高点),防止除零 const denom = Math.abs(yearHigh - yearLow) < 1e-8 ? 1e-8 : yearHigh - yearLow; const positionInRange = ((currentPrice - yearLow) / denom) * 100; // 语义修正:relativeToHigh 表示区间位置(越大越靠近高点);relativeToLow 表示距离低点的百分比(越大越靠近低点) const relativeToYearHigh = positionInRange; const relativeToYearLow = 100 - positionInRange; // 计算长期均线(如果有足够数据) let relativeTo200MA = 0; if (data.length >= volatilityConfig.periods.smaLong) { const ma200 = calculateSMA(closes, volatilityConfig.periods.smaLong); const denomMA = Math.abs(ma200) < 1e-8 ? 1e-8 : ma200; relativeTo200MA = ((currentPrice - ma200) / denomMA) * 100; } return { relativeToHigh: relativeToYearHigh, relativeToLow: relativeToYearLow, relativeTo200MA, }; } // TODO, 小时周期判定 /** * 分析波动率渐变特征 */ function analyzeVolatilityTransition(data) { // 默认结果 const defaultResult = { isTransitioning: false, fromRegime: 'medium', toRegime: 'medium', transitionStrength: 0, }; if (data.length < 60) return defaultResult; // 分析两个不同时间窗口的波动率 const previousPeriod = data.slice(-60, -30); const currentPeriod = data.slice(-30); const previousAnalysis = calculateVolatilityAnalysis(previousPeriod, 10); const currentAnalysis = calculateVolatilityAnalysis(currentPeriod, 10); // 如果波动率状态不同,则识别为过渡期 if (previousAnalysis.volatilityRegime !== currentAnalysis.volatilityRegime) { // 计算过渡强度 // 通过ATR变化百分比来估计过渡强度,防止除零 const denom = Math.abs(previousAnalysis.atrPercent) < 1e-8 ? 1e-8 : previousAnalysis.atrPercent; const atrChange = (Math.abs(currentAnalysis.atrPercent - previousAnalysis.atrPercent) / denom) * 100; // 将变化映射到0-100的范围 const transitionStrength = Math.min(100, atrChange * volatilityConfig.transition.atrChangeToStrengthFactor); return { isTransitioning: true, fromRegime: previousAnalysis.volatilityRegime, toRegime: currentAnalysis.volatilityRegime, transitionStrength, }; } return defaultResult; } /** * 检测底部反转信号 */ function detectBottomSignals(data, baseAnalysis) { // 初始化信号强度 let signalStrength = 0; // 获取价格和成交量数据 const closes = data.map(d => d.close); const volumes = data.map(d => d.volume); // 1. 检查价格是否处于历史低位 const pricePosition = calculatePricePosition(data); if (pricePosition.relativeToLow > volatilityConfig.bottomSignal.nearYearLowPercent) { signalStrength += volatilityConfig.bottomSignal.weights.nearLow; // 价格接近年度低点 } // 2. 检查波动率特征 // 高波动率开始下降通常是底部特征之一 if (baseAnalysis.volatilityRegime === 'high' || baseAnalysis.volatilityRegime === 'extreme') { if (!baseAnalysis.isVolatilityIncreasing) { signalStrength += volatilityConfig.bottomSignal.weights.highVolFalling; // 高波动率开始下降 } } // 3. 检查布林带宽度 // 布林带收缩后开始扩张可能预示趋势反转 if (baseAnalysis.bollingerBandWidth < volatilityConfig.bottomSignal.bbSqueezeForBottom) { signalStrength += volatilityConfig.bottomSignal.weights.bbSqueeze; // 布林带收缩 } // 4. 通过短期价格走势判断是否企稳 const recentCloses = closes.slice(-volatilityConfig.bottomSignal.recentSlice); const previousCloses = closes.slice(-volatilityConfig.bottomSignal.recentSlice - volatilityConfig.bottomSignal.previousSlice, -volatilityConfig.bottomSignal.recentSlice); const previousTrend = calculateSimpleTrend(previousCloses); const recentTrend = calculateSimpleTrend(recentCloses); // 前期下跌,近期企稳或上涨 if (previousTrend < volatilityConfig.bottomSignal.previousTrendThreshold && recentTrend >= volatilityConfig.bottomSignal.recentTrendThreshold) { signalStrength += volatilityConfig.bottomSignal.weights.stabilize; } // 5. 检查成交量特征 const recentVolumes = volumes.slice(-volatilityConfig.bottomSignal.recentSlice); const previousVolumes = volumes.slice(-volatilityConfig.bottomSignal.recentSlice - volatilityConfig.bottomSignal.previousSlice, -volatilityConfig.bottomSignal.recentSlice); const avgRecentVolume = calculateAverageVolume(recentVolumes); const avgPreviousVolume = calculateAverageVolume(previousVolumes); // 成交量放大通常是底部特征之一 if (avgRecentVolume > avgPreviousVolume * volatilityConfig.bottomSignal.volumeIncreaseFactor) { signalStrength += volatilityConfig.bottomSignal.weights.volumeIncrease; // 近期成交量放大 } // 是否可能为底部反转 const potentialBottomReversal = signalStrength > volatilityConfig.bottomSignal.bottomStrongThreshold; return { potentialBottomReversal, reversalStrength: Math.min(100, signalStrength), }; } /** * 计算简单趋势 */ function calculateSimpleTrend(prices) { if (prices.length < 2) return 0; return (prices[prices.length - 1] - prices[0]) / prices[0]; } /** * 计算平均成交量 */ function calculateAverageVolume(volumes) { return volumes.reduce((sum, vol) => sum + vol, 0) / volumes.length; } /** * 格式化增强版波动率分析结果 */ export function formatEnhancedVolatilityAnalysis(analysis) { // 先获取基本格式化结果 let result = formatVolatilityAnalysis(analysis); // 添加价格位置分析 result += '\n=== 价格位置分析 ===\n'; result += `相对52周高点: ${analysis.pricePosition.relativeToHigh.toFixed(2)}%\n`; result += `相对52周低点: ${analysis.pricePosition.relativeToLow.toFixed(2)}%\n`; if (analysis.pricePosition.relativeTo200MA !== 0) { result += `相对200日均线: ${analysis.pricePosition.relativeTo200MA.toFixed(2)}%\n`; } // 添加波动率渐变分析 if (analysis.volatilityTransition.isTransitioning) { result += '\n=== 波动率渐变分析 ===\n'; result += `波动率状态: 从${translateVolatilityRegime(analysis.volatilityTransition.fromRegime)}${translateVolatilityRegime(analysis.volatilityTransition.toRegime)}过渡\n`; result += `渐变强度: ${analysis.volatilityTransition.transitionStrength.toFixed(0)}%\n`; } // 添加底部反转信号分析 if (analysis.bottomSignals.potentialBottomReversal) { result += '\n=== 底部反转信号分析 ===\n'; result += `反转信号强度: ${analysis.bottomSignals.reversalStrength.toFixed(0)}/100\n`; if (analysis.bottomSignals.reversalStrength > 70) { result += '底部反转信号强烈\n'; } else if (analysis.bottomSignals.reversalStrength > 50) { result += '存在明显的底部反转迹象\n'; } else { result += '出现初步底部反转特征\n'; } } return result; } /** * 增强版量能波动率综合分析 */ export function generateEnhancedCombinedAnalysis(volumeAnalysis, volatilityAnalysis) { let summary = '\n【波动率量能分析结论】\n\n'; // 1. 价格位置分析, 小时周期用过去60天的数据 const pricePosition = volatilityAnalysis.pricePosition; if (pricePosition.relativeToLow > 80) { summary += `价格位置: 接近年度低点(位于年度区间底部${(100 - pricePosition.relativeToLow).toFixed(1)}%处)\n`; } else if (pricePosition.relativeToHigh > 80) { summary += `价格位置: 接近年度高点(位于年度区间顶部${pricePosition.relativeToHigh.toFixed(1)}%处)\n`; } else { summary += `价格位置: 位于过去60天价格区间中段(距底部${(100 - pricePosition.relativeToLow).toFixed(1)}%,距顶部${(100 - pricePosition.relativeToHigh).toFixed(1)}%)\n`; } // 2. 波动率状态 summary += `波动率状态: ${translateVolatilityRegime(volatilityAnalysis.volatilityRegime)}`; summary += volatilityAnalysis.isVolatilityIncreasing ? '(波动率上升中)\n' : '(波动率下降中)\n'; // 3. 波动率渐变特征 if (volatilityAnalysis.volatilityTransition.isTransitioning) { summary += `波动率渐变: 从${translateVolatilityRegime(volatilityAnalysis.volatilityTransition.fromRegime)}${translateVolatilityRegime(volatilityAnalysis.volatilityTransition.toRegime)}过渡中\n`; } // 4. 资金流向 summary += `资金流向趋势: ${translateADTrend(volumeAnalysis.adTrend)}\n`; // 5. 底部反转信号 if (volatilityAnalysis.bottomSignals.potentialBottomReversal) { summary += `\n检测到潜在底部反转信号(强度: ${volatilityAnalysis.bottomSignals.reversalStrength.toFixed(0)}/100)\n`; } // 6. 潜在信号 summary += '\n'; // 检测常见底部形态信号 if (pricePosition.relativeToLow > 80 && !volatilityAnalysis.isVolatilityIncreasing) { summary += '- 价格处于低位且波动率开始下降,可能是恐慌情绪逐渐释放\n'; } if (volatilityAnalysis.bottomSignals.potentialBottomReversal && volumeAnalysis.adTrend !== 'bearish') { summary += '- 底部形态特征明显,同时资金流向未呈现明显流出,支持反转观点\n'; } if (volatilityAnalysis.volatilityRegime === 'extreme' && !volatilityAnalysis.isVolatilityIncreasing) { summary += '- 极端波动率开始下降,可能预示趋势将转为震荡或反转\n'; } if (volumeAnalysis.moneyFlowIndex < 30 && pricePosition.relativeToLow > 70) { summary += '- MFI处于超卖区域,且价格位于低位,具备反弹条件\n'; } if (volatilityAnalysis.bollingerBandWidth < 3.5) { summary += '- 布林带挤压,波动率较低,可能即将爆发行情\n'; } // 7. 交易建议 summary += '\n'; // 针对底部反转情况给出更具体的建议 if (volatilityAnalysis.bottomSignals.potentialBottomReversal && pricePosition.relativeToLow > 70) { // 底部区域 summary += '价格处于低位且展现底部特征,可考虑分批建仓,设置合理止损。'; // 根据波动率给出更具体的策略 if (volatilityAnalysis.volatilityRegime === 'high' || volatilityAnalysis.volatilityRegime === 'extreme') { summary += '由于波动率较高,建议采用小仓位、多批次方式逐步介入,避免一次性建仓。'; } else { summary += '波动率相对可控,可采用较为常规的建仓策略。'; } } // 其他情况保留原有判断逻辑 else if (volumeAnalysis.adTrend === 'bullish' && volatilityAnalysis.isVolatilityIncreasing) { summary += '资金流入且波动率上升,市场可能处于上升趋势初期,可考虑逐步建仓。'; } else if (volumeAnalysis.adTrend === 'bearish' && volatilityAnalysis.isVolatilityIncreasing) { summary += '资金流出且波动率上升,下跌趋势增强,建议保持观望或考虑做空。'; } else if (volumeAnalysis.adTrend === 'neutral') { summary += '资金流向中性,建议等待更明确的信号。'; } return summary; } /** * 波动率状态翻译 */ function translateVolatilityRegime(regime) { switch (regime) { case 'low': return '低波动'; case 'medium': return '中等波动'; case 'high': return '高波动'; case 'extreme': return '极端波动'; default: return regime; } } /** * 积累分布线趋势翻译 */ function translateADTrend(trend) { switch (trend) { case 'bullish': return '资金流入占优(看涨)'; case 'bearish': return '资金流出占优(看跌)'; case 'neutral': return '资金流向中性'; default: return trend; } } /** * 执行增强版波动率分析 */ export function executeEnhancedVolatilityAnalysis(data, lookbackPeriod = 20) { try { // 获取增强版波动率分析结果 const enhancedVolatilityAnalysis = calculateEnhancedVolatilityAnalysis(data, lookbackPeriod); // 格式化输出 const formattedVolatilityAnalysis = formatEnhancedVolatilityAnalysis(enhancedVolatilityAnalysis); return { volatilityAnalysis: enhancedVolatilityAnalysis, formattedVolatilityAnalysis, }; } catch (error) { console.error('执行增强版波动率分析时出错:', error); throw error; } } /** * 成交量和波动率综合分析 */ export function analyzeVolumeVolatilityCombined(data, lookbackPeriod = 20) { try { // 执行量价分析 const volumeAnalysisResult = executeVolumeAnalysis(data, lookbackPeriod); const volumeAnalysisReason = volumeAnalysisResult.formattedVolumeAnalysis; // 执行增强版波动率分析 const volatilityAnalysisResult = executeEnhancedVolatilityAnalysis(data, lookbackPeriod); const volatilityAnalysisReason = volatilityAnalysisResult.formattedVolatilityAnalysis; // 生成增强版综合分析结论 const enhancedSummary = generateEnhancedCombinedAnalysis(volumeAnalysisResult.volumeAnalysis, volatilityAnalysisResult.volatilityAnalysis); return { volumeAnalysis: volumeAnalysisResult, volumeAnalysisReason, volatilityAnalysis: volatilityAnalysisResult, volatilityAnalysisReason, combinedAnalysisSummary: enhancedSummary, }; } catch (error) { console.error('执行增强版综合分析时出错:', error); throw error; } }