UNPKG

@gabriel3615/ta_analysis

Version:

stock ta analysis

473 lines (472 loc) 21.6 kB
/** * 计算基尼系数 - 衡量分布不平等程度 * 值范围0-1,值越高表示分布越不平等(即筹码越集中) */ export function calculateGiniCoefficient(chipDistribution) { const n = chipDistribution.length; if (n <= 1) return 0; // 按权重排序 const sortedWeights = chipDistribution .map(item => item.weight) .sort((a, b) => a - b); let sumNumerator = 0; for (let i = 0; i < n; i++) { sumNumerator += sortedWeights[i] * (i + 1); } const sumWeights = sortedWeights.reduce((sum, weight) => sum + weight, 0); return (2 * sumNumerator) / (n * sumWeights) - (n + 1) / n; } /** * 识别筹码峰 * 使用自适应峰值检测算法识别筹码峰 */ export function identifyChipPeaks(chipDistribution, currentPrice) { // 过滤掉权重为0的数据点 const filteredDist = chipDistribution.filter(item => item.weight > 0); // 按价格排序 const sortedDist = [...filteredDist].sort((a, b) => a.price - b.price); if (sortedDist.length === 0) return []; // 计算统计特性,用于自适应参数 const weights = sortedDist.map(item => item.percentage); const meanWeight = weights.reduce((sum, w) => sum + w, 0) / weights.length; const stdDev = Math.sqrt(weights.reduce((sum, w) => sum + Math.pow(w - meanWeight, 2), 0) / weights.length); // 自适应窗口大小: 基于数据点数量和分布 // 数据点越多,窗口越大;分布越均匀,窗口越小 const dataSparsity = stdDev / meanWeight; // 分布的离散程度 const baseWindowSize = Math.max(3, Math.min(7, Math.floor(sortedDist.length / 20))); const windowSize = Math.max(2, Math.round(baseWindowSize * (1 - 0.5 * dataSparsity))); // 自适应峰值类型阈值 const majorThreshold = Math.max(5, meanWeight + 2 * stdDev); const secondaryThreshold = Math.max(2, meanWeight + stdDev); // 峰值检测的宽容度,允许接近峰值的点也被视为峰值的一部分 const peakTolerance = 0.85; // 如果一个点的权重至少是最高点的85%,将其视为同一峰的一部分 const candidatePeaks = []; const minWindowPadding = 1; // 即使在边界附近,我们也至少检查相邻的1个点 // 处理所有点,包括边界情况 for (let i = 0; i < sortedDist.length; i++) { const current = sortedDist[i]; let isPeak = true; // 确定实际窗口范围(处理边界情况) const leftBound = Math.max(0, i - windowSize); const rightBound = Math.min(sortedDist.length - 1, i + windowSize); // 如果窗口太小,则不考虑此点作为峰值 if (i - leftBound < minWindowPadding || rightBound - i < minWindowPadding) { continue; } // 记录窗口内的最大值 let windowMax = current.weight; for (let j = leftBound; j <= rightBound; j++) { if (j !== i && sortedDist[j].weight > windowMax) { windowMax = sortedDist[j].weight; } } // 检查是否是局部最大值,考虑容差 // 如果当前点至少是窗口最大值的peakTolerance比例,则认为是局部峰值的一部分 if (current.weight < peakTolerance * windowMax) { isPeak = false; } // 如果是峰值,添加到候选列表 if (isPeak) { const distance = ((current.price - currentPrice) / currentPrice) * 100; // 根据相对阈值确定峰值类型 let peakType = 'minor'; if (current.percentage >= majorThreshold) { peakType = 'major'; } else if (current.percentage >= secondaryThreshold) { peakType = 'secondary'; } candidatePeaks.push({ price: current.price, weight: current.weight, percentage: current.percentage, peakType, distance, }); } } // 峰值抑制 - 合并相近的峰 const finalPeaks = []; if (candidatePeaks.length > 0) { // 按权重排序候选峰值 candidatePeaks.sort((a, b) => b.weight - a.weight); // 使用最小价格间隔来防止过于接近的峰值 const minPriceGap = (sortedDist[sortedDist.length - 1].price - sortedDist[0].price) * 0.02; for (const peak of candidatePeaks) { // 检查这个峰值是否足够远离已添加的峰值 const isFarEnough = finalPeaks.every(existingPeak => Math.abs(existingPeak.price - peak.price) >= minPriceGap); if (isFarEnough) { finalPeaks.push(peak); } else { // 如果有一个已存在的峰值接近这个峰值,并且这个峰值更强,则替换它 const nearbyPeakIndex = finalPeaks.findIndex(existingPeak => Math.abs(existingPeak.price - peak.price) < minPriceGap && existingPeak.weight < peak.weight); if (nearbyPeakIndex !== -1) { finalPeaks[nearbyPeakIndex] = peak; } } } } // 按权重排序 return finalPeaks.sort((a, b) => b.weight - a.weight); } /** * 分析筹码峰形态 * 判断筹码峰的分布特征,用更明确的术语表示价格高低 */ export function analyzeChipPeakPattern(peaks, currentPrice, chipDistribution) { // 若没有识别到明显的峰,返回默认值 if (peaks.length === 0) { return { peakDistribution: '无明显峰值', chipShape: '分散分布', shapeBuySignal: false, peakComment: '筹码分布较为分散,无明显集中区域。', }; } // 计算当前价格下方(低价)和上方(高价)的筹码总量 const lowerPriceChips = chipDistribution .filter(item => item.price < currentPrice) .reduce((sum, item) => sum + item.percentage, 0); const higherPriceChips = chipDistribution .filter(item => item.price > currentPrice) .reduce((sum, item) => sum + item.percentage, 0); // 计算总筹码百分比和各区域占比 const totalChips = lowerPriceChips + higherPriceChips; const lowerChipRatio = lowerPriceChips / totalChips; const higherChipRatio = higherPriceChips / totalChips; // 筹码集中度,用于调整判断阈值 const chipConcentration = peaks.reduce((sum, p) => sum + p.percentage, 0) / totalChips; // 计算低价区和高价区的峰值,并确保按权重排序 const lowerPricePeaks = peaks .filter(p => p.price < currentPrice) .sort((a, b) => b.weight - a.weight); const higherPricePeaks = peaks .filter(p => p.price > currentPrice) .sort((a, b) => b.weight - a.weight); // 判断主要峰的相对强度 const hasLowerPeak = lowerPricePeaks.length > 0; const hasHigherPeak = higherPricePeaks.length > 0; // 根据市场环境调整的动态阈值 // 当筹码集中度高时,需要更明显的差异才能判断形态 const dominanceThreshold = 1.5 + chipConcentration * 0.5; // 判断主要筹码区域的极端性 const extremeImbalance = Math.abs(lowerChipRatio - higherChipRatio) > 0.7; const moderateImbalance = Math.abs(lowerChipRatio - higherChipRatio) > 0.3; // 判断形态 let peakDistribution = ''; let chipShape = ''; let shapeBuySignal = false; let peakComment = ''; // 判断峰的分布特征 if (hasLowerPeak && hasHigherPeak) { // 存在低价和高价两个区域的峰 const lowerPeakStrength = lowerPricePeaks[0].weight; const higherPeakStrength = higherPricePeaks[0].weight; const peakRatio = lowerPeakStrength / higherPeakStrength; if (peakRatio > dominanceThreshold && lowerChipRatio > 0.6) { // 低价区主峰明显强于高价区,且低价区筹码占比超过60% peakDistribution = '低价区集中'; chipShape = '低价高密度'; shapeBuySignal = true; peakComment = '筹码主要集中在当前价格下方,形成强支撑,上方阻力较小,有利于价格上涨。'; } else if (1 / peakRatio > dominanceThreshold && higherChipRatio > 0.6) { // 高价区主峰明显强于低价区,且高价区筹码占比超过60% peakDistribution = '高价区集中'; chipShape = '高价高密度'; shapeBuySignal = false; peakComment = '筹码主要集中在当前价格上方,形成较强阻力,建议等待筹码结构改善后再考虑买入。'; } else if (Math.abs(peakRatio - 1) < 0.3) { // 双峰强度接近,真正的双峰分布 peakDistribution = '双峰分布'; chipShape = '均衡双峰'; // 根据当前价格相对于两峰的位置判断信号 const lowerPeakPrice = lowerPricePeaks[0].price; const higherPeakPrice = higherPricePeaks[0].price; const isCloserToLowerPeak = currentPrice - lowerPeakPrice < higherPeakPrice - currentPrice; if (isCloserToLowerPeak) { shapeBuySignal = true; peakComment = '筹码呈双峰均衡分布,当前价格靠近低价区峰值,有一定支撑,可以考虑适量买入。'; } else { shapeBuySignal = false; peakComment = '筹码呈双峰均衡分布,当前价格靠近高价区峰值,上方阻力较大,建议等待价格回调。'; } } else if (peakRatio > 1) { // 低价区峰值略强 peakDistribution = '低价区略强'; chipShape = '低峰占优'; shapeBuySignal = lowerChipRatio > 0.55; peakComment = `筹码分布呈现低价区略强形态,${lowerChipRatio > 0.55 ? '形成一定支撑,谨慎买入' : '但支撑力度有限,建议观望'}。`; } else { // 高价区峰值略强 peakDistribution = '高价区略强'; chipShape = '高峰占优'; shapeBuySignal = false; peakComment = '筹码分布呈现高价区略强形态,上方阻力较大,不建议现在买入。'; } } else if (hasLowerPeak) { // 只有低价区有明显峰值 if (extremeImbalance) { peakDistribution = '极度低价集中'; chipShape = '底部密集'; shapeBuySignal = true; peakComment = '筹码极度集中在低价区,几乎无上方阻力,是理想的买入时机。'; } else if (moderateImbalance) { peakDistribution = '低价区集中'; chipShape = '低价高密度'; shapeBuySignal = true; peakComment = '筹码主要集中在当前价格下方,形成较强支撑,上涨阻力较小,适合买入。'; } else { peakDistribution = '低价略占优'; chipShape = '低价略高密度'; shapeBuySignal = true; peakComment = '筹码略偏向低价区集中,有一定支撑,可以考虑小仓位买入。'; } } else if (hasHigherPeak) { // 只有高价区有明显峰值 if (extremeImbalance) { peakDistribution = '极度高价集中'; chipShape = '顶部密集'; shapeBuySignal = false; peakComment = '筹码极度集中在高价区,上方阻力极强,建议回避。'; } else if (moderateImbalance) { peakDistribution = '高价区集中'; chipShape = '高价高密度'; shapeBuySignal = false; peakComment = '筹码主要集中在当前价格上方,形成较强阻力,不适合现在买入。'; } else { peakDistribution = '高价略占优'; chipShape = '高价略高密度'; shapeBuySignal = false; peakComment = '筹码略偏向高价区集中,上方存在一定阻力,建议观望。'; } } else { // 理论上不应该到达这里,但为了逻辑完整性保留 // 这种情况可能出现在所有峰值都刚好等于currentPrice的极端情况 peakDistribution = '均衡分布'; chipShape = '均衡分布'; shapeBuySignal = lowerChipRatio >= 0.5; peakComment = `筹码分布较为均衡,${lowerChipRatio >= 0.5 ? '支撑略强于阻力,可以考虑小仓位买入' : '阻力略强于支撑,建议观望'}。`; } return { peakDistribution, chipShape, shapeBuySignal, peakComment, }; } /** * 计算筹码分布的熵值 - 新增 * 熵值可以用来衡量分布的不确定性,熵值越低表示分布越集中 */ export function calculateEntropyOfDistribution(chipDistribution) { const totalPercentage = chipDistribution.reduce((sum, item) => sum + item.percentage, 0); let entropy = 0; for (const item of chipDistribution) { if (item.percentage > 0) { const p = item.percentage / totalPercentage; entropy -= p * Math.log2(p); } } return entropy; } /** * 计算筹码分布的累积分布函数(CDF) - 新增 * 用于进一步分析筹码分布特征 */ export function calculateCumulativeDistribution(chipDistribution) { // 按价格排序 const sortedDist = [...chipDistribution].sort((a, b) => a.price - b.price); let cumulative = 0; return sortedDist.map(item => { cumulative += item.percentage; return { price: item.price, cumulativePercentage: cumulative, }; }); } /** * 计算多空比率 - 新增 * 根据筹码分布计算多空比,用于判断市场情绪 * 0-1: 极度看空 - 高价筹码占绝对优势,下跌风险极高 * 1-3: 强势看空 - 高价筹码明显占优,下跌趋势强劲 * 3-4: 偏空 - 高价筹码略占优势,市场偏向下行 * 4-6: 中性 - 多空力量基本平衡,市场处于震荡整理 * 6-7: 偏多 - 低价筹码略占优势,市场偏向上行 * 7-9: 强势看多 - 低价筹码明显占优,上涨趋势强劲 * 9-10: 极度看多 - 低价筹码占绝对优势,强势上涨概率高 */ export function calculateBullBearRatio(chipDistribution, currentPrice, volatility = 0.02 // 增加市场波动率参数 ) { // 防御性检查 if (!chipDistribution || chipDistribution.length === 0) { return 1.0; // 返回中性值 } // 动态设置阈值,基于市场波动率 const bullThreshold = 1 - Math.min(0.1, volatility * 2.5); // 例如波动率2%时为0.95 const bearThreshold = 1 + Math.min(0.1, volatility * 2.5); // 例如波动率2%时为1.05 const neutralWeight = 0.5; // 中性区域的权重因子 // 计算加权筹码量 const bullChips = chipDistribution .filter(item => item.price < currentPrice * bullThreshold) .reduce((sum, item) => { // 距离当前价格越远,权重越小 const distanceFactor = Math.max(0.5, 1 - (currentPrice - item.price) / currentPrice); return sum + item.percentage * distanceFactor; }, 0); const bearChips = chipDistribution .filter(item => item.price > currentPrice * bearThreshold) .reduce((sum, item) => { // 距离当前价格越远,权重越小 const distanceFactor = Math.max(0.5, 1 - (item.price - currentPrice) / currentPrice); return sum + item.percentage * distanceFactor; }, 0); // 计算中性区域的筹码,并按比例分配给多空双方 const neutralChips = chipDistribution .filter(item => item.price >= currentPrice * bullThreshold && item.price <= currentPrice * bearThreshold) .reduce((sum, item) => sum + item.percentage, 0); // 计算最终的多空比率 (规范化到0-10的范围) const adjustedBullChips = bullChips + neutralChips * neutralWeight; const adjustedBearChips = bearChips + neutralChips * (1 - neutralWeight); if (adjustedBearChips === 0 && adjustedBullChips === 0) return 1.0; // 中性 if (adjustedBearChips === 0) return 10.0; // 极度看多,设置上限 if (adjustedBullChips === 0) return 0.0; // 极度看空,设置下限 // 将比率映射到0-10的范围 const rawRatio = adjustedBullChips / adjustedBearChips; return Math.min(10, (rawRatio * 5) / (1 + rawRatio * 4)); } /** * 识别可能的关键价格水平 * 通过筹码分布识别关键支撑和阻力位 */ export function identifyKeyPriceLevels(chipDistribution, currentPrice, volatility = 0.02, // 市场波动率参数 mergeTolerance = 0.01 // 合并相近价格水平的容差(占当前价格的百分比) ) { // 计算总筹码分布的标准差作为基准 const meanWeight = chipDistribution.reduce((sum, item) => sum + item.percentage, 0) / chipDistribution.length; const stdDev = Math.sqrt(chipDistribution.reduce((sum, item) => sum + Math.pow(item.percentage - meanWeight, 2), 0) / chipDistribution.length); // 动态设置阈值:将波动率纳入计算 // 高波动市场需要更高的阈值来识别真正重要的水平 const volatilityFactor = 1 + volatility * 10; // 将0.02的波动率转换为1.2的因子 const strongThreshold = Math.max(3, stdDev * 2 * volatilityFactor); const moderateThreshold = Math.max(1.5, strongThreshold / 2); // 按权重排序 const sortedByWeight = [...chipDistribution].sort((a, b) => b.weight - a.weight); // 按价格排序(用于后续聚类) const sortedByPrice = [...chipDistribution].sort((a, b) => a.price - b.price); // 使用动态阈值筛选候选支撑位和阻力位 let candidateSupports = sortedByWeight .filter(item => item.price < currentPrice && item.percentage > moderateThreshold) .map(item => ({ price: item.price, weight: item.weight, percentage: item.percentage, isStrong: item.percentage > strongThreshold, })); let candidateResistances = sortedByWeight .filter(item => item.price > currentPrice && item.percentage > moderateThreshold) .map(item => ({ price: item.price, weight: item.weight, percentage: item.percentage, isStrong: item.percentage > strongThreshold, })); // 计算局部密度:检查每个候选点周围的筹码累计 const calculateLocalDensity = candidate => { // 定义价格窗口(基于当前价格的一定百分比) const windowSize = currentPrice * 0.01; // 1%的价格窗口 // 找到窗口内的所有点 const nearbyPoints = sortedByPrice.filter(item => Math.abs(item.price - candidate.price) <= windowSize); // 计算窗口内的总权重 const localDensity = nearbyPoints.reduce((sum, item) => sum + item.percentage, 0); return { ...candidate, localDensity, }; }; // 计算局部密度 candidateSupports = candidateSupports.map(calculateLocalDensity); candidateResistances = candidateResistances.map(calculateLocalDensity); // 按局部密度重新排序 candidateSupports.sort((a, b) => b.localDensity - a.localDensity); candidateResistances.sort((a, b) => b.localDensity - a.localDensity); // 合并相近的价格水平 const mergeNearbyLevels = levels => { if (levels.length <= 1) return levels; const mergedLevels = []; let currentGroup = [levels[0]]; for (let i = 1; i < levels.length; i++) { const lastLevel = currentGroup[currentGroup.length - 1]; // 如果当前价格与上一个价格足够接近,合并它们 if (Math.abs(levels[i].price - lastLevel.price) / currentPrice <= mergeTolerance) { currentGroup.push(levels[i]); } else { // 处理当前组,添加到结果中,然后开始新的组 // 选择组中权重最高的价格水平作为代表 currentGroup.sort((a, b) => b.localDensity - a.localDensity); mergedLevels.push(currentGroup[0]); currentGroup = [levels[i]]; } } // 处理最后一组 if (currentGroup.length > 0) { currentGroup.sort((a, b) => b.localDensity - a.localDensity); mergedLevels.push(currentGroup[0]); } return mergedLevels; }; // 合并相近的水平 const mergedSupports = mergeNearbyLevels(candidateSupports); const mergedResistances = mergeNearbyLevels(candidateResistances); // 返回结果 return { strongSupports: mergedSupports .filter(item => item.isStrong) .map(item => item.price), moderateSupports: mergedSupports .filter(item => !item.isStrong) .map(item => item.price), strongResistances: mergedResistances .filter(item => item.isStrong) .map(item => item.price), moderateResistances: mergedResistances .filter(item => !item.isStrong) .map(item => item.price), }; }