powinno działać

This commit is contained in:
BTC Bot
2026-02-16 13:04:38 +01:00
parent c2e5b23b89
commit fcac738e64

View File

@ -190,6 +190,7 @@
background: var(--tv-panel-bg); background: var(--tv-panel-bg);
border-top: 1px solid var(--tv-border); border-top: 1px solid var(--tv-border);
display: flex; display: flex;
flex-direction: column;
} }
/* Right Sidebar - Strategy Simulation */ /* Right Sidebar - Strategy Simulation */
@ -521,12 +522,52 @@
background: rgba(0,0,0,0.7); background: rgba(0,0,0,0.7);
z-index: 9999; z-index: 9999;
} }
flex-direction: column;
min-height: 200px; /* Price Scale Controls */
max-height: 400px; .price-scale-controls {
position: absolute;
right: 10px;
bottom: 10px;
display: flex;
flex-direction: row;
gap: 2px;
z-index: 10;
opacity: 0;
transition: opacity 0.2s;
} }
.ta-header { .chart-wrapper:hover .price-scale-controls {
opacity: 1;
}
.ps-control-btn {
width: 20px;
height: 20px;
background: rgba(42, 46, 57, 0.9);
border: 1px solid #363c4e;
color: #d1d4dc;
font-size: 10px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border-radius: 2px;
transition: all 0.2s;
padding: 0;
}
.ps-control-btn:hover {
background: #363c4e;
border-color: #4a4f5e;
}
.ps-control-btn.active {
background: #2962ff;
border-color: #2962ff;
color: white;
}
.ha-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
@ -947,10 +988,14 @@
<div class="chart-area"> <div class="chart-area">
<div class="chart-wrapper"> <div class="chart-wrapper">
<div id="chart"></div> <div id="chart"></div>
<div class="price-scale-controls" id="priceScaleControls">
<button class="ps-control-btn auto-scale active" id="btnAutoScale" title="Auto Scale (A)">A</button>
<button class="ps-control-btn log-scale" id="btnLogScale" title="Logarithmic Scale">L</button>
</div>
</div> </div>
<div class="ta-panel" id="taPanel"> <div class="ta-panel" id="taPanel">
<div class="ta-header"> <div class="ha-header">
<div class="ta-title"> <div class="ta-title">
Technical Analysis Technical Analysis
<span class="ta-interval" id="taInterval">1D</span> <span class="ta-interval" id="taInterval">1D</span>
@ -1006,22 +1051,7 @@
</div> </div>
<div class="config-group"> <div class="config-group">
<label class="config-label">Timeframe</label> <label class="config-label">Confirmation TF (Optional)</label>
<select id="simTimeframe" class="config-input">
<option value="1m">1m</option>
<option value="3m">3m</option>
<option value="5m">5m</option>
<option value="15m">15m</option>
<option value="30m">30m</option>
<option value="37m" selected>37m</option>
<option value="1h">1h</option>
<option value="4h">4h</option>
<option value="1d">1d</option>
</select>
</div>
<div class="config-group">
<label class="config-label">Confirmation TF</label>
<select id="simSecondaryTF" class="config-input"> <select id="simSecondaryTF" class="config-input">
<option value="">None</option> <option value="">None</option>
<option value="1h">1h</option> <option value="1h">1h</option>
@ -1353,16 +1383,21 @@
// 1. Calculate Indicators for all TFs // 1. Calculate Indicators for all TFs
const indicatorResults = {}; const indicatorResults = {};
console.log('Calculating indicators for timeframes:', Object.keys(candlesMap));
for (const tf in candlesMap) { for (const tf in candlesMap) {
indicatorResults[tf] = {}; indicatorResults[tf] = {};
const tfCandles = candlesMap[tf]; const tfCandles = candlesMap[tf];
const tfIndicators = (strategyConfig.indicators || []).filter(ind => (ind.timeframe || primaryTF) === tf); const tfIndicators = (strategyConfig.indicators || []).filter(ind => (ind.timeframe || primaryTF) === tf);
console.log(` TF ${tf}: ${tfIndicators.length} indicators to calculate`);
for (const ind of tfIndicators) { for (const ind of tfIndicators) {
const IndicatorClass = this.indicatorTypes[ind.type]; const IndicatorClass = this.indicatorTypes[ind.type];
if (IndicatorClass) { if (IndicatorClass) {
const instance = new IndicatorClass(ind); const instance = new IndicatorClass(ind);
indicatorResults[tf][ind.name] = instance.calculate(tfCandles); indicatorResults[tf][ind.name] = instance.calculate(tfCandles);
const validValues = indicatorResults[tf][ind.name].filter(v => v !== null).length;
console.log(` Calculated ${ind.name} on ${tf}: ${validValues} valid values`);
} }
} }
} }
@ -1371,32 +1406,40 @@
const risk = new RiskManager(riskConfig); const risk = new RiskManager(riskConfig);
const trades = []; const trades = [];
let position = null; let position = null;
const startTimeMs = new Date(simulationStart).getTime(); // Convert simulation start to seconds (to match candle timestamps)
const startTimeSec = Math.floor(new Date(simulationStart).getTime() / 1000);
console.log('Simulation start (seconds):', startTimeSec, 'Date:', simulationStart);
console.log('Total candles available:', candles.length);
console.log('First candle time:', candles[0].time, 'Last candle time:', candles[candles.length - 1].time);
// Optimized Pointer-based Timeframe Alignment // Optimized Pointer-based Timeframe Alignment
const pointers = {}; const pointers = {};
for (const tf in candlesMap) pointers[tf] = 0; for (const tf in candlesMap) pointers[tf] = 0;
let processedCandles = 0;
for (let i = 1; i < candles.length; i++) { for (let i = 1; i < candles.length; i++) {
const time = new Date(candles[i].time).getTime(); const time = candles[i].time; // Already in seconds
const price = candles[i].close; const price = candles[i].close;
// Skip candles before simulation start (used for indicator warm-up) // Skip candles before simulation start (used for indicator warm-up)
if (time < startTimeMs) { if (time < startTimeSec) {
// Update pointers even for skipped candles // Update pointers even for skipped candles
for (const tf in candlesMap) { for (const tf in candlesMap) {
while (pointers[tf] < candlesMap[tf].length - 1 && while (pointers[tf] < candlesMap[tf].length - 1 &&
new Date(candlesMap[tf][pointers[tf] + 1].time).getTime() <= time) { candlesMap[tf][pointers[tf] + 1].time <= time) {
pointers[tf]++; pointers[tf]++;
} }
} }
continue; continue;
} }
processedCandles++;
// Update pointers to current time // Update pointers to current time
for (const tf in candlesMap) { for (const tf in candlesMap) {
while (pointers[tf] < candlesMap[tf].length - 1 && while (pointers[tf] < candlesMap[tf].length - 1 &&
new Date(candlesMap[tf][pointers[tf] + 1].time).getTime() <= time) { candlesMap[tf][pointers[tf] + 1].time <= time) {
pointers[tf]++; pointers[tf]++;
} }
} }
@ -1414,6 +1457,8 @@
} }
} }
console.log(`Simulation complete: ${processedCandles} candles processed after start date, ${trades.length} trades`);
return { return {
total_trades: trades.length, total_trades: trades.length,
win_rate: (trades.filter(t => t.pnl > 0).length / (trades.length || 1)) * 100, win_rate: (trades.filter(t => t.pnl > 0).length / (trades.length || 1)) * 100,
@ -1438,6 +1483,12 @@
return tfCandles[pointers[tf]].close; return tfCandles[pointers[tf]].close;
}; };
// Debug logging for first evaluation
if (index === 1) {
console.log('First candle time:', candles[index].time, 'Date:', new Date(candles[index].time * 1000));
console.log('MA44 value:', getVal('ma44', primaryTF));
}
// Simple logic for MVP strategies // Simple logic for MVP strategies
if (config.id === 'ma44_strategy') { if (config.id === 'ma44_strategy') {
const ma44 = getVal('ma44', primaryTF); const ma44 = getVal('ma44', primaryTF);
@ -1445,7 +1496,15 @@
// Optional: Multi-TF trend filter // Optional: Multi-TF trend filter
const secondaryTF = config.timeframes?.secondary?.[0]; const secondaryTF = config.timeframes?.secondary?.[0];
const trendOk = !secondaryTF || (getPrice(secondaryTF) > getVal(`ma44_${secondaryTF}`, secondaryTF)); let trendOk = true;
if (secondaryTF) {
const secondaryPrice = getPrice(secondaryTF);
const secondaryMA = getVal(`ma44_${secondaryTF}`, secondaryTF);
trendOk = secondaryPrice > secondaryMA;
if (index === 1) {
console.log(`Trend check: ${secondaryTF} price=${secondaryPrice}, MA=${secondaryMA}, trendOk=${trendOk}`);
}
}
if (ma44) { if (ma44) {
if (price > ma44 && trendOk) return 'BUY'; if (price > ma44 && trendOk) return 'BUY';
@ -1456,8 +1515,21 @@
if (config.id === 'ma125_strategy') { if (config.id === 'ma125_strategy') {
const ma125 = getVal('ma125', primaryTF); const ma125 = getVal('ma125', primaryTF);
const price = candles[index].close; const price = candles[index].close;
// Optional: Multi-TF trend filter
const secondaryTF = config.timeframes?.secondary?.[0];
let trendOk = true;
if (secondaryTF) {
const secondaryPrice = getPrice(secondaryTF);
const secondaryMA = getVal(`ma125_${secondaryTF}`, secondaryTF);
trendOk = secondaryPrice > secondaryMA;
if (index === 1) {
console.log(`Trend check: ${secondaryTF} price=${secondaryPrice}, MA=${secondaryMA}, trendOk=${trendOk}`);
}
}
if (ma125) { if (ma125) {
if (price > ma125) return 'BUY'; if (price > ma125 && trendOk) return 'BUY';
if (price < ma125) return 'SELL'; if (price < ma125) return 'SELL';
} }
} }
@ -1582,14 +1654,17 @@
}); });
this.candleSeries = this.chart.addCandlestickSeries({ this.candleSeries = this.chart.addCandlestickSeries({
upColor: '#26a69a', upColor: '#ff9800',
downColor: '#ef5350', downColor: '#ff9800',
borderUpColor: '#26a69a', borderUpColor: '#ff9800',
borderDownColor: '#ef5350', borderDownColor: '#ff9800',
wickUpColor: '#26a69a', wickUpColor: '#ff9800',
wickDownColor: '#ef5350', wickDownColor: '#ff9800',
}); });
// Initialize price scale controls
this.initPriceScaleControls();
this.chart.timeScale().subscribeVisibleLogicalRangeChange(this.onVisibleRangeChange.bind(this)); this.chart.timeScale().subscribeVisibleLogicalRangeChange(this.onVisibleRangeChange.bind(this));
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
@ -1612,6 +1687,93 @@
}); });
} }
initPriceScaleControls() {
const btnAutoScale = document.getElementById('btnAutoScale');
const btnLogScale = document.getElementById('btnLogScale');
if (!btnAutoScale || !btnLogScale) return;
// Track current state
this.priceScaleState = {
autoScale: true,
logScale: false
};
// Auto Scale toggle
btnAutoScale.addEventListener('click', () => {
this.priceScaleState.autoScale = !this.priceScaleState.autoScale;
btnAutoScale.classList.toggle('active', this.priceScaleState.autoScale);
this.candleSeries.priceScale().applyOptions({
autoScale: this.priceScaleState.autoScale
});
console.log('Auto Scale:', this.priceScaleState.autoScale ? 'ON' : 'OFF');
});
// Log Scale toggle
btnLogScale.addEventListener('click', () => {
this.priceScaleState.logScale = !this.priceScaleState.logScale;
btnLogScale.classList.toggle('active', this.priceScaleState.logScale);
// Get current visible price range and time range before changing mode
let currentPriceRange = null;
let currentTimeRange = null;
if (!this.priceScaleState.autoScale) {
try {
currentPriceRange = this.candleSeries.priceScale().getVisiblePriceRange();
} catch (e) {
console.log('Could not get price range');
}
}
try {
currentTimeRange = this.chart.timeScale().getVisibleLogicalRange();
} catch (e) {
console.log('Could not get time range');
}
// Apply log/linear mode change
this.candleSeries.priceScale().applyOptions({
mode: this.priceScaleState.logScale ? LightweightCharts.PriceScaleMode.Logarithmic : LightweightCharts.PriceScaleMode.Normal
});
// Force chart recalculation and redraw
this.chart.applyOptions({});
// Restore ranges after mode change
setTimeout(() => {
// Restore time range (X-axis position)
if (currentTimeRange) {
try {
this.chart.timeScale().setVisibleLogicalRange(currentTimeRange);
} catch (e) {
console.log('Could not restore time range');
}
}
// Restore price range if auto-scale is off
if (!this.priceScaleState.autoScale && currentPriceRange) {
try {
this.candleSeries.priceScale().setVisiblePriceRange(currentPriceRange);
} catch (e) {
console.log('Could not restore price range');
}
}
}, 100);
console.log('Log Scale:', this.priceScaleState.logScale ? 'ON' : 'OFF');
});
// Keyboard shortcut 'A' for auto-scale
document.addEventListener('keydown', (e) => {
if (e.key === 'a' || e.key === 'A') {
if (e.target.tagName !== 'INPUT') {
btnAutoScale.click();
}
}
});
}
initEventListeners() { initEventListeners() {
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON') return; if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON') return;
@ -1742,17 +1904,22 @@
return; return;
} }
console.log('Visible range:', visibleRange);
const data = this.candleSeries.data(); const data = this.candleSeries.data();
if (!data || data.length === 0) { if (!data || data.length === 0) {
console.log('No data available'); console.log('No data available');
return; return;
} }
// Load when user scrolls close to the left edge (earlier time) // Check if we need to load more historical data
if (visibleRange.from < 20) { // Load when user scrolls within 50 bars of the leftmost visible data
console.log('Near left edge, loading historical data...'); const barsFromLeft = visibleRange.from;
const totalBars = data.length;
console.log('Visible range:', { from: visibleRange.from, to: visibleRange.to, barsFromLeft, totalBars });
// Trigger when within 50 bars of the earliest loaded data
if (barsFromLeft < 50) {
console.log('Near left edge (within 50 bars), loading historical data...');
const oldestCandle = data[0]; const oldestCandle = data[0];
if (oldestCandle) { if (oldestCandle) {
this.loadHistoricalData(oldestCandle.time); this.loadHistoricalData(oldestCandle.time);
@ -1761,17 +1928,52 @@
} }
async loadHistoricalData(beforeTime) { async loadHistoricalData(beforeTime) {
if (this.isLoading) return; if (this.isLoading) {
console.log('Already loading, skipping...');
return;
}
this.isLoading = true; this.isLoading = true;
console.log(`Loading historical data for ${this.currentInterval} before ${beforeTime}`);
// Get the timestamp of the leftmost visible candle to preserve view
const currentData = this.candleSeries.data();
const visibleRange = this.chart.timeScale().getVisibleLogicalRange();
let leftmostTime = null;
// Save current price range if autoScale is disabled
let savedPriceRange = null;
const isAutoScale = this.priceScaleState?.autoScale !== false;
if (!isAutoScale) {
try {
savedPriceRange = this.candleSeries.priceScale().getVisiblePriceRange();
console.log('Saving price range:', savedPriceRange);
} catch (e) {
console.log('Could not save price range');
}
}
if (visibleRange && currentData.length > 0) {
const leftmostIndex = Math.floor(visibleRange.from);
if (leftmostIndex >= 0 && leftmostIndex < currentData.length) {
leftmostTime = currentData[leftmostIndex].time;
}
}
try { try {
// Fetch candles before the oldest visible candle // Fetch candles before the oldest visible candle
// Use end parameter to get candles up to (but not including) the oldest candle // Use end parameter to get candles up to (but not including) the oldest candle
const endTime = new Date((beforeTime - 1) * 1000); // 1 second before oldest const endTime = new Date((beforeTime - 1) * 1000); // 1 second before oldest
console.log(`Loading historical data before ${new Date((beforeTime - 1) * 1000).toISOString()}`);
const response = await fetch( const response = await fetch(
`/api/v1/candles?symbol=BTC&interval=${this.currentInterval}&end=${endTime.toISOString()}&limit=500` `/api/v1/candles?symbol=BTC&interval=${this.currentInterval}&end=${endTime.toISOString()}&limit=500`
); );
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json(); const data = await response.json();
if (data.candles && data.candles.length > 0) { if (data.candles && data.candles.length > 0) {
@ -1788,6 +1990,24 @@
this.allData.set(this.currentInterval, mergedData); this.allData.set(this.currentInterval, mergedData);
this.candleSeries.setData(mergedData); this.candleSeries.setData(mergedData);
// Scroll back to the same timestamp to keep chart view static
if (leftmostTime) {
this.chart.timeScale().scrollToTime(leftmostTime);
}
// Restore price range if autoScale was disabled
if (!isAutoScale && savedPriceRange) {
setTimeout(() => {
try {
this.candleSeries.priceScale().setVisiblePriceRange(savedPriceRange);
console.log('Restored price range:', savedPriceRange);
} catch (e) {
console.log('Could not restore price range');
}
}, 50);
}
console.log(`Loaded ${chartData.length} historical candles`); console.log(`Loaded ${chartData.length} historical candles`);
} else { } else {
console.log('No more historical data available'); console.log('No more historical data available');
@ -1872,6 +2092,16 @@
</div> </div>
</div> </div>
<div class="ta-section">
<div class="ta-section-title">AI Insight</div>
<div style="font-size: 13px; margin-top: 8px;">
${data.ai_placeholder ? data.ai_placeholder.message : 'AI analysis available'}
</div>
<button class="ta-btn ai-btn" onclick="openAIAnalysis()" style="width: 100%; margin-top: 12px; font-size: 11px;">
Analyze
</button>
</div>
`; `;
} }
@ -1900,6 +2130,9 @@
this.allData.delete(interval); this.allData.delete(interval);
this.loadInitialData(); this.loadInitialData();
this.loadTA(); this.loadTA();
// Update simulation panel timeframe display
updateTimeframeDisplay();
} }
} }
@ -2277,21 +2510,45 @@
try { try {
const start = new Date(startDateInput); const start = new Date(startDateInput);
const fetchStart = new Date(start.getTime() - 200 * 24 * 60 * 60 * 1000); const fetchStart = new Date(start.getTime() - 200 * 24 * 60 * 60 * 1000);
const interval = document.getElementById('simTimeframe').value;
// Use chart's current timeframe
if (!window.dashboard) {
throw new Error('Dashboard not initialized');
}
const interval = window.dashboard.currentInterval;
const secondaryTF = document.getElementById('simSecondaryTF').value; const secondaryTF = document.getElementById('simSecondaryTF').value;
const riskPercent = parseFloat(document.getElementById('simRiskPercent').value); const riskPercent = parseFloat(document.getElementById('simRiskPercent').value);
const stopLossPercent = parseFloat(document.getElementById('simStopLoss').value); const stopLossPercent = parseFloat(document.getElementById('simStopLoss').value);
// Fetch data // Fetch data
const response = await fetch(`/api/v1/candles/bulk?symbol=BTC&start=${fetchStart.toISOString()}&timeframes=${interval}&timeframes=${secondaryTF}`); const timeframes = [interval];
const data = await response.json(); if (secondaryTF && secondaryTF !== '') {
timeframes.push(secondaryTF);
}
if (!data.candles || !data.candles[interval]) { const query = new URLSearchParams({ symbol: 'BTC', start: fetchStart.toISOString() });
throw new Error('No candle data available'); timeframes.forEach(tf => query.append('timeframes', tf));
console.log('Fetching candles with query:', query.toString());
const response = await fetch(`/api/v1/candles/bulk?${query.toString()}`);
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
const data = await response.json();
console.log('Candle data received:', data);
console.log('Looking for interval:', interval);
console.log('Available timeframes:', Object.keys(data));
// The API returns data directly by timeframe, not wrapped in 'candles' property
if (!data[interval] || data[interval].length === 0) {
throw new Error(`No candle data available for ${interval} timeframe. Check if data exists in database.`);
} }
const candlesMap = { const candlesMap = {
[interval]: data.candles[interval].map(c => ({ [interval]: data[interval].map(c => ({
time: Math.floor(new Date(c.time).getTime() / 1000), time: Math.floor(new Date(c.time).getTime() / 1000),
open: parseFloat(c.open), open: parseFloat(c.open),
high: parseFloat(c.high), high: parseFloat(c.high),
@ -2300,6 +2557,17 @@
})) }))
}; };
// Add secondary timeframe data if requested and available
if (secondaryTF && data[secondaryTF]) {
candlesMap[secondaryTF] = data[secondaryTF].map(c => ({
time: Math.floor(new Date(c.time).getTime() / 1000),
open: parseFloat(c.open),
high: parseFloat(c.high),
low: parseFloat(c.low),
close: parseFloat(c.close)
}));
}
// Build strategy config // Build strategy config
const engineConfig = { const engineConfig = {
id: strategyConfig.id, id: strategyConfig.id,
@ -2307,22 +2575,49 @@
indicators: [] indicators: []
}; };
console.log('Building strategy config:');
console.log(' Primary TF:', interval);
console.log(' Secondary TF:', secondaryTF);
console.log(' Available candles:', Object.keys(candlesMap));
// Add indicator based on strategy // Add indicator based on strategy
if (strategyConfig.id === 'ma44_strategy') { if (strategyConfig.id === 'ma44_strategy') {
// Primary timeframe indicator
engineConfig.indicators.push({ engineConfig.indicators.push({
name: 'ma44', name: 'ma44',
type: 'sma', type: 'sma',
params: { period: strategyConfig.params.maPeriod || 44 }, params: { period: strategyConfig.params.maPeriod || 44 },
timeframe: interval timeframe: interval
}); });
// Confirmation timeframe indicator (for trend filter)
if (secondaryTF) {
engineConfig.indicators.push({
name: `ma44_${secondaryTF}`,
type: 'sma',
params: { period: strategyConfig.params.maPeriod || 44 },
timeframe: secondaryTF
});
}
} else if (strategyConfig.id === 'ma125_strategy') { } else if (strategyConfig.id === 'ma125_strategy') {
// Primary timeframe indicator
engineConfig.indicators.push({ engineConfig.indicators.push({
name: 'ma125', name: 'ma125',
type: 'sma', type: 'sma',
params: { period: strategyConfig.params.maPeriod || 125 }, params: { period: strategyConfig.params.maPeriod || 125 },
timeframe: interval timeframe: interval
}); });
// Confirmation timeframe indicator (for trend filter)
if (secondaryTF) {
engineConfig.indicators.push({
name: `ma125_${secondaryTF}`,
type: 'sma',
params: { period: strategyConfig.params.maPeriod || 125 },
timeframe: secondaryTF
});
} }
}
console.log(' Indicators configured:', engineConfig.indicators.map(i => `${i.name} on ${i.timeframe}`));
// Run engine // Run engine
const riskConfig = { const riskConfig = {
@ -2709,9 +3004,30 @@
// Clear existing trade lines // Clear existing trade lines
clearSimulationMarkers(); clearSimulationMarkers();
trades.forEach(trade => { console.log('Plotting trades:', trades.length);
const entryTime = Math.floor(new Date(trade.entryTime).getTime() / 1000); console.log('First trade entryTime:', trades[0]?.entryTime, 'type:', typeof trades[0]?.entryTime);
const exitTime = Math.floor(new Date(trade.exitTime).getTime() / 1000);
trades.forEach((trade, i) => {
// Trade times are already in seconds (Unix timestamp)
// If they're strings (ISO dates), convert to seconds
let entryTime, exitTime;
if (typeof trade.entryTime === 'number') {
entryTime = trade.entryTime;
} else {
entryTime = Math.floor(new Date(trade.entryTime).getTime() / 1000);
}
if (typeof trade.exitTime === 'number') {
exitTime = trade.exitTime;
} else {
exitTime = Math.floor(new Date(trade.exitTime).getTime() / 1000);
}
if (i === 0) {
console.log('Converted entryTime:', entryTime, 'Date:', new Date(entryTime * 1000));
}
const pnlSymbol = trade.pnl > 0 ? '+' : ''; const pnlSymbol = trade.pnl > 0 ? '+' : '';
// Entry Marker - Buy signal (blue) // Entry Marker - Buy signal (blue)
@ -2776,6 +3092,62 @@
} }
} }
// ==========================================
// TIMEFRAME DISPLAY UPDATE
// ==========================================
function updateTimeframeDisplay() {
const display = document.getElementById('simTimeframeDisplay');
if (display && window.dashboard) {
display.value = window.dashboard.currentInterval.toUpperCase();
}
}
// ==========================================
// STRATEGY LOADING
// ==========================================
async function loadStrategies() {
try {
console.log('Fetching strategies from API...');
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
const response = await fetch('/api/v1/strategies', {
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Strategies loaded:', data);
if (!data.strategies) {
throw new Error('Invalid response format: missing strategies array');
}
window.availableStrategies = data.strategies;
renderStrategies(data.strategies);
} catch (error) {
console.error('Error loading strategies:', error);
let errorMessage = error.message;
if (error.name === 'AbortError') {
errorMessage = 'Request timeout - API server not responding';
} else if (error.message.includes('Failed to fetch')) {
errorMessage = 'Cannot connect to API server - is it running?';
}
document.getElementById('strategyList').innerHTML =
`<div style="color: var(--tv-red); padding: 20px; text-align: center;">
${errorMessage}<br>
<small style="color: var(--tv-text-secondary);">Check console (F12) for details</small>
</div>`;
}
}
// ========================================== // ==========================================
// INITIALIZATION // INITIALIZATION
// ========================================== // ==========================================
@ -2783,6 +3155,7 @@
window.dashboard = new TradingDashboard(); window.dashboard = new TradingDashboard();
restoreSidebarState(); restoreSidebarState();
setDefaultStartDate(); setDefaultStartDate();
updateTimeframeDisplay();
renderSavedSimulations(); renderSavedSimulations();
// Load strategies // Load strategies