@gabriel3615/ta_analysis
Version:
stock ta analysis
203 lines (202 loc) • 10.1 kB
JavaScript
import { backtestStrategiesConfig } from '../strategyConfig.js';
import { analyzeTrendlinesAndChannels } from '../../analyzer/trendline/trendlineDetector.js';
export function TrendlineBreakoutStrategy(symbol, timeframe) {
// 追踪先前的信号,用于减少信号频率和避免连续相反信号
let lastSignalIndex = -1;
let lastSignalDirection = undefined;
// 追踪趋势强度
let trendStrengthCount = 0;
return {
name: 'TrendlineAdaptiveStrategy',
generateSignal(history, i) {
const cfg = backtestStrategiesConfig.trendline;
if (i < cfg.minLookbackBars)
return null;
// 避免频繁信号
if (i - lastSignalIndex < cfg.coolDownMinGap)
return null;
const window = history.slice(Math.max(0, i - 200), i + 1);
const res = analyzeTrendlinesAndChannels(symbol, window, timeframe);
// 如果没有有效通道,不生成信号
if (!res.channel)
return null;
const candle = history[i];
const prevCandle = i > 0 ? history[i - 1] : null;
const lastIndex = window.length - 1;
// 计算价格在通道中的相对位置 (0 = 下边界, 1 = 上边界)
const upperNow = res.channel.upper.slope * lastIndex + res.channel.upper.intercept;
const lowerNow = res.channel.lower.slope * lastIndex + res.channel.lower.intercept;
const midNow = res.channel.mid.slope * lastIndex + res.channel.mid.intercept;
const channelHeight = upperNow - lowerNow;
// 避免除以零
if (channelHeight <= 0)
return null;
const relativePosition = (candle.close - lowerNow) / channelHeight;
// 定义价格区域位置
const isNearLower = relativePosition < 0.25; // 更严格的底部区域
const isNearUpper = relativePosition > 0.75; // 更严格的顶部区域
const isInMiddle = !isNearLower && !isNearUpper;
// 获取通道斜率和方向
const channelSlope = res.channel.slope;
const isRisingChannel = channelSlope > cfg.risingChannelSlopeMin;
const isFallingChannel = channelSlope < cfg.fallingChannelSlopeMax;
// 计算动量指标 - 使用收盘价的20日变化率
const momentumPeriod = cfg.momentumPeriod;
const momentumIndex = Math.max(0, i - momentumPeriod);
const priceChange = (candle.close - history[momentumIndex].close) /
history[momentumIndex].close;
const strongUpMomentum = priceChange > cfg.momentumUpThreshold;
const strongDownMomentum = priceChange < cfg.momentumDownThreshold;
// 计算交易量趋势
const volPeriod = cfg.volumePeriod;
let avgVol = 0;
for (let j = i - volPeriod + 1; j <= i; j++) {
if (j >= 0)
avgVol += history[j].volume;
}
avgVol /= volPeriod;
const highVolume = candle.volume > avgVol * cfg.highVolumeFactor;
// 强势趋势确认 - 连续的价格方向
if (prevCandle && candle.close > prevCandle.close) {
trendStrengthCount =
trendStrengthCount > 0 ? trendStrengthCount + 1 : 1;
}
else if (prevCandle && candle.close < prevCandle.close) {
trendStrengthCount =
trendStrengthCount < 0 ? trendStrengthCount - 1 : -1;
}
const isStrongUptrend = trendStrengthCount >= cfg.trendStrengthConsecutive;
const isStrongDowntrend = trendStrengthCount <= -cfg.trendStrengthConsecutive;
let signal = null;
// 基于通道斜率、价格位置和动量的策略逻辑
if (isRisingChannel) {
// 上升通道策略
if (isNearLower && (strongUpMomentum || isStrongUptrend)) {
// 上升通道底部且具有强上升动量 - 做多信号
signal = {
timestamp: history[i].timestamp,
direction: 'long',
strength: 85 + Math.min(15, Math.abs(channelSlope) * 10000),
reason: '上升通道底部+强动量支撑',
};
}
else if (isNearUpper &&
!strongUpMomentum &&
res.breakoutRetest &&
res.breakoutRetest.direction === 'up') {
// 上升通道顶部、无强上升动量且有向上突破后回踩 - 做空信号 (通道过充反转)
signal = {
timestamp: history[i].timestamp,
direction: 'short',
strength: 60 + Math.min(20, Math.abs(channelSlope) * 10000),
reason: '上升通道顶部过充区域',
};
}
}
else if (isFallingChannel) {
// 下降通道策略
if (isNearUpper && (strongDownMomentum || isStrongDowntrend)) {
// 下降通道顶部且具有强下降动量 - 做空信号
signal = {
timestamp: history[i].timestamp,
direction: 'short',
strength: 85 + Math.min(15, Math.abs(channelSlope) * 10000),
reason: '下降通道顶部+强动量压力',
};
}
else if (isNearLower &&
!strongDownMomentum &&
res.breakoutRetest &&
res.breakoutRetest.direction === 'down') {
// 下降通道底部、无强下降动量且有向下突破后回踩 - 做多信号 (通道过充反转)
signal = {
timestamp: history[i].timestamp,
direction: 'long',
strength: 60 + Math.min(20, Math.abs(channelSlope) * 10000),
reason: '下降通道底部过充区域',
};
}
}
else {
// 水平通道策略 - 增加额外的筛选条件
if (isNearLower && (candle.close > candle.open || highVolume)) {
signal = {
timestamp: history[i].timestamp,
direction: 'long',
strength: 75,
reason: '水平通道底部支撑+成交量确认',
};
}
else if (isNearUpper && (candle.close < candle.open || highVolume)) {
signal = {
timestamp: history[i].timestamp,
direction: 'short',
strength: 75,
reason: '水平通道顶部压力+成交量确认',
};
}
}
// 避免在强势趋势中做反向交易
if (signal) {
if ((strongUpMomentum && signal.direction === 'short') ||
(strongDownMomentum && signal.direction === 'long')) {
// 不与强动量作对
if (Math.abs(priceChange) > cfg.strongMomentumCancelAbs) {
// 15%以上的超强动量
signal = null; // 取消信号
}
else {
// 降低信号强度
signal.strength = Math.max(50, signal.strength - 20);
signal.reason += ' (注意:与强动量相反)';
}
}
}
// 如果没有根据位置生成信号,但有高质量的突破回踩信号,则考虑使用
if (!signal &&
res.breakoutRetest &&
res.breakoutRetest.qualityScore > 80) {
// 重要调整:强动量环境下,不反转突破方向
const shouldReverseSignal = !((res.breakoutRetest.direction === 'up' && strongUpMomentum) ||
(res.breakoutRetest.direction === 'down' && strongDownMomentum));
if (shouldReverseSignal) {
// 通常情况下反转信号方向
signal = {
timestamp: history[i].timestamp,
direction: res.breakoutRetest.direction === 'up' ? 'short' : 'long',
strength: res.breakoutRetest.qualityScore,
reason: 'Trendline breakout + retest fakeout reversal',
};
}
else {
// 在强动量环境下,保持突破方向一致
signal = {
timestamp: history[i].timestamp,
direction: res.breakoutRetest.direction === 'up' ? 'long' : 'short',
strength: res.breakoutRetest.qualityScore,
reason: 'Trendline breakout + retest continuation (strong momentum)',
};
}
}
// 避免连续相反方向的信号
if (signal &&
lastSignalDirection &&
signal.direction !== lastSignalDirection &&
i - lastSignalIndex < cfg.reverseMinGap) {
// 如果信号不够强,就取消它
if (signal.strength < cfg.reverseSignalStrengthMin) {
signal = null;
}
else {
signal.reason += ' (方向反转:高置信度)';
}
}
// 更新信号追踪
if (signal) {
lastSignalIndex = i;
lastSignalDirection = signal.direction;
}
return signal;
},
};
}