UNPKG

@gabriel3615/ta_analysis

Version:

stock ta analysis

911 lines (814 loc) 27.1 kB
import { Candle } from '../../../types.js'; export type PatternConfig = { windows: { sliceRecentCount: number; // 参与分析的最近K线数量 peakWindow: number; // 峰谷检测窗口 recentEmphasis: boolean; // 是否强调最近数据 formingNearDistance: number; // 形态"正在形成且临近"判定的最大距离 }; weights: { timeframe: { weekly: number; daily: number; '1hour': number }; confirmedBoost: number; // 已确认突破形态的权重倍数 formingBoost: number; // 正在形成且临近的权重倍数 patternDistanceDecay: number; // 形态距离衰减系数 (用于 exp(-k*distance)) peakImportanceDecay: number; // 峰谷重要性衰减系数 (用于 exp(-k*distance)) }; signals: { strongRatio: number; // 单边强信号阈值(bullishScore > bearishScore * strongRatio) combineBiasRatio: number; // 多时间框架偏向阈值(bullishCount > bearishCount * combineBiasRatio) recentCount: number; // 参与综合信号计算的最近形态数量 reliabilityBoostThreshold: number; // 可靠性提升阈值 recencyHighThreshold: number; // 形态新近度高阈值 recencyMediumThreshold: number; // 形态新近度中阈值 recencyHighBonus: number; // 高新近度加分 recencyMediumBonus: number; // 中新近度加分 }; // 新增:统一的形态验证参数 validation: { // 价格相似度阈值(用于判断两点是否在同一水平) priceSimilarityThreshold: number; // 默认0.05 (5%) // 最小形态高度(相对于价格的百分比) minPatternHeightRatio: number; // 默认0.02 (2%) // 最小形态持续时间(K线数量) minPatternDuration: number; // 默认5 // 最大形态持续时间(K线数量) maxPatternDuration: number; // 默认100 // 突破确认所需的最小K线数量 breakoutConfirmationBars: number; // 默认3 // 突破确认的最小价格变动百分比 breakoutConfirmationPercent: number; // 默认0.01 (1%) // 形态失败判定:突破后回撤超过突破幅度的百分比 failureRetracementThreshold: number; // 默认0.6 (60%) // 成交量确认:突破时成交量应超过平均成交量的倍数 volumeConfirmationMultiplier: number; // 默认1.5 // 趋势线触碰的最小次数 minTrendlineTouches: number; // 默认3 // 趋势线触碰的容差百分比 trendlineTouchTolerance: number; // 默认0.005 (0.5%) }; }; export let patternConfig: PatternConfig = { windows: { sliceRecentCount: 100, peakWindow: 5, recentEmphasis: true, formingNearDistance: 5, }, weights: { timeframe: { weekly: 1.5, daily: 1.3, '1hour': 1.0 }, confirmedBoost: 1.5, formingBoost: 1.3, patternDistanceDecay: 0.05, peakImportanceDecay: 0.01, }, signals: { strongRatio: 1.5, combineBiasRatio: 1.2, recentCount: 10, reliabilityBoostThreshold: 70, recencyHighThreshold: 0.8, recencyMediumThreshold: 0.6, recencyHighBonus: 10, recencyMediumBonus: 5, }, // 新增:统一的形态验证参数默认值 validation: { priceSimilarityThreshold: 0.05, // 5% minPatternHeightRatio: 0.02, // 2% minPatternDuration: 5, maxPatternDuration: 100, breakoutConfirmationBars: 3, breakoutConfirmationPercent: 0.01, // 1% failureRetracementThreshold: 0.6, // 60% volumeConfirmationMultiplier: 1.5, minTrendlineTouches: 3, trendlineTouchTolerance: 0.005, // 0.5% }, }; export function updatePatternConfig(partial: Partial<PatternConfig>) { patternConfig = { ...patternConfig, ...partial, windows: { ...patternConfig.windows, ...(partial.windows || {}) }, weights: { ...patternConfig.weights, ...(partial.weights || {}), timeframe: { ...patternConfig.weights.timeframe, ...((partial.weights && partial.weights.timeframe) || {}), }, }, signals: { ...patternConfig.signals, ...(partial.signals || {}) }, validation: { ...patternConfig.validation, ...(partial.validation || {}) }, }; } /** * 检查两个价格是否在同一水平(相似度检查) */ export function arePricesSimilar(price1: number, price2: number): boolean { const avgPrice = (price1 + price2) / 2; const priceDiff = Math.abs(price1 - price2) / avgPrice; return priceDiff <= patternConfig.validation.priceSimilarityThreshold; } /** * 检查价格是否接近趋势线 */ export function isPriceNearTrendline( price: number, trendlinePrice: number ): boolean { const priceDiff = Math.abs(price - trendlinePrice) / trendlinePrice; return priceDiff <= patternConfig.validation.trendlineTouchTolerance; } /** * 计算形态高度是否足够显著 */ export function isPatternHeightSignificant( patternHeight: number, avgPrice: number ): boolean { const heightRatio = patternHeight / avgPrice; return heightRatio >= patternConfig.validation.minPatternHeightRatio; } /** * 检查形态持续时间是否在合理范围内 */ export function isPatternDurationValid(duration: number): boolean { return ( duration >= patternConfig.validation.minPatternDuration && duration <= patternConfig.validation.maxPatternDuration ); } /** * 检查是否为有效突破 */ export function isValidBreakout( data: Candle[], breakoutIndex: number, breakoutLevel: number, isUpward: boolean ): boolean { // 检查是否有足够的确认K线 const confirmationBars = Math.min( patternConfig.validation.breakoutConfirmationBars, data.length - breakoutIndex - 1 ); if (confirmationBars < 1) return false; let confirmedBars = 0; const requiredMove = breakoutLevel * patternConfig.validation.breakoutConfirmationPercent; for (let i = 1; i <= confirmationBars; i++) { const candle = data[breakoutIndex + i]; if (isUpward) { // 向上突破:收盘价应持续高于突破水平 if (candle.close > breakoutLevel + requiredMove) { confirmedBars++; } } else { // 向下突破:收盘价应持续低于突破水平 if (candle.close < breakoutLevel - requiredMove) { confirmedBars++; } } } // 至少需要60%的确认K线满足条件 return confirmedBars >= confirmationBars * 0.6; } /** * 检查形态是否失败 */ export function isPatternFailed( data: Candle[], patternEndIndex: number, breakoutLevel: number, isUpward: boolean ): boolean { // 检查突破后的价格走势 for (let i = patternEndIndex + 1; i < data.length; i++) { const candle = data[i]; if (isUpward) { // 向上突破后,如果价格大幅回撤到突破水平以下,可能失败 const retracement = (breakoutLevel - candle.low) / (candle.high - breakoutLevel); if (retracement > patternConfig.validation.failureRetracementThreshold) { return true; } } else { // 向下突破后,如果价格大幅反弹到突破水平以上,可能失败 const retracement = (candle.high - breakoutLevel) / (breakoutLevel - candle.low); if (retracement > patternConfig.validation.failureRetracementThreshold) { return true; } } } return false; } /** * 标准化的形态可靠性评分系统 */ export interface ReliabilityFactors { // 基础因素 patternHeight: number; // 形态高度(绝对值) avgPrice: number; // 平均价格 duration: number; // 形态持续时间(K线数量) // 对称性因素(适用于对称形态) symmetry?: number; // 对称性评分(0-1) // 触碰因素(适用于趋势线形态) trendlineTouches?: number; // 趋势线触碰次数 expectedTouches?: number; // 预期触碰次数 // 成交量因素 volumeConfirmation?: boolean; // 是否有成交量确认 volumePattern?: 'ideal' | 'good' | 'acceptable' | 'poor'; // 成交量模式质量 // 突破因素 breakoutConfirmed?: boolean; // 是否确认突破 breakoutStrength?: number; // 突破强度(0-1) // 时间因素 recency?: number; // 新近度(0-1,1表示最近) // 形态特定因素 specificFactors?: { [key: string]: number }; // 形态特定因素 } /** * 计算标准化的可靠性评分 */ export function calculateStandardReliability( factors: ReliabilityFactors ): number { let score = 50; // 基础分数 // 1. 形态高度评分(0-15分) const heightRatio = factors.patternHeight / factors.avgPrice; if (heightRatio > 0.1) { score += 15; // 高度非常显著 } else if (heightRatio > 0.05) { score += 12; // 高度显著 } else if (heightRatio > 0.03) { score += 8; // 高度中等 } else if (heightRatio > 0.02) { score += 4; // 高度较小 } // 2. 形态持续时间评分(0-10分) if (factors.duration > 30) { score += 10; // 长期形态 } else if (factors.duration > 20) { score += 8; // 中长期形态 } else if (factors.duration > 10) { score += 6; // 中期形态 } else if (factors.duration > 5) { score += 3; // 短期形态 } // 3. 对称性评分(0-10分) if (factors.symmetry !== undefined) { score += factors.symmetry * 10; } // 4. 趋势线触碰评分(0-10分) if ( factors.trendlineTouches !== undefined && factors.expectedTouches !== undefined ) { const touchRatio = factors.trendlineTouches / factors.expectedTouches; if (touchRatio >= 1) { score += 10; // 触碰次数达到或超过预期 } else if (touchRatio >= 0.8) { score += 8; // 触碰次数接近预期 } else if (touchRatio >= 0.6) { score += 5; // 触碰次数中等 } else if (touchRatio >= 0.4) { score += 2; // 触碰次数较少 } } // 5. 成交量确认评分(0-15分) if (factors.volumeConfirmation !== undefined) { if (factors.volumeConfirmation) { score += 15; // 有成交量确认 } else { score += 5; // 无成交量确认 } } // 6. 成交量模式质量评分(0-10分) if (factors.volumePattern !== undefined) { switch (factors.volumePattern) { case 'ideal': score += 10; break; case 'good': score += 7; break; case 'acceptable': score += 4; break; case 'poor': score += 0; break; } } // 7. 突破确认评分(0-15分) if (factors.breakoutConfirmed !== undefined) { if (factors.breakoutConfirmed) { score += 15; // 突破已确认 // 突破强度额外加分(0-5分) if (factors.breakoutStrength !== undefined) { score += factors.breakoutStrength * 5; } } else { score += 5; // 突破未确认 } } // 8. 新近度评分(0-10分) if (factors.recency !== undefined) { score += factors.recency * 10; } // 9. 形态特定因素评分(0-15分) if (factors.specificFactors !== undefined) { for (const [key, value] of Object.entries(factors.specificFactors)) { // 根据因素权重分配分数 switch (key) { case 'headProminence': // 头肩形态头部突出程度 score += value * 8; break; case 'necklineSlope': // 头肩形态颈线斜率 score += value * 5; break; case 'cupDepth': // 杯柄形态杯深度 score += value * 7; break; case 'handleDepth': // 杯柄形态柄深度 score += value * 5; break; case 'convergenceProximity': // 三角形收敛接近度 score += value * 6; break; default: score += value * 3; // 默认权重 break; } } } // 确保分数在0-100范围内 return Math.max(0, Math.min(100, score)); } /** * 分析成交量模式质量 */ export function analyzeVolumePatternQuality( data: Candle[], startIndex: number, endIndex: number, patternType: 'reversal' | 'continuation' | 'consolidation' ): 'ideal' | 'good' | 'acceptable' | 'poor' { // 计算各阶段成交量 const patternVolumes = data .slice(startIndex, endIndex + 1) .map(d => d.volume); const avgPatternVolume = patternVolumes.reduce((sum, v) => sum + v, 0) / patternVolumes.length; // 计算成交量趋势 let volumeTrend = 0; for (let i = 1; i < patternVolumes.length; i++) { volumeTrend += patternVolumes[i] > patternVolumes[i - 1] ? 1 : -1; } const trendStrength = Math.abs(volumeTrend) / (patternVolumes.length - 1); // 检查突破时的成交量(如果有) let breakoutVolume = 0; if (endIndex + 1 < data.length) { breakoutVolume = data[endIndex + 1].volume; } // 根据形态类型评估成交量模式 if (patternType === 'reversal') { // 反转形态:理想情况是形态内成交量萎缩,突破时放量 if (trendStrength < 0.3 && breakoutVolume > avgPatternVolume * 1.5) { return 'ideal'; } else if (trendStrength < 0.5 && breakoutVolume > avgPatternVolume * 1.2) { return 'good'; } else if (trendStrength < 0.7) { return 'acceptable'; } else { return 'poor'; } } else if (patternType === 'continuation') { // 持续形态:理想情况是形态内成交量萎缩,突破时放量 if (trendStrength < 0.3 && breakoutVolume > avgPatternVolume * 1.5) { return 'ideal'; } else if (trendStrength < 0.5 && breakoutVolume > avgPatternVolume * 1.2) { return 'good'; } else if (trendStrength < 0.7) { return 'acceptable'; } else { return 'poor'; } } else { // 整理形态:理想情况是成交量逐渐萎缩 if (trendStrength < 0.2) { return 'ideal'; } else if (trendStrength < 0.4) { return 'good'; } else if (trendStrength < 0.6) { return 'acceptable'; } else { return 'poor'; } } } /** * 计算突破强度 */ export function calculateBreakoutStrength( data: Candle[], breakoutIndex: number, breakoutLevel: number, isUpward: boolean ): number { if (breakoutIndex >= data.length) return 0; const breakoutCandle = data[breakoutIndex]; const breakoutPrice = isUpward ? breakoutCandle.close : breakoutCandle.close; const breakoutSize = Math.abs(breakoutPrice - breakoutLevel) / breakoutLevel; // 计算突破成交量 const avgVolume = data .slice(Math.max(0, breakoutIndex - 20), breakoutIndex) .reduce((sum, d) => sum + d.volume, 0) / Math.min(20, breakoutIndex); const volumeRatio = breakoutCandle.volume / avgVolume; // 综合价格突破和成交量因素 const priceStrength = Math.min(breakoutSize * 10, 1); // 标准化到0-1 const volumeStrength = Math.min(volumeRatio / 3, 1); // 标准化到0-1 return (priceStrength + volumeStrength) / 2; } /** * 形态冲突处理机制 */ // 需要导入PatternType和PatternAnalysisResult类型 import { PatternType, PatternDirection, PatternAnalysisResult, } from './analyzeMultiTimeframePatterns.js'; /** * 形态优先级枚举 */ export enum PatternPriority { Critical = 5, // 关键形态(如头肩、双顶双底) High = 4, // 高优先级形态(如三角形、楔形) Medium = 3, // 中等优先级形态(如旗形、三角旗) Low = 2, // 低优先级形态(如圆底圆顶) Minor = 1, // 次要形态(如买入/卖出高潮) } /** * 获取形态类型的优先级 */ export function getPatternPriority(patternType: PatternType): PatternPriority { switch (patternType) { case PatternType.HeadAndShoulders: case PatternType.InverseHeadAndShoulders: case PatternType.DoubleTop: case PatternType.DoubleBottom: case PatternType.TripleTop: case PatternType.TripleBottom: return PatternPriority.Critical; case PatternType.AscendingTriangle: case PatternType.DescendingTriangle: case PatternType.SymmetricalTriangle: case PatternType.RisingWedge: case PatternType.FallingWedge: return PatternPriority.High; case PatternType.Flag: case PatternType.Pennant: case PatternType.Rectangle: return PatternPriority.Medium; case PatternType.RoundingBottom: case PatternType.RoundingTop: case PatternType.CupAndHandle: return PatternPriority.Low; case PatternType.BuyingClimax: case PatternType.SellingClimax: return PatternPriority.Minor; default: return PatternPriority.Medium; } } /** * 形态冲突类型 */ export enum ConflictType { DirectionConflict = 'direction_conflict', // 方向冲突(看涨vs看跌) OverlapConflict = 'overlap_conflict', // 重叠冲突(形态重叠) TimingConflict = 'timing_conflict', // 时间冲突(形成时间冲突) LevelConflict = 'level_conflict', // 水平冲突(关键价位冲突) } /** * 形态冲突信息 */ export interface PatternConflict { type: ConflictType; pattern1: PatternAnalysisResult; pattern2: PatternAnalysisResult; severity: number; // 冲突严重程度(0-1) description: string; } /** * 检测形态冲突 */ export function detectPatternConflicts( patterns: PatternAnalysisResult[] ): PatternConflict[] { const conflicts: PatternConflict[] = []; // 检查所有形态对之间的冲突 for (let i = 0; i < patterns.length; i++) { for (let j = i + 1; j < patterns.length; j++) { const pattern1 = patterns[i]; const pattern2 = patterns[j]; // 检测方向冲突 if (pattern1.direction !== pattern2.direction) { const directionConflict = detectDirectionConflict(pattern1, pattern2); if (directionConflict) { conflicts.push(directionConflict); } } // 检测重叠冲突 const overlapConflict = detectOverlapConflict(pattern1, pattern2); if (overlapConflict) { conflicts.push(overlapConflict); } // 检测水平冲突 const levelConflict = detectLevelConflict(pattern1, pattern2); if (levelConflict) { conflicts.push(levelConflict); } } } return conflicts; } /** * 检测方向冲突 */ function detectDirectionConflict( pattern1: PatternAnalysisResult, pattern2: PatternAnalysisResult ): PatternConflict | null { // 如果方向不同且可靠性都较高,则存在方向冲突 if ( pattern1.direction !== pattern2.direction && pattern1.reliability > 60 && pattern2.reliability > 60 ) { const severity = Math.min(pattern1.reliability, pattern2.reliability) / 100; return { type: ConflictType.DirectionConflict, pattern1, pattern2, severity, description: `${pattern1.patternType}(${pattern1.direction})与${pattern2.patternType}(${pattern2.direction})方向冲突`, }; } return null; } /** * 检测重叠冲突 */ function detectOverlapConflict( pattern1: PatternAnalysisResult, pattern2: PatternAnalysisResult ): PatternConflict | null { // 检查形态时间范围是否重叠 const overlapStart = Math.max( pattern1.component.startIndex, pattern2.component.startIndex ); const overlapEnd = Math.min( pattern1.component.endIndex, pattern2.component.endIndex ); if (overlapStart <= overlapEnd) { // 计算重叠程度 const pattern1Duration = pattern1.component.endIndex - pattern1.component.startIndex; const pattern2Duration = pattern2.component.endIndex - pattern2.component.startIndex; const overlapDuration = overlapEnd - overlapStart; const overlapRatio1 = overlapDuration / pattern1Duration; const overlapRatio2 = overlapDuration / pattern2Duration; const maxOverlapRatio = Math.max(overlapRatio1, overlapRatio2); // 如果重叠程度超过50%,则认为存在冲突 if (maxOverlapRatio > 0.5) { return { type: ConflictType.OverlapConflict, pattern1, pattern2, severity: maxOverlapRatio, description: `${pattern1.patternType}${pattern2.patternType}在时间上重叠${(maxOverlapRatio * 100).toFixed(1)}%`, }; } } return null; } /** * 检测水平冲突 */ function detectLevelConflict( pattern1: PatternAnalysisResult, pattern2: PatternAnalysisResult ): PatternConflict | null { // 检查关键价位是否冲突 const level1 = pattern1.component.breakoutLevel; const level2 = pattern2.component.breakoutLevel; if (level1 && level2) { const levelDiff = Math.abs(level1 - level2) / Math.min(level1, level2); // 如果关键价位差异小于5%,则认为存在冲突 if (levelDiff < 0.05) { return { type: ConflictType.LevelConflict, pattern1, pattern2, severity: 1 - levelDiff / 0.05, // 差异越小,冲突越严重 description: `${pattern1.patternType}${pattern2.patternType}的关键价位接近`, }; } } return null; } /** * 解决形态冲突 */ export function resolvePatternConflicts( patterns: PatternAnalysisResult[], conflicts: PatternConflict[] ): PatternAnalysisResult[] { // 如果没有冲突,直接返回 if (conflicts.length === 0) { return patterns; } // 创建形态副本用于处理 const resolvedPatterns = [...patterns]; // 按冲突严重程度排序,优先处理严重冲突 conflicts.sort((a, b) => b.severity - a.severity); for (const conflict of conflicts) { const { pattern1, pattern2, type, severity } = conflict; // 根据冲突类型和严重程度采取不同策略 switch (type) { case ConflictType.DirectionConflict: resolveDirectionConflict( resolvedPatterns, pattern1, pattern2, severity ); break; case ConflictType.OverlapConflict: resolveOverlapConflict(resolvedPatterns, pattern1, pattern2, severity); break; case ConflictType.LevelConflict: resolveLevelConflict(resolvedPatterns, pattern1, pattern2); break; } } return resolvedPatterns; } /** * 解决方向冲突 */ function resolveDirectionConflict( patterns: PatternAnalysisResult[], pattern1: PatternAnalysisResult, pattern2: PatternAnalysisResult, severity: number ): void { // 获取形态优先级 const priority1 = getPatternPriority(pattern1.patternType); const priority2 = getPatternPriority(pattern2.patternType); // 如果优先级不同,保留高优先级形态 if (priority1 !== priority2) { if (priority1 > priority2) { // 降低pattern2的可靠性 const index2 = patterns.findIndex(p => p === pattern2); if (index2 !== -1) { patterns[index2].reliability *= 1 - severity * 0.5; } } else { // 降低pattern1的可靠性 const index1 = patterns.findIndex(p => p === pattern1); if (index1 !== -1) { patterns[index1].reliability *= 1 - severity * 0.5; } } } else { // 优先级相同,根据可靠性和重要性决定 const score1 = pattern1.reliability * pattern1.significance; const score2 = pattern2.reliability * pattern2.significance; if (score1 > score2) { // 降低pattern2的可靠性 const index2 = patterns.findIndex(p => p === pattern2); if (index2 !== -1) { patterns[index2].reliability *= 1 - severity * 0.3; } } else { // 降低pattern1的可靠性 const index1 = patterns.findIndex(p => p === pattern1); if (index1 !== -1) { patterns[index1].reliability *= 1 - severity * 0.3; } } } } /** * 解决重叠冲突 */ function resolveOverlapConflict( patterns: PatternAnalysisResult[], pattern1: PatternAnalysisResult, pattern2: PatternAnalysisResult, severity: number ): void { // 对于重叠冲突,根据形态类型和可靠性调整 const priority1 = getPatternPriority(pattern1.patternType); const priority2 = getPatternPriority(pattern2.patternType); // 如果优先级不同,保留高优先级形态 if (priority1 !== priority2) { if (priority1 > priority2) { // 降低pattern2的重要性 const index2 = patterns.findIndex(p => p === pattern2); if (index2 !== -1) { patterns[index2].significance *= 1 - severity * 0.4; } } else { // 降低pattern1的重要性 const index1 = patterns.findIndex(p => p === pattern1); if (index1 !== -1) { patterns[index1].significance *= 1 - severity * 0.4; } } } else { // 优先级相同,根据可靠性调整 if (pattern1.reliability > pattern2.reliability) { // 降低pattern2的重要性 const index2 = patterns.findIndex(p => p === pattern2); if (index2 !== -1) { patterns[index2].significance *= 1 - severity * 0.3; } } else { // 降低pattern1的重要性 const index1 = patterns.findIndex(p => p === pattern1); if (index1 !== -1) { patterns[index1].significance *= 1 - severity * 0.3; } } } } /** * 解决水平冲突 */ function resolveLevelConflict( patterns: PatternAnalysisResult[], pattern1: PatternAnalysisResult, pattern2: PatternAnalysisResult ): void { // 对于水平冲突,根据形态类型调整 const priority1 = getPatternPriority(pattern1.patternType); const priority2 = getPatternPriority(pattern2.patternType); // 如果优先级不同,保留高优先级形态的关键价位 if (priority1 !== priority2) { if (priority1 > priority2) { // 调整pattern2的突破水平 const index2 = patterns.findIndex(p => p === pattern2); if (index2 !== -1) { // 将突破水平调整5%以避免冲突 if (pattern2.direction === PatternDirection.Bullish) { patterns[index2].component.breakoutLevel *= 1.05; } else { patterns[index2].component.breakoutLevel *= 0.95; } } } else { // 调整pattern1的突破水平 const index1 = patterns.findIndex(p => p === pattern1); if (index1 !== -1) { // 将突破水平调整5%以避免冲突 if (pattern1.direction === PatternDirection.Bullish) { patterns[index1].component.breakoutLevel *= 1.05; } else { patterns[index1].component.breakoutLevel *= 0.95; } } } } else { // 优先级相同,根据可靠性调整 if (pattern1.reliability > pattern2.reliability) { // 调整pattern2的突破水平 const index2 = patterns.findIndex(p => p === pattern2); if (index2 !== -1) { if (pattern2.direction === PatternDirection.Bullish) { patterns[index2].component.breakoutLevel *= 1.03; } else { patterns[index2].component.breakoutLevel *= 0.97; } } } else { // 调整pattern1的突破水平 const index1 = patterns.findIndex(p => p === pattern1); if (index1 !== -1) { if (pattern1.direction === PatternDirection.Bullish) { patterns[index1].component.breakoutLevel *= 1.03; } else { patterns[index1].component.breakoutLevel *= 0.97; } } } } }