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);