@gabriel3615/ta_analysis
Version:
stock ta analysis
708 lines (627 loc) • 22.4 kB
text/typescript
/**
* 信号汇总与权重计算模块(简化版)
* 负责将各个分析模块的信号进行汇总和权重计算
*/
import { TradeDirection, SignalStrength } from '../../types.js';
import { PatternDirection } from '../basic/patterns/analyzeMultiTimeframePatterns.js';
import type {
AnalysisInputData,
SignalAggregationResult,
DirectionConversionResult,
ScoreCalculationResult,
IntegrationContext,
AnalyzerPlugin,
} from './IntegrationTypes.js';
import type {
IntegrationConfig,
IntegrationWeights,
} from './IntegrationConfig.js';
export class SignalAggregator {
// 可选插件列表:用于扩展更多分析器
private plugins: AnalyzerPlugin[] = [];
constructor(private config: IntegrationConfig) {}
/** 注册插件(遵循开闭原则,无需改核心逻辑即可扩展) */
registerPlugin(plugin: AnalyzerPlugin): void {
this.plugins.push(plugin);
}
/** 获取已注册插件(用于外部如 orchestrator 获取 summarize 能力) */
getPlugins(): ReadonlyArray<AnalyzerPlugin> {
return this.plugins;
}
/** 将数值限制在区间内,默认 [0,100] */
private clamp(value: number, min: number = 0, max: number = 100): number {
return Math.max(min, Math.min(max, value));
}
/**
* 汇总所有分析信号
*/
aggregateSignals(
input: AnalysisInputData,
context: IntegrationContext
): SignalAggregationResult {
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: Record<string, number> = {};
const extraContributions: Record<string, number> = {};
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,
};
}
/**
* 权重归一化
*/
private normalizeWeights(weights: IntegrationWeights): IntegrationWeights {
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,
};
}
/**
* 提取筹码分析信号
*/
private extractChipSignal(
chipAnalysis: AnalysisInputData['analyses']['chip']
): DirectionConversionResult {
let direction: TradeDirection = 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' };
}
/**
* 提取形态分析信号
*/
private extractPatternSignal(
patternAnalysis: AnalysisInputData['analyses']['pattern']
): DirectionConversionResult {
const direction = this.convertPatternDirection(
patternAnalysis.combinedSignal
);
// 基于多周期主导形态状态微调
const timeframeWeights: Record<'weekly' | 'daily' | '1hour', number> = {
weekly: 0.4,
daily: 0.4,
'1hour': 0.2,
};
let statusAdjustment = 0;
const timeframeAnalyses = (patternAnalysis as any).timeframeAnalyses as
| {
timeframe: 'weekly' | 'daily' | '1hour';
dominantPattern?: { status?: string };
}[]
| undefined;
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 as any).signalStrength ?? 0) + statusAdjustment;
confidence = this.clamp(confidence);
return { direction, confidence, source: 'pattern' };
}
/**
* 提取波动率/成交量信号
*/
private extractVolumeSignal(
volatilityAnalysis: AnalysisInputData['analyses']['volatility']
): DirectionConversionResult {
const vRoot: any = volatilityAnalysis || {};
const v = vRoot.volumeAnalysis?.volumeAnalysis || {};
let direction: TradeDirection = 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信号
*/
private extractBBSRSignal(
bbsrAnalysis: AnalysisInputData['analyses']['bbsr']
): DirectionConversionResult {
const daily = (bbsrAnalysis as any).dailyBBSRResult;
const weekly = (bbsrAnalysis as any).weeklyBBSRResult;
const signals = [daily, weekly].filter(Boolean) as any[];
if (signals.length === 0) {
return {
direction: TradeDirection.Neutral,
confidence: 0,
source: 'bbsr',
};
}
let bestDirection: TradeDirection = 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' };
}
/**
* 提取结构分析信号
*/
private extractStructureSignal(
structureAnalysis: AnalysisInputData['analyses']['structure']
): DirectionConversionResult {
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 as any).lastEvent as
| {
type?: string;
direction?: 'bullish' | 'bearish' | 'neutral';
timeframe?: string;
}
| undefined;
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' };
}
/**
* 提取供需分析信号
*/
private extractSupplyDemandSignal(
sdAnalysis: AnalysisInputData['analyses']['supplyDemand']
): DirectionConversionResult {
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' };
}
/**
* 提取区间突破分析信号
*/
private extractRangeSignal(
rangeAnalysis: AnalysisInputData['analyses']['range']
): DirectionConversionResult {
let direction = TradeDirection.Neutral;
let confidence = 50;
if ((rangeAnalysis as any).breakout) {
const br = (rangeAnalysis as any).breakout as any;
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 as any).compressionScore > 70) {
confidence = 35;
}
confidence = this.clamp(confidence);
return { direction, confidence, source: 'range' };
}
/**
* 提取趋势线分析信号
*/
private extractTrendlineSignal(
trendlineAnalysis: AnalysisInputData['analyses']['trendline']
): DirectionConversionResult {
const tl = trendlineAnalysis as any;
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' };
}
/**
* 计算加权分数
*/
private calculateWeightedScore(
signal: DirectionConversionResult,
weight: number
): ScoreCalculationResult {
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,
};
}
/**
* 计算附加分数(不计入主要权重)
*/
private calculateAdditionalScore(
signal: DirectionConversionResult,
weight: number
): number {
const directionMultiplier =
signal.direction === TradeDirection.Long
? 1
: signal.direction === TradeDirection.Short
? -1
: 0;
return signal.confidence * weight * directionMultiplier;
}
/**
* 转换形态方向
*/
private convertPatternDirection(
patternDirection: PatternDirection
): TradeDirection {
switch (patternDirection) {
case PatternDirection.Bullish:
return TradeDirection.Long;
case PatternDirection.Bearish:
return TradeDirection.Short;
default:
return TradeDirection.Neutral;
}
}
/**
* 确定最终方向
*/
private determineDirection(finalScore: number): TradeDirection {
if (finalScore > this.config.thresholds.scoreLong) {
return TradeDirection.Long;
} else if (finalScore < this.config.thresholds.scoreShort) {
return TradeDirection.Short;
}
return TradeDirection.Neutral;
}
/**
* 确定信号强度
*/
private determineSignalStrength(
absScore: number,
chipDirection: TradeDirection,
patternDirection: TradeDirection
): SignalStrength {
// 考虑筹码和形态方向的一致性
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;
}
/**
* 计算置信度
*/
private calculateConfidenceScore(
finalScore: number,
signalStrength: SignalStrength,
volatilityAnalysis: AnalysisInputData['analyses']['volatility']
): number {
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));
}
/**
* 计算波动率信号强度(恢复原有逻辑)
*/
private calculateVolatilitySignalStrength(
volatilityAnalysis: AnalysisInputData['analyses']['volatility']
): number {
const vRoot: any = 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);
}
}