powinno działać
This commit is contained in:
@ -190,6 +190,7 @@
|
||||
background: var(--tv-panel-bg);
|
||||
border-top: 1px solid var(--tv-border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Right Sidebar - Strategy Simulation */
|
||||
@ -521,12 +522,52 @@
|
||||
background: rgba(0,0,0,0.7);
|
||||
z-index: 9999;
|
||||
}
|
||||
flex-direction: column;
|
||||
min-height: 200px;
|
||||
max-height: 400px;
|
||||
|
||||
/* Price Scale Controls */
|
||||
.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;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@ -947,10 +988,14 @@
|
||||
<div class="chart-area">
|
||||
<div class="chart-wrapper">
|
||||
<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 class="ta-panel" id="taPanel">
|
||||
<div class="ta-header">
|
||||
<div class="ha-header">
|
||||
<div class="ta-title">
|
||||
Technical Analysis
|
||||
<span class="ta-interval" id="taInterval">1D</span>
|
||||
@ -1006,22 +1051,7 @@
|
||||
</div>
|
||||
|
||||
<div class="config-group">
|
||||
<label class="config-label">Timeframe</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>
|
||||
<label class="config-label">Confirmation TF (Optional)</label>
|
||||
<select id="simSecondaryTF" class="config-input">
|
||||
<option value="">None</option>
|
||||
<option value="1h">1h</option>
|
||||
@ -1353,16 +1383,21 @@
|
||||
|
||||
// 1. Calculate Indicators for all TFs
|
||||
const indicatorResults = {};
|
||||
console.log('Calculating indicators for timeframes:', Object.keys(candlesMap));
|
||||
for (const tf in candlesMap) {
|
||||
indicatorResults[tf] = {};
|
||||
const tfCandles = candlesMap[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) {
|
||||
const IndicatorClass = this.indicatorTypes[ind.type];
|
||||
if (IndicatorClass) {
|
||||
const instance = new IndicatorClass(ind);
|
||||
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 trades = [];
|
||||
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
|
||||
const pointers = {};
|
||||
for (const tf in candlesMap) pointers[tf] = 0;
|
||||
|
||||
let processedCandles = 0;
|
||||
|
||||
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;
|
||||
|
||||
// Skip candles before simulation start (used for indicator warm-up)
|
||||
if (time < startTimeMs) {
|
||||
if (time < startTimeSec) {
|
||||
// Update pointers even for skipped candles
|
||||
for (const tf in candlesMap) {
|
||||
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]++;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
processedCandles++;
|
||||
|
||||
// Update pointers to current time
|
||||
for (const tf in candlesMap) {
|
||||
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]++;
|
||||
}
|
||||
}
|
||||
@ -1414,6 +1457,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Simulation complete: ${processedCandles} candles processed after start date, ${trades.length} trades`);
|
||||
|
||||
return {
|
||||
total_trades: trades.length,
|
||||
win_rate: (trades.filter(t => t.pnl > 0).length / (trades.length || 1)) * 100,
|
||||
@ -1437,6 +1482,12 @@
|
||||
if (!tfCandles) return null;
|
||||
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
|
||||
if (config.id === 'ma44_strategy') {
|
||||
@ -1445,7 +1496,15 @@
|
||||
|
||||
// Optional: Multi-TF trend filter
|
||||
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 (price > ma44 && trendOk) return 'BUY';
|
||||
@ -1456,8 +1515,21 @@
|
||||
if (config.id === 'ma125_strategy') {
|
||||
const ma125 = getVal('ma125', primaryTF);
|
||||
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 (price > ma125) return 'BUY';
|
||||
if (price > ma125 && trendOk) return 'BUY';
|
||||
if (price < ma125) return 'SELL';
|
||||
}
|
||||
}
|
||||
@ -1582,14 +1654,17 @@
|
||||
});
|
||||
|
||||
this.candleSeries = this.chart.addCandlestickSeries({
|
||||
upColor: '#26a69a',
|
||||
downColor: '#ef5350',
|
||||
borderUpColor: '#26a69a',
|
||||
borderDownColor: '#ef5350',
|
||||
wickUpColor: '#26a69a',
|
||||
wickDownColor: '#ef5350',
|
||||
upColor: '#ff9800',
|
||||
downColor: '#ff9800',
|
||||
borderUpColor: '#ff9800',
|
||||
borderDownColor: '#ff9800',
|
||||
wickUpColor: '#ff9800',
|
||||
wickDownColor: '#ff9800',
|
||||
});
|
||||
|
||||
// Initialize price scale controls
|
||||
this.initPriceScaleControls();
|
||||
|
||||
this.chart.timeScale().subscribeVisibleLogicalRangeChange(this.onVisibleRangeChange.bind(this));
|
||||
|
||||
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() {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON') return;
|
||||
@ -1742,17 +1904,22 @@
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Visible range:', visibleRange);
|
||||
|
||||
const data = this.candleSeries.data();
|
||||
if (!data || data.length === 0) {
|
||||
console.log('No data available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Load when user scrolls close to the left edge (earlier time)
|
||||
if (visibleRange.from < 20) {
|
||||
console.log('Near left edge, loading historical data...');
|
||||
// Check if we need to load more historical data
|
||||
// Load when user scrolls within 50 bars of the leftmost visible 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];
|
||||
if (oldestCandle) {
|
||||
this.loadHistoricalData(oldestCandle.time);
|
||||
@ -1761,17 +1928,52 @@
|
||||
}
|
||||
|
||||
async loadHistoricalData(beforeTime) {
|
||||
if (this.isLoading) return;
|
||||
if (this.isLoading) {
|
||||
console.log('Already loading, skipping...');
|
||||
return;
|
||||
}
|
||||
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 {
|
||||
// Fetch candles before the oldest visible 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
|
||||
|
||||
console.log(`Loading historical data before ${new Date((beforeTime - 1) * 1000).toISOString()}`);
|
||||
|
||||
const response = await fetch(
|
||||
`/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();
|
||||
|
||||
if (data.candles && data.candles.length > 0) {
|
||||
@ -1788,6 +1990,24 @@
|
||||
this.allData.set(this.currentInterval, 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`);
|
||||
} else {
|
||||
console.log('No more historical data available');
|
||||
@ -1872,6 +2092,16 @@
|
||||
</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.loadInitialData();
|
||||
this.loadTA();
|
||||
|
||||
// Update simulation panel timeframe display
|
||||
updateTimeframeDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2277,21 +2510,45 @@
|
||||
try {
|
||||
const start = new Date(startDateInput);
|
||||
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 riskPercent = parseFloat(document.getElementById('simRiskPercent').value);
|
||||
const stopLossPercent = parseFloat(document.getElementById('simStopLoss').value);
|
||||
|
||||
// Fetch data
|
||||
const response = await fetch(`/api/v1/candles/bulk?symbol=BTC&start=${fetchStart.toISOString()}&timeframes=${interval}&timeframes=${secondaryTF}`);
|
||||
const data = await response.json();
|
||||
const timeframes = [interval];
|
||||
if (secondaryTF && secondaryTF !== '') {
|
||||
timeframes.push(secondaryTF);
|
||||
}
|
||||
|
||||
if (!data.candles || !data.candles[interval]) {
|
||||
throw new Error('No candle data available');
|
||||
const query = new URLSearchParams({ symbol: 'BTC', start: fetchStart.toISOString() });
|
||||
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 = {
|
||||
[interval]: data.candles[interval].map(c => ({
|
||||
[interval]: data[interval].map(c => ({
|
||||
time: Math.floor(new Date(c.time).getTime() / 1000),
|
||||
open: parseFloat(c.open),
|
||||
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
|
||||
const engineConfig = {
|
||||
id: strategyConfig.id,
|
||||
@ -2307,23 +2575,50 @@
|
||||
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
|
||||
if (strategyConfig.id === 'ma44_strategy') {
|
||||
// Primary timeframe indicator
|
||||
engineConfig.indicators.push({
|
||||
name: 'ma44',
|
||||
type: 'sma',
|
||||
params: { period: strategyConfig.params.maPeriod || 44 },
|
||||
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') {
|
||||
// Primary timeframe indicator
|
||||
engineConfig.indicators.push({
|
||||
name: 'ma125',
|
||||
type: 'sma',
|
||||
params: { period: strategyConfig.params.maPeriod || 125 },
|
||||
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
|
||||
const riskConfig = {
|
||||
positionSizing: { method: 'percent', value: riskPercent },
|
||||
@ -2709,9 +3004,30 @@
|
||||
// Clear existing trade lines
|
||||
clearSimulationMarkers();
|
||||
|
||||
trades.forEach(trade => {
|
||||
const entryTime = Math.floor(new Date(trade.entryTime).getTime() / 1000);
|
||||
const exitTime = Math.floor(new Date(trade.exitTime).getTime() / 1000);
|
||||
console.log('Plotting trades:', trades.length);
|
||||
console.log('First trade entryTime:', trades[0]?.entryTime, 'type:', typeof trades[0]?.entryTime);
|
||||
|
||||
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 ? '+' : '';
|
||||
|
||||
// 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
|
||||
// ==========================================
|
||||
@ -2783,6 +3155,7 @@
|
||||
window.dashboard = new TradingDashboard();
|
||||
restoreSidebarState();
|
||||
setDefaultStartDate();
|
||||
updateTimeframeDisplay();
|
||||
renderSavedSimulations();
|
||||
|
||||
// Load strategies
|
||||
|
||||
Reference in New Issue
Block a user