@gabriel3615/ta_analysis
Version:
stock ta analysis
109 lines (108 loc) • 6.01 kB
JavaScript
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
}
},
};
}