@gabriel3615/ta_analysis
Version:
stock ta analysis
75 lines (74 loc) • 3.74 kB
JavaScript
import { backtestStrategiesConfig } from '../strategyConfig.js';
import { analyzeMultiTimeframePatterns, PatternDirection, } from '../../basic/patterns/analyzeMultiTimeframePatterns.js';
export function PatternConsensusStrategy(symbol, timeframe, params = {}) {
const { minSignalStrength = backtestStrategiesConfig.patternConsensus
.minSignalStrength, requireAllAligned = backtestStrategiesConfig.patternConsensus
.requireAllAligned, coolDownBars = backtestStrategiesConfig.patternConsensus.coolDownBars, lookback = backtestStrategiesConfig.patternConsensus.lookback, } = params;
let lastSignalIndex = -1;
let lastDirection = undefined;
return {
name: 'PatternConsensus',
generateSignal(history, i) {
if (i < Math.min(120, lookback / 2))
return null;
if (i - lastSignalIndex < coolDownBars)
return null;
const dailyData = history.slice(Math.max(0, i - lookback + 1), i + 1);
// 近似聚合周线:每5天取一根(已存在 util.aggregateDailyToWeekly 但此处只需近似)
const weeklyData = [];
for (let j = 0; j < dailyData.length; j += 5) {
const slice = dailyData.slice(j, Math.min(j + 5, dailyData.length));
if (!slice.length)
continue;
weeklyData.push({
symbol: slice[0].symbol,
open: slice[0].open,
high: Math.max(...slice.map(c => c.high)),
low: Math.min(...slice.map(c => c.low)),
close: slice[slice.length - 1].close,
volume: slice.reduce((s, c) => s + c.volume, 0),
timestamp: slice[slice.length - 1].timestamp,
});
}
// 小时数据缺失时传空数组,内部不会报错
const hourlyData = [];
const analysis = analyzeMultiTimeframePatterns(weeklyData, dailyData, hourlyData);
if (analysis.signalStrength < minSignalStrength)
return null;
const tfa = analysis.timeframeAnalyses;
const weeklySig = tfa.find(a => a.timeframe === 'weekly')?.patternSignal;
const dailySig = tfa.find(a => a.timeframe === 'daily')?.patternSignal;
const hourlySig = tfa.find(a => a.timeframe === '1hour')?.patternSignal;
const allAligned = weeklySig &&
dailySig &&
hourlySig &&
weeklySig === dailySig &&
dailySig === hourlySig;
if (requireAllAligned && !allAligned)
return null;
let direction = 'flat';
if (analysis.combinedSignal === PatternDirection.Bullish)
direction = 'long';
else if (analysis.combinedSignal === PatternDirection.Bearish)
direction = 'short';
if (direction === 'flat')
return null;
const signal = {
timestamp: history[i].timestamp,
direction,
strength: Math.min(95, Math.max(60, analysis.signalStrength)),
reason: `形态共识(${requireAllAligned ? '三周期一致' : '综合偏向'}) ${analysis.description}`,
};
// 避免短时间内方向反转噪声
if (lastDirection &&
signal.direction !== lastDirection &&
i - lastSignalIndex < coolDownBars * 2 &&
(signal.strength ?? 0) < 80) {
return null;
}
lastSignalIndex = i;
lastDirection = signal.direction;
return signal;
},
};
}