@gabriel3615/ta_analysis
Version:
stock ta analysis
500 lines (442 loc) • 15.5 kB
text/typescript
import {
analyzeMultiTimeframePatterns,
ComprehensivePatternAnalysis,
} from '../../basic/patterns/analyzeMultiTimeframePatterns.js';
import { Candle } from '../../../types.js';
import { trendReversalConfig } from './trendReversalConfig.js';
/**
* 趋势逆转信号接口
*/
interface TrendReversalSignal {
isReversal: boolean; // 是否出现逆转信号
direction: number; // 大周期趋势方向(1:上涨,-1:下跌,0:盘整)
reversalStrength: number; // 逆转信号强度 (0-100)
smallTimeframe: string; // 小周期类型
largeTimeframe: string; // 大周期类型
entryPrice?: number; // 建议入场价
stopLoss?: number; // 建议止损价
targets?: {
target1: number; // 目标1 (保守目标)
target2: number; // 目标2 (标准量度移动目标)
target3: number; // 目标3 (扩展目标,通常是1.618倍)
riskRewardRatio1: number; // 目标1的风险回报比
riskRewardRatio2: number; // 目标2的风险回报比
riskRewardRatio3: number; // 目标3的风险回报比
};
}
/**
* 增强的综合形态分析结果,含趋势逆转信号
*/
interface EnhancedPatternAnalysis extends ComprehensivePatternAnalysis {
reversalSignals: TrendReversalSignal[]; // 可能有多个逆转信号(小时对日线,日线对周线)
primaryReversalSignal?: TrendReversalSignal; // 最主要的逆转信号
}
/**
* 计算量度移动 (Measured Move) 目标价位
*
* @param smallTimeframeData 小周期K线数据
* @param windowSize 窗口大小
* @param largerTrendDirection 大周期趋势方向
* @param entryPrice 入场价
* @param stopLoss 止损价
* @returns 多个目标价位和风险回报比
*/
export function calculateMeasuredMoveTargets(
smallTimeframeData: Candle[],
windowSize: number,
largerTrendDirection: number,
entryPrice: number,
stopLoss: number
): {
target1: number;
target2: number;
target3: number;
riskRewardRatio1: number;
riskRewardRatio2: number;
riskRewardRatio3: number;
} {
// 确保数据足够
if (smallTimeframeData.length < windowSize * 3) {
// 数据不足,返回基于风险的保守估计
const risk = Math.abs(entryPrice - stopLoss);
return {
target1: largerTrendDirection > 0 ? entryPrice + risk : entryPrice - risk,
target2:
largerTrendDirection > 0
? entryPrice + risk * 2
: entryPrice - risk * 2,
target3:
largerTrendDirection > 0
? entryPrice + risk * 3
: entryPrice - risk * 3,
riskRewardRatio1: 1,
riskRewardRatio2: 2,
riskRewardRatio3: 3,
};
}
// 寻找关键支点
const lookbackPeriod = windowSize * 3;
const recentData = smallTimeframeData.slice(-lookbackPeriod);
let pivotHigh = -Infinity;
let pivotLow = Infinity;
let pivotHighIndex = -1;
let pivotLowIndex = -1;
if (largerTrendDirection > 0) {
// 上涨趋势
// 寻找之前下跌的最高点
for (let i = 0; i < recentData.length - windowSize; i++) {
if (recentData[i].high > pivotHigh) {
pivotHigh = recentData[i].high;
pivotHighIndex = i;
}
}
// 寻找之前下跌的最低点
for (let i = pivotHighIndex; i < recentData.length - windowSize / 3; i++) {
if (recentData[i].low < pivotLow) {
pivotLow = recentData[i].low;
pivotLowIndex = i;
}
}
if (pivotHighIndex >= 0 && pivotLowIndex > pivotHighIndex) {
const previousDownMove = pivotHigh - pivotLow;
const risk = entryPrice - stopLoss;
// 计算不同的目标位
const target1 = entryPrice + risk; // 1:1 风险回报比
const target2 = pivotLow + previousDownMove; // 标准量度移动
const target3 = pivotLow + previousDownMove * 1.618; // 斐波那契扩展
return {
target1,
target2,
target3,
riskRewardRatio1: risk > 0 ? (target1 - entryPrice) / risk : 1,
riskRewardRatio2: risk > 0 ? (target2 - entryPrice) / risk : 2,
riskRewardRatio3: risk > 0 ? (target3 - entryPrice) / risk : 3,
};
}
} else {
// 下跌趋势
// 寻找之前上涨的最低点
for (let i = 0; i < recentData.length - windowSize; i++) {
if (recentData[i].low < pivotLow) {
pivotLow = recentData[i].low;
pivotLowIndex = i;
}
}
// 寻找之前上涨的最高点
for (let i = pivotLowIndex; i < recentData.length - windowSize / 3; i++) {
if (recentData[i].high > pivotHigh) {
pivotHigh = recentData[i].high;
pivotHighIndex = i;
}
}
if (pivotLowIndex >= 0 && pivotHighIndex > pivotLowIndex) {
const previousUpMove = pivotHigh - pivotLow;
const risk = stopLoss - entryPrice;
// 计算不同的目标位
const target1 = entryPrice - risk; // 1:1 风险回报比
const target2 = pivotHigh - previousUpMove; // 标准量度移动
const target3 = pivotHigh - previousUpMove * 1.618; // 斐波那契扩展
return {
target1,
target2,
target3,
riskRewardRatio1: risk > 0 ? (entryPrice - target1) / risk : 1,
riskRewardRatio2: risk > 0 ? (entryPrice - target2) / risk : 2,
riskRewardRatio3: risk > 0 ? (entryPrice - target3) / risk : 3,
};
}
}
// 默认返回基于风险的保守估计
const risk = Math.abs(entryPrice - stopLoss);
return {
target1: largerTrendDirection > 0 ? entryPrice + risk : entryPrice - risk,
target2:
largerTrendDirection > 0 ? entryPrice + risk * 2 : entryPrice - risk * 2,
target3:
largerTrendDirection > 0 ? entryPrice + risk * 3 : entryPrice - risk * 3,
riskRewardRatio1: 1,
riskRewardRatio2: 2,
riskRewardRatio3: 3,
};
}
/**
* 判断当前趋势方向 - 改进版,能够处理较小数量的数据
* @param data K线数据
* @param period 均线周期,默认为20
* @param minSlopePoints 计算斜率的最小点数,默认为5
* @returns 趋势方向:1表示上涨,-1表示下跌,0表示盘整
*/
function determineTrendDirection(
data: Candle[],
period: number = trendReversalConfig.trendDirection.defaultPeriod,
minSlopePoints: number = trendReversalConfig.trendDirection.minSlopePoints
): number {
// 动态调整周期,确保能够处理较小数据集
const effectivePeriod = Math.min(period, Math.floor(data.length / 2));
// 如果数据量实在太少,无法计算趋势,返回0
if (data.length < Math.max(effectivePeriod + 2, minSlopePoints + 1)) {
return 0; // 数据不足,默认为盘整
}
// 计算简单移动平均线 (SMA)
const sma = [];
for (let i = effectivePeriod - 1; i < data.length; i++) {
const sum = data
.slice(i - effectivePeriod + 1, i + 1)
.reduce((acc, candle) => acc + candle.close, 0);
sma.push(sum / effectivePeriod);
}
// 确定用于计算斜率的点数
const slopePoints = Math.min(minSlopePoints, sma.length);
// 如果计算出的SMA点数太少,无法计算趋势
if (slopePoints < 3) {
return 0; // 点数太少,无法可靠判断趋势
}
// 计算均线斜率,使用最后的几个点
const recentSMA = sma.slice(-slopePoints);
// 简单线性回归计算斜率
let sumX = 0,
sumY = 0,
sumXY = 0,
sumX2 = 0;
const n = recentSMA.length;
for (let i = 0; i < n; i++) {
sumX += i;
sumY += recentSMA[i];
sumXY += i * recentSMA[i];
sumX2 += i * i;
}
// 防止分母为0
if (n * sumX2 - sumX * sumX === 0) {
return 0;
}
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
// 根据数据量动态调整斜率阈值
// 较小的数据集需要更大的斜率才能判定为趋势
const slopeThreshold = 0.0005 * (30 / Math.min(data.length, 30));
// 用均线斜率判断趋势
if (slope > slopeThreshold) return 1; // 上涨趋势
if (slope < -slopeThreshold) return -1; // 下跌趋势
return 0; // 盘整
}
/**
* 判断小周期是否在逆转并顺从大周期趋势 - 含目标价位计算
*
* @param smallTimeframeData 小周期K线数据
* @param largeTimeframeData 大周期K线数据
* @param smallTimeframe 小周期类型
* @param largeTimeframe 大周期类型
* @param smallPeriod 小周期均线周期
* @param largePeriod 大周期均线周期
* @returns 增强版逆转信号数据,含目标价位
*/
function detectTrendReversal(
smallTimeframeData: Candle[],
largeTimeframeData: Candle[],
smallTimeframe: string,
largeTimeframe: string,
smallPeriod: number = trendReversalConfig.movingAverage.smallPeriod,
largePeriod: number = trendReversalConfig.movingAverage.largePeriod
): TrendReversalSignal {
// 确定大趋势方向
const largerTrendDirection = determineTrendDirection(
largeTimeframeData,
largePeriod
);
// 如果大趋势不明确,返回无信号
if (largerTrendDirection === 0) {
return {
isReversal: false,
direction: 0,
reversalStrength: 0,
smallTimeframe,
largeTimeframe,
};
}
// 定义时间窗口大小,根据数据量动态调整
const availableDataPoints = smallTimeframeData.length;
// 每个窗口最少需要5根K线,否则可能无法可靠判断趋势
const minWindowSize = trendReversalConfig.window.minWindowSize;
// 根据可用数据量确定窗口大小
const windowSize = Math.min(
trendReversalConfig.window.idealWindowSize,
Math.max(minWindowSize, Math.floor(availableDataPoints / 4))
);
// 确保数据足够计算两个窗口
if (availableDataPoints < windowSize * 2) {
return {
isReversal: false,
direction: 0,
reversalStrength: 0,
smallTimeframe,
largeTimeframe,
};
}
// 计算当前趋势 - 使用最近的数据
const recentData = smallTimeframeData.slice(-windowSize);
const currentSmallTrend = determineTrendDirection(
recentData,
Math.min(smallPeriod, Math.floor(windowSize / 2))
);
// 计算之前的趋势 - 使用再往前的一段数据
const previousData = smallTimeframeData.slice(-windowSize * 2, -windowSize);
const previousSmallTrend = determineTrendDirection(
previousData,
Math.min(smallPeriod, Math.floor(windowSize / 2))
);
// 判断小周期是否从逆向大趋势转向顺向大趋势
const wasCounterTrend =
previousSmallTrend === -largerTrendDirection || previousSmallTrend === 0;
const isNowAligned = currentSmallTrend === largerTrendDirection;
// 获取最近的价格以计算入场价和止损价
const recentCandles = smallTimeframeData.slice(-10);
const currentPrice = smallTimeframeData[smallTimeframeData.length - 1].close;
// 计算建议的入场价和止损价
const entryPrice = currentPrice;
let stopLoss: number;
// 寻找小周期的最近支撑位或阻力位作为止损
if (largerTrendDirection > 0) {
// 做多
// 找最近的低点作为止损
const recentLow = Math.min(...recentCandles.map(c => c.low));
stopLoss = recentLow * (1 - trendReversalConfig.stopLossOffsetPercent.long);
} else {
// 做空
// 找最近的高点作为止损
const recentHigh = Math.max(...recentCandles.map(c => c.high));
stopLoss =
recentHigh * (1 + trendReversalConfig.stopLossOffsetPercent.short);
}
// 计算逆转强度 (0-100)
let reversalStrength = 0;
if (wasCounterTrend && isNowAligned) {
// 检查逆转的强度,使用以下指标:
// 1. 最近K线的价格变化幅度
const priceChangePercent =
Math.abs(
(recentCandles[recentCandles.length - 1].close -
recentCandles[0].close) /
recentCandles[0].close
) * 100;
// 2. 成交量变化
const recentVolumes = recentCandles.slice(0, 5).map(c => c.volume);
const avgRecentVolume =
recentVolumes.length > 0
? recentVolumes.reduce((sum, v) => sum + v, 0) / recentVolumes.length
: 0;
const currentVolume = recentCandles[recentCandles.length - 1].volume;
let volumeIncreasePercent = 0;
if (currentVolume !== 0) {
volumeIncreasePercent =
avgRecentVolume > 0 ? (currentVolume / avgRecentVolume) * 100 - 100 : 0;
}
// 3. 与大周期趋势的一致性
const trendAlignmentScore =
currentSmallTrend === largerTrendDirection ? 30 : 0;
// 4. 计算趋势强度 - 基于趋势的斜率
let trendStrengthScore = 0;
if (recentData.length >= 5) {
const firstPrice = recentData[0].close;
const lastPrice = recentData[recentData.length - 1].close;
const priceChange = Math.abs((lastPrice - firstPrice) / firstPrice) * 100;
trendStrengthScore = Math.min(15, priceChange);
}
// 组合计算强度分数
reversalStrength = Math.min(
100,
Math.max(
0,
Math.min(35, priceChangePercent * 7) +
Math.min(25, volumeIncreasePercent * 0.5) +
trendAlignmentScore +
trendStrengthScore
)
);
// 计算目标价位
const targets = calculateMeasuredMoveTargets(
smallTimeframeData,
windowSize,
largerTrendDirection,
entryPrice,
stopLoss
);
return {
isReversal: true,
direction: largerTrendDirection,
reversalStrength,
smallTimeframe,
largeTimeframe,
entryPrice,
stopLoss,
targets,
};
}
return {
isReversal: false,
direction: largerTrendDirection,
reversalStrength: 0,
smallTimeframe,
largeTimeframe,
};
}
/**
* 增强版多时间周期价格形态分析,仅包含小时对日线的顺势逆转检测
* 适用于波段交易,专注于中短期价格变动
*/
function enhancePatternWithTrendReversal(
baseAnalysis: ComprehensivePatternAnalysis,
weeklyData: Candle[],
dailyData: Candle[],
hourlyData: Candle[]
): EnhancedPatternAnalysis {
// 只检查小时对比日线的逆转
const hourlyVsDailyReversal = detectTrendReversal(
hourlyData,
dailyData,
'1hour',
'daily'
);
const reversalSignals = [hourlyVsDailyReversal].filter(
signal => signal.isReversal
);
let primaryReversalSignal: TrendReversalSignal | undefined;
const enhanced: EnhancedPatternAnalysis = {
...baseAnalysis,
reversalSignals: [],
} as EnhancedPatternAnalysis;
if (reversalSignals.length > 0) {
primaryReversalSignal = reversalSignals[0];
const directionText = primaryReversalSignal.direction > 0 ? '做多' : '做空';
enhanced.description = `${baseAnalysis.description} 检测到小时周期趋势逆转并顺从日线周期趋势,提供${directionText}交易机会,逆转强度: ${primaryReversalSignal.reversalStrength.toFixed(1)}/100。`;
if (primaryReversalSignal.reversalStrength > 50) {
enhanced.signalStrength = Math.min(100, baseAnalysis.signalStrength + 10);
}
}
enhanced.reversalSignals = reversalSignals;
enhanced.primaryReversalSignal = primaryReversalSignal;
return enhanced;
}
function analyzeMultiTimeframePattern(
weeklyData: Candle[],
dailyData: Candle[],
hourlyData: Candle[]
): EnhancedPatternAnalysis {
const baseAnalysis = analyzeMultiTimeframePatterns(
weeklyData,
dailyData,
hourlyData
);
return enhancePatternWithTrendReversal(
baseAnalysis,
weeklyData,
dailyData,
hourlyData
);
}
export {
determineTrendDirection,
detectTrendReversal,
TrendReversalSignal,
EnhancedPatternAnalysis,
enhancePatternWithTrendReversal,
analyzeMultiTimeframePattern,
};