UNPKG

@gabriel3615/ta_analysis

Version:

stock ta analysis

539 lines (538 loc) 22.5 kB
/** * 信号汇总与权重计算模块(简化版) * 负责将各个分析模块的信号进行汇总和权重计算 */ import { TradeDirection, SignalStrength } from '../../types.js'; import { PatternDirection } from '../basic/patterns/analyzeMultiTimeframePatterns.js'; export class SignalAggregator { constructor(config) { this.config = config; // 可选插件列表:用于扩展更多分析器 this.plugins = []; } /** 注册插件(遵循开闭原则,无需改核心逻辑即可扩展) */ registerPlugin(plugin) { this.plugins.push(plugin); } /** 获取已注册插件(用于外部如 orchestrator 获取 summarize 能力) */ getPlugins() { return this.plugins; } /** 将数值限制在区间内,默认 [0,100] */ clamp(value, min = 0, max = 100) { return Math.max(min, Math.min(max, value)); } /** * 汇总所有分析信号 */ aggregateSignals(input, context) { const normalizedWeights = this.normalizeWeights(this.config.weights); // 提取各模块的方向和分数 const chipResult = this.extractChipSignal(input.analyses.chip); const patternResult = this.extractPatternSignal(input.analyses.pattern); const volumeResult = this.extractVolumeSignal(input.analyses.volatility); const bbsrResult = this.extractBBSRSignal(input.analyses.bbsr); // 附加分析模块 const structureResult = this.extractStructureSignal(input.analyses.structure); const supplyDemandResult = this.extractSupplyDemandSignal(input.analyses.supplyDemand); const rangeResult = this.extractRangeSignal(input.analyses.range); const trendlineResult = this.extractTrendlineSignal(input.analyses.trendline); // 计算加权分数 const chipWeighted = this.calculateWeightedScore(chipResult, normalizedWeights.chip); const patternWeighted = this.calculateWeightedScore(patternResult, normalizedWeights.pattern); const volumeWeighted = this.calculateWeightedScore(volumeResult, normalizedWeights.volume); const bbsrWeighted = this.calculateWeightedScore(bbsrResult, normalizedWeights.bbsr); // 附加权重(不计入基础100%) const structureWeighted = this.calculateAdditionalScore(structureResult, normalizedWeights.structure || 0); const supplyDemandWeighted = this.calculateAdditionalScore(supplyDemandResult, normalizedWeights.supplyDemand || 0); const rangeWeighted = this.calculateAdditionalScore(rangeResult, normalizedWeights.range || 0); const trendlineWeighted = this.calculateAdditionalScore(trendlineResult, normalizedWeights.trendline || 0); // 插件信号处理 const extraWeightedScores = {}; const extraContributions = {}; let pluginsTotalWeighted = 0; for (const plugin of this.plugins) { try { const res = plugin.extract(input, context); // 插件均按独立配置权重处理,不参与主权重归一(开闭原则) const weight = this.config.weights.plugins?.[plugin.id] ?? 0; const ws = this.calculateAdditionalScore(res, weight); pluginsTotalWeighted += ws; extraWeightedScores[plugin.id] = ws; extraContributions[plugin.id] = Math.abs(weight === 0 ? 0 : ws / weight); } catch { // 忽略单插件错误,避免影响主流程 } } // 计算最终分数(主模块 + 附加模块 + 插件) const finalScore = this.config.options.usePluginsOnly ? pluginsTotalWeighted : chipWeighted.weightedScore + patternWeighted.weightedScore + volumeWeighted.weightedScore + bbsrWeighted.weightedScore + structureWeighted + supplyDemandWeighted + rangeWeighted + trendlineWeighted + pluginsTotalWeighted; // 确定方向 const direction = this.determineDirection(finalScore); // 确定信号强度 const signalStrength = this.determineSignalStrength(Math.abs(finalScore), chipResult.direction, patternResult.direction); // 计算置信度 const confidenceScore = this.calculateConfidenceScore(finalScore, signalStrength, input.analyses.volatility); return { finalScore, direction, signalStrength, confidenceScore, contributions: { chip: chipWeighted.contribution, pattern: patternWeighted.contribution, volume: volumeWeighted.contribution, bbsr: bbsrWeighted.contribution, structure: structureWeighted / (normalizedWeights.structure || 1), supplyDemand: supplyDemandWeighted / (normalizedWeights.supplyDemand || 1), range: rangeWeighted / (normalizedWeights.range || 1), trendline: trendlineWeighted / (normalizedWeights.trendline || 1), }, extraContributions, weightedScores: { chip: chipWeighted.weightedScore, pattern: patternWeighted.weightedScore, volume: volumeWeighted.weightedScore, bbsr: bbsrWeighted.weightedScore, structure: structureWeighted, supplyDemand: supplyDemandWeighted, range: rangeWeighted, trendline: trendlineWeighted, }, extraWeightedScores, }; } /** * 权重归一化 */ normalizeWeights(weights) { const totalWeight = weights.chip + weights.pattern + weights.volume + weights.bbsr; return { chip: weights.chip / totalWeight, pattern: weights.pattern / totalWeight, volume: weights.volume / totalWeight, bbsr: weights.bbsr / totalWeight, // 附加权重保持不变 structure: weights.structure, supplyDemand: weights.supplyDemand, range: weights.range, trendline: weights.trendline, }; } /** * 提取筹码分析信号 */ extractChipSignal(chipAnalysis) { let direction = TradeDirection.Neutral; let confidence = 0; const buyStrength = chipAnalysis.combinedBuySignalStrength; const shortStrength = chipAnalysis.combinedShortSignalStrength; const threshold = this.config.thresholds.chipDirectionThreshold; if (buyStrength > shortStrength + threshold) { direction = TradeDirection.Long; confidence = Math.min(100, buyStrength); } else if (shortStrength > buyStrength + threshold) { direction = TradeDirection.Short; confidence = Math.min(100, shortStrength); } else { confidence = Math.max(buyStrength, shortStrength) * 0.5; // 中性时置信度降低 } return { direction, confidence, source: 'chip' }; } /** * 提取形态分析信号 */ extractPatternSignal(patternAnalysis) { const direction = this.convertPatternDirection(patternAnalysis.combinedSignal); // 基于多周期主导形态状态微调 const timeframeWeights = { weekly: 0.4, daily: 0.4, '1hour': 0.2, }; let statusAdjustment = 0; const timeframeAnalyses = patternAnalysis.timeframeAnalyses; if (Array.isArray(timeframeAnalyses)) { for (const tf of timeframeAnalyses) { const weight = timeframeWeights[tf.timeframe] ?? 0.2; const status = tf.dominantPattern?.status; if (status === 'confirmed') statusAdjustment += 20 * weight; else if (status === 'completed') statusAdjustment += 10 * weight; else if (status === 'forming') statusAdjustment += -10 * weight; else if (status === 'failed') statusAdjustment += -20 * weight; } } let confidence = Math.abs(patternAnalysis.signalStrength ?? 0) + statusAdjustment; confidence = this.clamp(confidence); return { direction, confidence, source: 'pattern' }; } /** * 提取波动率/成交量信号 */ extractVolumeSignal(volatilityAnalysis) { const vRoot = volatilityAnalysis || {}; const v = vRoot.volumeAnalysis?.volumeAnalysis || {}; let direction = TradeDirection.Neutral; if (v.adTrend === 'bullish') direction = TradeDirection.Long; else if (v.adTrend === 'bearish') direction = TradeDirection.Short; if (v.divergence?.type === 'bullish' || v.divergence?.type === 'hidden_bullish') { direction = TradeDirection.Long; } else if (v.divergence?.type === 'bearish' || v.divergence?.type === 'hidden_bearish') { direction = TradeDirection.Short; } let confidence = 50; confidence += v.volumePriceConfirmation ? 15 : -10; confidence += Math.min(25, Math.max(0, Math.abs(v.volumeForce ?? 0) * 0.5)); confidence += Math.min(15, Math.abs(v.obvSlope ?? 0) * 50); confidence += Math.min(10, Math.max(0, v.divergence?.strength ?? 0) * 0.2); if (typeof v.moneyFlowIndex === 'number') { if (v.moneyFlowIndex > 80) confidence -= 5; else if (v.moneyFlowIndex < 20) confidence += 5; } confidence = this.clamp(confidence); return { direction, confidence, source: 'volume' }; } /** * 提取BBSR信号 */ extractBBSRSignal(bbsrAnalysis) { const daily = bbsrAnalysis.dailyBBSRResult; const weekly = bbsrAnalysis.weeklyBBSRResult; const signals = [daily, weekly].filter(Boolean); if (signals.length === 0) { return { direction: TradeDirection.Neutral, confidence: 0, source: 'bbsr', }; } let bestDirection = TradeDirection.Neutral; let bestScore = 0; const now = Date.now(); for (const s of signals) { const pt = s.signal?.patternType; const dir = pt === 'bullish' ? TradeDirection.Long : pt === 'bearish' ? TradeDirection.Short : TradeDirection.Neutral; const proximityPct = (Math.abs(s.currentPrice - s.SRLevel) / Math.max(1e-8, s.SRLevel)) * 100; const proximityScore = Math.max(0, 30 - proximityPct); // 靠近关键位更高 const days = Math.max(0, (now - new Date(s.signalDate).getTime()) / 86400000); const recencyScore = Math.max(0, 20 - days * 2); const score = this.clamp(0.6 * (s.strength ?? 0) + proximityScore + recencyScore); if (score > bestScore) { bestScore = score; bestDirection = dir; } } const d = daily?.signal?.patternType; const w = weekly?.signal?.patternType; if (d && w && d !== w) bestScore = Math.max(0, bestScore - 10); return { direction: bestDirection, confidence: bestScore, source: 'bbsr' }; } /** * 提取结构分析信号 */ extractStructureSignal(structureAnalysis) { let direction = TradeDirection.Neutral; let confidence = 50; if (structureAnalysis.trend === 'up') { direction = TradeDirection.Long; confidence = 65; } else if (structureAnalysis.trend === 'down') { direction = TradeDirection.Short; confidence = 65; } const evt = structureAnalysis.lastEvent; if (evt) { if (evt.type === 'CHOCH') { confidence += 25; if (evt.direction === 'bullish') direction = TradeDirection.Long; else if (evt.direction === 'bearish') direction = TradeDirection.Short; } else if (evt.type === 'BOS') { confidence += 15; if (evt.direction === 'bullish') direction = TradeDirection.Long; else if (evt.direction === 'bearish') direction = TradeDirection.Short; } else { confidence -= 10; } if (evt.timeframe === 'daily') confidence += 5; } confidence = this.clamp(confidence); return { direction, confidence, source: 'structure' }; } /** * 提取供需分析信号 */ extractSupplyDemandSignal(sdAnalysis) { const current = sdAnalysis.premiumDiscount?.currentPrice ?? NaN; const zones = sdAnalysis.recentEffectiveZones ?? []; let direction = TradeDirection.Neutral; let confidence = 50; const inZone = zones.find(z => current >= z.low && current <= z.high); if (inZone) { direction = inZone.type === 'demand' ? TradeDirection.Long : TradeDirection.Short; confidence = inZone.status === 'fresh' ? 75 : 65; const mid = (inZone.low + inZone.high) / 2; const proxPct = (Math.abs(current - mid) / Math.max(1e-8, mid)) * 100; confidence += Math.max(0, 10 - proxPct); } else { const position = sdAnalysis.premiumDiscount?.position ?? 50; if (position < 30) { direction = TradeDirection.Long; confidence = 60 + (30 - position); } else if (position > 70) { direction = TradeDirection.Short; confidence = 60 + (position - 70); } } confidence = this.clamp(confidence); return { direction, confidence, source: 'supplyDemand' }; } /** * 提取区间突破分析信号 */ extractRangeSignal(rangeAnalysis) { let direction = TradeDirection.Neutral; let confidence = 50; if (rangeAnalysis.breakout) { const br = rangeAnalysis.breakout; direction = br.direction === 'up' ? TradeDirection.Long : TradeDirection.Short; confidence = br.qualityScore ?? 60; if (br.volumeExpansion) confidence += 10; if (br.followThrough) confidence += 10; if (br.retested) confidence += 10; } else if (rangeAnalysis.compressionScore > 70) { confidence = 35; } confidence = this.clamp(confidence); return { direction, confidence, source: 'range' }; } /** * 提取趋势线分析信号 */ extractTrendlineSignal(trendlineAnalysis) { const tl = trendlineAnalysis; let direction = TradeDirection.Neutral; let confidence = 50; if (tl.channel) { if (tl.channel.slope > 0) direction = TradeDirection.Long; else if (tl.channel.slope < 0) direction = TradeDirection.Short; confidence = 55 + Math.min(15, Math.abs(tl.channel.slope) * 1e4 * 0.5); const touches = (tl.channel.touchesUpper ?? 0) + (tl.channel.touchesLower ?? 0); confidence += Math.min(10, touches * 1.5); } if (tl.breakoutRetest) { direction = tl.breakoutRetest.direction === 'up' ? TradeDirection.Long : TradeDirection.Short; confidence = Math.max(confidence, tl.breakoutRetest.qualityScore ?? 65); if (tl.breakoutRetest.retested) confidence += 20; } confidence = this.clamp(confidence); return { direction, confidence, source: 'trendline' }; } /** * 计算加权分数 */ calculateWeightedScore(signal, weight) { const directionMultiplier = signal.direction === TradeDirection.Long ? 1 : signal.direction === TradeDirection.Short ? -1 : 0; const rawScore = signal.confidence; const weightedScore = rawScore * weight * directionMultiplier; const contribution = Math.abs(weightedScore) / weight; return { rawScore, weightedScore, direction: signal.direction, contribution, source: signal.source, }; } /** * 计算附加分数(不计入主要权重) */ calculateAdditionalScore(signal, weight) { const directionMultiplier = signal.direction === TradeDirection.Long ? 1 : signal.direction === TradeDirection.Short ? -1 : 0; return signal.confidence * weight * directionMultiplier; } /** * 转换形态方向 */ convertPatternDirection(patternDirection) { switch (patternDirection) { case PatternDirection.Bullish: return TradeDirection.Long; case PatternDirection.Bearish: return TradeDirection.Short; default: return TradeDirection.Neutral; } } /** * 确定最终方向 */ determineDirection(finalScore) { if (finalScore > this.config.thresholds.scoreLong) { return TradeDirection.Long; } else if (finalScore < this.config.thresholds.scoreShort) { return TradeDirection.Short; } return TradeDirection.Neutral; } /** * 确定信号强度 */ determineSignalStrength(absScore, chipDirection, patternDirection) { // 考虑筹码和形态方向的一致性 const directionsAlign = chipDirection === patternDirection && chipDirection !== TradeDirection.Neutral; let threshold = this.config.thresholds.volatilityAdjustedScoreStrong; if (directionsAlign) { threshold *= 0.8; // 方向一致时降低强信号阈值 } if (absScore > threshold) { return SignalStrength.Strong; } else if (absScore > this.config.thresholds.volatilityAdjustedScoreModerate) { return SignalStrength.Moderate; } else if (absScore > this.config.thresholds.volatilityAdjustedScoreWeak) { return SignalStrength.Weak; } return SignalStrength.Neutral; } /** * 计算置信度 */ calculateConfidenceScore(finalScore, signalStrength, volatilityAnalysis) { let baseConfidence = Math.min(100, Math.abs(finalScore)); // 根据信号强度调整 switch (signalStrength) { case SignalStrength.Strong: baseConfidence = Math.min(100, baseConfidence * 1.2); break; case SignalStrength.Moderate: break; case SignalStrength.Weak: baseConfidence = baseConfidence * 0.8; break; default: baseConfidence = baseConfidence * 0.5; } // 根据波动率调整置信度(恢复原有逻辑) const volatilityScore = this.calculateVolatilitySignalStrength(volatilityAnalysis); const volatilityMultiplier = 0.5 + (volatilityScore / 100) * 0.5; // 0.5 - 1.0 return Math.min(100, Math.max(0, baseConfidence * volatilityMultiplier)); } /** * 计算波动率信号强度(恢复原有逻辑) */ calculateVolatilitySignalStrength(volatilityAnalysis) { const vRoot = volatilityAnalysis || {}; const volAnalysis = vRoot.volatilityAnalysis?.volatilityAnalysis || {}; const volPriceConfirmation = vRoot.volumeAnalysis?.volumeAnalysis || {}; // 计算波动率强度(基于ATR百分比和布林带宽度)- 修正原有计算 const volatilityStrength = Math.min(100, Math.max(0, (volAnalysis.atrPercent ?? 0) * 20 + // ATR百分比贡献 (volAnalysis.bollingerBandWidth ?? 0) * 5 // 布林带宽度贡献 )); // 波动率分析逻辑 - 计算波动率信号强度而非方向 let directionScore = 0; if (volatilityStrength > 50) { // 高波动率情况 if (volAnalysis.isVolatilityIncreasing) { // 高波动率上升 - 波动率上升时,成交量确认更重要 directionScore += volPriceConfirmation.volumePriceConfirmation ? 40 : -40; } else { // 高波动率下降 - 波动率下降时,通常是反转信号 directionScore += volPriceConfirmation.volumePriceConfirmation ? -30 : 30; } // 极高波动率时的额外调整 if ((volAnalysis.atrPercent ?? 0) > 3.5) { // 极高波动率通常意味着趋势加速或即将反转 const extremeVolatilityAdjustment = volAnalysis.isVolatilityIncreasing ? 15 : -15; directionScore += extremeVolatilityAdjustment; } } else if (volatilityStrength > 25) { // 中等波动率情况 - 更平衡地考虑波动率和成交量 if (volAnalysis.isVolatilityIncreasing) { directionScore += 20; // 波动率上升 } else { directionScore -= 20; // 波动率下降 } // 成交量确认在中等波动率下的贡献 directionScore += volPriceConfirmation.volumePriceConfirmation ? 25 : -25; } else { // 低波动率情况 - 信号较弱,主要依赖成交量确认 directionScore += volPriceConfirmation.volumePriceConfirmation ? 15 : -15; // 低波动率环境下,整体信号强度降低 directionScore = directionScore * 0.7; } // 返回波动率信号强度(绝对值) return Math.abs(directionScore); } }