Spaces:
Running
Running
File size: 14,740 Bytes
66b7a93 f43243e 66b7a93 2d6848e 66b7a93 01369a3 66b7a93 8497958 66b7a93 5bbdd04 66b7a93 cb4f623 66b7a93 cb4f623 66b7a93 cb4f623 66b7a93 cb4f623 66b7a93 f43243e 66b7a93 d3a66fe 66b7a93 cb4f623 66b7a93 ba1d9b3 66b7a93 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 | /**
* 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);
|