UNPKG

@gabriel3615/ta_analysis

Version:

stock ta analysis

109 lines (108 loc) 6.01 kB
import { IntegratedOrchestrator } from '../../integration/IntegratedOrchestrator.js'; import { analyzeRange } from '../../analyzer/range/rangeDetector.js'; import { analyzeMarketStructure } from '../../analyzer/structure/structureDetector.js'; import { backtestStrategiesConfig } from '../strategyConfig.js'; import { aggregateDailyToWeekly } from '../../../util/util.js'; import { TradeDirection } from '../../../types.js'; /** * Strategy that queries the IntegratedOrchestrator on each step using the available history. * This version is corrected to use an offline analysis method to prevent lookahead bias. */ export function IntegrationSignalStrategy(symbol, timeframe, // This strategy is designed to work with daily data slices config) { // Instantiate the orchestrator once to be reused const orchestrator = new IntegratedOrchestrator(config); return { name: `IntegrationSignalStrategy_${timeframe}`, async generateSignal(history, i) { // We need a sufficient amount of data to perform meaningful analysis if (history.length < 60) { return null; // Not enough data yet } // The history slice represents the daily data available at step `i` const dailyData = history; // Aggregate daily data to weekly data for multi-timeframe analysis const weeklyData = aggregateDailyToWeekly(dailyData); // Hourly data cannot be derived from daily data, so we pass an empty array. // The orchestrator's fallback mechanism will handle missing hourly analysis. const hourlyData = []; try { // Execute the offline analysis to avoid lookahead bias const result = await orchestrator.executeOfflineAnalysis(symbol, dailyData, weeklyData, hourlyData, config); const direction = result.tradePlan.direction; const currentPrice = dailyData[dailyData.length - 1].close; // 额外过滤:要求 Range/Structure 同向确认(可配置) const filterCfg = backtestStrategiesConfig.integrationFilter; // 冷静期:限制最近 N 根内的重复信号(以生成信号的索引记忆,集成在闭包) // 由于本策略无 lastSignalIndex 状态,改为用最近N根内共现判断即可。 if (filterCfg.requireRangeConfirm || filterCfg.requireStructureConfirm) { const recentWindow = dailyData.slice(-Math.max(200, filterCfg.confirmWithinBars + 50)); let rangeOk = true; let structureOk = true; if (filterCfg.requireRangeConfirm) { const rg = analyzeRange(symbol, recentWindow, 'daily'); if (rg.breakout) { const wantUp = direction === 'long'; const breakoutUp = rg.breakout.direction === 'up'; const dirMatch = (wantUp && breakoutUp) || (!wantUp && !breakoutUp); const within = recentWindow.length - 1 - rg.breakout.breakoutIndex <= filterCfg.confirmWithinBars; rangeOk = dirMatch && within && (rg.breakout.qualityScore ?? 0) >= filterCfg.rangeMinQuality; } else { rangeOk = false; } } if (filterCfg.requireStructureConfirm) { const st = analyzeMarketStructure(recentWindow, 'daily'); if (st.lastEvent && (st.lastEvent.direction === 'bullish' || st.lastEvent.direction === 'bearish') && (filterCfg.structureEventType === 'any' || st.lastEvent.type === filterCfg.structureEventType)) { const wantUp = direction === 'long'; const evtUp = st.lastEvent.direction === 'bullish'; const evtMatch = (wantUp && evtUp) || (!wantUp && !evtUp); const within = recentWindow.length - 1 - st.lastEvent.index <= filterCfg.confirmWithinBars; structureOk = evtMatch && within; } else { structureOk = false; } } if (!(rangeOk && structureOk)) { return null; // 不满足确认条件则不发信号 } } // Create a signal based on the trade plan's direction const signal = { timestamp: new Date(history[i].timestamp), direction: 'flat', // Default to flat reason: result.tradePlan.summary, entry: currentPrice, stop: result.tradePlan.exitStrategy.stopLossLevels[0]?.price, targets: result.tradePlan.exitStrategy.takeProfitLevels.map(l => l.price), }; if (direction === TradeDirection.Long) { signal.direction = 'long'; } else if (direction === TradeDirection.Short) { signal.direction = 'short'; } else { signal.direction = 'flat'; } return signal; } catch (error) { console.error(`Error in IntegrationSignalStrategy at step ${i}:`, error); return null; // Return no signal on error } }, }; }