@gabriel3615/ta_analysis
Version:
stock ta analysis
501 lines (500 loc) • 22.6 kB
JavaScript
import { PatternDirection, PatternStatus, PatternType, } from './analyzeMultiTimeframePatterns.js';
import { getStatusDescription } from '../../../util/util.js';
/**
* 买入高潮形态枚举,添加到PatternType枚举中
*/
// 在实际使用时,请在PatternType枚举中添加这个类型
// enum PatternType {
// ...
// BuyingClimax = 'buying_climax',
// }
/**
* 寻找买入高潮信号
* 买入高潮可以出现在顶部或底部,具有不同的交易含义:
*
* 顶部买入高潮(看跌)特征:
* 1. 成交量突然放大(通常是近期平均成交量的2倍以上)
* 2. 价格创出近期新高
* 3. 当日振幅较大(通常大于平均振幅的1.5倍)
* 4. K线通常是大阳线或上影线明显的十字星
* 5. 之后通常伴随价格回落
*
* 底部买入高潮(看涨)特征:
* 1. 成交量突然放大(通常是近期平均成交量的2倍以上)
* 2. 价格在下跌趋势中出现大幅反弹
* 3. 当日振幅较大(通常大于平均振幅的1.5倍)
* 4. K线通常是大阳线,表明强力买入
* 5. 之前有明显的下跌趋势
* 6. 之后通常伴随价格继续上涨
*
* @param data K线数据
* @param peaksValleys 峰谷点
* @param lookbackPeriod 回溯周期
* @returns 检测到的买入高潮形态
*/
export function findBuyingClimax(data, peaksValleys, lookbackPeriod = 30) {
const patterns = [];
// 确保有足够的数据
if (data.length < lookbackPeriod + 5) {
return patterns;
}
// 计算近期的平均成交量
const recentData = data.slice(-lookbackPeriod);
const avgVolume = recentData.reduce((sum, candle) => sum + candle.volume, 0) /
recentData.length;
// 计算近期的平均振幅
const avgRange = recentData.reduce((sum, candle) => sum + (candle.high - candle.low) / candle.low, 0) / recentData.length;
// 从最近的数据开始分析,但排除最后一根K线(可能未完成)
for (let i = data.length - 2; i >= data.length - lookbackPeriod; i--) {
const currentCandle = data[i];
// 判断之前的趋势方向(用于区分顶部和底部买入高潮)
const previousCandles = data.slice(Math.max(0, i - 20), i);
const downTrendCount = previousCandles.filter(c => c.close < c.open).length;
const upTrendCount = previousCandles.filter(c => c.close > c.open).length;
// 如果下跌K线数量明显多于上涨K线,则认为是在下跌趋势中
const isInDowntrend = downTrendCount > upTrendCount * 1.5;
// 找出之前的高点和低点
const previousHighs = data
.slice(Math.max(0, i - lookbackPeriod), i)
.map(c => c.high);
const recentHigh = Math.max(...previousHighs);
const previousLows = data
.slice(Math.max(0, i - lookbackPeriod), i)
.map(c => c.low);
const recentLow = Math.min(...previousLows);
// 条件1: 成交量是近期平均的2倍以上(两种情况都需要)
const volumeCondition = currentCandle.volume > avgVolume * 2;
// 条件2: 价格条件(顶部和底部买入高潮的条件不同)
// 顶部买入高潮:价格创出近期新高或接近新高
const topHighPriceCondition = currentCandle.high >= recentHigh * 0.98;
// 底部买入高潮:价格从近期低点反弹明显(至少5%以上)
const priceReboundPercent = (currentCandle.high - recentLow) / recentLow;
const bottomReboundCondition = priceReboundPercent > 0.05 && isInDowntrend;
// 条件3: 当日振幅较大(两种情况都需要)
const range = (currentCandle.high - currentCandle.low) / currentCandle.low;
const rangeCondition = range > avgRange * 1.5;
// 条件4: K线形态
// 4.1 大阳线: 收盘价明显高于开盘价(至少3%的涨幅)
const bigBullishCandle = (currentCandle.close - currentCandle.open) / currentCandle.open > 0.03;
// 4.2 上影线明显的十字星:实体小,上影线长(主要用于顶部买入高潮)
const upperShadow = currentCandle.high - Math.max(currentCandle.open, currentCandle.close);
const body = Math.abs(currentCandle.close - currentCandle.open);
const dojiWithUpperShadow = body / currentCandle.open < 0.01 && upperShadow > body * 2;
// 顶部买入高潮的K线形态条件
const topCandlePatternCondition = bigBullishCandle || dojiWithUpperShadow;
// 底部买入高潮的K线形态条件(主要是大阳线,表示强力买入)
const bottomCandlePatternCondition = bigBullishCandle;
// 条件5: 后续价格走势检查
let topReversalCondition = false;
let bottomContinuationCondition = false;
if (i < data.length - 1) {
const nextCandle = data[i + 1];
// 顶部买入高潮:之后价格回落
topReversalCondition =
nextCandle.close < nextCandle.open &&
((nextCandle.open - nextCandle.close) / nextCandle.open > 0.01 ||
(currentCandle.close > currentCandle.open &&
nextCandle.open - nextCandle.close >
(currentCandle.close - currentCandle.open) * 0.5));
// 底部买入高潮:之后价格继续上涨
bottomContinuationCondition =
nextCandle.close > nextCandle.open &&
(nextCandle.close - nextCandle.open) / nextCandle.open > 0.01;
}
// 综合所有条件,确定是否存在顶部买入高潮
const isTopBuyingClimax = volumeCondition &&
topHighPriceCondition &&
rangeCondition &&
topCandlePatternCondition &&
!isInDowntrend;
// 综合所有条件,确定是否存在底部买入高潮
const isBottomBuyingClimax = volumeCondition &&
bottomReboundCondition &&
rangeCondition &&
bottomCandlePatternCondition &&
isInDowntrend;
// 如果检测到任一类型的买入高潮
if (isTopBuyingClimax || isBottomBuyingClimax) {
// 确定形态状态
let status = PatternStatus.Forming;
// 根据买入高潮类型选择相应的条件
const isTopPattern = isTopBuyingClimax;
const isBottomPattern = isBottomBuyingClimax;
if (isTopPattern && topReversalCondition && i < data.length - 2) {
// 顶部买入高潮:如果后续确实出现了下跌,则确认
const furtherReversal = data
.slice(i + 1, Math.min(i + 6, data.length))
.some(c => c.close < data[i].low);
if (furtherReversal) {
status = PatternStatus.Confirmed;
}
else {
status = PatternStatus.Completed;
}
}
else if (isBottomPattern &&
bottomContinuationCondition &&
i < data.length - 2) {
// 底部买入高潮:如果后续确实出现了上涨,则确认
const furtherContinuation = data
.slice(i + 1, Math.min(i + 6, data.length))
.some(c => c.close > data[i].high);
if (furtherContinuation) {
status = PatternStatus.Confirmed;
}
else {
status = PatternStatus.Completed;
}
}
// 检查形态是否失败
if (status === PatternStatus.Confirmed && i < data.length - 5) {
const laterPrices = data.slice(i + 1, Math.min(i + 10, data.length));
if (isTopPattern &&
laterPrices.some(c => c.high > currentCandle.high)) {
// 顶部买入高潮:价格回升到高点以上,则形态失败
status = PatternStatus.Failed;
}
else if (isBottomPattern &&
laterPrices.some(c => c.low < currentCandle.low)) {
// 底部买入高潮:价格跌破低点,则形态失败
status = PatternStatus.Failed;
}
}
// 计算形态的可靠性得分
const reliability = calculateBuyingClimaxReliability(data, i, volumeCondition, isTopPattern ? topHighPriceCondition : bottomReboundCondition, rangeCondition, isTopPattern ? topCandlePatternCondition : bottomCandlePatternCondition, isTopPattern ? topReversalCondition : bottomContinuationCondition, status, isInDowntrend);
// 计算形态的重要性(结合可靠性和价格波动幅度)
const significance = reliability * (range / avgRange) * 0.8;
// 分析成交量特征
const volumePattern = analyzeBuyingClimaxVolume(data.slice(Math.max(0, i - 10), Math.min(i + 5, data.length)), i - Math.max(0, i - 10), isBottomPattern);
// 根据形态类型计算不同的价格目标和关键点
let priceTarget, stopLoss, probableBreakoutZone;
let keyPoint1, keyPoint2;
let patternHeight, breakoutLevel;
let description, tradingImplication;
if (isTopPattern) {
// 顶部买入高潮:计算下跌目标
const supportLevel = Math.min(...previousLows);
const priceMoveUp = currentCandle.high - supportLevel;
priceTarget = currentCandle.high - priceMoveUp * 0.618;
stopLoss = currentCandle.high * 1.03; // 止损位:高点再上方一点
probableBreakoutZone = [supportLevel * 0.95, supportLevel * 1.05];
keyPoint1 = {
index: i,
price: currentCandle.high,
date: currentCandle.timestamp,
type: 'peak',
};
keyPoint2 = {
index: Math.max(0, i - lookbackPeriod),
price: supportLevel,
date: data[Math.max(0, i - lookbackPeriod)].timestamp,
type: 'valley',
};
patternHeight = currentCandle.high - supportLevel;
breakoutLevel = supportLevel; // 突破支撑位确认看跌
description = `顶部买入高潮, ${getStatusDescription(status)}, 高点在 ${currentCandle.high.toFixed(2)}, 支撑位在 ${supportLevel.toFixed(2)}`;
tradingImplication = `看跌信号, 预计回撤目标: ${priceTarget.toFixed(2)}, 止损位: ${stopLoss.toFixed(2)}`;
}
else {
// 底部买入高潮:计算上涨目标
const resistanceLevel = Math.max(...previousHighs);
const priceMoveDown = resistanceLevel - currentCandle.low;
priceTarget = currentCandle.low + priceMoveDown * 0.618;
stopLoss = currentCandle.low * 0.97; // 止损位:低点再下方一点
probableBreakoutZone = [resistanceLevel * 0.95, resistanceLevel * 1.05];
keyPoint1 = {
index: i,
price: currentCandle.low,
date: currentCandle.timestamp,
type: 'valley',
};
keyPoint2 = {
index: Math.max(0, i - lookbackPeriod),
price: resistanceLevel,
date: data[Math.max(0, i - lookbackPeriod)].timestamp,
type: 'peak',
};
patternHeight = resistanceLevel - currentCandle.low;
breakoutLevel = resistanceLevel; // 突破阻力位确认看涨
description = `底部买入高潮, ${getStatusDescription(status)}, 低点在 ${currentCandle.low.toFixed(2)}, 阻力位在 ${resistanceLevel.toFixed(2)}`;
tradingImplication = `看涨信号, 预计上涨目标: ${priceTarget.toFixed(2)}, 止损位: ${stopLoss.toFixed(2)}`;
}
// 添加到检测到的形态列表
patterns.push({
patternType: PatternType.BuyingClimax,
status,
direction: isTopPattern
? PatternDirection.Bearish
: PatternDirection.Bullish,
reliability,
significance,
component: {
startIndex: Math.max(0, i - 5), // 形态前几根K线
endIndex: i, // 当前K线是形态的结束点
keyPoints: [keyPoint1, keyPoint2],
patternHeight,
breakoutLevel,
volumePattern,
},
priceTarget,
stopLoss,
breakoutExpected: status === PatternStatus.Completed,
breakoutDirection: isTopPattern
? PatternDirection.Bearish
: PatternDirection.Bullish,
probableBreakoutZone,
description,
tradingImplication,
keyDates: [
data[Math.max(0, i - lookbackPeriod)].timestamp,
currentCandle.timestamp,
],
keyPrices: isTopPattern
? [keyPoint2.price, keyPoint1.price]
: [keyPoint1.price, keyPoint2.price],
});
}
}
return patterns;
}
/**
* 计算买入高潮形态的可靠性
* @param data
* @param climaxIndex
* @param volumeCondition
* @param highPriceCondition
* @param rangeCondition
* @param candlePatternCondition
* @param reversalCondition
* @param status
* @param isInDowntrend 是否在下跌趋势中(用于区分顶部和底部买入高潮)
*/
function calculateBuyingClimaxReliability(data, climaxIndex, volumeCondition, highPriceCondition, rangeCondition, candlePatternCondition, reversalCondition, status, isInDowntrend) {
let score = 50; // 初始可靠性分数
// 1. 成交量条件
if (volumeCondition) {
const currentCandle = data[climaxIndex];
const avgVolume = data
.slice(Math.max(0, climaxIndex - 20), climaxIndex)
.reduce((sum, c) => sum + c.volume, 0) / Math.min(20, climaxIndex);
// 成交量越大,可靠性越高
const volumeRatio = currentCandle.volume / avgVolume;
if (volumeRatio > 5)
score += 15;
else if (volumeRatio > 3)
score += 10;
else if (volumeRatio > 2)
score += 5;
}
// 2. 价格创新高条件
if (highPriceCondition) {
score += 10;
// 如果是明显突破前高,额外加分
const currentCandle = data[climaxIndex];
const previousHighs = data
.slice(Math.max(0, climaxIndex - 30), climaxIndex)
.map(c => c.high);
const recentHigh = Math.max(...previousHighs);
if (currentCandle.high > recentHigh * 1.03) {
score += 5; // 明显突破前高
}
}
// 3. 振幅条件
if (rangeCondition) {
score += 10;
}
// 4. K线形态条件
if (candlePatternCondition) {
score += 10;
// 检查是否是大阳线
const currentCandle = data[climaxIndex];
if ((currentCandle.close - currentCandle.open) / currentCandle.open >
0.05) {
score += 5; // 大阳线加分
}
}
// 5. 反转确认条件
if (reversalCondition) {
score += 15;
}
// 6. 形态状态
if (status === PatternStatus.Confirmed) {
score += 15;
}
else if (status === PatternStatus.Completed) {
score += 5;
}
else if (status === PatternStatus.Failed) {
score -= 25;
}
// 7. 根据趋势环境调整评分
if (isInDowntrend) {
// 底部买入高潮:检查之前的跌势持续性
if (climaxIndex > 10) {
let downTrendCount = 0;
for (let i = climaxIndex - 10; i < climaxIndex; i++) {
if (data[i].close < data[i].open) {
downTrendCount++;
}
}
// 如果之前有强势下跌趋势,底部买入高潮更可靠
if (downTrendCount >= 7) {
score += 15; // 底部买入高潮在强势下跌后更可靠
}
else if (downTrendCount >= 5) {
score += 10;
}
}
// 检查超卖因素(简化版,实际中应使用正式的RSI计算)
if (climaxIndex > 14) {
let upMove = 0;
let downMove = 0;
for (let i = climaxIndex - 14; i < climaxIndex; i++) {
const change = data[i].close - data[i - 1].close;
if (change > 0) {
upMove += change;
}
else {
downMove -= change;
}
}
// 简化的RSI计算
const rs = upMove / (downMove === 0 ? 1 : downMove);
const rsi = 100 - 100 / (1 + rs);
// RSI低于30表示超卖,增加底部买入高潮可靠性
if (rsi < 30) {
score += 15;
}
else if (rsi < 40) {
score += 10;
}
}
}
else {
// 顶部买入高潮:检查之前的涨势持续性
if (climaxIndex > 10) {
let upTrendCount = 0;
for (let i = climaxIndex - 10; i < climaxIndex; i++) {
if (data[i].close > data[i].open) {
upTrendCount++;
}
}
// 如果之前有强势上涨趋势,顶部买入高潮更可靠
if (upTrendCount >= 7) {
score += 10;
}
else if (upTrendCount >= 5) {
score += 5;
}
}
// 检查FOMO因素:连续大阳线
if (climaxIndex > 3) {
let consecutiveBullish = 0;
for (let i = climaxIndex - 3; i < climaxIndex; i++) {
if (data[i].close > data[i].open &&
(data[i].close - data[i].open) / data[i].open > 0.02) {
consecutiveBullish++;
}
else {
consecutiveBullish = 0;
}
}
if (consecutiveBullish >= 2) {
score += 10; // 连续大阳线表明FOMO情绪,顶部买入高潮更可能出现
}
}
}
// 确保分数在0-100范围内
return Math.max(0, Math.min(100, score));
}
/**
* 分析买入高潮的成交量特征
* @param isBottomPattern 是否是底部买入高潮
*/
function analyzeBuyingClimaxVolume(data, climaxIndex, isBottomPattern = false) {
const beforeClimax = data.slice(0, climaxIndex);
const climaxCandle = data[climaxIndex];
const afterClimax = data.slice(climaxIndex + 1);
// 计算高潮前的平均成交量
const avgVolumeBefore = beforeClimax.length > 0
? beforeClimax.reduce((sum, c) => sum + c.volume, 0) / beforeClimax.length
: 0;
// 计算高潮后的平均成交量
const avgVolumeAfter = afterClimax.length > 0
? afterClimax.reduce((sum, c) => sum + c.volume, 0) / afterClimax.length
: 0;
// 高潮K线的成交量比例
const climaxVolumeRatio = avgVolumeBefore > 0 ? climaxCandle.volume / avgVolumeBefore : 0;
// 高潮后的成交量变化比例
const afterVolumeRatio = avgVolumeBefore > 0 ? avgVolumeAfter / avgVolumeBefore : 0;
// 生成成交量特征描述
let volumeDescription = '';
if (climaxVolumeRatio > 5) {
volumeDescription =
'极端成交量爆发,成交量是之前平均水平的' +
climaxVolumeRatio.toFixed(1) +
'倍';
}
else if (climaxVolumeRatio > 3) {
volumeDescription =
'显著成交量放大,成交量是之前平均水平的' +
climaxVolumeRatio.toFixed(1) +
'倍';
}
else if (climaxVolumeRatio > 2) {
volumeDescription =
'明显成交量增加,成交量是之前平均水平的' +
climaxVolumeRatio.toFixed(1) +
'倍';
}
else {
volumeDescription =
'成交量略有增加,成交量是之前平均水平的' +
climaxVolumeRatio.toFixed(1) +
'倍';
}
// 根据是否是底部买入高潮添加不同的描述
if (isBottomPattern) {
// 底部买入高潮的成交量特征描述
volumeDescription += ',表明强力买盘进场';
}
else {
// 顶部买入高潮的成交量特征描述
volumeDescription += ',可能表明买盘力量即将耗尽';
}
// 添加高潮后的成交量变化描述
if (afterClimax.length > 0) {
if (isBottomPattern) {
// 底部买入高潮后的成交量变化描述
if (afterVolumeRatio < 0.5) {
volumeDescription += ',之后成交量减少但价格上涨,表明卖盘力量减弱';
}
else if (afterVolumeRatio < 0.8) {
volumeDescription += ',之后成交量略有减少,市场情绪逐渐稳定';
}
else if (afterVolumeRatio < 1.2) {
volumeDescription += ',之后成交量保持稳定,支撑上涨趋势';
}
else {
volumeDescription += ',之后成交量继续放大,表明上涨趋势可能加速';
}
}
else {
// 顶部买入高潮后的成交量变化描述
if (afterVolumeRatio < 0.5) {
volumeDescription += ',之后成交量显著萎缩,暗示买盘力量耗尽';
}
else if (afterVolumeRatio < 0.8) {
volumeDescription += ',之后成交量明显减少,买盘力量减弱';
}
else if (afterVolumeRatio < 1.2) {
volumeDescription += ',之后成交量保持在相似水平,市场仍有分歧';
}
else {
volumeDescription +=
',之后成交量继续放大,可能表明趋势尚未结束,需谨慎判断';
}
}
}
return volumeDescription;
}