Aditya4573 commited on
Commit
7a6768e
·
1 Parent(s): 2d6848e

Remove unprofitable 15m timeframe symbols (ETHUSDT & EURUSD) from the entire application

Browse files
scratch/analyzeETH.js ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // scratch/analyzeETH.js
2
+ import { fetchCandles } from '../src/data/yahooFinanceAPI.js';
3
+ import { analyzeTrend } from '../src/analysis/trendDetector.js';
4
+ import { detectStructure } from '../src/analysis/marketStructure.js';
5
+ import { detectOrderBlocks, updateMitigation } from '../src/analysis/orderBlocks.js';
6
+ import { detectZones, updateZoneStatus } from '../src/analysis/supplyDemand.js';
7
+ import { rsi, ema, macd } from '../src/analysis/indicators.js';
8
+ import { analyzeExit } from '../src/analysis/exitManager.js';
9
+
10
+ // Polyfill fetch exactly like server.js
11
+ const originalFetch = globalThis.fetch;
12
+ globalThis.fetch = async (input, init) => {
13
+ let url = typeof input === 'string' ? input : input.url;
14
+
15
+ if (url.startsWith('/api/swissquote')) {
16
+ url = 'https://forex-data-feed.swissquote.com/public-quotes/bboquotes/instrument/XAU/USD';
17
+ } else if (url.startsWith('/api/yahoo')) {
18
+ url = 'https://query1.finance.yahoo.com' + url.replace(/^\/api\/yahoo/, '');
19
+ init = init || {};
20
+ init.headers = {
21
+ ...init.headers,
22
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
23
+ 'Origin': 'https://finance.yahoo.com',
24
+ 'Referer': 'https://finance.yahoo.com',
25
+ };
26
+ }
27
+
28
+ return originalFetch(url, init);
29
+ };
30
+
31
+ async function run() {
32
+ console.log('=== ETHUSDT Live Analysis Dashboard ===\n');
33
+ try {
34
+ const symbol = 'ETHUSDT';
35
+ const candles = await fetchCandles(symbol, '15m');
36
+ console.log(`[Market Data] Loaded ${candles.length} candles from Yahoo Finance.`);
37
+
38
+ if (candles.length === 0) {
39
+ console.error('No market data loaded.');
40
+ return;
41
+ }
42
+
43
+ const latest = candles[candles.length - 1];
44
+ console.log(`Current Price: $${latest.close.toFixed(2)} | Time: ${new Date(latest.time * 1000).toLocaleString()}`);
45
+
46
+ // 1. Indicators
47
+ const closes = candles.map(c => c.close);
48
+ const rsiVals = rsi(closes);
49
+ const ema9 = ema(closes, 9);
50
+ const ema21 = ema(closes, 21);
51
+ const ema50 = ema(closes, 50);
52
+ const ema200 = ema(closes, 200);
53
+ const macdData = macd(closes);
54
+
55
+ const lastRSI = rsiVals[rsiVals.length - 1];
56
+ const lastE9 = ema9[ema9.length - 1];
57
+ const lastE21 = ema21[ema21.length - 1];
58
+ const lastE50 = ema50[ema50.length - 1];
59
+ const lastE200 = ema200[ema200.length - 1];
60
+
61
+ console.log('\n📊 Indicators:');
62
+ console.log(`- RSI (14): ${lastRSI.toFixed(2)} (${lastRSI > 70 ? 'Overbought' : lastRSI < 30 ? 'Oversold' : 'Neutral'})`);
63
+ console.log(`- EMA 9: $${lastE9.toFixed(2)}`);
64
+ console.log(`- EMA 21: $${lastE21.toFixed(2)}`);
65
+ console.log(`- EMA 50: $${lastE50.toFixed(2)}`);
66
+ console.log(`- EMA 200: $${lastE200.toFixed(2)}`);
67
+
68
+ // 2. Trend & Structure
69
+ const trend = analyzeTrend(candles);
70
+ const structure = detectStructure(candles);
71
+ console.log('\n📈 Trend & Market Structure:');
72
+ console.log(`- Direction: ${trend.direction.toUpperCase()} (Strength: ${trend.strength}/100)`);
73
+ console.log(`- Structure Trend: ${structure.trend}`);
74
+ console.log(`- EMA Alignment: ${trend.emaAlignment}`);
75
+ console.log(`- Momentum: ${trend.momentum}`);
76
+
77
+ // 3. SMC Areas
78
+ let obs = detectOrderBlocks(candles);
79
+ obs = updateMitigation(obs, candles);
80
+ const activeOBs = obs.filter(o => !o.mitigated);
81
+ console.log(`- Active Order Blocks: ${activeOBs.length} detected.`);
82
+ if (activeOBs.length > 0) {
83
+ console.log(` * Recent OB: Type: ${activeOBs[activeOBs.length - 1].type.toUpperCase()}, Top: $${activeOBs[activeOBs.length - 1].top.toFixed(2)}, Bottom: $${activeOBs[activeOBs.length - 1].bottom.toFixed(2)}`);
84
+ }
85
+
86
+ // 4. Exit Manager for User's trade
87
+ const userTrade = {
88
+ type: 'SHORT',
89
+ entry: 1979.05,
90
+ sl: 1996.46,
91
+ tp1: 1979.05 - (1996.46 - 1979.05) * 2, // approximate TP1 = entry - 2R
92
+ tp2: 1926.81,
93
+ lotSize: 2.87,
94
+ symbol: 'ETHUSDT'
95
+ };
96
+
97
+ const exitAnalysis = analyzeExit(candles, userTrade);
98
+ console.log('\n🛡️ Active Trade Analysis (SHORT ETHUSDT):');
99
+ console.log(`- Current Unrealized P&L: $${exitAnalysis.currentPnL.toFixed(2)}`);
100
+ console.log(`- Current R:R: ${exitAnalysis.currentRR.toFixed(2)}`);
101
+ console.log(`- Suggestion: ${exitAnalysis.suggestion}`);
102
+ console.log(`- Recommendation: ${exitAnalysis.reason}`);
103
+ if (exitAnalysis.warnings && exitAnalysis.warnings.length > 0) {
104
+ console.log('- Warnings:');
105
+ exitAnalysis.warnings.forEach(w => console.log(` * ⚠️ ${w}`));
106
+ } else {
107
+ console.log('- Warnings: None (normal parameters)');
108
+ }
109
+
110
+ } catch (err) {
111
+ console.error('Analysis failed:', err);
112
+ }
113
+ }
114
+
115
+ run();
scratch/backtestSignals.js ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // backtestSignals.js
2
+ import { fetchCandles } from '../src/data/yahooFinanceAPI.js';
3
+ import { generateSignals } from '../src/analysis/signalGenerator.js';
4
+
5
+ // Polyfill fetch exactly like server.js
6
+ const originalFetch = globalThis.fetch;
7
+ globalThis.fetch = async (input, init) => {
8
+ let url = typeof input === 'string' ? input : input.url;
9
+
10
+ if (url.startsWith('/api/swissquote')) {
11
+ url = 'https://forex-data-feed.swissquote.com/public-quotes/bboquotes/instrument/XAU/USD';
12
+ } else if (url.startsWith('/api/yahoo')) {
13
+ url = 'https://query1.finance.yahoo.com' + url.replace(/^\/api\/yahoo/, '');
14
+ init = init || {};
15
+ init.headers = {
16
+ ...init.headers,
17
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
18
+ 'Origin': 'https://finance.yahoo.com',
19
+ 'Referer': 'https://finance.yahoo.com',
20
+ };
21
+ }
22
+
23
+ return originalFetch(url, init);
24
+ };
25
+
26
+ function calculatePnL(symbol, type, entry, exit, lots) {
27
+ const direction = type === 'LONG' ? 1 : -1;
28
+ const diff = (exit - entry) * direction;
29
+ if (symbol === 'BTCUSDT' || symbol === 'ETHUSDT') {
30
+ return diff * lots;
31
+ } else if (symbol === 'XAUUSD') {
32
+ return diff * lots * 100;
33
+ } else if (symbol === 'EURUSD' || symbol === 'GBPUSD' || symbol === 'USDCAD') {
34
+ const rate = symbol === 'USDCAD' ? 1.38 : 1.0; // approximation for USDCAD USD-conversion
35
+ const pipScale = symbol === 'USDCAD' ? (10 / rate) : 10;
36
+ return (diff / 0.0001) * lots * pipScale;
37
+ }
38
+ return 0;
39
+ }
40
+
41
+ function calculateCommission(symbol, entryPrice, lots) {
42
+ if (symbol === 'BTCUSDT' || symbol === 'ETHUSDT') {
43
+ return 0.0008 * entryPrice * lots;
44
+ } else {
45
+ return 5.0 * lots;
46
+ }
47
+ }
48
+
49
+ function getSpread(symbol) {
50
+ switch (symbol) {
51
+ case 'BTCUSDT': return 25.0;
52
+ case 'ETHUSDT': return 5.0;
53
+ case 'XAUUSD': return 0.7;
54
+ case 'EURUSD':
55
+ case 'GBPUSD':
56
+ case 'USDCAD':
57
+ return 0.00007;
58
+ default:
59
+ return 0;
60
+ }
61
+ }
62
+
63
+ function backtestTrade(symbol, signal, candles, startIndex) {
64
+ let partialClosed = false;
65
+ let sl = signal.sl;
66
+ let entry = signal.entry;
67
+ let lots = signal.lotSize;
68
+ let commission = calculateCommission(symbol, entry, lots);
69
+ const spread = getSpread(symbol);
70
+
71
+ for (let c = startIndex; c < candles.length; c++) {
72
+ const candle = candles[c];
73
+
74
+ if (signal.type === 'LONG') {
75
+ // Check SL hit
76
+ if (candle.low <= sl) {
77
+ if (!partialClosed) {
78
+ // Full SL hit
79
+ const pnl = calculatePnL(symbol, 'LONG', entry, sl, lots) - commission;
80
+ return { status: 'LOSS', pnl, exitTime: candle.time, exitPrice: sl };
81
+ } else {
82
+ // Stopped out at breakeven (entry) on remaining 30%
83
+ const tp1PnL = calculatePnL(symbol, 'LONG', entry, signal.tp1, lots * 0.7);
84
+ const pnl = tp1PnL - commission;
85
+ return { status: 'PARTIAL_STOP', pnl, exitTime: candle.time, exitPrice: sl };
86
+ }
87
+ }
88
+
89
+ // Check TP1 hit
90
+ if (!partialClosed && candle.high >= signal.tp1) {
91
+ partialClosed = true;
92
+ sl = entry; // Trail SL to breakeven
93
+ }
94
+
95
+ // Check TP2 hit
96
+ if (partialClosed && candle.high >= signal.tp2) {
97
+ const tp1PnL = calculatePnL(symbol, 'LONG', entry, signal.tp1, lots * 0.7);
98
+ const tp2PnL = calculatePnL(symbol, 'LONG', entry, signal.tp2, lots * 0.3);
99
+ const pnl = tp1PnL + tp2PnL - commission;
100
+ return { status: 'WIN', pnl, exitTime: candle.time, exitPrice: signal.tp2 };
101
+ }
102
+ } else {
103
+ // SHORT: exits occur at Ask price = chart price + spread
104
+ // Check SL hit
105
+ if (candle.high + spread >= sl) {
106
+ if (!partialClosed) {
107
+ const pnl = calculatePnL(symbol, 'SHORT', entry, sl, lots) - commission;
108
+ return { status: 'LOSS', pnl, exitTime: candle.time, exitPrice: sl };
109
+ } else {
110
+ const tp1PnL = calculatePnL(symbol, 'SHORT', entry, signal.tp1, lots * 0.7);
111
+ const pnl = tp1PnL - commission;
112
+ return { status: 'PARTIAL_STOP', pnl, exitTime: candle.time, exitPrice: sl };
113
+ }
114
+ }
115
+
116
+ // Check TP1 hit
117
+ if (!partialClosed && candle.low + spread <= signal.tp1) {
118
+ partialClosed = true;
119
+ sl = entry; // Trail SL to breakeven
120
+ }
121
+
122
+ // Check TP2 hit
123
+ if (partialClosed && candle.low + spread <= signal.tp2) {
124
+ const tp1PnL = calculatePnL(symbol, 'SHORT', entry, signal.tp1, lots * 0.7);
125
+ const tp2PnL = calculatePnL(symbol, 'SHORT', entry, signal.tp2, lots * 0.3);
126
+ const pnl = tp1PnL + tp2PnL - commission;
127
+ return { status: 'WIN', pnl, exitTime: candle.time, exitPrice: signal.tp2 };
128
+ }
129
+ }
130
+ }
131
+
132
+ // Trade still open at end of history
133
+ const lastCandle = candles[candles.length - 1];
134
+ const valuation = lastCandle.close;
135
+ if (!partialClosed) {
136
+ const valuationPrice = signal.type === 'SHORT' ? valuation + spread : valuation;
137
+ const pnl = calculatePnL(symbol, signal.type, entry, valuationPrice, lots) - commission;
138
+ return { status: 'OPEN', pnl, exitTime: lastCandle.time, exitPrice: valuationPrice };
139
+ } else {
140
+ const valuationPrice = signal.type === 'SHORT' ? valuation + spread : valuation;
141
+ const tp1PnL = calculatePnL(symbol, signal.type, entry, signal.tp1, lots * 0.7);
142
+ const tp2PnL = calculatePnL(symbol, signal.type, entry, valuationPrice, lots * 0.3);
143
+ const pnl = tp1PnL + tp2PnL - commission;
144
+ return { status: 'OPEN_PARTIAL', pnl, exitTime: lastCandle.time, exitPrice: valuationPrice };
145
+ }
146
+ }
147
+
148
+ const SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'XAUUSD', 'GBPUSD', 'EURUSD', 'USDCAD'];
149
+
150
+ async function runBacktest() {
151
+ console.log('=== RUNNING HISTORICAL BACKTEST & QUALITY METRICS ===');
152
+ console.log('Parameters: 1H Timeframe, Spread & Commission Enabled, TP1 (70%) + BE SL Trailing\n');
153
+
154
+ for (const symbol of SYMBOLS) {
155
+ try {
156
+ const candles = await fetchCandles(symbol, '1H');
157
+ if (!candles || candles.length < 100) {
158
+ console.log(`[${symbol}] Insufficient data for backtest.`);
159
+ continue;
160
+ }
161
+
162
+ let totalTrades = 0;
163
+ let fullWins = 0; // Hit TP2
164
+ let partialWins = 0; // Hit TP1, then stopped at BE
165
+ let fullLosses = 0; // Hit SL before TP1
166
+ let openTrades = 0;
167
+ let totalPnL = 0;
168
+
169
+ // Let's slide a window through historical candles
170
+ // To simulate real execution, we keep a cooldown timer per symbol
171
+ let lastClosedTime = 0;
172
+ let isCooldown = false;
173
+ let activeTradeEndIndex = -1;
174
+
175
+ for (let i = 50; i < candles.length; i++) {
176
+ // Check if there is an active trade running
177
+ if (i <= activeTradeEndIndex) {
178
+ continue; // skip scanning if in a trade
179
+ }
180
+
181
+ // Check cooldown (10 minutes = 1 candle on 15m TF)
182
+ if (lastClosedTime > 0 && (candles[i].time - lastClosedTime) < 600) {
183
+ continue; // skip scanning if cooling down
184
+ }
185
+
186
+ const window = candles.slice(0, i);
187
+ const signals = generateSignals(window, symbol, 0);
188
+
189
+ // Filter signals to Grade A and B
190
+ const validSignals = signals.filter(sig => ['A', 'B'].includes(sig.quality));
191
+ if (validSignals.length === 0) continue;
192
+
193
+ const signal = validSignals[0];
194
+ // We evaluate the trade outcome starting from candle index `i` (entry is at the close of candle `i-1`)
195
+ const result = backtestTrade(symbol, signal, candles, i);
196
+
197
+ totalTrades++;
198
+ totalPnL += result.pnl;
199
+
200
+ // Estimate index of trade exit
201
+ let exitIndex = candles.findIndex(c => c.time === result.exitTime);
202
+ if (exitIndex === -1) exitIndex = candles.length - 1;
203
+
204
+ activeTradeEndIndex = exitIndex;
205
+ lastClosedTime = result.exitTime;
206
+
207
+ if (result.status === 'WIN') fullWins++;
208
+ else if (result.status === 'PARTIAL_STOP') partialWins++;
209
+ else if (result.status === 'LOSS') fullLosses++;
210
+ else openTrades++;
211
+ }
212
+
213
+ const completedTrades = fullWins + partialWins + fullLosses;
214
+ const winRate = completedTrades > 0 ? ((fullWins + partialWins) / completedTrades) * 100 : 0;
215
+ const fullWinRate = completedTrades > 0 ? (fullWins / completedTrades) * 100 : 0;
216
+ const avgPnL = completedTrades > 0 ? totalPnL / completedTrades : 0;
217
+
218
+ console.log(`[${symbol}] Backtest results over ${candles.length} candles (~${Math.round(candles.length / 96)} days):`);
219
+ console.log(` Total Scanned Signals Executed: ${totalTrades}`);
220
+ console.log(` Completed Outcomes: ${completedTrades} (Open: ${openTrades})`);
221
+ console.log(` - Hit TP2 (Full Win): ${fullWins} (${fullWinRate.toFixed(1)}%)`);
222
+ console.log(` - Hit TP1, then BE Stop (Partial Profit): ${partialWins}`);
223
+ console.log(` - Hit SL (Full Loss): ${fullLosses}`);
224
+ console.log(` - Net Win Rate (Profit/BE Trades): ${winRate.toFixed(1)}%`);
225
+ console.log(` - Total Cumulative Net P&L: $${totalPnL.toFixed(2)}`);
226
+ console.log(` - Average P&L per completed trade: $${avgPnL.toFixed(2)}\n`);
227
+
228
+ } catch (e) {
229
+ console.error(`Error backtesting ${symbol}:`, e.message);
230
+ }
231
+ }
232
+ }
233
+
234
+ runBacktest();
scratch/checkAllTimeframes.js ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // checkAllTimeframes.js
2
+ const originalFetch = globalThis.fetch;
3
+ globalThis.fetch = async (input, init) => {
4
+ let url = typeof input === 'string' ? input : input.url;
5
+ if (url.startsWith('/api/yahoo')) {
6
+ url = 'https://query1.finance.yahoo.com' + url.replace(/^\/api\/yahoo/, '');
7
+ init = init || {};
8
+ init.headers = {
9
+ ...init.headers,
10
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
11
+ 'Origin': 'https://finance.yahoo.com',
12
+ 'Referer': 'https://finance.yahoo.com',
13
+ };
14
+ }
15
+ return originalFetch(url, init);
16
+ };
17
+
18
+ const TIMEFRAMES = {
19
+ '1m': { interval: '1m', range: '1d' },
20
+ '5m': { interval: '5m', range: '5d' },
21
+ '15m': { interval: '15m', range: '15d' },
22
+ '1H': { interval: '60m', range: '30d' },
23
+ '1D': { interval: '1d', range: '1y' }
24
+ };
25
+
26
+ async function test() {
27
+ for (const [tf, config] of Object.entries(TIMEFRAMES)) {
28
+ const url = `https://query1.finance.yahoo.com/v8/finance/chart/USDCAD=X?interval=${config.interval}&range=${config.range}`;
29
+ try {
30
+ const res = await fetch(url);
31
+ const data = await res.json();
32
+ const result = data.chart.result[0];
33
+ if (!result || !result.timestamp) {
34
+ console.log(`Timeframe ${tf}: No data`);
35
+ continue;
36
+ }
37
+ const timestamps = result.timestamp;
38
+ const quotes = result.indicators.quote[0];
39
+
40
+ let maxWick = 0;
41
+ let maxWickTime = null;
42
+ let count = 0;
43
+
44
+ for (let i = 0; i < timestamps.length; i++) {
45
+ const o = quotes.open[i];
46
+ const h = quotes.high[i];
47
+ const l = quotes.low[i];
48
+ const c = quotes.close[i];
49
+ if (o === null || h === null || l === null || c === null) continue;
50
+
51
+ count++;
52
+ const maxBody = Math.max(o, c);
53
+ const minBody = Math.min(o, c);
54
+ const upper = h - maxBody;
55
+ const lower = minBody - l;
56
+ const wick = Math.max(upper, lower);
57
+
58
+ if (wick > maxWick) {
59
+ maxWick = wick;
60
+ maxWickTime = new Date(timestamps[i] * 1000).toLocaleString();
61
+ }
62
+ }
63
+
64
+ console.log(`Timeframe ${tf}: Total candles=${count}, Max Wick=${maxWick.toFixed(5)} (${Math.round(maxWick * 10000)} pips) at ${maxWickTime}`);
65
+ } catch (e) {
66
+ console.log(`Timeframe ${tf}: Failed with error: ${e.message}`);
67
+ }
68
+ }
69
+ }
70
+
71
+ test();
scratch/checkForexVolume.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // checkForexVolume.js
2
+ import { fetchCandles } from '../src/data/yahooFinanceAPI.js';
3
+
4
+ // Polyfill fetch exactly like server.js
5
+ const originalFetch = globalThis.fetch;
6
+ globalThis.fetch = async (input, init) => {
7
+ let url = typeof input === 'string' ? input : input.url;
8
+
9
+ if (url.startsWith('/api/swissquote')) {
10
+ url = 'https://forex-data-feed.swissquote.com/public-quotes/bboquotes/instrument/XAU/USD';
11
+ } else if (url.startsWith('/api/yahoo')) {
12
+ url = 'https://query1.finance.yahoo.com' + url.replace(/^\/api\/yahoo/, '');
13
+ init = init || {};
14
+ init.headers = {
15
+ ...init.headers,
16
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
17
+ 'Origin': 'https://finance.yahoo.com',
18
+ 'Referer': 'https://finance.yahoo.com',
19
+ };
20
+ }
21
+
22
+ return originalFetch(url, init);
23
+ };
24
+
25
+ async function test() {
26
+ const symbols = ['EURUSD', 'GBPUSD', 'USDCAD', 'XAUUSD', 'BTCUSDT', 'ETHUSDT'];
27
+ for (const sym of symbols) {
28
+ try {
29
+ const candles = await fetchCandles(sym, '15m');
30
+ const sample = candles.slice(-5);
31
+ console.log(`\n=== Symbol: ${sym} ===`);
32
+ console.log(`Total candles loaded: ${candles.length}`);
33
+ sample.forEach(c => {
34
+ console.log(`Candle Time: ${new Date(c.time * 1000).toLocaleString()} | Close: ${c.close} | Volume: ${c.volume}`);
35
+ });
36
+ } catch (e) {
37
+ console.error(`Error for ${sym}:`, e.message);
38
+ }
39
+ }
40
+ }
41
+
42
+ test();
scratch/findWicks.js ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { fetchCandles } from '../src/data/yahooFinanceAPI.js';
2
+
3
+ // Polyfill fetch
4
+ const originalFetch = globalThis.fetch;
5
+ globalThis.fetch = async (input, init) => {
6
+ let url = typeof input === 'string' ? input : input.url;
7
+ if (url.startsWith('/api/yahoo')) {
8
+ url = 'https://query1.finance.yahoo.com' + url.replace(/^\/api\/yahoo/, '');
9
+ init = init || {};
10
+ init.headers = {
11
+ ...init.headers,
12
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
13
+ 'Origin': 'https://finance.yahoo.com',
14
+ 'Referer': 'https://finance.yahoo.com',
15
+ };
16
+ }
17
+ return originalFetch(url, init);
18
+ };
19
+
20
+ async function check() {
21
+ const symbol = 'USDCAD';
22
+ console.log(`--- Fetching ${symbol} from Yahoo Finance ---`);
23
+ const url = 'https://query1.finance.yahoo.com/v8/finance/chart/USDCAD=X?interval=15m&range=15d';
24
+ const res = await fetch(url);
25
+ const data = await res.json();
26
+ const result = data.chart.result[0];
27
+ const timestamps = result.timestamp;
28
+ const quotes = result.indicators.quote[0];
29
+
30
+ console.log(`Total timestamps: ${timestamps.length}`);
31
+
32
+ let nonNullCount = 0;
33
+ let minLow = Infinity;
34
+ let maxHigh = -Infinity;
35
+ let maxWick = 0;
36
+ let maxWickIdx = -1;
37
+
38
+ for (let i = 0; i < timestamps.length; i++) {
39
+ const o = quotes.open[i];
40
+ const h = quotes.high[i];
41
+ const l = quotes.low[i];
42
+ const c = quotes.close[i];
43
+ if (o === null || h === null || l === null || c === null) continue;
44
+
45
+ nonNullCount++;
46
+ if (l < minLow) minLow = l;
47
+ if (h > maxHigh) maxHigh = h;
48
+
49
+ const maxBody = Math.max(o, c);
50
+ const minBody = Math.min(o, c);
51
+ const upperWick = h - maxBody;
52
+ const lowerWick = minBody - l;
53
+ const wick = Math.max(upperWick, lowerWick);
54
+
55
+ if (wick > maxWick) {
56
+ maxWick = wick;
57
+ maxWickIdx = i;
58
+ }
59
+ }
60
+
61
+ console.log(`Non-null quotes: ${nonNullCount}`);
62
+ console.log(`Absolute Min Low: ${minLow}`);
63
+ console.log(`Absolute Max High: ${maxHigh}`);
64
+ console.log(`Max Wick size: ${maxWick} at index ${maxWickIdx}`);
65
+ if (maxWickIdx !== -1) {
66
+ console.log('Candle with max wick:', {
67
+ time: new Date(timestamps[maxWickIdx] * 1000).toLocaleString(),
68
+ open: quotes.open[maxWickIdx],
69
+ high: quotes.high[maxWickIdx],
70
+ low: quotes.low[maxWickIdx],
71
+ close: quotes.close[maxWickIdx]
72
+ });
73
+ }
74
+ }
75
+
76
+ check();
scratch/highFidelityBacktest.js ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { fetchCandles } from '../src/data/yahooFinanceAPI.js';
2
+ import { generateSignals } from '../src/analysis/signalGenerator.js';
3
+
4
+ // Polyfill fetch exactly like server.js
5
+ const originalFetch = globalThis.fetch;
6
+ globalThis.fetch = async (input, init) => {
7
+ let url = typeof input === 'string' ? input : input.url;
8
+ if (url.startsWith('/api/swissquote')) {
9
+ url = 'https://forex-data-feed.swissquote.com/public-quotes/bboquotes/instrument/XAU/USD';
10
+ } else if (url.startsWith('/api/yahoo')) {
11
+ url = 'https://query1.finance.yahoo.com' + url.replace(/^\/api\/yahoo/, '');
12
+ init = init || {};
13
+ init.headers = {
14
+ ...init.headers,
15
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
16
+ 'Origin': 'https://finance.yahoo.com',
17
+ 'Referer': 'https://finance.yahoo.com',
18
+ };
19
+ }
20
+ return originalFetch(url, init);
21
+ };
22
+
23
+ function getSpread(symbol) {
24
+ switch (symbol) {
25
+ case 'BTCUSDT': return 25.0;
26
+ case 'ETHUSDT': return 5.0;
27
+ case 'XAUUSD': return 0.7;
28
+ case 'EURUSD':
29
+ case 'GBPUSD':
30
+ case 'USDCAD':
31
+ return 0.00007;
32
+ default:
33
+ return 0;
34
+ }
35
+ }
36
+
37
+ function calculatePnL(symbol, type, entry, exit, lots) {
38
+ const direction = type === 'LONG' ? 1 : -1;
39
+ const diff = (exit - entry) * direction;
40
+ if (symbol === 'BTCUSDT' || symbol === 'ETHUSDT') {
41
+ return diff * lots;
42
+ } else if (symbol === 'XAUUSD') {
43
+ return diff * lots * 100;
44
+ } else if (symbol === 'EURUSD' || symbol === 'GBPUSD' || symbol === 'USDCAD') {
45
+ const rate = symbol === 'USDCAD' ? 1.38 : 1.0;
46
+ const pipScale = symbol === 'USDCAD' ? (10 / rate) : 10;
47
+ return (diff / 0.0001) * lots * pipScale;
48
+ }
49
+ return 0;
50
+ }
51
+
52
+ function calculateCommission(symbol, entryPrice, lots) {
53
+ if (symbol === 'BTCUSDT' || symbol === 'ETHUSDT') {
54
+ return 0.0008 * entryPrice * lots;
55
+ } else {
56
+ return 5.0 * lots;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Runs high-fidelity exit check using lower timeframe candles.
62
+ */
63
+ function checkExitHighFidelity(symbol, signal, lowerCandles, startLtIndex) {
64
+ let partialClosed = false;
65
+ let sl = signal.sl;
66
+ let entry = signal.entry;
67
+ let lots = signal.lotSize;
68
+ let commission = calculateCommission(symbol, entry, lots);
69
+ const spread = getSpread(symbol);
70
+
71
+ for (let c = startLtIndex; c < lowerCandles.length; c++) {
72
+ const candle = lowerCandles[c];
73
+
74
+ if (signal.type === 'LONG') {
75
+ // Check SL hit
76
+ if (candle.low <= sl) {
77
+ if (!partialClosed) {
78
+ const pnl = calculatePnL(symbol, 'LONG', entry, sl, lots) - commission;
79
+ return { status: 'LOSS', pnl, exitTime: candle.time, exitPrice: sl };
80
+ } else {
81
+ const tp1PnL = calculatePnL(symbol, 'LONG', entry, signal.tp1, lots * 0.7);
82
+ const pnl = tp1PnL - commission;
83
+ return { status: 'PARTIAL_STOP', pnl, exitTime: candle.time, exitPrice: sl };
84
+ }
85
+ }
86
+
87
+ // Check TP1 hit
88
+ if (!partialClosed && candle.high >= signal.tp1) {
89
+ partialClosed = true;
90
+ sl = entry;
91
+ }
92
+
93
+ // Check TP2 hit
94
+ if (partialClosed && candle.high >= signal.tp2) {
95
+ const tp1PnL = calculatePnL(symbol, 'LONG', entry, signal.tp1, lots * 0.7);
96
+ const tp2PnL = calculatePnL(symbol, 'LONG', entry, signal.tp2, lots * 0.3);
97
+ const pnl = tp1PnL + tp2PnL - commission;
98
+ return { status: 'WIN', pnl, exitTime: candle.time, exitPrice: signal.tp2 };
99
+ }
100
+ } else {
101
+ // SHORT
102
+ if (candle.high + spread >= sl) {
103
+ if (!partialClosed) {
104
+ const pnl = calculatePnL(symbol, 'SHORT', entry, sl, lots) - commission;
105
+ return { status: 'LOSS', pnl, exitTime: candle.time, exitPrice: sl };
106
+ } else {
107
+ const tp1PnL = calculatePnL(symbol, 'SHORT', entry, signal.tp1, lots * 0.7);
108
+ const pnl = tp1PnL - commission;
109
+ return { status: 'PARTIAL_STOP', pnl, exitTime: candle.time, exitPrice: sl };
110
+ }
111
+ }
112
+
113
+ if (!partialClosed && candle.low + spread <= signal.tp1) {
114
+ partialClosed = true;
115
+ sl = entry;
116
+ }
117
+
118
+ if (partialClosed && candle.low + spread <= signal.tp2) {
119
+ const tp1PnL = calculatePnL(symbol, 'SHORT', entry, signal.tp1, lots * 0.7);
120
+ const tp2PnL = calculatePnL(symbol, 'SHORT', entry, signal.tp2, lots * 0.3);
121
+ const pnl = tp1PnL + tp2PnL - commission;
122
+ return { status: 'WIN', pnl, exitTime: candle.time, exitPrice: signal.tp2 };
123
+ }
124
+ }
125
+ }
126
+
127
+ // Still open
128
+ const lastCandle = lowerCandles[lowerCandles.length - 1];
129
+ const valuation = lastCandle.close;
130
+ if (!partialClosed) {
131
+ const valuationPrice = signal.type === 'SHORT' ? valuation + spread : valuation;
132
+ const pnl = calculatePnL(symbol, signal.type, entry, valuationPrice, lots) - commission;
133
+ return { status: 'OPEN', pnl, exitTime: lastCandle.time, exitPrice: valuationPrice };
134
+ } else {
135
+ const valuationPrice = signal.type === 'SHORT' ? valuation + spread : valuation;
136
+ const tp1PnL = calculatePnL(symbol, signal.type, entry, signal.tp1, lots * 0.7);
137
+ const tp2PnL = calculatePnL(symbol, signal.type, entry, valuationPrice, lots * 0.3);
138
+ const pnl = tp1PnL + tp2PnL - commission;
139
+ return { status: 'OPEN_PARTIAL', pnl, exitTime: lastCandle.time, exitPrice: valuationPrice };
140
+ }
141
+ }
142
+
143
+ const SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'XAUUSD', 'GBPUSD', 'EURUSD', 'USDCAD'];
144
+
145
+ async function runHighFidelityBacktest(signalTimeframe) {
146
+ console.log(`\n=== RUNNING HIGH-FIDELITY BACKTEST (Signals: ${signalTimeframe}, Exits checked on: 5m candles) ===`);
147
+
148
+ // Fetch lower timeframe candles for exit checking (range 30d for 5m is supported by Yahoo)
149
+ // Let's modify TIMEFRAME_CONFIG dynamically inside yahooFinanceAPI to make sure GC=F and others return 30d of 5m
150
+ // We can just query directly or fetch it.
151
+
152
+ for (const symbol of SYMBOLS) {
153
+ try {
154
+ const sigCandles = await fetchCandles(symbol, signalTimeframe);
155
+ // Fetch 5m candles to track entries/exits at fine resolution
156
+ // We temporarily fetch with '5m' timeframe. Let's make sure it returns enough candles.
157
+ const ltCandles = await fetchCandles(symbol, '5m');
158
+
159
+ if (!sigCandles || sigCandles.length < 100 || !ltCandles || ltCandles.length < 100) {
160
+ console.log(`[${symbol}] Insufficient candles. Signal Count: ${sigCandles?.length}, 5m Count: ${ltCandles?.length}`);
161
+ continue;
162
+ }
163
+
164
+ let totalTrades = 0;
165
+ let fullWins = 0;
166
+ let partialWins = 0;
167
+ let fullLosses = 0;
168
+ let openTrades = 0;
169
+ let totalPnL = 0;
170
+
171
+ let lastClosedTime = 0;
172
+ let activeTradeEndIndex = -1; // index in sigCandles
173
+ let activeTradeEndLtTime = 0;
174
+
175
+ for (let i = 50; i < sigCandles.length; i++) {
176
+ // Skip scanning if we are currently inside an active trade (using time constraint)
177
+ if (sigCandles[i].time <= activeTradeEndLtTime) {
178
+ continue;
179
+ }
180
+
181
+ // Cooldown check (10 minutes)
182
+ if (lastClosedTime > 0 && (sigCandles[i].time - lastClosedTime) < 600) {
183
+ continue;
184
+ }
185
+
186
+ const window = sigCandles.slice(0, i);
187
+ const signals = generateSignals(window, symbol, 0);
188
+ const validSignals = signals.filter(sig => ['A', 'B'].includes(sig.quality));
189
+ if (validSignals.length === 0) continue;
190
+
191
+ const signal = validSignals[0];
192
+
193
+ // Find corresponding index in the lower-timeframe 5m candles
194
+ // Entry is at the close of candle i-1, which is the start of candle i
195
+ const entryTime = sigCandles[i].time;
196
+ let ltIndex = ltCandles.findIndex(c => c.time >= entryTime);
197
+ if (ltIndex === -1) continue;
198
+
199
+ const result = checkExitHighFidelity(symbol, signal, ltCandles, ltIndex);
200
+
201
+ totalTrades++;
202
+ totalPnL += result.pnl;
203
+
204
+ if (result.status === 'WIN') fullWins++;
205
+ else if (result.status === 'PARTIAL_STOP') partialWins++;
206
+ else if (result.status === 'LOSS') fullLosses++;
207
+ else openTrades++;
208
+
209
+ activeTradeEndLtTime = result.exitTime;
210
+ lastClosedTime = result.exitTime;
211
+ }
212
+
213
+ const completedTrades = fullWins + partialWins + fullLosses;
214
+ const winRate = completedTrades > 0 ? ((fullWins + partialWins) / completedTrades) * 100 : 0;
215
+ const fullWinRate = completedTrades > 0 ? (fullWins / completedTrades) * 100 : 0;
216
+ const avgPnL = completedTrades > 0 ? totalPnL / completedTrades : 0;
217
+
218
+ console.log(`[${symbol}] Results over ~10-15 days of overlap:`);
219
+ console.log(` Total Scanned Signals Executed: ${totalTrades}`);
220
+ console.log(` Completed Outcomes: ${completedTrades} (Open: ${openTrades})`);
221
+ console.log(` - Hit TP2 (Full Win): ${fullWins} (${fullWinRate.toFixed(1)}%)`);
222
+ console.log(` - Hit TP1, then BE Stop (Partial Profit): ${partialWins}`);
223
+ console.log(` - Hit SL (Full Loss): ${fullLosses}`);
224
+ console.log(` - Net Win Rate (Profit/BE Trades): ${winRate.toFixed(1)}%`);
225
+ console.log(` - Total Cumulative Net P&L: $${totalPnL.toFixed(2)}`);
226
+ console.log(` - Average P&L per completed trade: $${avgPnL.toFixed(2)}\n`);
227
+
228
+ } catch (e) {
229
+ console.error(`Error backtesting ${symbol}:`, e.message);
230
+ }
231
+ }
232
+ }
233
+
234
+ async function run() {
235
+ // Let's modify TIMEFRAME_CONFIG range for 5m to 30d so we have enough candles to overlap with 1H's 60d
236
+ // We will patch the config dynamically
237
+ try {
238
+ const { TIMEFRAME_CONFIG } = await import('../src/data/yahooFinanceAPI.js');
239
+ if (TIMEFRAME_CONFIG && TIMEFRAME_CONFIG['5m']) {
240
+ TIMEFRAME_CONFIG['5m'].range = '30d';
241
+ console.log('[Info] Dynamically expanded 5m Yahoo range to 30d.');
242
+ }
243
+ } catch (err) {
244
+ // Ignore
245
+ }
246
+
247
+ await runHighFidelityBacktest('15m');
248
+ await runHighFidelityBacktest('1H');
249
+ }
250
+
251
+ run();
scratch/optimizeParams.js ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { fetchCandles } from '../src/data/yahooFinanceAPI.js';
2
+ import { generateSignals, ATR_MULTIPLIERS, ATR_CONFIG } from '../src/analysis/signalGenerator.js';
3
+
4
+ // Polyfill fetch exactly like server.js
5
+ const originalFetch = globalThis.fetch;
6
+ globalThis.fetch = async (input, init) => {
7
+ let url = typeof input === 'string' ? input : input.url;
8
+ if (url.startsWith('/api/swissquote')) {
9
+ url = 'https://forex-data-feed.swissquote.com/public-quotes/bboquotes/instrument/XAU/USD';
10
+ } else if (url.startsWith('/api/yahoo')) {
11
+ url = 'https://query1.finance.yahoo.com' + url.replace(/^\/api\/yahoo/, '');
12
+ init = init || {};
13
+ init.headers = {
14
+ ...init.headers,
15
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
16
+ 'Origin': 'https://finance.yahoo.com',
17
+ 'Referer': 'https://finance.yahoo.com',
18
+ };
19
+ }
20
+ return originalFetch(url, init);
21
+ };
22
+
23
+ function getSpread(symbol) {
24
+ switch (symbol) {
25
+ case 'BTCUSDT': return 25.0;
26
+ case 'ETHUSDT': return 5.0;
27
+ case 'XAUUSD': return 0.7;
28
+ case 'EURUSD':
29
+ case 'GBPUSD':
30
+ case 'USDCAD':
31
+ return 0.00007;
32
+ default:
33
+ return 0;
34
+ }
35
+ }
36
+
37
+ function calculatePnL(symbol, type, entry, exit, lots) {
38
+ const direction = type === 'LONG' ? 1 : -1;
39
+ const diff = (exit - entry) * direction;
40
+ if (symbol === 'BTCUSDT' || symbol === 'ETHUSDT') {
41
+ return diff * lots;
42
+ } else if (symbol === 'XAUUSD') {
43
+ return diff * lots * 100;
44
+ } else if (symbol === 'EURUSD' || symbol === 'GBPUSD' || symbol === 'USDCAD') {
45
+ const rate = symbol === 'USDCAD' ? 1.38 : 1.0;
46
+ const pipScale = symbol === 'USDCAD' ? (10 / rate) : 10;
47
+ return (diff / 0.0001) * lots * pipScale;
48
+ }
49
+ return 0;
50
+ }
51
+
52
+ function calculateCommission(symbol, entryPrice, lots) {
53
+ if (symbol === 'BTCUSDT' || symbol === 'ETHUSDT') {
54
+ return 0.0008 * entryPrice * lots;
55
+ } else {
56
+ return 5.0 * lots;
57
+ }
58
+ }
59
+
60
+ function backtestTrade(symbol, signal, candles, startIndex) {
61
+ let partialClosed = false;
62
+ let sl = signal.sl;
63
+ let entry = signal.entry;
64
+ let lots = signal.lotSize;
65
+ let commission = calculateCommission(symbol, entry, lots);
66
+ const spread = getSpread(symbol);
67
+
68
+ for (let c = startIndex; c < candles.length; c++) {
69
+ const candle = candles[c];
70
+ if (signal.type === 'LONG') {
71
+ if (candle.low <= sl) {
72
+ if (!partialClosed) {
73
+ const pnl = calculatePnL(symbol, 'LONG', entry, sl, lots) - commission;
74
+ return { status: 'LOSS', pnl, exitTime: candle.time };
75
+ } else {
76
+ const tp1PnL = calculatePnL(symbol, 'LONG', entry, signal.tp1, lots * 0.7);
77
+ const pnl = tp1PnL - commission;
78
+ return { status: 'PARTIAL_STOP', pnl, exitTime: candle.time };
79
+ }
80
+ }
81
+ if (!partialClosed && candle.high >= signal.tp1) {
82
+ partialClosed = true;
83
+ sl = entry;
84
+ }
85
+ if (partialClosed && candle.high >= signal.tp2) {
86
+ const tp1PnL = calculatePnL(symbol, 'LONG', entry, signal.tp1, lots * 0.7);
87
+ const tp2PnL = calculatePnL(symbol, 'LONG', entry, signal.tp2, lots * 0.3);
88
+ const pnl = tp1PnL + tp2PnL - commission;
89
+ return { status: 'WIN', pnl, exitTime: candle.time };
90
+ }
91
+ } else {
92
+ if (candle.high + spread >= sl) {
93
+ if (!partialClosed) {
94
+ const pnl = calculatePnL(symbol, 'SHORT', entry, sl, lots) - commission;
95
+ return { status: 'LOSS', pnl, exitTime: candle.time };
96
+ } else {
97
+ const tp1PnL = calculatePnL(symbol, 'SHORT', entry, signal.tp1, lots * 0.7);
98
+ const pnl = tp1PnL - commission;
99
+ return { status: 'PARTIAL_STOP', pnl, exitTime: candle.time };
100
+ }
101
+ }
102
+ if (!partialClosed && candle.low + spread <= signal.tp1) {
103
+ partialClosed = true;
104
+ sl = entry;
105
+ }
106
+ if (partialClosed && candle.low + spread <= signal.tp2) {
107
+ const tp1PnL = calculatePnL(symbol, 'SHORT', entry, signal.tp1, lots * 0.7);
108
+ const tp2PnL = calculatePnL(symbol, 'SHORT', entry, signal.tp2, lots * 0.3);
109
+ const pnl = tp1PnL + tp2PnL - commission;
110
+ return { status: 'WIN', pnl, exitTime: candle.time };
111
+ }
112
+ }
113
+ }
114
+ const lastCandle = candles[candles.length - 1];
115
+ const valuation = lastCandle.close;
116
+ if (!partialClosed) {
117
+ const valuationPrice = signal.type === 'SHORT' ? valuation + spread : valuation;
118
+ const pnl = calculatePnL(symbol, signal.type, entry, valuationPrice, lots) - commission;
119
+ return { status: 'OPEN', pnl, exitTime: lastCandle.time };
120
+ } else {
121
+ const valuationPrice = signal.type === 'SHORT' ? valuation + spread : valuation;
122
+ const tp1PnL = calculatePnL(symbol, signal.type, entry, signal.tp1, lots * 0.7);
123
+ const tp2PnL = calculatePnL(symbol, signal.type, entry, valuationPrice, lots * 0.3);
124
+ const pnl = tp1PnL + tp2PnL - commission;
125
+ return { status: 'OPEN_PARTIAL', pnl, exitTime: lastCandle.time };
126
+ }
127
+ }
128
+
129
+ const SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'XAUUSD', 'GBPUSD', 'EURUSD', 'USDCAD'];
130
+ const ATR_MULTIPLIERS_TO_TEST = [0.5, 1.0, 1.5, 2.0, 2.5];
131
+
132
+ async function runOptimization() {
133
+ ATR_CONFIG.useDynamic = false;
134
+ const timeframes = ['15m', '1H'];
135
+ const data = {};
136
+
137
+ // Pre-load candles
138
+ for (const tf of timeframes) {
139
+ data[tf] = {};
140
+ for (const sym of SYMBOLS) {
141
+ data[tf][sym] = await fetchCandles(sym, tf);
142
+ }
143
+ }
144
+
145
+ for (const tf of timeframes) {
146
+ console.log(`\n======================= TIMEFRAME: ${tf} =======================`);
147
+ for (const symbol of SYMBOLS) {
148
+ console.log(`\n--- Symbol: ${symbol} ---`);
149
+ const candles = data[tf][symbol];
150
+ if (!candles || candles.length < 100) continue;
151
+
152
+ for (const multiplier of ATR_MULTIPLIERS_TO_TEST) {
153
+ // Mutate the global config object
154
+ ATR_MULTIPLIERS[symbol] = multiplier;
155
+
156
+ let totalPnL = 0;
157
+ let totalTrades = 0;
158
+ let wins = 0;
159
+ let activeTradeEndIndex = -1;
160
+ let lastClosedTime = 0;
161
+
162
+ for (let i = 50; i < candles.length; i++) {
163
+ if (i <= activeTradeEndIndex) continue;
164
+ if (lastClosedTime > 0 && (candles[i].time - lastClosedTime) < (tf === '15m' ? 600 : 3600)) {
165
+ continue;
166
+ }
167
+
168
+ const window = candles.slice(0, i);
169
+ const signals = generateSignals(window, symbol, 0);
170
+ const validSignals = signals.filter(sig => ['A', 'B'].includes(sig.quality));
171
+ if (validSignals.length === 0) continue;
172
+
173
+ const signal = validSignals[0];
174
+ const result = backtestTrade(symbol, signal, candles, i);
175
+
176
+ totalTrades++;
177
+ totalPnL += result.pnl;
178
+ if (result.status === 'WIN' || result.status === 'PARTIAL_STOP') {
179
+ wins++;
180
+ }
181
+
182
+ let exitIndex = candles.findIndex(c => c.time === result.exitTime);
183
+ if (exitIndex === -1) exitIndex = candles.length - 1;
184
+ activeTradeEndIndex = exitIndex;
185
+ lastClosedTime = result.exitTime;
186
+ }
187
+
188
+ const winRate = totalTrades > 0 ? (wins / totalTrades) * 100 : 0;
189
+ console.log(`ATR Mult: ${multiplier.toFixed(1)} | Trades: ${totalTrades} | WinRate: ${winRate.toFixed(1)}% | Net P&L: $${totalPnL.toFixed(2)}`);
190
+ }
191
+ }
192
+ }
193
+ }
194
+
195
+ runOptimization();
scratch/simulateSignals.js ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // simulateSignals.js
2
+ import { fetchCandles } from '../src/data/yahooFinanceAPI.js';
3
+ import { generateSignals } from '../src/analysis/signalGenerator.js';
4
+
5
+ // Polyfill fetch exactly like server.js
6
+ const originalFetch = globalThis.fetch;
7
+ globalThis.fetch = async (input, init) => {
8
+ let url = typeof input === 'string' ? input : input.url;
9
+
10
+ if (url.startsWith('/api/swissquote')) {
11
+ url = 'https://forex-data-feed.swissquote.com/public-quotes/bboquotes/instrument/XAU/USD';
12
+ } else if (url.startsWith('/api/yahoo')) {
13
+ url = 'https://query1.finance.yahoo.com' + url.replace(/^\/api\/yahoo/, '');
14
+ init = init || {};
15
+ init.headers = {
16
+ ...init.headers,
17
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
18
+ 'Origin': 'https://finance.yahoo.com',
19
+ 'Referer': 'https://finance.yahoo.com',
20
+ };
21
+ }
22
+
23
+ return originalFetch(url, init);
24
+ };
25
+
26
+ const SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'XAUUSD', 'GBPUSD', 'EURUSD', 'USDCAD'];
27
+
28
+ async function simulate() {
29
+ console.log('=== Simulating Historical Signal Scans (Last 500 candles) ===\n');
30
+
31
+ for (const symbol of SYMBOLS) {
32
+ try {
33
+ const candles = await fetchCandles(symbol, '15m');
34
+ console.log(`[${symbol}] Loaded ${candles.length} historical candles.`);
35
+
36
+ // Let's run a backtest on the history. We'll feed a sliding window of candles to generateSignals
37
+ let gradeACount = 0;
38
+ let gradeBCount = 0;
39
+
40
+ // Minimum history size needed is 50
41
+ for (let i = 50; i <= candles.length; i++) {
42
+ const window = candles.slice(0, i);
43
+ const signals = generateSignals(window, symbol, 0);
44
+
45
+ for (const sig of signals) {
46
+ // If the signal is on the latest candle in the window
47
+ if (sig.time === window[window.length - 1].time) {
48
+ if (sig.quality === 'A') {
49
+ gradeACount++;
50
+ console.log(` 🎯 [Grade A] ${symbol} ${sig.type} at ${new Date(sig.time * 1000).toLocaleString()} (Score: ${sig.score})`);
51
+ } else if (sig.quality === 'B') {
52
+ gradeBCount++;
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ console.log(` -> Total Grade A (Score >= 5): ${gradeACount}`);
59
+ console.log(` -> Total Grade B (Score == 4): ${gradeBCount}\n`);
60
+ } catch (err) {
61
+ console.error(`Error simulating for ${symbol}:`, err.message);
62
+ }
63
+ }
64
+ }
65
+
66
+ simulate();
scratch/testMarketDataEndpoint.js ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // testMarketDataEndpoint.js
2
+ import { fetchCandles } from '../src/data/yahooFinanceAPI.js';
3
+ import { filterForexOutlierWicks } from '../src/data/yahooFinanceAPI.js';
4
+
5
+ // Polyfill fetch
6
+ const originalFetch = globalThis.fetch;
7
+ globalThis.fetch = async (input, init) => {
8
+ let url = typeof input === 'string' ? input : input.url;
9
+ if (url.startsWith('/api/yahoo')) {
10
+ url = 'https://query1.finance.yahoo.com' + url.replace(/^\/api\/yahoo/, '');
11
+ init = init || {};
12
+ init.headers = {
13
+ ...init.headers,
14
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
15
+ 'Origin': 'https://finance.yahoo.com',
16
+ 'Referer': 'https://finance.yahoo.com',
17
+ };
18
+ }
19
+ return originalFetch(url, init);
20
+ };
21
+
22
+ const SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'XAUUSD', 'GBPUSD', 'EURUSD', 'USDCAD'];
23
+
24
+ async function test() {
25
+ console.log('--- Testing /api/market-data filter logic for all symbols ---');
26
+ for (const symbol of SYMBOLS) {
27
+ try {
28
+ const rawCandles = await fetchCandles(symbol, '15m');
29
+ console.log(`[${symbol}] Loaded ${rawCandles.length} raw candles.`);
30
+
31
+ // Let's run it through the filterForexOutlierWicks function
32
+ const filtered = filterForexOutlierWicks(rawCandles, symbol);
33
+ console.log(`[${symbol}] Filtered successfully. Count: ${filtered.length}`);
34
+ } catch (err) {
35
+ console.error(`[${symbol}] FAILED with error:`, err);
36
+ }
37
+ }
38
+ }
39
+
40
+ test();
scratch/testTelegram.js ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // scratch/testTelegram.js
2
+ import dotenv from 'dotenv';
3
+ dotenv.config();
4
+
5
+ const token = process.env.TELEGRAM_BOT_TOKEN;
6
+ const chatId = process.env.TELEGRAM_CHAT_ID;
7
+
8
+ console.log('=== Telegram Bot Diagnostics ===');
9
+ console.log('Loaded bot token:', token ? `${token.substring(0, 10)}... (configured)` : 'MISSING');
10
+ console.log('Loaded chat ID:', chatId ? `${chatId}... (configured)` : 'MISSING');
11
+
12
+ if (!token || !chatId) {
13
+ console.error('\nCRITICAL: TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID must be set in environment variables or your HF secrets.');
14
+ process.exit(1);
15
+ }
16
+
17
+ async function sendTestMessage() {
18
+ const url = `https://api.telegram.org/bot${token}/sendMessage`;
19
+ const msg = `🔔 <b>Trade Analyzer Connection Test</b>\n` +
20
+ `Status: 🟢 Connected successfully!\n` +
21
+ `Time: <code>${new Date().toLocaleString()}</code>\n` +
22
+ `This confirms your Telegram configuration is working.`;
23
+
24
+ console.log('\nSending test message to Telegram...');
25
+ try {
26
+ const res = await fetch(url, {
27
+ method: 'POST',
28
+ headers: {
29
+ 'Content-Type': 'application/json',
30
+ },
31
+ body: JSON.stringify({
32
+ chat_id: chatId,
33
+ text: msg,
34
+ parse_mode: 'HTML',
35
+ }),
36
+ });
37
+
38
+ const data = await res.json();
39
+ if (res.ok && data.ok) {
40
+ console.log('✅ Success! Message sent. Check your Telegram chat/channel.');
41
+ } else {
42
+ console.error('❌ Telegram API Error:', data.description || 'Unknown error');
43
+ }
44
+ } catch (err) {
45
+ console.error('❌ Network/Connection Error:', err.message);
46
+ }
47
+ }
48
+
49
+ sendTestMessage();
scratch/testUSDCAD.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import WebSocket from 'ws';
2
+ globalThis.WebSocket = WebSocket;
3
+
4
+ // Polyfill fetch exactly like server.js
5
+ const originalFetch = globalThis.fetch;
6
+ globalThis.fetch = async (input, init) => {
7
+ let url = typeof input === 'string' ? input : input.url;
8
+
9
+ if (url.startsWith('/api/swissquote')) {
10
+ url = 'https://forex-data-feed.swissquote.com/public-quotes/bboquotes/instrument/XAU/USD';
11
+ } else if (url.startsWith('/api/yahoo')) {
12
+ url = 'https://query1.finance.yahoo.com' + url.replace(/^\/api\/yahoo/, '');
13
+ init = init || {};
14
+ init.headers = {
15
+ ...init.headers,
16
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
17
+ 'Origin': 'https://finance.yahoo.com',
18
+ 'Referer': 'https://finance.yahoo.com',
19
+ };
20
+ }
21
+
22
+ return originalFetch(url, init);
23
+ };
24
+
25
+ // Import our API
26
+ import { fetchCandles } from '../src/data/yahooFinanceAPI.js';
27
+
28
+ async function test() {
29
+ console.log('--- Printing Last 10 USDCAD Candles ---');
30
+ try {
31
+ const candles = await fetchCandles('USDCAD', '15m');
32
+ console.log(`Fetched ${candles.length} candles.`);
33
+ const last10 = candles.slice(-10);
34
+ last10.forEach((c, idx) => {
35
+ console.log(`[${idx}] Time: ${new Date(c.time * 1000).toLocaleString()}, Open: ${c.open.toFixed(5)}, High: ${c.high.toFixed(5)}, Low: ${c.low.toFixed(5)}, Close: ${c.close.toFixed(5)}, Volume: ${c.volume}`);
36
+ });
37
+ } catch (err) {
38
+ console.error('Test failed:', err);
39
+ }
40
+ }
41
+
42
+ test();
server.js CHANGED
@@ -66,12 +66,10 @@ app.use(express.json());
66
 
67
  const PORT = process.env.PORT || 7860;
68
  const TIMEFRAME = process.env.AUTOPILOT_TIMEFRAME || '15m';
69
- const SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'XAUUSD', 'GBPUSD', 'EURUSD', 'USDCAD'];
70
 
71
- // Filter active autopilot symbols based on timeframe to maximize returns and eliminate noise
72
- const AUTOPILOT_SYMBOLS = TIMEFRAME === '1H'
73
- ? ['BTCUSDT', 'XAUUSD', 'GBPUSD', 'EURUSD', 'USDCAD']
74
- : ['BTCUSDT', 'XAUUSD', 'GBPUSD', 'USDCAD'];
75
 
76
  // Per-symbol Twelve Data API keys (OANDA-grade forex/metals that match TradingView).
77
  // Read from environment so keys stay server-side and out of the public repo/bundle.
@@ -80,7 +78,6 @@ const TWELVEDATA_KEYS = {
80
  XAUUSD: process.env.TWELVEDATA_KEY_XAUUSD,
81
  GBPUSD: process.env.TWELVEDATA_KEY_GBPUSD,
82
  USDCAD: process.env.TWELVEDATA_KEY_USDCAD,
83
- EURUSD: process.env.TWELVEDATA_KEY_EURUSD,
84
  };
85
  const getTdKey = (symbol) => TWELVEDATA_KEYS[symbol] || null;
86
 
 
66
 
67
  const PORT = process.env.PORT || 7860;
68
  const TIMEFRAME = process.env.AUTOPILOT_TIMEFRAME || '15m';
69
+ const SYMBOLS = ['BTCUSDT', 'XAUUSD', 'GBPUSD', 'USDCAD'];
70
 
71
+ // Filter active autopilot symbols to maximize returns and eliminate noise
72
+ const AUTOPILOT_SYMBOLS = ['BTCUSDT', 'XAUUSD', 'GBPUSD', 'USDCAD'];
 
 
73
 
74
  // Per-symbol Twelve Data API keys (OANDA-grade forex/metals that match TradingView).
75
  // Read from environment so keys stay server-side and out of the public repo/bundle.
 
78
  XAUUSD: process.env.TWELVEDATA_KEY_XAUUSD,
79
  GBPUSD: process.env.TWELVEDATA_KEY_GBPUSD,
80
  USDCAD: process.env.TWELVEDATA_KEY_USDCAD,
 
81
  };
82
  const getTdKey = (symbol) => TWELVEDATA_KEYS[symbol] || null;
83
 
src/analysis/exitManager.js CHANGED
@@ -13,9 +13,7 @@ import { rsi, ema, macd } from './indicators.js';
13
  */
14
  const CONTRACT_SPECS = {
15
  BTCUSDT: { type: 'crypto', pipValue: 1, pipSize: 1 },
16
- ETHUSDT: { type: 'crypto', pipValue: 1, pipSize: 1 },
17
  XAUUSD: { type: 'commodity', pipValue: 100, pipSize: 1 },
18
- EURUSD: { type: 'forex', pipValue: 10, pipSize: 0.0001 },
19
  GBPUSD: { type: 'forex', pipValue: 10, pipSize: 0.0001 },
20
  USDCAD: { type: 'forex_quote', pipValue: null, pipSize: 0.0001 },
21
  };
@@ -54,9 +52,7 @@ const CONTRACT_SPECS = {
54
  function getSpread(symbol) {
55
  switch (symbol) {
56
  case 'BTCUSDT': return 25.0;
57
- case 'ETHUSDT': return 5.0;
58
  case 'XAUUSD': return 0.7;
59
- case 'EURUSD':
60
  case 'GBPUSD':
61
  case 'USDCAD':
62
  return 0.00007; // 0.7 pips
@@ -93,7 +89,7 @@ export function analyzeExit(candles, trade) {
93
 
94
  // Guard against micro-stop division anomalies in older trades
95
  let minThreshold = 0.00005; // half a pip for Forex
96
- if (trade.symbol === 'BTCUSDT' || trade.symbol === 'ETHUSDT') {
97
  minThreshold = 0.1; // 10 cents for crypto
98
  } else if (trade.symbol === 'XAUUSD') {
99
  minThreshold = 0.05; // 5 cents for gold
 
13
  */
14
  const CONTRACT_SPECS = {
15
  BTCUSDT: { type: 'crypto', pipValue: 1, pipSize: 1 },
 
16
  XAUUSD: { type: 'commodity', pipValue: 100, pipSize: 1 },
 
17
  GBPUSD: { type: 'forex', pipValue: 10, pipSize: 0.0001 },
18
  USDCAD: { type: 'forex_quote', pipValue: null, pipSize: 0.0001 },
19
  };
 
52
  function getSpread(symbol) {
53
  switch (symbol) {
54
  case 'BTCUSDT': return 25.0;
 
55
  case 'XAUUSD': return 0.7;
 
56
  case 'GBPUSD':
57
  case 'USDCAD':
58
  return 0.00007; // 0.7 pips
 
89
 
90
  // Guard against micro-stop division anomalies in older trades
91
  let minThreshold = 0.00005; // half a pip for Forex
92
+ if (trade.symbol === 'BTCUSDT') {
93
  minThreshold = 0.1; // 10 cents for crypto
94
  } else if (trade.symbol === 'XAUUSD') {
95
  minThreshold = 0.05; // 5 cents for gold
src/analysis/signalGenerator.js CHANGED
@@ -17,9 +17,7 @@ import { rsi, ema, atr, macd } from './indicators.js';
17
  /** @type {Record<string, { type: string, pipValue: number|null, pipSize: number, label: string }>} */
18
  export const CONTRACT_SPECS = {
19
  BTCUSDT: { type: 'crypto', pipValue: 1, pipSize: 1, label: '$/coin' },
20
- ETHUSDT: { type: 'crypto', pipValue: 1, pipSize: 1, label: '$/coin' },
21
  XAUUSD: { type: 'commodity', pipValue: 100, pipSize: 1, label: '$/point' },
22
- EURUSD: { type: 'forex', pipValue: 10, pipSize: 0.0001, label: '$/pip' },
23
  GBPUSD: { type: 'forex', pipValue: 10, pipSize: 0.0001, label: '$/pip' },
24
  USDCAD: { type: 'forex_quote', pipValue: null, pipSize: 0.0001, label: '$/pip (dynamic)' },
25
  };
@@ -33,9 +31,7 @@ export const MAX_DAILY_LOSSES = 3;
33
  /** ATR multipliers config for Stop Loss buffers. */
34
  export const ATR_MULTIPLIERS = {
35
  BTCUSDT: 1.5,
36
- ETHUSDT: 1.5,
37
  XAUUSD: 0.5,
38
- EURUSD: 0.5,
39
  GBPUSD: 0.5,
40
  USDCAD: 0.5,
41
  };
@@ -53,9 +49,7 @@ export const ATR_CONFIG = {
53
  function getSpread(symbol) {
54
  switch (symbol) {
55
  case 'BTCUSDT': return 25.0;
56
- case 'ETHUSDT': return 5.0;
57
  case 'XAUUSD': return 0.7;
58
- case 'EURUSD':
59
  case 'GBPUSD':
60
  case 'USDCAD':
61
  return 0.00007; // 0.7 pips
@@ -137,8 +131,6 @@ export function calculateLotSize(symbol, entryPrice, slPrice, currentRate = null
137
  maxLots = 0.14;
138
  } else if (symbol === 'XAUUSD') {
139
  maxLots = 0.3;
140
- } else if (symbol === 'ETHUSDT') {
141
- maxLots = 3.0;
142
  } else if (spec.type === 'forex' || spec.type === 'forex_quote') {
143
  maxLots = 3.0;
144
  }
@@ -205,7 +197,7 @@ export function generateSignals(candles, symbol, dailyLossCount = 0) {
205
  const lastCandle = candles[candles.length - 1];
206
 
207
  // Option B: Session-based Kill Zones for Forex and Gold
208
- const isCrypto = symbol === 'BTCUSDT' || symbol === 'ETHUSDT';
209
  if (!isCrypto) {
210
  const lastCandleDate = new Date(lastCandle.time * 1000);
211
  const utcHour = lastCandleDate.getUTCHours();
@@ -379,10 +371,8 @@ function getAtrMultiplier(symbol, timeframe) {
379
  if (isHigherTimeframe) {
380
  switch (symbol) {
381
  case 'BTCUSDT': return 2.5;
382
- case 'ETHUSDT': return 1.5;
383
  case 'XAUUSD': return 0.5;
384
  case 'GBPUSD': return 2.0;
385
- case 'EURUSD': return 1.0;
386
  case 'USDCAD': return 2.5;
387
  default: return 0.5;
388
  }
@@ -390,10 +380,8 @@ function getAtrMultiplier(symbol, timeframe) {
390
  // 15m, 5m, 1m
391
  switch (symbol) {
392
  case 'BTCUSDT': return 1.5;
393
- case 'ETHUSDT': return 1.5;
394
  case 'XAUUSD': return 1.5;
395
  case 'GBPUSD': return 2.0;
396
- case 'EURUSD': return 0.5;
397
  case 'USDCAD': return 1.0;
398
  default: return 0.5;
399
  }
 
17
  /** @type {Record<string, { type: string, pipValue: number|null, pipSize: number, label: string }>} */
18
  export const CONTRACT_SPECS = {
19
  BTCUSDT: { type: 'crypto', pipValue: 1, pipSize: 1, label: '$/coin' },
 
20
  XAUUSD: { type: 'commodity', pipValue: 100, pipSize: 1, label: '$/point' },
 
21
  GBPUSD: { type: 'forex', pipValue: 10, pipSize: 0.0001, label: '$/pip' },
22
  USDCAD: { type: 'forex_quote', pipValue: null, pipSize: 0.0001, label: '$/pip (dynamic)' },
23
  };
 
31
  /** ATR multipliers config for Stop Loss buffers. */
32
  export const ATR_MULTIPLIERS = {
33
  BTCUSDT: 1.5,
 
34
  XAUUSD: 0.5,
 
35
  GBPUSD: 0.5,
36
  USDCAD: 0.5,
37
  };
 
49
  function getSpread(symbol) {
50
  switch (symbol) {
51
  case 'BTCUSDT': return 25.0;
 
52
  case 'XAUUSD': return 0.7;
 
53
  case 'GBPUSD':
54
  case 'USDCAD':
55
  return 0.00007; // 0.7 pips
 
131
  maxLots = 0.14;
132
  } else if (symbol === 'XAUUSD') {
133
  maxLots = 0.3;
 
 
134
  } else if (spec.type === 'forex' || spec.type === 'forex_quote') {
135
  maxLots = 3.0;
136
  }
 
197
  const lastCandle = candles[candles.length - 1];
198
 
199
  // Option B: Session-based Kill Zones for Forex and Gold
200
+ const isCrypto = symbol === 'BTCUSDT';
201
  if (!isCrypto) {
202
  const lastCandleDate = new Date(lastCandle.time * 1000);
203
  const utcHour = lastCandleDate.getUTCHours();
 
371
  if (isHigherTimeframe) {
372
  switch (symbol) {
373
  case 'BTCUSDT': return 2.5;
 
374
  case 'XAUUSD': return 0.5;
375
  case 'GBPUSD': return 2.0;
 
376
  case 'USDCAD': return 2.5;
377
  default: return 0.5;
378
  }
 
380
  // 15m, 5m, 1m
381
  switch (symbol) {
382
  case 'BTCUSDT': return 1.5;
 
383
  case 'XAUUSD': return 1.5;
384
  case 'GBPUSD': return 2.0;
 
385
  case 'USDCAD': return 1.0;
386
  default: return 0.5;
387
  }
src/chart/chartManager.js CHANGED
@@ -137,7 +137,7 @@ export class ChartManager {
137
  this.lastClose = candles[candles.length - 1].close;
138
 
139
  // Configure exact precision based on asset class
140
- const isForex = ['EURUSD', 'GBPUSD', 'USDCAD'].includes(symbol);
141
  const precision = isForex ? 5 : 2;
142
  const minMove = isForex ? 0.00001 : 0.01;
143
 
@@ -260,7 +260,7 @@ export class ChartManager {
260
  if (!orderBlocks || orderBlocks.length === 0 || !candles || candles.length === 0) return;
261
 
262
  const lastClose = candles[candles.length - 1].close;
263
- const isCrypto = this.symbol === 'BTCUSDT' || this.symbol === 'ETHUSDT';
264
  const limitPct = isCrypto ? 0.12 : 0.03; // 12% for crypto, 3% for forex/gold
265
  const maxDiff = lastClose * limitPct;
266
 
@@ -308,7 +308,7 @@ export class ChartManager {
308
  if (!zones || zones.length === 0) return;
309
 
310
  const lastClose = this.lastClose || zones[0].top;
311
- const isCrypto = this.symbol === 'BTCUSDT' || this.symbol === 'ETHUSDT';
312
  const limitPct = isCrypto ? 0.12 : 0.03;
313
  const maxDiff = lastClose * limitPct;
314
 
 
137
  this.lastClose = candles[candles.length - 1].close;
138
 
139
  // Configure exact precision based on asset class
140
+ const isForex = ['GBPUSD', 'USDCAD'].includes(symbol);
141
  const precision = isForex ? 5 : 2;
142
  const minMove = isForex ? 0.00001 : 0.01;
143
 
 
260
  if (!orderBlocks || orderBlocks.length === 0 || !candles || candles.length === 0) return;
261
 
262
  const lastClose = candles[candles.length - 1].close;
263
+ const isCrypto = this.symbol === 'BTCUSDT';
264
  const limitPct = isCrypto ? 0.12 : 0.03; // 12% for crypto, 3% for forex/gold
265
  const maxDiff = lastClose * limitPct;
266
 
 
308
  if (!zones || zones.length === 0) return;
309
 
310
  const lastClose = this.lastClose || zones[0].top;
311
+ const isCrypto = this.symbol === 'BTCUSDT';
312
  const limitPct = isCrypto ? 0.12 : 0.03;
313
  const maxDiff = lastClose * limitPct;
314
 
src/components/analysisPanel.js CHANGED
@@ -512,7 +512,6 @@ export class AnalysisPanel {
512
 
513
  switch (symbol) {
514
  case 'BTCUSDT':
515
- case 'ETHUSDT':
516
  lots = 50 / slDist;
517
  risk = slDist * lots;
518
  distLabel = `$${slDist.toFixed(2)}`;
@@ -522,7 +521,6 @@ export class AnalysisPanel {
522
  risk = slDist * lots * 100;
523
  distLabel = `${slDist.toFixed(2)} pts`;
524
  break;
525
- case 'EURUSD':
526
  case 'GBPUSD':
527
  const pips = slDist / 0.0001;
528
  lots = 50 / (pips * 10);
@@ -549,9 +547,7 @@ export class AnalysisPanel {
549
  maxLots = 0.14;
550
  } else if (symbol === 'XAUUSD') {
551
  maxLots = 0.3;
552
- } else if (symbol === 'ETHUSDT') {
553
- maxLots = 3.0;
554
- } else if (['EURUSD', 'GBPUSD', 'USDCAD'].includes(symbol)) {
555
  maxLots = 3.0;
556
  }
557
 
@@ -560,13 +556,11 @@ export class AnalysisPanel {
560
  // Recalculate precise actual risk based on final clamped lot size
561
  switch (symbol) {
562
  case 'BTCUSDT':
563
- case 'ETHUSDT':
564
  risk = slDist * lots;
565
  break;
566
  case 'XAUUSD':
567
  risk = slDist * lots * 100;
568
  break;
569
- case 'EURUSD':
570
  case 'GBPUSD':
571
  risk = (slDist / 0.0001) * lots * 10;
572
  break;
@@ -614,7 +608,7 @@ export class AnalysisPanel {
614
  _getDecimals(symbol) {
615
  const sym = symbol || this.currentSymbol;
616
  if (sym === 'XAUUSD') return 2;
617
- if (sym === 'BTCUSDT' || sym === 'ETHUSDT') return 2;
618
  return 5;
619
  }
620
 
 
512
 
513
  switch (symbol) {
514
  case 'BTCUSDT':
 
515
  lots = 50 / slDist;
516
  risk = slDist * lots;
517
  distLabel = `$${slDist.toFixed(2)}`;
 
521
  risk = slDist * lots * 100;
522
  distLabel = `${slDist.toFixed(2)} pts`;
523
  break;
 
524
  case 'GBPUSD':
525
  const pips = slDist / 0.0001;
526
  lots = 50 / (pips * 10);
 
547
  maxLots = 0.14;
548
  } else if (symbol === 'XAUUSD') {
549
  maxLots = 0.3;
550
+ } else if (['GBPUSD', 'USDCAD'].includes(symbol)) {
 
 
551
  maxLots = 3.0;
552
  }
553
 
 
556
  // Recalculate precise actual risk based on final clamped lot size
557
  switch (symbol) {
558
  case 'BTCUSDT':
 
559
  risk = slDist * lots;
560
  break;
561
  case 'XAUUSD':
562
  risk = slDist * lots * 100;
563
  break;
 
564
  case 'GBPUSD':
565
  risk = (slDist / 0.0001) * lots * 10;
566
  break;
 
608
  _getDecimals(symbol) {
609
  const sym = symbol || this.currentSymbol;
610
  if (sym === 'XAUUSD') return 2;
611
+ if (sym === 'BTCUSDT') return 2;
612
  return 5;
613
  }
614
 
src/components/header.js CHANGED
@@ -139,7 +139,6 @@ export class Header {
139
  _getDecimals(symbol) {
140
  if (symbol === 'XAUUSD') return 2;
141
  if (symbol === 'BTCUSDT') return 2;
142
- if (symbol === 'ETHUSDT') return 2;
143
  return 5; // forex pairs
144
  }
145
 
 
139
  _getDecimals(symbol) {
140
  if (symbol === 'XAUUSD') return 2;
141
  if (symbol === 'BTCUSDT') return 2;
 
142
  return 5; // forex pairs
143
  }
144
 
src/components/tradeManager.js CHANGED
@@ -617,7 +617,7 @@ export class TradeManager {
617
 
618
  getSymbolStats() {
619
  const stats = {};
620
- const symbols = ['BTCUSDT', 'ETHUSDT', 'XAUUSD', 'GBPUSD', 'EURUSD', 'USDCAD'];
621
  for (const sym of symbols) {
622
  stats[sym] = {
623
  total: 0,
 
617
 
618
  getSymbolStats() {
619
  const stats = {};
620
+ const symbols = ['BTCUSDT', 'XAUUSD', 'GBPUSD', 'USDCAD'];
621
  for (const sym of symbols) {
622
  stats[sym] = {
623
  total: 0,
src/data/dataManager.js CHANGED
@@ -23,10 +23,8 @@ import { ServerStream } from './serverStream.js';
23
  */
24
  export const SYMBOL_CONFIG = {
25
  BTCUSDT: { provider: 'yahoo', wsSymbol: 'BTC-USD', displayName: 'BTC/USDT', type: 'crypto' },
26
- ETHUSDT: { provider: 'yahoo', wsSymbol: 'ETH-USD', displayName: 'ETH/USDT', type: 'crypto' },
27
  XAUUSD: { provider: 'yahoo', wsSymbol: 'GC=F', displayName: 'XAU/USD', type: 'commodity' },
28
  GBPUSD: { provider: 'yahoo', wsSymbol: 'GBPUSD=X', displayName: 'GBP/USD', type: 'forex' },
29
- EURUSD: { provider: 'yahoo', wsSymbol: 'EURUSD=X', displayName: 'EUR/USD', type: 'forex' },
30
  USDCAD: { provider: 'yahoo', wsSymbol: 'USDCAD=X', displayName: 'USD/CAD', type: 'forex' },
31
  };
32
 
 
23
  */
24
  export const SYMBOL_CONFIG = {
25
  BTCUSDT: { provider: 'yahoo', wsSymbol: 'BTC-USD', displayName: 'BTC/USDT', type: 'crypto' },
 
26
  XAUUSD: { provider: 'yahoo', wsSymbol: 'GC=F', displayName: 'XAU/USD', type: 'commodity' },
27
  GBPUSD: { provider: 'yahoo', wsSymbol: 'GBPUSD=X', displayName: 'GBP/USD', type: 'forex' },
 
28
  USDCAD: { provider: 'yahoo', wsSymbol: 'USDCAD=X', displayName: 'USD/CAD', type: 'forex' },
29
  };
30
 
src/data/twelveDataAPI.js CHANGED
@@ -12,11 +12,9 @@
12
  // App symbol -> Twelve Data symbol (forex/metals use slash notation).
13
  const TD_SYMBOL = {
14
  XAUUSD: 'XAU/USD',
15
- EURUSD: 'EUR/USD',
16
  GBPUSD: 'GBP/USD',
17
  USDCAD: 'USD/CAD',
18
  BTCUSDT: 'BTC/USD',
19
- ETHUSDT: 'ETH/USD',
20
  };
21
 
22
  // App timeframe -> Twelve Data interval.
 
12
  // App symbol -> Twelve Data symbol (forex/metals use slash notation).
13
  const TD_SYMBOL = {
14
  XAUUSD: 'XAU/USD',
 
15
  GBPUSD: 'GBP/USD',
16
  USDCAD: 'USD/CAD',
17
  BTCUSDT: 'BTC/USD',
 
18
  };
19
 
20
  // App timeframe -> Twelve Data interval.
src/data/yahooFinanceAPI.js CHANGED
@@ -8,10 +8,8 @@ const YAHOO_BASE = '/api/yahoo/v8/finance/chart';
8
 
9
  export const YAHOO_SYMBOL_MAP = {
10
  BTCUSDT: 'BTC-USD', // Bitcoin Spot
11
- ETHUSDT: 'ETH-USD', // Ethereum Spot
12
  XAUUSD: 'GC=F', // Gold Futures
13
  GBPUSD: 'GBPUSD=X', // Spot GBP/USD
14
- EURUSD: 'EURUSD=X', // Spot EUR/USD
15
  USDCAD: 'USDCAD=X', // Spot USD/CAD
16
  };
17
 
@@ -85,7 +83,7 @@ const TIMEFRAME_CONFIG = {
85
  * @returns {Array} filtered candles (forex only; other symbols returned unchanged)
86
  */
87
  export function filterForexOutlierWicks(candles, symbol) {
88
- if (!['EURUSD', 'GBPUSD', 'USDCAD'].includes(symbol) || !candles || candles.length === 0) {
89
  return candles;
90
  }
91
 
 
8
 
9
  export const YAHOO_SYMBOL_MAP = {
10
  BTCUSDT: 'BTC-USD', // Bitcoin Spot
 
11
  XAUUSD: 'GC=F', // Gold Futures
12
  GBPUSD: 'GBPUSD=X', // Spot GBP/USD
 
13
  USDCAD: 'USDCAD=X', // Spot USD/CAD
14
  };
15
 
 
83
  * @returns {Array} filtered candles (forex only; other symbols returned unchanged)
84
  */
85
  export function filterForexOutlierWicks(candles, symbol) {
86
+ if (!['GBPUSD', 'USDCAD'].includes(symbol) || !candles || candles.length === 0) {
87
  return candles;
88
  }
89
 
src/main.js CHANGED
@@ -29,9 +29,7 @@ let history = [];
29
  let lastTickPrice = null;
30
  const livePrices = {
31
  BTCUSDT: 0,
32
- ETHUSDT: 0,
33
  XAUUSD: 0,
34
- EURUSD: 0,
35
  GBPUSD: 0,
36
  USDCAD: 1.38, // approximate fallback
37
  };
 
29
  let lastTickPrice = null;
30
  const livePrices = {
31
  BTCUSDT: 0,
 
32
  XAUUSD: 0,
 
33
  GBPUSD: 0,
34
  USDCAD: 1.38, // approximate fallback
35
  };