@gabriel3615/ta_analysis
Version:
stock ta analysis
461 lines (400 loc) • 16.1 kB
text/typescript
import { Candle, PatternResult } from '../../../types.js';
import EnhancedBullishPatterns from '../../../util/EnhancedBullishPatterns.js';
import EnhancedBearishPatterns from '../../../util/EnhancedBearishPatterns.js';
import { PatternDirection } from '../patterns/analyzeMultiTimeframePatterns.js';
/**
* 检测K线形态
* @param candles K线数据
* @param patternCheckingWindow 形态检测窗口大小
* @returns 看涨和看跌形态的结果
*/
export const detectBullOrBear = (
candles: Candle[],
patternCheckingWindow: number = 5
): {
bullishPatterns: PatternResult[];
bearishPatterns: PatternResult[];
} => {
const bullishPatterns: PatternResult[] = [];
const bearishPatterns: PatternResult[] = [];
for (let i = patternCheckingWindow; i < candles.length; i++) {
const currentWindowSize = Math.min(
patternCheckingWindow,
candles.length - i
);
// i+1 为了包含当前K线
const windowCandles = candles.slice(i - currentWindowSize, i + 1);
// 获取最新K线的信息
const currentCandle = windowCandles[windowCandles.length - 1];
const currentDate = currentCandle.timestamp;
const currentClose = currentCandle.close;
// 检查看涨形态
const bullishPatternNames = new EnhancedBullishPatterns().hasPattern(
windowCandles
);
if (bullishPatternNames.length > 0) {
// 计算形态强度
const patternStrength = calculatePatternStrength(
windowCandles,
PatternDirection.Bullish
);
bullishPatterns.push({
date: currentDate,
patternType: PatternDirection.Bullish,
priceLevel: currentClose,
strength: patternStrength,
patternNames: bullishPatternNames,
});
}
// 检查看跌形态
const bearishPatternNames = new EnhancedBearishPatterns().hasPattern(
windowCandles
);
if (bearishPatternNames.length > 0) {
// 计算形态强度
const patternStrength = calculatePatternStrength(
windowCandles,
PatternDirection.Bearish
);
bearishPatterns.push({
date: currentDate,
patternType: PatternDirection.Bearish,
priceLevel: currentClose,
strength: patternStrength,
patternNames: bearishPatternNames,
});
}
}
return {
bullishPatterns,
bearishPatterns,
};
};
/**
* 计算K线形态的强度
* @param candles K线数组
* @param patternType 形态类型
* @returns 强度值 (0-100)
*/
function calculatePatternStrength(
candles: Candle[],
patternType: PatternDirection
): number {
const lastCandle = candles[candles.length - 1];
const lastBody = Math.abs(lastCandle.close - lastCandle.open);
const lastRange = lastCandle.high - lastCandle.low;
// 基础强度
let strength = 50;
// 考虑实体大小相对于波动范围 (实体越大,信号越强)
const bodyToRangeRatio = lastBody / lastRange;
if (bodyToRangeRatio > 0.7) {
strength += 20;
} else if (bodyToRangeRatio > 0.5) {
strength += 10;
}
// 考虑成交量因素 (成交量越大,信号越强)
const volumes = candles.map(c => c.volume);
const avgVolume =
volumes.slice(0, volumes.length - 1).reduce((a, b) => a + b, 0) /
(volumes.length - 1);
const lastVolume = lastCandle.volume;
if (lastVolume > avgVolume * 2) {
strength += 30;
} else if (lastVolume > avgVolume * 1.5) {
strength += 20;
} else if (lastVolume > avgVolume) {
strength += 10;
}
// 看涨形态:价格收于上方,看跌形态:价格收于下方
if (
patternType === PatternDirection.Bullish &&
lastCandle.close > lastCandle.open
) {
strength += 10;
} else if (
patternType === PatternDirection.Bearish &&
lastCandle.close < lastCandle.open
) {
strength += 10;
}
// 确保强度在 0-100 范围内
return Math.min(100, Math.max(0, strength));
}
/**
* 检查最近几天是否出现看涨或看跌形态
* @param candles K线数据
* @param daysBefore 检查多少天以内
* @param minStrength 最小强度要求 (0-100)
* @returns 过滤后的结果
*/
export const checkBullOrBearRecently = (
candles: Candle[],
daysBefore: number = 5,
minStrength: number = 60
) => {
if (candles.length === 0) {
return {
bullishDatesWithinLast5Days: [],
bearishDatesWithinLast5Days: [],
bullishPatternsDetails: [],
bearishPatternsDetails: [],
};
}
const { bullishPatterns, bearishPatterns } = detectBullOrBear(candles);
// 计算日期范围
const today = new Date();
const cutoffDate = new Date();
cutoffDate.setDate(today.getDate() - daysBefore);
// 筛选出符合条件的模式
const recentBullishPatterns = bullishPatterns.filter(
pattern =>
pattern.date >= cutoffDate &&
pattern.date <= today &&
pattern.strength >= minStrength
);
const recentBearishPatterns = bearishPatterns.filter(
pattern =>
pattern.date >= cutoffDate &&
pattern.date <= today &&
pattern.strength >= minStrength
);
// 为了与原有接口兼容
return {
bullishDatesWithinLast5Days: recentBullishPatterns.map(p => p.date),
bearishDatesWithinLast5Days: recentBearishPatterns.map(p => p.date),
// 添加详细信息供策略分析使用
bullishPatternsDetails: recentBullishPatterns,
bearishPatternsDetails: recentBearishPatterns,
};
};
/**
* 多时间周期确认(日,周)
* @param dailyCandles 日K线数据
* @param weeklyCandles 周K线数据
* @returns 多周期确认结果
*/
export const multiTimeframeConfirmation = async (
dailyCandles: Candle[],
weeklyCandles: Candle[]
) => {
// 检测形态
const dailyResult = checkBullOrBearRecently(dailyCandles, 5, 60);
// 周K线应该检查更长的周期,比如8-12周,而小时K线可以检查2天
const weeklyResult = checkBullOrBearRecently(
weeklyCandles,
5 * 5, // 5周
60
);
// 检查两个时间周期是否有相同方向的信号
const hasBullishConfirmation =
dailyResult.bullishDatesWithinLast5Days.length > 0 &&
weeklyResult.bullishDatesWithinLast5Days.length > 0;
const hasBearishConfirmation =
dailyResult.bearishDatesWithinLast5Days.length > 0 &&
weeklyResult.bearishDatesWithinLast5Days.length > 0;
return {
hasBullishConfirmation,
hasBearishConfirmation,
dailyBullishSignals: dailyResult.bullishDatesWithinLast5Days,
dailyBearishSignals: dailyResult.bearishDatesWithinLast5Days,
weeklyBullishSignals: weeklyResult.bullishDatesWithinLast5Days,
weeklyBearishSignals: weeklyResult.bearishDatesWithinLast5Days,
dailyResult,
weeklyResult,
};
};
/**
* 根据多时间周期信号生成交易建议
* @param symbol 股票代码
* @param dailyCandles
* @param weeklyCandles
* @returns 交易建议,包括方向、入场价和止损价
*/
export const generateTradeRecommendation = async (
symbol: string,
dailyCandles: Candle[],
weeklyCandles: Candle[]
) => {
// 使用周线和日线进行多时间周期确认
const mtfResult = await multiTimeframeConfirmation(
dailyCandles,
weeklyCandles
);
const latestCandle = dailyCandles[dailyCandles.length - 1];
const currentPrice = latestCandle.close;
// 默认没有信号
const result = {
symbol,
hasSignal: false,
direction: PatternDirection.Neutral,
signalStrength: 0,
currentPrice,
entryPrice: null as number | null,
stopLossPrice: null as number | null,
takeProfitPrice: null as number | null,
signalDate: new Date(),
reasoning: '',
dailySignals: {
bullish: mtfResult.dailyBullishSignals,
bearish: mtfResult.dailyBearishSignals,
bullishDetails: mtfResult.dailyResult.bullishPatternsDetails,
bearishDetails: mtfResult.dailyResult.bearishPatternsDetails,
},
weeklySignals: {
bullish: mtfResult.weeklyBullishSignals,
bearish: mtfResult.weeklyBearishSignals,
bullishDetails: mtfResult.weeklyResult.bullishPatternsDetails,
bearishDetails: mtfResult.weeklyResult.bearishPatternsDetails,
},
};
// 计算平均信号强度
let avgBullishStrength = 0;
let avgBearishStrength = 0;
let bullishSignalsCount = 0;
let bearishSignalsCount = 0;
// 计算日线信号的平均强度
if (
mtfResult.dailyResult.bullishPatternsDetails &&
mtfResult.dailyResult.bullishPatternsDetails.length > 0
) {
avgBullishStrength += mtfResult.dailyResult.bullishPatternsDetails.reduce(
(acc, pattern) => acc + pattern.strength,
0
);
bullishSignalsCount += mtfResult.dailyResult.bullishPatternsDetails.length;
}
if (
mtfResult.dailyResult.bearishPatternsDetails &&
mtfResult.dailyResult.bearishPatternsDetails.length > 0
) {
avgBearishStrength += mtfResult.dailyResult.bearishPatternsDetails.reduce(
(acc, pattern) => acc + pattern.strength,
0
);
bearishSignalsCount += mtfResult.dailyResult.bearishPatternsDetails.length;
}
// 计算周线信号的平均强度 (周线信号权重更高)
if (
mtfResult.weeklyResult.bullishPatternsDetails &&
mtfResult.weeklyResult.bullishPatternsDetails.length > 0
) {
avgBullishStrength += mtfResult.weeklyResult.bullishPatternsDetails.reduce(
(acc, pattern) => acc + pattern.strength * 1.5,
0
); // 周线权重1.5倍
bullishSignalsCount += mtfResult.weeklyResult.bullishPatternsDetails.length;
}
if (
mtfResult.weeklyResult.bearishPatternsDetails &&
mtfResult.weeklyResult.bearishPatternsDetails.length > 0
) {
avgBearishStrength += mtfResult.weeklyResult.bearishPatternsDetails.reduce(
(acc, pattern) => acc + pattern.strength * 1.5,
0
); // 周线权重1.5倍
bearishSignalsCount += mtfResult.weeklyResult.bearishPatternsDetails.length;
}
// 计算最终的平均强度
if (bullishSignalsCount > 0) {
avgBullishStrength = avgBullishStrength / bullishSignalsCount;
}
if (bearishSignalsCount > 0) {
avgBearishStrength = avgBearishStrength / bearishSignalsCount;
}
// 根据多时间周期确认结果生成建议
if (mtfResult.hasBullishConfirmation && !mtfResult.hasBearishConfirmation) {
// 看涨信号
result.hasSignal = true;
result.direction = PatternDirection.Bullish;
result.signalStrength = avgBullishStrength;
// 计算建议的入场价、止损价和目标价
// 入场价: 建议在当前价格上方0.5%设置买入限价单,或在当前价格直接市价买入
result.entryPrice = Math.round(currentPrice * 100) / 100; // 四舍五入到小数点后两位
// 止损价: 根据信号强度和最近的支撑位确定
// 从最近5根K线中找出最低点作为支撑位参考
const recentLows = dailyCandles.slice(-5).map(c => c.low);
const supportLevel = Math.min(...recentLows);
// 根据信号强度调整止损幅度(强度越高止损越宽松)
const stopLossPercentage = 0.02 + (1 - avgBullishStrength / 100) * 0.03; // 2%-5%的止损
result.stopLossPrice =
Math.round(currentPrice * (1 - stopLossPercentage) * 100) / 100;
// 如果计算的止损价低于支撑位,则使用支撑位作为止损
if (result.stopLossPrice < supportLevel) {
result.stopLossPrice = Math.round(supportLevel * 100) / 100;
}
// 目标价: 风险回报比至少为1:2
const riskAmount = currentPrice - result.stopLossPrice;
result.takeProfitPrice =
Math.round((currentPrice + riskAmount * 2) * 100) / 100;
result.reasoning = `多时间周期信号确认:日线和周线同时出现看涨信号,平均信号强度为${avgBullishStrength.toFixed(2)}。建议买入${symbol},入场价${result.entryPrice},止损价${result.stopLossPrice},目标价${result.takeProfitPrice}。风险回报比为1:2。`;
} else if (
mtfResult.hasBearishConfirmation &&
!mtfResult.hasBullishConfirmation
) {
// 看跌信号
result.hasSignal = true;
result.direction = PatternDirection.Bearish;
result.signalStrength = avgBearishStrength;
// 计算建议的入场价、止损价和目标价
// 入场价: 建议在当前价格下方0.5%设置卖出限价单,或在当前价格直接市价卖出
result.entryPrice = Math.round(currentPrice * 100) / 100; // 四舍五入到小数点后两位
// 止损价: 根据信号强度和最近的阻力位确定
// 从最近5根K线中找出最高点作为阻力位参考
const recentHighs = dailyCandles.slice(-5).map(c => c.high);
const resistanceLevel = Math.max(...recentHighs);
// 根据信号强度调整止损幅度(强度越高止损越宽松)
const stopLossPercentage = 0.02 + (1 - avgBearishStrength / 100) * 0.03; // 2%-5%的止损
result.stopLossPrice =
Math.round(currentPrice * (1 + stopLossPercentage) * 100) / 100;
// 如果计算的止损价高于阻力位,则使用阻力位作为止损
if (result.stopLossPrice > resistanceLevel) {
result.stopLossPrice = Math.round(resistanceLevel * 100) / 100;
}
// 目标价: 风险回报比至少为1:2
const riskAmount = result.stopLossPrice - currentPrice;
result.takeProfitPrice =
Math.round((currentPrice - riskAmount * 2) * 100) / 100;
result.reasoning = `多时间周期信号确认:日线和周线同时出现看跌信号,平均信号强度为${avgBearishStrength.toFixed(2)}。建议卖出${symbol},入场价${result.entryPrice},止损价${result.stopLossPrice},目标价${result.takeProfitPrice}。风险回报比为1:2。`;
} else if (
mtfResult.hasBullishConfirmation &&
mtfResult.hasBearishConfirmation
) {
// 出现混合信号,比较信号强度决定方向
result.hasSignal = true;
if (avgBullishStrength > avgBearishStrength) {
// 看涨信号更强
result.direction = PatternDirection.Bullish;
result.signalStrength = avgBullishStrength - avgBearishStrength; // 信号强度为两者差值
// 入场价、止损价计算(相对保守)
result.entryPrice = Math.round(currentPrice * 100) / 100;
// 更保守的止损(由于存在混合信号)
const recentLows = dailyCandles.slice(-7).map(c => c.low);
const supportLevel = Math.min(...recentLows);
result.stopLossPrice = Math.round(supportLevel * 100) / 100;
// 保守的目标价设置
const riskAmount = currentPrice - result.stopLossPrice;
result.takeProfitPrice =
Math.round((currentPrice + riskAmount * 1.5) * 100) / 100;
result.reasoning = `出现混合信号,但看涨信号强度(${avgBullishStrength.toFixed(2)})高于看跌信号强度(${avgBearishStrength.toFixed(2)})。建议谨慎买入${symbol},入场价${result.entryPrice},止损价${result.stopLossPrice},目标价${result.takeProfitPrice}。`;
} else {
// 看跌信号更强
result.direction = PatternDirection.Bearish;
result.signalStrength = avgBearishStrength - avgBullishStrength; // 信号强度为两者差值
// 入场价、止损价计算(相对保守)
result.entryPrice = Math.round(currentPrice * 100) / 100;
// 更保守的止损(由于存在混合信号)
const recentHighs = dailyCandles.slice(-7).map(c => c.high);
const resistanceLevel = Math.max(...recentHighs);
result.stopLossPrice = Math.round(resistanceLevel * 100) / 100;
// 保守的目标价设置
const riskAmount = result.stopLossPrice - currentPrice;
result.takeProfitPrice =
Math.round((currentPrice - riskAmount * 1.5) * 100) / 100;
result.reasoning = `出现混合信号,但看跌信号强度(${avgBearishStrength.toFixed(2)})高于看涨信号强度(${avgBullishStrength.toFixed(2)})。建议谨慎卖出${symbol},入场价${result.entryPrice},止损价${result.stopLossPrice},目标价${result.takeProfitPrice}。`;
}
} else {
// 无明确信号
result.reasoning = `没有明确的交易信号。周线和日线没有同时出现看涨或看跌信号。建议观望,等待明确的多时间周期确认信号出现。`;
}
return result;
};