trade-analyzer / src /main.js
Aditya4573's picture
feat: add backtester and enable ranging-regime filter
f43243e
Raw
History Blame Contribute Delete
14.7 kB
/**
* main.js
* Main entry point of the Trade Analyzer application.
* Manages states, coordinates UI components, coordinates WebSocket streams,
* and executes analysis runs.
*/
import DataManager from './data/dataManager.js';
import ChartManager from './chart/chartManager.js';
import Header from './components/header.js';
import SignalPanel from './components/signalPanel.js';
import AnalysisPanel from './components/analysisPanel.js';
import TradeManager from './components/tradeManager.js';
// Import technical analysis tools
import { detectOrderBlocks, updateMitigation } from './analysis/orderBlocks.js';
import { detectZones, updateZoneStatus } from './analysis/supplyDemand.js';
import { detectStructure } from './analysis/marketStructure.js';
import { analyzeTrend } from './analysis/trendDetector.js';
import { rsi, ema, atr, macd, bollingerBands } from './analysis/indicators.js';
import { generateSignals, LIVE_STRATEGY_OPTS } from './analysis/signalGenerator.js';
import { analyzeExit } from './analysis/exitManager.js';
// --- Application States ---
let currentSymbol = 'BTCUSDT';
let currentTimeframe = '15m';
/** @type {Array<{time:number, open:number, high:number, low:number, close:number, volume:number}>} */
let history = [];
let lastTickPrice = null;
const livePrices = {
BTCUSDT: 0,
XAUUSD: 0,
GBPUSD: 0,
USDCAD: 1.38, // approximate fallback
};
// --- Instantiate Core Classes ---
const dataManager = new DataManager();
const chartManager = new ChartManager('chart-container');
// Header
const header = new Header({
onSymbolChange: (symbol) => {
if (symbol !== currentSymbol) {
initSymbolTimeframe(symbol, currentTimeframe);
}
},
onTimeframeChange: (tf) => {
if (tf !== currentTimeframe) {
initSymbolTimeframe(currentSymbol, tf);
}
},
});
// Trade Manager
const tradeManager = new TradeManager({
onDailyLossUpdate: (count) => {
header.updateDailyLosses(count);
// Halt checking disabled - trade anytime setup meets
},
});
// Signal Panel
const signalPanel = new SignalPanel({
onTakeTrade: (signal) => {
if (tradeManager.shouldStopTrading()) {
alert('Trading is halted today. You have reached 3 daily losses.');
return;
}
const trade = tradeManager.takeTrade(signal);
console.log('[Main] Trade taken:', trade);
// Draw the active trade levels on the chart
chartManager.drawSignal(signal);
},
});
// Analysis Panel
const analysisPanel = new AnalysisPanel();
// Set initial daily losses state in header
const initialLosses = tradeManager.getDailyLosses();
header.updateDailyLosses(initialLosses);
// Set initial account summary in header
const initSummary = tradeManager.getAccountSummary();
header.updateAccountSummary(initSummary.balance, initSummary.equity);
// Ensure daily stop overlay remains hidden
const overlay = document.getElementById('daily-stop-overlay');
if (overlay) {
overlay.classList.add('hidden');
}
// Acknowledge stop button listener
const ackBtn = document.getElementById('acknowledge-stop');
if (ackBtn) {
ackBtn.addEventListener('click', () => {
const stopOverlay = document.getElementById('daily-stop-overlay');
if (stopOverlay) stopOverlay.classList.add('hidden');
});
}
// Reset Account button listener
const resetAccountBtn = document.getElementById('btn-reset-account');
if (resetAccountBtn) {
resetAccountBtn.addEventListener('click', async () => {
if (confirm('Are you sure you want to reset your account? This will clear all trade history, active trades, and restore your balance to $5,000.')) {
resetAccountBtn.disabled = true;
const originalText = resetAccountBtn.innerHTML;
resetAccountBtn.innerHTML = '🔄 Resetting...';
try {
await tradeManager.resetAccount();
console.log('[Main] Account reset successful.');
} catch (err) {
console.error('[Main] Account reset failed:', err);
alert('Failed to reset account. Please try again.');
} finally {
resetAccountBtn.disabled = false;
resetAccountBtn.innerHTML = originalText;
}
}
});
}
// --- Subscription / Initialization Flow ---
/**
* Switch the active symbol and timeframe, load history, and subscribe to feeds.
*
* @param {string} symbol
* @param {string} timeframe
*/
async function initSymbolTimeframe(symbol, timeframe) {
try {
currentSymbol = symbol;
currentTimeframe = timeframe;
lastTickPrice = null;
console.log(`[Main] Initializing ${symbol} on ${timeframe}`);
// Unsubscribe existing WebSockets
dataManager.unsubscribeAll();
// Clear chart overlays
chartManager.clearAll();
// Show connecting status
header.setConnectionStatus('connecting');
// Fetch historical candles
const candles = await dataManager.loadHistory(symbol, timeframe);
if (!candles || candles.length === 0) {
throw new Error('No historical data returned');
}
history = candles;
console.log(`[Main] Loaded ${history.length} historical candles.`);
// Live-only engine: trades are opened/closed server-side on live ticks only.
// Set chart base data
chartManager.setData(history, symbol);
// Sync header open price for percentage change
if (history.length > 0) {
const lastCandle = history[history.length - 1];
// Find the candle closest to exactly 24 hours ago (86400 seconds ago) for a true 24h change
const targetTime = Math.floor(Date.now() / 1000) - 86400;
let openCandle = history[0];
let minDiff = Math.abs(openCandle.time - targetTime);
for (const c of history) {
const diff = Math.abs(c.time - targetTime);
if (diff < minDiff) {
minDiff = diff;
openCandle = c;
}
}
header.setOpenPrice(openCandle.close);
header.updatePrice(lastCandle.close, null);
lastTickPrice = lastCandle.close;
livePrices[symbol] = lastCandle.close;
// Immediately calculate active trades' P&L and R:R with the loaded price
tradeManager.updatePrices(lastCandle.close, symbol);
analysisPanel.setLiveRates(livePrices);
}
// Run structural analytics
runAnalysis();
// Update connection status
header.setConnectionStatus('connected');
// Subscribe to live feed
dataManager.subscribeRealtime(symbol, timeframe, {
onTick: (price) => {
if (symbol !== currentSymbol) return; // Stale stream protection
handleTick(price);
},
onCandleUpdate: (candle) => {
if (symbol !== currentSymbol) return; // Stale stream protection
handleCandleUpdate(candle);
},
});
} catch (error) {
console.error(`[Main] Initialization failed for ${symbol} (${timeframe}):`, error);
header.setConnectionStatus('disconnected');
}
}
/**
* Handle real-time price updates.
*
* @param {number} price
*/
function handleTick(price) {
if (price === lastTickPrice) return;
// Update header ticker
header.updatePrice(price, lastTickPrice);
// Feed to trade manager to update active positions
tradeManager.updatePrices(price, currentSymbol);
// If we have active positions, update exit suggestions in real-time
if (tradeManager.activeTrades.length > 0) {
for (const trade of tradeManager.activeTrades) {
if (trade.symbol === currentSymbol) {
// Clone history and replace latest close with live price
const candlesCopy = [...history];
if (candlesCopy.length > 0) {
const idx = candlesCopy.length - 1;
candlesCopy[idx] = {
...candlesCopy[idx],
close: price,
high: Math.max(candlesCopy[idx].high, price),
low: Math.min(candlesCopy[idx].low, price),
};
}
const exitAnalysis = analyzeExit(candlesCopy, trade);
tradeManager.updateExitAnalysis(trade.id, exitAnalysis);
}
}
}
// Update live price caches
livePrices[currentSymbol] = price;
analysisPanel.setLiveRates(livePrices);
// Update account summary display in header in real-time!
const summary = tradeManager.getAccountSummary();
header.updateAccountSummary(summary.balance, summary.equity);
lastTickPrice = price;
}
/**
* Handle streaming kline updates.
*
* @param {object} candle
*/
function handleCandleUpdate(candle) {
const lastIndex = history.findIndex(c => c.time === candle.time);
let isNewCandle = false;
if (lastIndex !== -1) {
// Update current in-progress candle
history[lastIndex] = {
time: candle.time,
open: candle.open,
high: candle.high,
low: candle.low,
close: candle.close,
volume: candle.volume,
};
} else {
// Transition detected! A new candle has started.
// The previous candle in history is now closed and finalized.
history.push({
time: candle.time,
open: candle.open,
high: candle.high,
low: candle.low,
close: candle.close,
volume: candle.volume,
});
if (history.length > 1000) {
history.shift();
}
isNewCandle = true;
}
// Rerender the chart candle
chartManager.updateCandle(candle);
// Recalculate setups if the candle has officially closed, or if a new candle started (transition)
if (candle.isClosed || isNewCandle) {
console.log(`[Main] Candle closed at ${new Date(candle.time * 1000).toLocaleTimeString()}. Recalculating setups.`);
runAnalysis();
}
}
/**
* Run technical indicator formulas and smart money scanning algorithms.
*/
function runAnalysis() {
if (history.length < 50) {
console.warn('[Main] Insufficient historical data to run scanners.');
return;
}
const closes = history.map(c => c.close);
// 1. Indicators
const rsiValues = rsi(closes);
const ema9 = ema(closes, 9);
const ema21 = ema(closes, 21);
const ema50 = ema(closes, 50);
const ema200 = ema(closes, 200);
const atrValues = atr(history);
const macdData = macd(closes);
const bb = bollingerBands(closes);
const indicators = {
rsi: rsiValues,
emas: { 9: ema9, 21: ema21, 50: ema50, 200: ema200 },
atr: atrValues,
macd: macdData,
bollingerBands: bb,
};
// 2. Structural Analysis
const structure = detectStructure(history);
// 3. Smart Money Concepts (Order Blocks)
let orderBlocks = detectOrderBlocks(history);
orderBlocks = updateMitigation(orderBlocks, history);
// 4. Supply & Demand Zones
let zones = detectZones(history);
zones = updateZoneStatus(zones, history);
// 5. Overall Trend
const trend = analyzeTrend(history);
// 6. Signal Scanner
const dailyLossCount = tradeManager.getDailyLosses();
let signals = generateSignals(history, currentSymbol, dailyLossCount, LIVE_STRATEGY_OPTS);
// Expose/Suggest Grade A and B setups (Score >= 4)
signals = signals.filter(sig => ['A', 'B'].includes(sig.quality));
// Do not suggest or trade if there's already an active trade running on this symbol
signals = signals.filter(sig => !tradeManager.activeTrades.some(t => t.symbol === sig.symbol && t.status === 'active'));
// Do not suggest or trade if this symbol is in a 10-minute cooldown after close
signals = signals.filter(sig => !tradeManager.isSymbolCoolingDown(sig.symbol));
// Client-side auto-execution is disabled; server runs scans and autopilot auto-execution 24/7.
// --- Render Chart Overlays ---
const emaDataForChart = {
9: history.map((c, i) => ({ time: c.time, value: ema9[i] })),
21: history.map((c, i) => ({ time: c.time, value: ema21[i] })),
50: history.map((c, i) => ({ time: c.time, value: ema50[i] })),
200: history.map((c, i) => ({ time: c.time, value: ema200[i] })),
};
chartManager.drawEMAs(emaDataForChart);
chartManager.drawOrderBlocks(orderBlocks, history);
chartManager.drawZones(zones);
chartManager.setMarkers(structure, history);
// Draw active trade levels or first high-probability scanned signal overlays on the chart
const activeTrade = tradeManager.activeTrades.find(t => t.symbol === currentSymbol);
if (activeTrade) {
chartManager.drawSignal({
entry: activeTrade.entry,
sl: activeTrade.sl,
tp1: activeTrade.tp1,
tp2: activeTrade.tp2,
symbol: activeTrade.symbol,
});
} else if (signals.length > 0) {
chartManager.drawSignal(signals[0]);
} else {
chartManager._clearDrawnObjects('signalLines');
}
// --- Update UI Panels ---
signalPanel.updateSignals(signals, currentSymbol);
const symbolStats = tradeManager.getSymbolStats();
analysisPanel.updateOverview(trend, structure, orderBlocks, zones, indicators, currentSymbol, symbolStats, tradeManager.tradeHistory, signals);
// Update active positions' suggestions based on newly calculated structure & indicators
for (const trade of tradeManager.activeTrades) {
if (trade.symbol === currentSymbol) {
const exitAnalysis = analyzeExit(history, trade);
tradeManager.updateExitAnalysis(trade.id, exitAnalysis);
}
}
// Update account summary display in header
const summary = tradeManager.getAccountSummary();
header.updateAccountSummary(summary.balance, summary.equity);
}
/// --- State Synchronization from 24/7 Server Autopilot ---
async function syncTerminalState() {
try {
const response = await fetch('/api/terminal-state');
if (!response.ok) throw new Error('API server returned error');
const state = await response.json();
// Synchronize client-side TradeManager properties
tradeManager.activeTrades = state.activeTrades || [];
tradeManager.tradeHistory = state.tradeHistory || [];
tradeManager.accountBalance = state.accountBalance ?? 5000.0;
tradeManager.dailyLosses = state.dailyLosses ?? 0;
tradeManager.lastClosedTime = state.lastClosedTime || {};
tradeManager.lastExecutedCandleTime = state.lastExecutedCandleTime || {};
// Update UI elements
header.updateDailyLosses(tradeManager.dailyLosses);
// Calculate equity and update header summary
const summary = tradeManager.getAccountSummary();
header.updateAccountSummary(summary.balance, summary.equity);
// Re-render trade manager panel card list
tradeManager._render();
} catch (err) {
console.error('[Main] Failed to sync terminal state with server:', err);
}
}
// Make it globally accessible so manual trade triggers can cause immediate syncs
globalThis.syncTerminalState = syncTerminalState;
// --- Initial Startup ---
async function startApp() {
await syncTerminalState(); // Discover Twelve Data status first
initSymbolTimeframe(currentSymbol, currentTimeframe);
}
startApp();
// Synchronize state from 24/7 server every 3 seconds
setInterval(syncTerminalState, 3000);