@gabriel3615/ta_analysis
Version:
stock ta analysis
539 lines (538 loc) • 22.5 kB
JavaScript
/**
* 信号汇总与权重计算模块(简化版)
* 负责将各个分析模块的信号进行汇总和权重计算
*/
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);
}
}