@gabriel3615/ta_analysis
Version:
stock ta analysis
693 lines (692 loc) • 36.8 kB
JavaScript
import { PatternDirection, PatternStatus, PatternType, } from './analyzeMultiTimeframePatterns.js';
import { getStatusDescription } from '../../../util/util.js';
import { arePricesSimilar, isPatternHeightSignificant, isPatternDurationValid, isValidBreakout, isPatternFailed, isPriceNearTrendline, calculateStandardReliability, analyzeVolumePatternQuality, calculateBreakoutStrength, } from './patternConfig.js';
// 新增:三角形形态无效的原因枚举
var InvalidPatternReason;
(function (InvalidPatternReason) {
InvalidPatternReason["None"] = "none";
InvalidPatternReason["PriceCrossesTrendlines"] = "price_crosses_trendlines";
InvalidPatternReason["TooFlat"] = "too_flat";
InvalidPatternReason["NearConvergenceNoBreakout"] = "near_convergence_no_breakout";
InvalidPatternReason["AbnormalVolume"] = "abnormal_volume";
InvalidPatternReason["IrregularStructure"] = "irregular_structure";
InvalidPatternReason["InsufficientTouches"] = "insufficient_touches";
InvalidPatternReason["ExcessivePriceVolatility"] = "excessive_price_volatility";
})(InvalidPatternReason || (InvalidPatternReason = {}));
/**
* 获取无效原因的描述文本
*/
function getInvalidReasonDescription(reason) {
switch (reason) {
case InvalidPatternReason.PriceCrossesTrendlines:
return '价格多次穿越趋势线';
case InvalidPatternReason.TooFlat:
return '形态过于扁平';
case InvalidPatternReason.NearConvergenceNoBreakout:
return '接近收敛点但未突破';
case InvalidPatternReason.AbnormalVolume:
return '成交量出现异常峰值';
case InvalidPatternReason.IrregularStructure:
return '形态结构不规则';
case InvalidPatternReason.InsufficientTouches:
return '趋势线触碰次数不足';
case InvalidPatternReason.ExcessivePriceVolatility:
return '价格波动过大';
default:
return '';
}
}
/**
* 寻找三角形形态
*/
export function findTriangles(data, peaksValleys, lookbackPeriod = 30) {
const patterns = [];
// 确保有足够的数据
if (data.length < lookbackPeriod) {
return patterns;
}
// 过滤出最近的峰谷
const recentPoints = peaksValleys.filter(p => p.index >= data.length - lookbackPeriod);
// 排序,确保按时间顺序
recentPoints.sort((a, b) => a.index - b.index);
// 分别获取峰和谷
const peaks = recentPoints.filter(p => p.type === 'peak');
const valleys = recentPoints.filter(p => p.type === 'valley');
// 需要至少2个峰和2个谷来形成三角形
if (peaks.length < 2 || valleys.length < 2) {
return patterns;
}
// 检测上升三角形(水平阻力线,上升支撑线)
// 上升三角形需要至少两个相似的高点和两个上升的低点
if (peaks.length >= 2 && valleys.length >= 2) {
// 检查是否存在水平的高点
const laterPeaks = peaks.slice(-2);
const isHorizontalHighs = arePricesSimilar(laterPeaks[1].price, laterPeaks[0].price);
// 检查是否存在上升的低点
const isAscendingLows = valleys[valleys.length - 1].price > valleys[valleys.length - 2].price;
if (isHorizontalHighs && isAscendingLows) {
// 计算上升三角形的阻力线(水平线)
const resistanceLevel = (laterPeaks[0].price + laterPeaks[1].price) / 2;
// 计算上升三角形的支撑线(上升线)
const support1 = valleys[valleys.length - 2];
const support2 = valleys[valleys.length - 1];
const supportSlope = (support2.price - support1.price) / (support2.index - support1.index);
// 确定形态开始和结束的索引
const startIndex = Math.min(laterPeaks[0].index, support1.index);
const endIndex = Math.max(laterPeaks[1].index, support2.index);
// 计算当前的预期支撑位置
const currentIndex = data.length - 1;
const projectedSupport = support1.price + supportSlope * (currentIndex - support1.index);
// 计算三角形收敛点
let convergenceIndex = support1.index;
if (supportSlope !== 0) {
convergenceIndex =
support1.index + (resistanceLevel - support1.price) / supportSlope;
}
// 计算当前是否接近收敛点
const proximityToConvergence = 1 - Math.min(1, Math.abs(currentIndex - convergenceIndex) / 20);
// 确定当前价格相对于三角形的位置
const currentPrice = data[data.length - 1].close;
let status = PatternStatus.Forming;
if (currentIndex > endIndex) {
if (currentPrice > resistanceLevel) {
// 向上突破阻力线,检查是否为有效突破
const breakoutIndex = data.length - 1;
if (isValidBreakout(data, breakoutIndex, resistanceLevel, true)) {
status = PatternStatus.Confirmed; // 已确认突破上方
}
else {
status = PatternStatus.Completed; // 突破但未确认
}
}
else if (currentPrice < projectedSupport) {
// 向下突破支撑线,检查是否为有效突破
const breakoutIndex = data.length - 1;
if (isValidBreakout(data, breakoutIndex, projectedSupport, false)) {
status = PatternStatus.Failed; // 向下突破(失败)
}
else {
status = PatternStatus.Completed; // 突破但未确认
}
}
else {
status = PatternStatus.Completed; // 形成但未突破
}
// 检查形态是否失败
if (status === PatternStatus.Confirmed &&
isPatternFailed(data, endIndex, resistanceLevel, true)) {
status = PatternStatus.Failed;
}
}
// 计算形态高度
const patternHeight = resistanceLevel - support1.price;
// 计算平均价格和持续时间(只计算一次)
const avgPrice = data.reduce((sum, d) => sum + d.close, 0) / data.length;
const duration = endIndex - startIndex;
// 检查形态高度是否足够显著
if (!isPatternHeightSignificant(patternHeight, avgPrice)) {
// 形态高度不够显著,跳过此形态
return patterns;
}
// 检查形态持续时间是否合理
if (!isPatternDurationValid(duration)) {
// 形态持续时间不合理,跳过此形态
return patterns;
}
// 新增:检测形态是否无效
const isInvalid = checkTriangleInvalid(data, startIndex, endIndex, laterPeaks, [support1, support2], resistanceLevel, support1.price, supportSlope, 0, // 水平阻力线斜率为0
convergenceIndex, patternHeight, currentPrice, PatternType.AscendingTriangle);
// 如果形态无效,则调整状态
if (isInvalid.invalid && status !== PatternStatus.Confirmed) {
status = PatternStatus.Failed;
}
// 计算价格目标(向上突破的目标)
const priceTarget = resistanceLevel + patternHeight;
// 计算收敛接近度
const convergenceProximity = proximityToConvergence;
// 计算突破强度
let breakoutStrength = 0;
if (status === PatternStatus.Confirmed) {
breakoutStrength = calculateBreakoutStrength(data, data.length - 1, resistanceLevel, true);
}
// 计算新近度
const recencyDistance = data.length - 1 - endIndex;
const recency = Math.exp(-0.03 * recencyDistance);
// 分析成交量模式
const volumePatternQuality = analyzeVolumePatternQuality(data, startIndex, endIndex, 'continuation');
// 构建可靠性因素
const reliabilityFactors = {
patternHeight,
avgPrice,
duration,
trendlineTouches: 4, // 三角形通常有4个触点
expectedTouches: 4,
volumeConfirmation: status === PatternStatus.Confirmed,
volumePattern: volumePatternQuality,
breakoutConfirmed: status === PatternStatus.Confirmed,
breakoutStrength,
recency,
specificFactors: {
convergenceProximity: convergenceProximity,
},
};
const reliability = calculateStandardReliability(reliabilityFactors);
patterns.push({
patternType: PatternType.AscendingTriangle,
status,
direction: PatternDirection.Bullish,
reliability: isInvalid.invalid ? reliability * 0.5 : reliability, // 如果无效,降低可靠性
significance: reliability * (patternHeight / currentPrice),
component: {
startIndex,
endIndex,
keyPoints: [laterPeaks[0], laterPeaks[1], support1, support2],
patternHeight,
breakoutLevel: resistanceLevel,
volumePattern: analyzeTriangleVolume(data, startIndex, endIndex, status),
},
priceTarget,
stopLoss: projectedSupport * 0.98, // 支撑线下方2%
breakoutExpected: status === PatternStatus.Completed && proximityToConvergence > 0.7,
breakoutDirection: PatternDirection.Bullish,
probableBreakoutZone: [resistanceLevel * 0.98, resistanceLevel * 1.02],
description: `上升三角形, ${getStatusDescription(status)}${isInvalid.invalid ? `, 警告: ${getInvalidReasonDescription(isInvalid.reason)}` : ''}, 阻力位在 ${resistanceLevel.toFixed(2)}, 支撑趋势线当前在 ${projectedSupport.toFixed(2)}`,
tradingImplication: `看涨信号${isInvalid.invalid ? ' (可信度降低)' : ''}, 突破目标价位: ${priceTarget.toFixed(2)}, 止损位: ${(projectedSupport * 0.98).toFixed(2)}`,
keyDates: [...peaks.map(p => p.date), ...valleys.map(v => v.date)],
keyPrices: [...peaks.map(p => p.price), ...valleys.map(v => v.price)],
});
}
}
// 检测下降三角形(下降阻力线,水平支撑线)
if (peaks.length >= 2 && valleys.length >= 2) {
// 检查是否存在下降的高点
const isDescendingHighs = peaks[peaks.length - 1].price < peaks[peaks.length - 2].price;
// 检查是否存在水平的低点
const laterValleys = valleys.slice(-2);
const isHorizontalLows = arePricesSimilar(laterValleys[1].price, laterValleys[0].price);
if (isDescendingHighs && isHorizontalLows) {
// 计算下降三角形的支撑线(水平线)
const supportLevel = (laterValleys[0].price + laterValleys[1].price) / 2;
// 计算下降三角形的阻力线(下降线)
const resistance1 = peaks[peaks.length - 2];
const resistance2 = peaks[peaks.length - 1];
const resistanceSlope = (resistance2.price - resistance1.price) /
(resistance2.index - resistance1.index);
// 确定形态开始和结束的索引
const startIndex = Math.min(resistance1.index, laterValleys[0].index);
const endIndex = Math.max(resistance2.index, laterValleys[1].index);
// 计算当前的预期阻力位置
const currentIndex = data.length - 1;
const projectedResistance = resistance1.price +
resistanceSlope * (currentIndex - resistance1.index);
// 计算三角形收敛点
let convergenceIndex = resistance1.index;
if (resistanceSlope !== 0) {
convergenceIndex =
resistance1.index +
(supportLevel - resistance1.price) / resistanceSlope;
}
// 计算当前是否接近收敛点
const proximityToConvergence = 1 - Math.min(1, Math.abs(currentIndex - convergenceIndex) / 20);
// 确定当前价格相对于三角形的位置
const currentPrice = data[data.length - 1].close;
let status = PatternStatus.Forming;
if (currentIndex > endIndex) {
if (currentPrice < supportLevel) {
// 向下突破支撑线,检查是否为有效突破
const breakoutIndex = data.length - 1;
if (isValidBreakout(data, breakoutIndex, supportLevel, false)) {
status = PatternStatus.Confirmed; // 已确认突破下方
}
else {
status = PatternStatus.Completed; // 突破但未确认
}
}
else if (currentPrice > projectedResistance) {
// 向上突破阻力线,检查是否为有效突破
const breakoutIndex = data.length - 1;
if (isValidBreakout(data, breakoutIndex, projectedResistance, true)) {
status = PatternStatus.Failed; // 向上突破(失败)
}
else {
status = PatternStatus.Completed; // 突破但未确认
}
}
else {
status = PatternStatus.Completed; // 形成但未突破
}
// 检查形态是否失败
if (status === PatternStatus.Confirmed &&
isPatternFailed(data, endIndex, supportLevel, false)) {
status = PatternStatus.Failed;
}
}
// 计算形态高度
const patternHeight = resistance1.price - supportLevel;
// 检查形态高度是否足够显著
const avgPrice = data.reduce((sum, d) => sum + d.close, 0) / data.length;
if (!isPatternHeightSignificant(patternHeight, avgPrice)) {
// 形态高度不够显著,跳过此形态
return patterns;
}
// 检查形态持续时间是否合理
const duration = endIndex - startIndex;
if (!isPatternDurationValid(duration)) {
// 形态持续时间不合理,跳过此形态
return patterns;
}
// 新增:检测形态是否无效
const isInvalid = checkTriangleInvalid(data, startIndex, endIndex, [resistance1, resistance2], laterValleys, resistance1.price, supportLevel, resistanceSlope, 0, // 水平支撑线斜率为0
convergenceIndex, patternHeight, currentPrice, PatternType.DescendingTriangle);
// 如果形态无效,则调整状态
if (isInvalid.invalid && status !== PatternStatus.Confirmed) {
status = PatternStatus.Failed;
}
// 计算价格目标(向下突破的目标)
const priceTarget = supportLevel - patternHeight;
// 计算收敛接近度
const convergenceProximity = proximityToConvergence;
// 计算突破强度
let breakoutStrength = 0;
if (status === PatternStatus.Confirmed) {
breakoutStrength = calculateBreakoutStrength(data, data.length - 1, supportLevel, false);
}
// 计算新近度
const recencyDistance = data.length - 1 - endIndex;
const recency = Math.exp(-0.03 * recencyDistance);
// 分析成交量模式
const volumePatternQuality = analyzeVolumePatternQuality(data, startIndex, endIndex, 'continuation');
// 构建可靠性因素
const reliabilityFactors = {
patternHeight,
avgPrice,
duration,
trendlineTouches: 4, // 三角形通常有4个触点
expectedTouches: 4,
volumeConfirmation: status === PatternStatus.Confirmed,
volumePattern: volumePatternQuality,
breakoutConfirmed: status === PatternStatus.Confirmed,
breakoutStrength,
recency,
specificFactors: {
convergenceProximity: convergenceProximity,
},
};
const reliability = calculateStandardReliability(reliabilityFactors);
patterns.push({
patternType: PatternType.DescendingTriangle,
status,
direction: PatternDirection.Bearish,
reliability: isInvalid.invalid ? reliability * 0.5 : reliability, // 如果无效,降低可靠性
significance: reliability * (patternHeight / currentPrice),
component: {
startIndex,
endIndex,
keyPoints: [
resistance1,
resistance2,
laterValleys[0],
laterValleys[1],
],
patternHeight,
breakoutLevel: supportLevel,
volumePattern: analyzeTriangleVolume(data, startIndex, endIndex, status),
},
priceTarget,
stopLoss: projectedResistance * 1.02, // 阻力线上方2%
breakoutExpected: status === PatternStatus.Completed && proximityToConvergence > 0.7,
breakoutDirection: PatternDirection.Bearish,
probableBreakoutZone: [supportLevel * 0.98, supportLevel * 1.02],
description: `下降三角形, ${getStatusDescription(status)}${isInvalid.invalid ? `, 警告: ${getInvalidReasonDescription(isInvalid.reason)}` : ''}, 支撑位在 ${supportLevel.toFixed(2)}, 阻力趋势线当前在 ${projectedResistance.toFixed(2)}`,
tradingImplication: `看跌信号${isInvalid.invalid ? ' (可信度降低)' : ''}, 突破目标价位: ${priceTarget.toFixed(2)}, 止损位: ${(projectedResistance * 1.02).toFixed(2)}`,
keyDates: [...peaks.map(p => p.date), ...valleys.map(v => v.date)],
keyPrices: [...peaks.map(p => p.price), ...valleys.map(v => v.price)],
});
}
}
// 检测对称三角形(同时有下降阻力线和上升支撑线)
if (peaks.length >= 2 && valleys.length >= 2) {
// 检查是否存在下降的高点
const isDescendingHighs = peaks[peaks.length - 1].price < peaks[peaks.length - 2].price;
// 检查是否存在上升的低点
const isAscendingLows = valleys[valleys.length - 1].price > valleys[valleys.length - 2].price;
if (isDescendingHighs && isAscendingLows) {
// 计算对称三角形的阻力线(下降线)
const resistance1 = peaks[peaks.length - 2];
const resistance2 = peaks[peaks.length - 1];
const resistanceSlope = (resistance2.price - resistance1.price) /
(resistance2.index - resistance1.index);
// 计算对称三角形的支撑线(上升线)
const support1 = valleys[valleys.length - 2];
const support2 = valleys[valleys.length - 1];
const supportSlope = (support2.price - support1.price) / (support2.index - support1.index);
// 确定形态开始和结束的索引
const startIndex = Math.min(resistance1.index, support1.index);
const endIndex = Math.max(resistance2.index, support2.index);
// 计算当前的预期阻力和支撑位置
const currentIndex = data.length - 1;
const projectedResistance = resistance1.price +
resistanceSlope * (currentIndex - resistance1.index);
const projectedSupport = support1.price + supportSlope * (currentIndex - support1.index);
// 计算三角形收敛点
let convergenceIndex = startIndex;
if (resistanceSlope !== supportSlope) {
convergenceIndex =
startIndex +
(support1.price - resistance1.price) /
(resistanceSlope - supportSlope);
}
// 计算当前是否接近收敛点
const proximityToConvergence = 1 - Math.min(1, Math.abs(currentIndex - convergenceIndex) / 20);
// 确定当前价格相对于三角形的位置
const currentPrice = data[data.length - 1].close;
let status = PatternStatus.Forming;
let breakoutDirection = PatternDirection.Neutral;
if (currentIndex > endIndex) {
if (currentPrice > projectedResistance) {
// 向上突破阻力线,检查是否为有效突破
const breakoutIndex = data.length - 1;
if (isValidBreakout(data, breakoutIndex, projectedResistance, true)) {
status = PatternStatus.Confirmed;
breakoutDirection = PatternDirection.Bullish;
}
else {
status = PatternStatus.Completed;
}
}
else if (currentPrice < projectedSupport) {
// 向下突破支撑线,检查是否为有效突破
const breakoutIndex = data.length - 1;
if (isValidBreakout(data, breakoutIndex, projectedSupport, false)) {
status = PatternStatus.Confirmed;
breakoutDirection = PatternDirection.Bearish;
}
else {
status = PatternStatus.Completed;
}
}
else {
status = PatternStatus.Completed;
}
// 检查形态是否失败
if (status === PatternStatus.Confirmed) {
if (breakoutDirection === PatternDirection.Bullish &&
isPatternFailed(data, endIndex, projectedResistance, true)) {
status = PatternStatus.Failed;
}
else if (breakoutDirection === PatternDirection.Bearish &&
isPatternFailed(data, endIndex, projectedSupport, false)) {
status = PatternStatus.Failed;
}
}
}
// 计算形态高度
const patternHeight = resistance1.price - support1.price;
// 检查形态高度是否足够显著
const avgPrice = data.reduce((sum, d) => sum + d.close, 0) / data.length;
if (!isPatternHeightSignificant(patternHeight, avgPrice)) {
// 形态高度不够显著,跳过此形态
return patterns;
}
// 检查形态持续时间是否合理
const duration = endIndex - startIndex;
if (!isPatternDurationValid(duration)) {
// 形态持续时间不合理,跳过此形态
return patterns;
}
// 新增:检测形态是否无效
const isInvalid = checkTriangleInvalid(data, startIndex, endIndex, [resistance1, resistance2], [support1, support2], resistance1.price, support1.price, resistanceSlope, supportSlope, convergenceIndex, patternHeight, currentPrice, PatternType.SymmetricalTriangle);
// 如果形态无效,则调整状态
if (isInvalid.invalid && status !== PatternStatus.Confirmed) {
status = PatternStatus.Failed;
}
// 计算价格目标
let priceTarget = currentPrice;
if (breakoutDirection === PatternDirection.Bullish) {
priceTarget = projectedResistance + patternHeight;
}
else if (breakoutDirection === PatternDirection.Bearish) {
priceTarget = projectedSupport - patternHeight;
}
// 计算收敛接近度
const convergenceProximity = proximityToConvergence;
// 计算突破强度
let breakoutStrength = 0;
if (status === PatternStatus.Confirmed) {
const breakoutLevel = breakoutDirection === PatternDirection.Bullish
? projectedResistance
: projectedSupport;
const isUpward = breakoutDirection === PatternDirection.Bullish;
breakoutStrength = calculateBreakoutStrength(data, data.length - 1, breakoutLevel, isUpward);
}
// 计算新近度
const recencyDistance = data.length - 1 - endIndex;
const recency = Math.exp(-0.03 * recencyDistance);
// 分析成交量模式
const volumePatternQuality = analyzeVolumePatternQuality(data, startIndex, endIndex, 'continuation');
// 构建可靠性因素
const reliabilityFactors = {
patternHeight,
avgPrice,
duration,
trendlineTouches: 4, // 三角形通常有4个触点
expectedTouches: 4,
volumeConfirmation: status === PatternStatus.Confirmed,
volumePattern: volumePatternQuality,
breakoutConfirmed: status === PatternStatus.Confirmed,
breakoutStrength,
recency,
specificFactors: {
convergenceProximity: convergenceProximity,
},
};
const reliability = calculateStandardReliability(reliabilityFactors);
patterns.push({
patternType: PatternType.SymmetricalTriangle,
status,
direction: breakoutDirection,
reliability: isInvalid.invalid ? reliability * 0.5 : reliability, // 如果无效,降低可靠性
significance: reliability * (patternHeight / currentPrice),
component: {
startIndex,
endIndex,
keyPoints: [resistance1, resistance2, support1, support2],
patternHeight,
breakoutLevel: breakoutDirection === PatternDirection.Bullish
? projectedResistance
: projectedSupport,
volumePattern: analyzeTriangleVolume(data, startIndex, endIndex, status),
},
priceTarget,
stopLoss: breakoutDirection === PatternDirection.Bullish
? projectedSupport
: projectedResistance,
breakoutExpected: status === PatternStatus.Completed && proximityToConvergence > 0.7,
breakoutDirection,
probableBreakoutZone: [projectedSupport, projectedResistance],
description: `对称三角形, ${getStatusDescription(status)}${isInvalid.invalid ? `, 警告: ${getInvalidReasonDescription(isInvalid.reason)}` : ''}${breakoutDirection !== PatternDirection.Neutral ? ', ' + (breakoutDirection === PatternDirection.Bullish ? '向上' : '向下') + '突破' : ''}, 阻力趋势线当前在 ${projectedResistance.toFixed(2)}, 支撑趋势线当前在 ${projectedSupport.toFixed(2)}`,
tradingImplication: status === PatternStatus.Confirmed
? `${breakoutDirection === PatternDirection.Bullish ? '看涨' : '看跌'}信号${isInvalid.invalid ? ' (可信度降低)' : ''}, 目标价位: ${priceTarget.toFixed(2)}, 止损位: ${(breakoutDirection === PatternDirection.Bullish ? projectedSupport : projectedResistance).toFixed(2)}`
: '等待突破确认,突破方向将决定交易信号',
keyDates: [...peaks.map(p => p.date), ...valleys.map(v => v.date)],
keyPrices: [...peaks.map(p => p.price), ...valleys.map(v => v.price)],
});
}
}
return patterns;
}
/**
* 分析三角形形态的成交量特征
*/
function analyzeTriangleVolume(data, startIndex, endIndex, status) {
// 使用标准化的成交量分析
const volumePatternQuality = analyzeVolumePatternQuality(data, startIndex, endIndex, 'continuation');
// 检查突破时的成交量
let breakoutVolume = 0;
let avgVolume = 0;
if (startIndex <= endIndex) {
const volumes = data.slice(startIndex, endIndex + 1).map(d => d.volume);
avgVolume = volumes.reduce((sum, v) => sum + v, 0) / volumes.length;
if (status === PatternStatus.Confirmed && endIndex + 1 < data.length) {
breakoutVolume = data[endIndex + 1].volume;
}
}
// 根据成交量质量提供描述
switch (volumePatternQuality) {
case 'ideal':
if (status === PatternStatus.Confirmed &&
breakoutVolume > avgVolume * 1.5) {
return '理想的成交量模式:形态期间成交量收缩,突破时成交量明显放大';
}
else {
return '理想的成交量模式:形态期间成交量收缩';
}
case 'good':
if (status === PatternStatus.Confirmed &&
breakoutVolume > avgVolume * 1.2) {
return '良好的成交量模式:形态期间成交量收缩,突破时成交量放大';
}
else {
return '良好的成交量模式:形态期间成交量基本收缩';
}
case 'acceptable':
return '可接受的成交量模式:成交量变化不明显';
case 'poor':
return '非理想的成交量模式:形态期间成交量未收缩,降低形态可靠性';
default:
return '成交量模式中性,无明显趋势';
}
}
function checkTriangleInvalid(data, startIndex, endIndex, peaks, valleys, topInitialPrice, bottomInitialPrice, topSlope, bottomSlope, convergenceIndex, patternHeight, currentPrice, patternType) {
// 默认结果为有效
const result = {
invalid: false,
reason: InvalidPatternReason.None,
};
// 获取形态内的价格数据
const patternData = data.slice(startIndex, endIndex + 1);
// 1. 检查形态是否过于扁平
const avgPrice = patternData.reduce((sum, d) => sum + d.close, 0) / patternData.length;
const heightRatio = patternHeight / avgPrice;
if (heightRatio < 0.01) {
// 如果形态高度小于平均价格的1%,认为过于扁平
result.invalid = true;
result.reason = InvalidPatternReason.TooFlat;
return result;
}
// 2. 检查是否有足够的价格触碰趋势线
// 检查上趋势线和下趋势线的价格触碰次数
const touchesNeeded = 3; // 至少需要3次触碰才视为有效形态
// 计算有多少次价格接触到阻力线和支撑线
let topTouches = 0;
let bottomTouches = 0;
for (let i = startIndex; i <= endIndex; i++) {
const relativeIndex = i - startIndex;
// 计算当前位置的趋势线价格
const topTrendLine = topInitialPrice + topSlope * relativeIndex;
const bottomTrendLine = bottomInitialPrice + bottomSlope * relativeIndex;
const highPrice = data[i].high;
const lowPrice = data[i].low;
// 如果高价接近阻力线,计为一次触碰
if (isPriceNearTrendline(highPrice, topTrendLine)) {
topTouches++;
}
// 如果低价接近支撑线,计为一次触碰
if (isPriceNearTrendline(lowPrice, bottomTrendLine)) {
bottomTouches++;
}
}
// 如果触碰次数不足,标记为无效
if (topTouches < touchesNeeded || bottomTouches < touchesNeeded) {
result.invalid = true;
result.reason = InvalidPatternReason.InsufficientTouches;
return result;
}
// 3. 检查价格是否多次穿越趋势线(表明形态不稳定)
let topBreaches = 0;
let bottomBreaches = 0;
for (let i = startIndex; i <= endIndex; i++) {
const relativeIndex = i - startIndex;
const topTrendLine = topInitialPrice + topSlope * relativeIndex;
const bottomTrendLine = bottomInitialPrice + bottomSlope * relativeIndex;
const highPrice = data[i].high;
const lowPrice = data[i].low;
// 检查是否有穿越顶部趋势线
if (highPrice > topTrendLine * 1.01) {
topBreaches++;
}
// 检查是否有穿越底部趋势线
if (lowPrice < bottomTrendLine * 0.99) {
bottomBreaches++;
}
}
// 如果有多次严重穿越,形态可能无效
if (topBreaches > 2 || bottomBreaches > 2) {
result.invalid = true;
result.reason = InvalidPatternReason.PriceCrossesTrendlines;
return result;
}
// 4. 检测临近收敛点但长时间未突破
// 计算当前位置距离收敛点的距离
const currentIndex = data.length - 1;
const distanceToConvergence = Math.abs(currentIndex - convergenceIndex);
// 如果接近收敛点(距离小于10个bar)但尚未突破且长时间处于横盘状态
if (distanceToConvergence < 10 && currentIndex > endIndex + 15) {
// 判断最近是否横盘 - 计算最近价格的标准差
const recentPrices = data
.slice(endIndex + 1, currentIndex + 1)
.map(d => d.close);
const avg = recentPrices.reduce((sum, p) => sum + p, 0) / recentPrices.length;
const stdDev = Math.sqrt(recentPrices.reduce((sum, p) => sum + Math.pow(p - avg, 2), 0) /
recentPrices.length);
// 如果价格波动很小且接近收敛点,形态可能失效
if (stdDev / avg < 0.01) {
result.invalid = true;
result.reason = InvalidPatternReason.NearConvergenceNoBreakout;
return result;
}
}
// 5. 检测形态期间的异常成交量
// 计算成交量的均值和标准差
const volumes = patternData.map(d => d.volume);
const avgVolume = volumes.reduce((sum, v) => sum + v, 0) / volumes.length;
const volumeStdDev = Math.sqrt(volumes.reduce((sum, v) => sum + Math.pow(v - avgVolume, 2), 0) /
volumes.length);
// 检查是否有异常大的成交量
const abnormalVolumeCount = volumes.filter(v => v > avgVolume + 2.5 * volumeStdDev).length;
// 如果有多个异常大的成交量峰值,可能表明形态不稳定
if (abnormalVolumeCount > 2) {
result.invalid = true;
result.reason = InvalidPatternReason.AbnormalVolume;
return result;
}
// 6. 检测形态内部结构的规则性
// 计算价格的波动率
const closePrices = patternData.map(d => d.close);
const priceChanges = [];
for (let i = 1; i < closePrices.length; i++) {
priceChanges.push(Math.abs(closePrices[i] / closePrices[i - 1] - 1));
}
// 计算平均日波动率
const avgDailyChange = priceChanges.reduce((sum, c) => sum + c, 0) / priceChanges.length;
// 计算极端波动的天数
const extremeChangeDays = priceChanges.filter(c => c > avgDailyChange * 3).length;
// 如果有多个日内极端波动,形态可能不规则
if (extremeChangeDays > patternData.length * 0.15) {
result.invalid = true;
result.reason = InvalidPatternReason.ExcessivePriceVolatility;
return result;
}
// 7. 如果是对称三角形,检查形态的对称性
if (patternType === PatternType.SymmetricalTriangle) {
// 计算趋势线斜率的绝对值比率,理想情况下应接近1(对称)
const slopeRatio = Math.abs(topSlope) / Math.abs(bottomSlope);
// 如果斜率比率过大或过小,表明形态不够对称
if (slopeRatio > 2.5 || slopeRatio < 0.4) {
result.invalid = true;
result.reason = InvalidPatternReason.IrregularStructure;
return result;
}
}
return result;
}