UNPKG

@gabriel3615/ta_analysis

Version:

stock ta analysis

764 lines (683 loc) 22.3 kB
import { Candle } from '../../../types.js'; import { PatternAnalysisResult, PatternDirection, PatternStatus, PatternType, PeakValley, } from './analyzeMultiTimeframePatterns.js'; import { getStatusDescription } from '../../../util/util.js'; /** * 寻找旗形和三角旗形态 */ export function findFlagsAndPennants( data: Candle[], peaksValleys: PeakValley[], lookbackPeriod: number = 30 ): PatternAnalysisResult[] { const patterns: PatternAnalysisResult[] = []; /** * 检测数据中的趋势 */ function detectTrend( data: Candle[], lookbackPeriod: number ): { isBullishTrend: boolean; isBearishTrend: boolean } { const trendDetectionPeriod = Math.min(lookbackPeriod, 20); const startIdx = Math.max(0, data.length - lookbackPeriod); const endIdx = startIdx + trendDetectionPeriod; // 确保索引有效 if (endIdx >= data.length) { return { isBullishTrend: false, isBearishTrend: false }; } const priorCandles = data.slice(startIdx, endIdx); // 计算趋势强度 let upMoves = 0; let downMoves = 0; for (let i = 1; i < priorCandles.length; i++) { if (priorCandles[i].close > priorCandles[i - 1].close) { upMoves++; } else if (priorCandles[i].close < priorCandles[i - 1].close) { downMoves++; } } return { isBullishTrend: upMoves > downMoves * 1.5, isBearishTrend: downMoves > upMoves * 1.5, }; } /** * 按方向查找模式 */ function findPatternsByDirection( data: Candle[], lookbackPeriod: number, config: { minDuration: number; maxDuration: number; minFlagpoleMove: number; }, direction: PatternDirection ): PatternAnalysisResult[] { const patterns: PatternAnalysisResult[] = []; const isBullish = direction === PatternDirection.Bullish; // 寻找潜在的旗杆 const flagpoleInfo = findFlagpole( data, lookbackPeriod, config.minFlagpoleMove, direction ); if ( !flagpoleInfo || flagpoleInfo.flagpoleEndIndex <= flagpoleInfo.flagpoleStartIndex ) { return patterns; } // 收集关键日期和价格 const keyDates: Date[] = [data[flagpoleInfo.flagpoleEndIndex].timestamp]; const keyPrices: number[] = [ isBullish ? data[flagpoleInfo.flagpoleEndIndex].high : data[flagpoleInfo.flagpoleEndIndex].low, ]; // 旗杆后寻找旗形或三角旗 const consolidationStart = flagpoleInfo.flagpoleEndIndex; const consolidationEnd = Math.min( consolidationStart + config.maxDuration, data.length - 1 ); // 获取上下边界 const { upper, lower } = getBoundaries( data, consolidationStart, consolidationEnd, keyDates, keyPrices ); // 计算边界斜率 const upperSlope = calculateSlope(upper); const lowerSlope = calculateSlope(lower); // 检查是否形成了旗形 const isFlag = checkForFlag(upperSlope, lowerSlope, direction); if (isFlag && consolidationEnd - consolidationStart >= config.minDuration) { // 添加旗形模式 const flagPattern = createFlagPattern( data, flagpoleInfo, consolidationStart, consolidationEnd, upper, lower, upperSlope, lowerSlope, direction, keyDates, keyPrices ); patterns.push(flagPattern); } else { // 检查是否形成了三角旗 const isPennant = checkForPennant(upperSlope, lowerSlope, direction); if ( isPennant && consolidationEnd - consolidationStart >= config.minDuration ) { // 添加三角旗模式 const pennantPattern = createPennantPattern( data, flagpoleInfo, consolidationStart, consolidationEnd, upper, lower, upperSlope, lowerSlope, direction, keyDates, keyPrices ); patterns.push(pennantPattern); } } return patterns; } /** * 查找旗杆 */ function findFlagpole( data: Candle[], lookbackPeriod: number, minFlagpoleMove: number, direction: PatternDirection ): { flagpoleStartIndex: number; flagpoleEndIndex: number; flagpolePrice: number; } | null { const isBullish = direction === PatternDirection.Bullish; for (let i = data.length - lookbackPeriod; i < data.length - 20; i++) { // 确保索引有效 if (i < 0) continue; const moveEnd = Math.min(i + 10, data.length - 20); if (isBullish) { // 在看涨趋势中寻找向上的旗杆 const lowPrice = data[i].low; const highPrices = data.slice(i, moveEnd).map(d => d.high); const highPrice = Math.max(...highPrices); if ((highPrice - lowPrice) / lowPrice > minFlagpoleMove) { // 找出旗杆结束的点 for (let j = i + 1; j < moveEnd; j++) { if (data[j].high >= highPrice * 0.98) { return { flagpoleStartIndex: i, flagpoleEndIndex: j, flagpolePrice: highPrice, }; } } } } else { // 在看跌趋势中寻找向下的旗杆 const highPrice = data[i].high; const lowPrices = data.slice(i, moveEnd).map(d => d.low); const lowPrice = Math.min(...lowPrices); if ((highPrice - lowPrice) / highPrice > minFlagpoleMove) { // 找出旗杆结束的点 for (let j = i + 1; j < moveEnd; j++) { if (data[j].low <= lowPrice * 1.02) { return { flagpoleStartIndex: i, flagpoleEndIndex: j, flagpolePrice: lowPrice, }; } } } } } return null; } /** * 获取价格边界 */ function getBoundaries( data: Candle[], start: number, end: number, keyDates: Date[], keyPrices: number[] ): { upper: number[]; lower: number[] } { const upper: number[] = []; const lower: number[] = []; for (let i = start; i < end; i++) { if (i >= 0 && i < data.length) { upper.push(data[i].high); lower.push(data[i].low); keyDates.push(data[i].timestamp); keyPrices.push(data[i].high); } } return { upper, lower }; } /** * 计算斜率 */ function calculateSlope(prices: number[]): number { if (prices.length <= 1) return 0; return (prices[prices.length - 1] - prices[0]) / prices.length; } /** * 检查是否形成旗形 */ function checkForFlag( upperSlope: number, lowerSlope: number, direction: PatternDirection ): boolean { const isBullish = direction === PatternDirection.Bullish; if (isBullish) { // 看涨旗形通常是平行通道,且在上升趋势后向下倾斜 return ( upperSlope < 0 && lowerSlope < 0 && Math.abs(upperSlope - lowerSlope) / Math.abs(upperSlope) < 0.3 ); } else { // 看跌旗形通常是平行通道,且在下降趋势后向上倾斜 return ( upperSlope > 0 && lowerSlope > 0 && Math.abs(upperSlope - lowerSlope) / Math.abs(upperSlope) < 0.3 ); } } /** * 检查是否形成三角旗 */ function checkForPennant( upperSlope: number, lowerSlope: number, direction: PatternDirection ): boolean { const isBullish = direction === PatternDirection.Bullish; if (isBullish) { // 看涨三角旗表现为上边界向下,下边界向上(收敛) return upperSlope < 0 && lowerSlope > 0; } else { // 看跌三角旗表现为上边界向上,下边界向下(收敛) return upperSlope > 0 && lowerSlope < 0; } } /** * 创建旗形模式结果 */ function createFlagPattern( data: Candle[], flagpoleInfo: { flagpoleStartIndex: number; flagpoleEndIndex: number; flagpolePrice: number; }, consolidationStart: number, consolidationEnd: number, upper: number[], lower: number[], upperSlope: number, lowerSlope: number, direction: PatternDirection, keyDates: Date[], keyPrices: number[] ): PatternAnalysisResult { const isBullish = direction === PatternDirection.Bullish; const currentPrice = data[data.length - 1].close; // 计算旗形的特性 const flagHeight = upper[0] - lower[0]; const flagpoleMagnitude = isBullish ? flagpoleInfo.flagpolePrice - data[flagpoleInfo.flagpoleStartIndex].low : data[flagpoleInfo.flagpoleStartIndex].high - flagpoleInfo.flagpolePrice; // 计算价格目标和突破水平 const breakoutPrice = isBullish ? upper[upper.length - 1] : lower[lower.length - 1]; const priceTarget = isBullish ? breakoutPrice + flagpoleMagnitude : breakoutPrice - flagpoleMagnitude; // 确定模式状态 const status = determinePatternStatus( data, breakoutPrice, consolidationStart, consolidationEnd, direction ); // 计算可靠性 const reliability = calculateFlagReliability( data, flagpoleInfo.flagpoleStartIndex, flagpoleInfo.flagpoleEndIndex, consolidationStart, consolidationEnd, flagpoleMagnitude, flagHeight, status === PatternStatus.Confirmed ); // 构建模式结果 return { patternType: PatternType.Flag, status, direction, reliability, significance: reliability * (flagpoleMagnitude / currentPrice), component: { startIndex: flagpoleInfo.flagpoleStartIndex, endIndex: consolidationEnd, keyPoints: [], // 简化,不详细指定关键点 patternHeight: flagHeight, breakoutLevel: breakoutPrice, volumePattern: analyzeFlagVolume( data, flagpoleInfo.flagpoleStartIndex, flagpoleInfo.flagpoleEndIndex, consolidationStart, consolidationEnd ), }, priceTarget, stopLoss: isBullish ? lower[lower.length - 1] : upper[upper.length - 1], breakoutExpected: status === PatternStatus.Completed, breakoutDirection: direction, probableBreakoutZone: [breakoutPrice * 0.99, breakoutPrice * 1.01], description: `${isBullish ? '看涨' : '看跌'}旗形, ${getStatusDescription(status)}, 旗杆高度: ${flagpoleMagnitude.toFixed(2)}, 突破位: ${breakoutPrice.toFixed(2)}`, tradingImplication: `${isBullish ? '看涨' : '看跌'}信号, 目标价位: ${priceTarget.toFixed(2)}, 止损位: ${(isBullish ? lower[lower.length - 1] : upper[upper.length - 1]).toFixed(2)}`, keyDates, keyPrices, }; } /** * 创建三角旗模式结果 */ function createPennantPattern( data: Candle[], flagpoleInfo: { flagpoleStartIndex: number; flagpoleEndIndex: number; flagpolePrice: number; }, consolidationStart: number, consolidationEnd: number, upper: number[], lower: number[], upperSlope: number, lowerSlope: number, direction: PatternDirection, keyDates: Date[], keyPrices: number[] ): PatternAnalysisResult { const isBullish = direction === PatternDirection.Bullish; const currentPrice = data[data.length - 1].close; const currentIndex = data.length - 1; // 计算三角旗的特性 const pennantHeight = upper[0] - lower[0]; const flagpoleMagnitude = isBullish ? flagpoleInfo.flagpolePrice - data[flagpoleInfo.flagpoleStartIndex].low : data[flagpoleInfo.flagpoleStartIndex].high - flagpoleInfo.flagpolePrice; // 计算收敛点 const upperStartPrice = upper[0]; const lowerStartPrice = lower[0]; const convergenceIndex = consolidationStart + (upperStartPrice - lowerStartPrice) / (lowerSlope - upperSlope); // 计算当前的投影边界 const projectedUpper = upperStartPrice + upperSlope * (currentIndex - consolidationStart); const projectedLower = lowerStartPrice + lowerSlope * (currentIndex - consolidationStart); // 确定突破价格和目标价格 const breakoutPrice = isBullish ? projectedUpper : projectedLower; const priceTarget = isBullish ? breakoutPrice + flagpoleMagnitude : breakoutPrice - flagpoleMagnitude; // 确定模式状态 let status = PatternStatus.Forming; if ( (isBullish && currentPrice > projectedUpper) || (!isBullish && currentPrice < projectedLower) ) { status = PatternStatus.Confirmed; } else if (consolidationEnd - consolidationStart > 20) { // 使用maxDuration status = PatternStatus.Failed; } else { status = PatternStatus.Completed; } // 计算可靠性 const reliability = calculatePennantReliability( data, flagpoleInfo.flagpoleStartIndex, flagpoleInfo.flagpoleEndIndex, consolidationStart, consolidationEnd, flagpoleMagnitude, pennantHeight, convergenceIndex, currentIndex, status === PatternStatus.Confirmed ); // 构建模式结果 return { patternType: PatternType.Pennant, status, direction, reliability, significance: reliability * (flagpoleMagnitude / currentPrice), component: { startIndex: flagpoleInfo.flagpoleStartIndex, endIndex: consolidationEnd, keyPoints: [], // 简化,不详细指定关键点 patternHeight: pennantHeight, breakoutLevel: breakoutPrice, volumePattern: analyzeFlagVolume( data, flagpoleInfo.flagpoleStartIndex, flagpoleInfo.flagpoleEndIndex, consolidationStart, consolidationEnd ), }, priceTarget, stopLoss: isBullish ? projectedLower : projectedUpper, breakoutExpected: status === PatternStatus.Completed, breakoutDirection: direction, probableBreakoutZone: [breakoutPrice * 0.99, breakoutPrice * 1.01], description: `${isBullish ? '看涨' : '看跌'}三角旗, ${getStatusDescription(status)}, 旗杆高度: ${flagpoleMagnitude.toFixed(2)}, 突破位: ${breakoutPrice.toFixed(2)}`, tradingImplication: `${isBullish ? '看涨' : '看跌'}信号, 目标价位: ${priceTarget.toFixed(2)}, 止损位: ${(isBullish ? projectedLower : projectedUpper).toFixed(2)}`, keyDates, keyPrices, }; } /** * 确定模式状态 */ function determinePatternStatus( data: Candle[], breakoutPrice: number, consolidationStart: number, consolidationEnd: number, direction: PatternDirection ): PatternStatus { const currentPrice = data[data.length - 1].close; const isBullish = direction === PatternDirection.Bullish; const flagDuration = consolidationEnd - consolidationStart; if ( (isBullish && currentPrice > breakoutPrice) || (!isBullish && currentPrice < breakoutPrice) ) { return PatternStatus.Confirmed; } else if (flagDuration > 20) { // 使用maxDuration return PatternStatus.Failed; } else { return PatternStatus.Completed; } } // 确保有足够的数据 if (!data || data.length < lookbackPeriod) { return patterns; } // 检测之前的趋势 const trendInfo = detectTrend(data, lookbackPeriod); // 如果没有明显趋势,直接返回 if (!trendInfo.isBullishTrend && !trendInfo.isBearishTrend) { return patterns; } // 用于旗形的配置参数 const config = { minDuration: 5, maxDuration: 20, minFlagpoleMove: 0.05, // 5% }; // 处理看涨趋势 if (trendInfo.isBullishTrend) { const bullishPatterns = findPatternsByDirection( data, lookbackPeriod, config, PatternDirection.Bullish ); patterns.push(...bullishPatterns); } // 处理看跌趋势 if (trendInfo.isBearishTrend) { const bearishPatterns = findPatternsByDirection( data, lookbackPeriod, config, PatternDirection.Bearish ); patterns.push(...bearishPatterns); } return patterns; } /** * 计算旗形的可靠性 */ function calculateFlagReliability( data: Candle[], flagpoleStartIndex: number, flagpoleEndIndex: number, consolidationStartIndex: number, consolidationEndIndex: number, flagpoleMagnitude: number, flagHeight: number, isBreakoutConfirmed: boolean ): number { let score = 50; // 初始可靠性分数 // 1. 旗杆强度 const avgPrice = data .slice(flagpoleStartIndex, flagpoleEndIndex + 1) .reduce((sum, d) => sum + d.close, 0) / (flagpoleEndIndex - flagpoleStartIndex + 1); const flagpoleStrength = flagpoleMagnitude / avgPrice; if (flagpoleStrength > 0.1) score += 15; // 旗杆移动超过10% else if (flagpoleStrength > 0.05) score += 10; // 旗杆移动超过5% else score += 5; // 旗杆移动较小 // 2. 旗形持续时间(应该在合理范围内) const flagDuration = consolidationEndIndex - consolidationStartIndex; if (flagDuration >= 5 && flagDuration <= 15) score += 10; // 理想的持续时间 else if (flagDuration > 15) score += 5; // 持续时间略长 // 3. 旗形高度(相对于旗杆的高度) const heightRatio = flagHeight / flagpoleMagnitude; if (heightRatio <= 0.5) score += 15; // 旗形不超过旗杆一半高度,理想情况 else if (heightRatio <= 0.7) score += 10; // 旗形高度适中 else score += 5; // 旗形高度较大 // 4. 旗杆成交量 const flagpoleVolume = data .slice(flagpoleStartIndex, flagpoleEndIndex + 1) .reduce((sum, d) => sum + d.volume, 0) / (flagpoleEndIndex - flagpoleStartIndex + 1); const priorVolume = flagpoleStartIndex > 10 ? data .slice(flagpoleStartIndex - 10, flagpoleStartIndex) .reduce((sum, d) => sum + d.volume, 0) / 10 : flagpoleVolume; if (flagpoleVolume > priorVolume * 1.5) score += 10; // 旗杆期间成交量明显放大 else if (flagpoleVolume > priorVolume * 1.2) score += 5; // 旗杆期间成交量稍微放大 // 5. 确认突破 if (isBreakoutConfirmed) score += 10; // 最后确保分数在0-100范围内 return Math.max(0, Math.min(100, score)); } /** * 计算三角旗的可靠性 */ function calculatePennantReliability( data: Candle[], flagpoleStartIndex: number, flagpoleEndIndex: number, consolidationStartIndex: number, consolidationEndIndex: number, flagpoleMagnitude: number, pennantHeight: number, convergenceIndex: number, currentIndex: number, isBreakoutConfirmed: boolean ): number { let score = 50; // 初始可靠性分数 // 1. 旗杆强度 const avgPrice = data .slice(flagpoleStartIndex, flagpoleEndIndex + 1) .reduce((sum, d) => sum + d.close, 0) / (flagpoleEndIndex - flagpoleStartIndex + 1); const flagpoleStrength = flagpoleMagnitude / avgPrice; if (flagpoleStrength > 0.1) score += 15; // 旗杆移动超过10% else if (flagpoleStrength > 0.05) score += 10; // 旗杆移动超过5% else score += 5; // 旗杆移动较小 // 2. 三角旗持续时间(应该在合理范围内) const pennantDuration = consolidationEndIndex - consolidationStartIndex; if (pennantDuration >= 5 && pennantDuration <= 15) score += 10; // 理想的持续时间 else if (pennantDuration > 15) score += 5; // 持续时间略长 // 3. 收敛点的接近程度 const proximityToConvergence = 1 - Math.min(1, Math.abs(currentIndex - convergenceIndex) / 20); score += proximityToConvergence * 10; // 4. 旗杆成交量 const flagpoleVolume = data .slice(flagpoleStartIndex, flagpoleEndIndex + 1) .reduce((sum, d) => sum + d.volume, 0) / (flagpoleEndIndex - flagpoleStartIndex + 1); const priorVolume = flagpoleStartIndex > 10 ? data .slice(flagpoleStartIndex - 10, flagpoleStartIndex) .reduce((sum, d) => sum + d.volume, 0) / 10 : flagpoleVolume; if (flagpoleVolume > priorVolume * 1.5) score += 10; // 旗杆期间成交量明显放大 else if (flagpoleVolume > priorVolume * 1.2) score += 5; // 旗杆期间成交量稍微放大 // 5. 确认突破 if (isBreakoutConfirmed) score += 10; // 最后确保分数在0-100范围内 return Math.max(0, Math.min(100, score)); } /** * 分析旗形和三角旗的成交量特征 */ function analyzeFlagVolume( data: Candle[], flagpoleStartIndex: number, flagpoleEndIndex: number, consolidationStartIndex: number, consolidationEndIndex: number ): string { // 计算旗杆成交量 const flagpoleVolumes = data .slice(flagpoleStartIndex, flagpoleEndIndex + 1) .map(d => d.volume); const avgFlagpoleVolume = flagpoleVolumes.reduce((sum, v) => sum + v, 0) / flagpoleVolumes.length; // 计算整理期成交量 const consolidationVolumes = data .slice(consolidationStartIndex, consolidationEndIndex + 1) .map(d => d.volume); const avgConsolidationVolume = consolidationVolumes.reduce((sum, v) => sum + v, 0) / consolidationVolumes.length; // 计算整理期内的成交量趋势 let volumeTrend = 0; for (let i = 1; i < consolidationVolumes.length; i++) { volumeTrend += consolidationVolumes[i] > consolidationVolumes[i - 1] ? 1 : -1; } // 检查突破时的成交量 let breakoutVolume = 0; if (consolidationEndIndex + 1 < data.length) { breakoutVolume = data[consolidationEndIndex + 1].volume; } if (avgConsolidationVolume < avgFlagpoleVolume * 0.7) { // 理想情况:整理期间成交量萎缩 if (breakoutVolume > avgConsolidationVolume * 1.5) { return '理想的成交量模式:旗杆成交量大,整理期成交量萎缩,突破时成交量放大'; } else { return '良好的成交量模式:旗杆成交量大,整理期成交量萎缩'; } } else if (volumeTrend < 0) { return '可接受的成交量模式:整理期内成交量逐渐减少'; } else { return '非理想的成交量模式:整理期内成交量没有明显萎缩,降低形态可靠性'; } }