Pre-refactor: commit before converting indicators to self-contained files
This commit is contained in:
@ -1,140 +0,0 @@
|
||||
import { downloadFile } from '../utils/index.js';
|
||||
|
||||
export function showExportDialog() {
|
||||
if (!window.lastSimulationResults) {
|
||||
alert('Please run a simulation first');
|
||||
return;
|
||||
}
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'dialog-overlay';
|
||||
overlay.onclick = () => closeExportDialog();
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
const dialog = document.createElement('div');
|
||||
dialog.className = 'export-dialog';
|
||||
dialog.id = 'exportDialog';
|
||||
dialog.innerHTML = `
|
||||
<div class="export-dialog-title">📥 Export Simulation Report</div>
|
||||
<div class="export-options">
|
||||
<label class="export-option">
|
||||
<input type="radio" name="exportFormat" value="csv" checked>
|
||||
<span>CSV (Trades list)</span>
|
||||
</label>
|
||||
<label class="export-option">
|
||||
<input type="radio" name="exportFormat" value="json">
|
||||
<span>JSON (Full data)</span>
|
||||
</label>
|
||||
<label class="export-option">
|
||||
<input type="radio" name="exportFormat" value="both">
|
||||
<span>Both CSV + JSON</span>
|
||||
</label>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button class="action-btn secondary" onclick="closeExportDialog()" style="flex: 1;">Cancel</button>
|
||||
<button class="action-btn primary" onclick="performExport()" style="flex: 1;">Export</button>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(dialog);
|
||||
}
|
||||
|
||||
export function closeExportDialog() {
|
||||
const overlay = document.querySelector('.dialog-overlay');
|
||||
const dialog = document.getElementById('exportDialog');
|
||||
if (overlay) overlay.remove();
|
||||
if (dialog) dialog.remove();
|
||||
}
|
||||
|
||||
export function performExport() {
|
||||
const format = document.querySelector('input[name="exportFormat"]:checked').value;
|
||||
const sim = window.lastSimulationResults;
|
||||
const config = sim.config || {};
|
||||
const dateStr = new Date().toISOString().slice(0, 10);
|
||||
const baseFilename = generateSimulationName(config).replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||
|
||||
if (format === 'csv' || format === 'both') {
|
||||
exportToCSV(sim, `${baseFilename}.csv`);
|
||||
}
|
||||
|
||||
if (format === 'json' || format === 'both') {
|
||||
exportToJSON(sim, `${baseFilename}.json`);
|
||||
}
|
||||
|
||||
closeExportDialog();
|
||||
}
|
||||
|
||||
function generateSimulationName(config) {
|
||||
if (!config) return 'Unnamed Simulation';
|
||||
|
||||
const start = new Date(config.startDate);
|
||||
const now = new Date();
|
||||
const duration = now - start;
|
||||
const oneDay = 24 * 60 * 60 * 1000;
|
||||
|
||||
let dateStr;
|
||||
if (duration < oneDay) {
|
||||
dateStr = start.toISOString().slice(0, 16).replace('T', ' ');
|
||||
} else {
|
||||
dateStr = start.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
return `${config.strategyName}_${config.timeframe}_${dateStr}`;
|
||||
}
|
||||
|
||||
function exportToCSV(simulation, filename) {
|
||||
const results = simulation.results || simulation;
|
||||
const config = simulation.config || {};
|
||||
|
||||
let csv = 'Trade #,Entry Time,Exit Time,Entry Price,Exit Price,Size,P&L ($),P&L (%),Type\n';
|
||||
|
||||
(results.trades || []).forEach((trade, i) => {
|
||||
csv += `${i + 1},${trade.entryTime},${trade.exitTime},${trade.entryPrice},${trade.exitPrice},${trade.size},${trade.pnl},${trade.pnlPct},${trade.type}\n`;
|
||||
});
|
||||
|
||||
csv += '\n';
|
||||
csv += 'Summary\n';
|
||||
csv += `Strategy,${config.strategyName || 'Unknown'}\n`;
|
||||
csv += `Timeframe,${config.timeframe || 'Unknown'}\n`;
|
||||
csv += `Start Date,${config.startDate || 'Unknown'}\n`;
|
||||
csv += `Total Trades,${results.total_trades || 0}\n`;
|
||||
csv += `Win Rate (%),${(results.win_rate || 0).toFixed(2)}\n`;
|
||||
csv += `Total P&L ($),${(results.total_pnl || 0).toFixed(2)}\n`;
|
||||
csv += `Risk % per Trade,${config.riskPercent || 2}\n`;
|
||||
csv += `Stop Loss %,${config.stopLossPercent || 2}\n`;
|
||||
|
||||
downloadFile(csv, filename, 'text/csv');
|
||||
}
|
||||
|
||||
function exportToJSON(simulation, filename) {
|
||||
const exportData = {
|
||||
metadata: {
|
||||
exported_at: new Date().toISOString(),
|
||||
version: '1.0'
|
||||
},
|
||||
configuration: simulation.config || {},
|
||||
results: {
|
||||
summary: {
|
||||
total_trades: simulation.total_trades || simulation.results?.total_trades || 0,
|
||||
win_rate: simulation.win_rate || simulation.results?.win_rate || 0,
|
||||
total_pnl: simulation.total_pnl || simulation.results?.total_pnl || 0
|
||||
},
|
||||
trades: simulation.trades || simulation.results?.trades || [],
|
||||
equity_curve: simulation.equity_curve || []
|
||||
}
|
||||
};
|
||||
|
||||
downloadFile(JSON.stringify(exportData, null, 2), filename, 'application/json');
|
||||
}
|
||||
|
||||
export function exportSavedSimulation(id) {
|
||||
const sim = window.SimulationStorage?.get(id);
|
||||
if (!sim) {
|
||||
alert('Simulation not found');
|
||||
return;
|
||||
}
|
||||
|
||||
window.lastSimulationResults = sim;
|
||||
showExportDialog();
|
||||
}
|
||||
|
||||
window.generateSimulationName = generateSimulationName;
|
||||
@ -1,5 +1,3 @@
|
||||
import { HTSStrategyEngine } from '../strategies/hts-engine.js';
|
||||
|
||||
const HTS_COLORS = {
|
||||
fastHigh: '#00bcd4',
|
||||
fastLow: '#00bcd4',
|
||||
|
||||
@ -1,28 +1,5 @@
|
||||
export { TradingDashboard, refreshTA, openAIAnalysis } from './chart.js';
|
||||
export { toggleSidebar, restoreSidebarState } from './sidebar.js';
|
||||
export { SimulationStorage } from './storage.js';
|
||||
export { showExportDialog, closeExportDialog, performExport, exportSavedSimulation } from './export.js';
|
||||
export {
|
||||
runSimulation,
|
||||
displayEnhancedResults,
|
||||
showSimulationMarkers,
|
||||
clearSimulationMarkers,
|
||||
clearSimulationResults,
|
||||
getLastResults,
|
||||
setLastResults
|
||||
} from './simulation.js';
|
||||
export {
|
||||
renderStrategies,
|
||||
selectStrategy,
|
||||
renderStrategyParams,
|
||||
loadStrategies,
|
||||
saveSimulation,
|
||||
renderSavedSimulations,
|
||||
loadSavedSimulation,
|
||||
deleteSavedSimulation,
|
||||
getCurrentStrategy,
|
||||
setCurrentStrategy
|
||||
} from './strategies-panel.js';
|
||||
export {
|
||||
renderIndicatorList,
|
||||
addNewIndicator,
|
||||
|
||||
@ -295,9 +295,50 @@ function renderIndicatorConfig(indicator, meta) {
|
||||
<input type="range" min="1" max="5" value="${indicator.params._lineWidth || 2}" onchange="this.nextElementSibling.textContent = this.value; window.updateIndicatorSetting && window.updateIndicatorSetting('${indicator.id}', '_lineWidth', parseInt(this.value))">
|
||||
<span class="range-value">${indicator.params._lineWidth || 2}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
${window.dashboard?.indicatorSignals ? `
|
||||
<div class="config-section">
|
||||
<div class="section-subtitle">Signal Status</div>
|
||||
${(() => {
|
||||
const indSignal = window.dashboard.indicatorSignals.find(s => s.id === indicator.id);
|
||||
if (!indSignal) {
|
||||
return `<div class="config-row" style="color: var(--tv-text-secondary); font-size: 12px;">No signal data available</div>`;
|
||||
}
|
||||
|
||||
const signalType = indSignal.signal.toUpperCase();
|
||||
const signalIcon = signalType === 'BUY' ? '🟢' : signalType === 'SELL' ? '🔴' : '⚪';
|
||||
const signalColor = indSignal.color || 'var(--tv-text-secondary)';
|
||||
const signalDate = indSignal.lastSignalDate ? new Date(indSignal.lastSignalDate * 1000).toLocaleString() : 'Waiting for crossover...';
|
||||
|
||||
return `
|
||||
<div class="config-row" style="align-items: center;">
|
||||
<label style="flex: 0 0 80px;">Status</label>
|
||||
<span class="ta-signal ${indSignal.signal}" style="font-size: 12px; padding: 4px 12px; min-width: 70px; text-align: center; background: ${signalColor}; color: white; border-radius: 4px;">
|
||||
${signalIcon} ${signalType}
|
||||
</span>
|
||||
</div>
|
||||
<div class="config-row">
|
||||
<label style="flex: 0 0 80px;">Last Signal</label>
|
||||
<span style="color: var(--tv-text-primary); font-size: 12px;">${signalDate}</span>
|
||||
</div>
|
||||
<div class="config-row">
|
||||
<label style="flex: 0 0 80px;">Strength</label>
|
||||
<div style="display: flex; align-items: center; gap: 8px; flex: 1;">
|
||||
<input type="range" min="0" max="100" value="${indSignal.strength}" disabled style="width: 80px;">
|
||||
<span style="color: var(--tv-text-primary); font-size: 12px;">${indSignal.strength}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-row">
|
||||
<label style="flex: 0 0 80px;">Reasoning</label>
|
||||
<span style="color: var(--tv-text-primary); font-size: 11px;">${indSignal.reasoning}</span>
|
||||
</div>
|
||||
`;
|
||||
})()}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${meta?.inputs && meta.inputs.length > 0 ? `
|
||||
<div class="config-section">
|
||||
<div class="section-subtitle">Parameters</div>
|
||||
|
||||
@ -236,18 +236,16 @@ function calculateMASignal(indicator, lastCandle, prevCandle, values) {
|
||||
|
||||
let signalType, strength, reasoning;
|
||||
|
||||
if (close > ma * 1.02) {
|
||||
if (close > ma) {
|
||||
signalType = SIGNAL_TYPES.BUY;
|
||||
strength = Math.min(60 + ((close - ma) / ma) * 500, 100);
|
||||
reasoning = `Price (${close.toFixed(2)}) is strongly above ${maLabel} (${ma.toFixed(2)}), bullish trend`;
|
||||
} else if (close < ma * 0.98) {
|
||||
reasoning = `Price (${close.toFixed(2)}) is above ${maLabel} (${ma.toFixed(2)})`;
|
||||
} else if (close < ma) {
|
||||
signalType = SIGNAL_TYPES.SELL;
|
||||
strength = Math.min(60 + ((ma - close) / ma) * 500, 100);
|
||||
reasoning = `Price (${close.toFixed(2)}) is strongly below ${maLabel} (${ma.toFixed(2)}), bearish trend`;
|
||||
reasoning = `Price (${close.toFixed(2)}) is below ${maLabel} (${ma.toFixed(2)})`;
|
||||
} else {
|
||||
signalType = SIGNAL_TYPES.HOLD;
|
||||
strength = 30;
|
||||
reasoning = `Price (${close.toFixed(2)}) is near ${maLabel} (${ma.toFixed(2)}), sideways/consolidating`;
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log('[calculateMASignal] Result:', signalType, strength);
|
||||
@ -406,6 +404,36 @@ export function calculateAllIndicatorSignals() {
|
||||
|
||||
const signal = calculateIndicatorSignal(indicator, candles, values);
|
||||
|
||||
let currentSignal = signal;
|
||||
let lastSignalDate = indicator.lastSignalTimestamp || null;
|
||||
let lastSignalType = indicator.lastSignalType || null;
|
||||
|
||||
if (!currentSignal || !currentSignal.type) {
|
||||
console.log('[Signals] No valid signal for', indicator.type, '- Using last signal if available');
|
||||
|
||||
if (lastSignalType && lastSignalDate) {
|
||||
currentSignal = {
|
||||
type: lastSignalType,
|
||||
strength: 50,
|
||||
value: candles[candles.length - 1]?.close,
|
||||
reasoning: `No crossover (price equals MA)`
|
||||
};
|
||||
} else {
|
||||
console.log('[Signals] No previous signal available - Skipping');
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
const currentCandleTimestamp = candles[candles.length - 1].time;
|
||||
|
||||
if (currentSignal.type !== lastSignalType || !lastSignalType) {
|
||||
console.log('[Signals] Signal changed for', indicator.type, ':', lastSignalType, '->', currentSignal.type);
|
||||
lastSignalDate = currentCandleTimestamp;
|
||||
lastSignalType = currentSignal.type;
|
||||
indicator.lastSignalTimestamp = lastSignalDate;
|
||||
indicator.lastSignalType = lastSignalType;
|
||||
}
|
||||
}
|
||||
|
||||
const label = indicator.type?.toUpperCase();
|
||||
const params = indicator.params && typeof indicator.params === 'object'
|
||||
? Object.entries(indicator.params)
|
||||
@ -420,11 +448,12 @@ export function calculateAllIndicatorSignals() {
|
||||
label: label,
|
||||
params: params || null,
|
||||
type: indicator.type,
|
||||
signal: signal.type,
|
||||
strength: Math.round(signal.strength),
|
||||
value: signal.value,
|
||||
reasoning: signal.reasoning,
|
||||
color: SIGNAL_COLORS[signal.type]
|
||||
signal: currentSignal.type,
|
||||
strength: Math.round(currentSignal.strength),
|
||||
value: currentSignal.value,
|
||||
reasoning: currentSignal.reasoning,
|
||||
color: SIGNAL_COLORS[currentSignal.type],
|
||||
lastSignalDate: lastSignalDate
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,567 +0,0 @@
|
||||
import { ClientStrategyEngine } from '../strategies/index.js';
|
||||
import { HTSStrategyEngine } from '../strategies/hts-engine.js';
|
||||
import { addHTSVisualization } from './hts-visualizer.js';
|
||||
import { SimulationStorage } from './storage.js';
|
||||
import { downloadFile } from '../utils/index.js';
|
||||
import { showExportDialog, closeExportDialog, performExport } from './export.js';
|
||||
|
||||
let lastSimulationResults = null;
|
||||
|
||||
export function getLastResults() {
|
||||
return lastSimulationResults;
|
||||
}
|
||||
|
||||
export function setLastResults(results) {
|
||||
lastSimulationResults = results;
|
||||
window.lastSimulationResults = results;
|
||||
}
|
||||
|
||||
export async function runSimulation() {
|
||||
const strategyConfig = getStrategyConfig();
|
||||
if (!strategyConfig) {
|
||||
alert('Please select a strategy');
|
||||
return;
|
||||
}
|
||||
|
||||
const startDateInput = document.getElementById('simStartDate').value;
|
||||
if (!startDateInput) {
|
||||
alert('Please select a start date');
|
||||
return;
|
||||
}
|
||||
|
||||
const runBtn = document.getElementById('runSimBtn');
|
||||
runBtn.disabled = true;
|
||||
runBtn.textContent = '⏳ Running...';
|
||||
|
||||
try {
|
||||
const start = new Date(startDateInput);
|
||||
const fetchStart = new Date(start.getTime() - 200 * 24 * 60 * 60 * 1000);
|
||||
|
||||
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);
|
||||
|
||||
const timeframes = [interval];
|
||||
if (secondaryTF && secondaryTF !== '') {
|
||||
timeframes.push(secondaryTF);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
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[interval].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)
|
||||
}))
|
||||
};
|
||||
|
||||
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)
|
||||
}));
|
||||
}
|
||||
|
||||
const engineConfig = {
|
||||
id: strategyConfig.id,
|
||||
params: strategyConfig.params,
|
||||
timeframes: { primary: interval, secondary: secondaryTF ? [secondaryTF] : [] },
|
||||
indicators: []
|
||||
};
|
||||
|
||||
console.log('Building strategy config:');
|
||||
console.log(' Primary TF:', interval);
|
||||
console.log(' Secondary TF:', secondaryTF);
|
||||
console.log(' Available candles:', Object.keys(candlesMap));
|
||||
|
||||
let results;
|
||||
|
||||
if (strategyConfig.id === 'hts_trend') {
|
||||
console.log('Running HTS strategy simulation...');
|
||||
const htsEngine = new HTSStrategyEngine(strategyConfig.params);
|
||||
|
||||
let oneMinCandles = null;
|
||||
let h1hCandles = null;
|
||||
|
||||
if (strategyConfig.params?.useAutoHTS) {
|
||||
console.log('Fetching 1m candles for Auto HTS...');
|
||||
const oneMinQuery = `symbol=BTC&interval=1m&start=${fetchStart.toISOString()}&limit=10000`;
|
||||
const oneMinResponse = await fetch(`/api/v1/candles?${oneMinQuery}`);
|
||||
if (oneMinResponse.ok) {
|
||||
const oneMinData = await oneMinResponse.json();
|
||||
if (oneMinData.candles && oneMinData.candles.length > 0) {
|
||||
oneMinCandles = oneMinData.candles.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),
|
||||
volume: parseFloat(c.volume)
|
||||
}));
|
||||
console.log(`Got ${oneMinCandles.length} 1m candles`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strategyConfig.params?.use1HFilter) {
|
||||
console.log('Fetching 1H candles for HTS 1H filter...');
|
||||
const h1hQuery = `symbol=BTC&interval=1h&start=${fetchStart.toISOString()}&limit=500`;
|
||||
const h1hResponse = await fetch(`/api/v1/candles?${h1hQuery}`);
|
||||
if (h1hResponse.ok) {
|
||||
const h1hData = await h1hResponse.json();
|
||||
if (h1hData.candles && h1hData.candles.length > 0) {
|
||||
h1hCandles = h1hData.candles.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),
|
||||
volume: parseFloat(c.volume)
|
||||
}));
|
||||
console.log(`Got ${h1hCandles.length} 1H candles`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results = htsEngine.runSimulation(candlesMap[interval], oneMinCandles, h1hCandles);
|
||||
} else {
|
||||
if (strategyConfig.id === 'ma_trend') {
|
||||
const period = strategyConfig.params?.period || 44;
|
||||
engineConfig.indicators.push({
|
||||
name: `ma${period}`,
|
||||
type: 'sma',
|
||||
params: { period: period },
|
||||
timeframe: interval
|
||||
});
|
||||
if (secondaryTF) {
|
||||
engineConfig.indicators.push({
|
||||
name: `ma${period}_${secondaryTF}`,
|
||||
type: 'sma',
|
||||
params: { period: period },
|
||||
timeframe: secondaryTF
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log(' Indicators configured:', engineConfig.indicators.map(i => `${i.name} on ${i.timeframe}`));
|
||||
|
||||
const riskConfig = {
|
||||
positionSizing: { method: 'percent', value: riskPercent },
|
||||
stopLoss: { enabled: true, method: 'percent', value: stopLossPercent }
|
||||
};
|
||||
|
||||
const engine = new ClientStrategyEngine();
|
||||
results = engine.run(candlesMap, engineConfig, riskConfig, start);
|
||||
}
|
||||
|
||||
if (results.error) throw new Error(results.error);
|
||||
|
||||
setLastResults({
|
||||
...results,
|
||||
config: {
|
||||
strategyId: strategyConfig.id,
|
||||
strategyName: window.availableStrategies?.find(s => s.id === strategyConfig.id)?.name || strategyConfig.id,
|
||||
timeframe: interval,
|
||||
secondaryTimeframe: secondaryTF,
|
||||
startDate: startDateInput,
|
||||
riskPercent: riskPercent,
|
||||
stopLossPercent: stopLossPercent,
|
||||
params: strategyConfig.params
|
||||
},
|
||||
runAt: new Date().toISOString()
|
||||
});
|
||||
|
||||
displayEnhancedResults(lastSimulationResults);
|
||||
|
||||
document.getElementById('resultsSection').style.display = 'block';
|
||||
|
||||
if (window.dashboard && candlesMap[interval]) {
|
||||
const chartData = candlesMap[interval].map(c => ({
|
||||
time: c.time,
|
||||
open: c.open,
|
||||
high: c.high,
|
||||
low: c.low,
|
||||
close: c.close
|
||||
}));
|
||||
window.dashboard.candleSeries.setData(chartData);
|
||||
window.dashboard.allData.set(interval, chartData);
|
||||
console.log(`Chart updated with ${chartData.length} candles from simulation range`);
|
||||
}
|
||||
|
||||
showSimulationMarkers();
|
||||
|
||||
if (strategyConfig.id === 'hts_trend' && results.htsData && window.dashboard) {
|
||||
try {
|
||||
console.log('Visualizing HTS channels...');
|
||||
const candles = window.dashboard.allData.get(interval) || candlesMap[interval];
|
||||
htsVisualizer = addHTSVisualization(
|
||||
window.dashboard.chart,
|
||||
window.dashboard.candleSeries,
|
||||
results.htsData,
|
||||
candles,
|
||||
strategyConfig.params?.useAutoHTS
|
||||
);
|
||||
console.log('HTS channels visualized');
|
||||
} catch (e) {
|
||||
console.error('Error visualizing HTS channels:', e);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Simulation error:', error);
|
||||
alert('Simulation error: ' + error.message);
|
||||
} finally {
|
||||
runBtn.disabled = false;
|
||||
runBtn.textContent = '▶ Run Simulation';
|
||||
}
|
||||
}
|
||||
|
||||
export function displayEnhancedResults(simulation) {
|
||||
const results = simulation.results || simulation;
|
||||
|
||||
document.getElementById('simTrades').textContent = results.total_trades || '0';
|
||||
document.getElementById('simWinRate').textContent = (results.win_rate || 0).toFixed(1) + '%';
|
||||
|
||||
const pnl = results.total_pnl || 0;
|
||||
const pnlElement = document.getElementById('simPnL');
|
||||
pnlElement.textContent = (pnl >= 0 ? '+' : '') + '$' + pnl.toFixed(2);
|
||||
pnlElement.style.color = pnl >= 0 ? '#4caf50' : '#f44336';
|
||||
|
||||
let grossProfit = 0;
|
||||
let grossLoss = 0;
|
||||
(results.trades || []).forEach(trade => {
|
||||
if (trade.pnl > 0) grossProfit += trade.pnl;
|
||||
else grossLoss += Math.abs(trade.pnl);
|
||||
});
|
||||
const profitFactor = grossLoss > 0 ? (grossProfit / grossLoss).toFixed(2) : grossProfit > 0 ? '∞' : '0';
|
||||
document.getElementById('simProfitFactor').textContent = profitFactor;
|
||||
|
||||
drawEquitySparkline(results);
|
||||
}
|
||||
|
||||
function drawEquitySparkline(results) {
|
||||
const container = document.getElementById('equitySparkline');
|
||||
if (!container || !results.trades || results.trades.length === 0) {
|
||||
container.innerHTML = '<div style="text-align: center; color: var(--tv-text-secondary); padding: 20px; font-size: 11px;">No trades</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let equity = 1000;
|
||||
const equityData = [{ time: results.trades[0].entryTime, equity: equity }];
|
||||
|
||||
results.trades.forEach(trade => {
|
||||
equity += trade.pnl;
|
||||
equityData.push({ time: trade.exitTime, equity: equity });
|
||||
});
|
||||
|
||||
if (lastSimulationResults) {
|
||||
lastSimulationResults.equity_curve = equityData;
|
||||
}
|
||||
|
||||
container.innerHTML = '<canvas id="sparklineCanvas" width="300" height="60"></canvas>';
|
||||
const canvas = document.getElementById('sparklineCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
const minEquity = Math.min(...equityData.map(d => d.equity));
|
||||
const maxEquity = Math.max(...equityData.map(d => d.equity));
|
||||
const range = maxEquity - minEquity || 1;
|
||||
|
||||
ctx.strokeStyle = equityData[equityData.length - 1].equity >= equityData[0].equity ? '#4caf50' : '#f44336';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
|
||||
equityData.forEach((point, i) => {
|
||||
const x = (i / (equityData.length - 1)) * canvas.width;
|
||||
const y = canvas.height - ((point.equity - minEquity) / range) * canvas.height;
|
||||
|
||||
if (i === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
});
|
||||
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = '#888';
|
||||
ctx.font = '9px sans-serif';
|
||||
ctx.fillText('$' + equityData[0].equity.toFixed(0), 2, canvas.height - 2);
|
||||
ctx.fillText('$' + equityData[equityData.length - 1].equity.toFixed(0), canvas.width - 30, 10);
|
||||
}
|
||||
|
||||
let tradeLineSeries = [];
|
||||
let htsVisualizer = null;
|
||||
let tradeMarkerSeries = [];
|
||||
|
||||
export function showSimulationMarkers() {
|
||||
const results = getLastResults();
|
||||
|
||||
if (!results || !window.dashboard) {
|
||||
console.warn('Cannot show markers: no results or dashboard');
|
||||
return;
|
||||
}
|
||||
|
||||
const trades = results.trades || results.results?.trades || [];
|
||||
const markers = [];
|
||||
|
||||
clearSimulationMarkers();
|
||||
|
||||
console.log('Plotting trades:', trades.length);
|
||||
console.log('Dashboard check:', {
|
||||
dashboard: !!window.dashboard,
|
||||
chart: !!window.dashboard?.chart,
|
||||
candleSeries: !!window.dashboard?.candleSeries,
|
||||
setMarkers: typeof window.dashboard?.candleSeries?.setMarkers
|
||||
});
|
||||
|
||||
trades.forEach((trade, i) => {
|
||||
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);
|
||||
}
|
||||
|
||||
console.log(`[Trade ${i}] Entry: ${new Date(entryTime * 1000).toISOString()}, Exit: ${new Date(exitTime * 1000).toISOString()}`);
|
||||
console.log(` Entry Price: $${trade.entryPrice.toFixed(2)} | Exit Price: $${trade.exitPrice.toFixed(2)} | PnL: ${trade.pnl > 0 ? '+' : ''}$${trade.pnl.toFixed(2)} (${trade.pnlPct.toFixed(1)}%)`);
|
||||
|
||||
if (window.dashboard && window.dashboard.chart && window.dashboard.candleSeries) {
|
||||
try {
|
||||
const data = window.dashboard.candleSeries.data();
|
||||
if (!data || data.length === 0) {
|
||||
console.warn('No chart data available for markers');
|
||||
return;
|
||||
}
|
||||
|
||||
const chartTimeRange = {
|
||||
min: Math.min(...data.map(c => c.time)),
|
||||
max: Math.max(...data.map(c => c.time))
|
||||
};
|
||||
|
||||
if (entryTime < chartTimeRange.min || entryTime > chartTimeRange.max) {
|
||||
console.log(`Skipping trade ${i} - entry time outside chart range`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (exitTime < chartTimeRange.min || exitTime > chartTimeRange.max) {
|
||||
console.log(`Skipping trade ${i} - exit time outside chart range`);
|
||||
return;
|
||||
}
|
||||
|
||||
const buyMarkerSeries = window.dashboard.chart.addSeries(LightweightCharts.LineSeries, {
|
||||
color: '#2196f3',
|
||||
lineWidth: 2,
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false,
|
||||
crosshairMarkerVisible: false
|
||||
});
|
||||
|
||||
const entryPrice = trade.entryPrice;
|
||||
buyMarkerSeries.setData([
|
||||
{ time: entryTime, value: entryPrice },
|
||||
{ time: entryTime + 1, value: entryPrice }
|
||||
]);
|
||||
|
||||
tradeMarkerSeries.push({ series: buyMarkerSeries });
|
||||
|
||||
const pnlColor = trade.pnl > 0 ? '#4caf50' : '#f44336';
|
||||
|
||||
const sellMarkerSeries = window.dashboard.chart.addSeries(LightweightCharts.LineSeries, {
|
||||
color: pnlColor,
|
||||
lineWidth: 2,
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false,
|
||||
crosshairMarkerVisible: false
|
||||
});
|
||||
|
||||
const exitPrice = trade.exitPrice;
|
||||
sellMarkerSeries.setData([
|
||||
{ time: exitTime, value: exitPrice },
|
||||
{ time: exitTime + 1, value: exitPrice }
|
||||
]);
|
||||
|
||||
tradeMarkerSeries.push({ series: sellMarkerSeries });
|
||||
|
||||
const lineSeries = window.dashboard.chart.addSeries(LightweightCharts.LineSeries, {
|
||||
color: 'rgba(33, 150, 243, 0.3)',
|
||||
lineWidth: 1,
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false,
|
||||
crosshairMarkerVisible: false
|
||||
});
|
||||
|
||||
lineSeries.setData([
|
||||
{ time: entryTime, value: trade.entryPrice },
|
||||
{ time: exitTime, value: trade.exitPrice }
|
||||
]);
|
||||
|
||||
tradeLineSeries.push(lineSeries);
|
||||
} catch (e) {
|
||||
console.error(`Error adding marker for trade ${i}:`, e.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
markers.sort((a, b) => a.time - b.time);
|
||||
|
||||
const candleSeries = window.dashboard.candleSeries;
|
||||
if (candleSeries && typeof candleSeries.setMarkers === 'function') {
|
||||
try {
|
||||
console.log('Setting', markers.length, 'markers on candle series');
|
||||
|
||||
const currentData = candleSeries.data();
|
||||
const markerTimes = new Set(markers.map(m => m.time));
|
||||
const candleTimes = new Set(currentData.map(c => c.time));
|
||||
|
||||
const orphanMarkers = markers.filter(m => !candleTimes.has(m.time));
|
||||
if (orphanMarkers.length > 0) {
|
||||
console.warn(`${orphanMarkers.length} markers have timestamps not found in chart data:`);
|
||||
orphanMarkers.forEach(m => {
|
||||
console.warn(' - Time:', m.time, '(', new Date(m.time * 1000).toISOString(), ')');
|
||||
});
|
||||
console.log('First few candle times:', Array.from(candleTimes).slice(0, 5));
|
||||
console.log('Last few candle times:', Array.from(candleTimes).slice(-5));
|
||||
}
|
||||
|
||||
candleSeries.setMarkers(markers);
|
||||
console.log('Markers set successfully');
|
||||
} catch (e) {
|
||||
console.error('Error setting markers:', e);
|
||||
console.error('Marker methods available:', Object.getOwnPropertyNames(Object.getPrototypeOf(candleSeries)).filter(m => m.includes('marker')));
|
||||
}
|
||||
} else {
|
||||
console.warn('Markers not supported on this chart version - trades will be logged but not visualized on chart');
|
||||
}
|
||||
|
||||
console.log(`Plotted ${trades.length} trades with connection lines`);
|
||||
console.log(`Trade markers created for ${trades.length} positions`);
|
||||
}
|
||||
|
||||
export function clearSimulationMarkers() {
|
||||
try {
|
||||
if (window.dashboard && window.dashboard.candleSeries && typeof window.dashboard.candleSeries.setMarkers === 'function') {
|
||||
window.dashboard.candleSeries.setMarkers([]);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors clearing markers
|
||||
}
|
||||
|
||||
try {
|
||||
tradeLineSeries.forEach(series => {
|
||||
try {
|
||||
if (window.dashboard && window.dashboard.chart) {
|
||||
window.dashboard.chart.removeSeries(series);
|
||||
}
|
||||
} catch (e) {
|
||||
// Series might already be removed
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
// Ignore errors removing series
|
||||
}
|
||||
|
||||
tradeLineSeries = [];
|
||||
|
||||
try {
|
||||
if (htsVisualizer) {
|
||||
htsVisualizer.clear();
|
||||
htsVisualizer = null;
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors clearing HTS visualizer
|
||||
}
|
||||
|
||||
// Clear trade marker series
|
||||
try {
|
||||
tradeMarkerSeries.forEach(marker => {
|
||||
try {
|
||||
if (window.dashboard && window.dashboard.chart && marker.series) {
|
||||
window.dashboard.chart.removeSeries(marker.series);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
});
|
||||
tradeMarkerSeries = [];
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
export function clearSimulationResults() {
|
||||
clearSimulationMarkers();
|
||||
|
||||
setLastResults(null);
|
||||
|
||||
const resultsSection = document.getElementById('resultsSection');
|
||||
if (resultsSection) {
|
||||
resultsSection.style.display = 'none';
|
||||
}
|
||||
|
||||
const simTrades = document.getElementById('simTrades');
|
||||
const simWinRate = document.getElementById('simWinRate');
|
||||
const simPnL = document.getElementById('simPnL');
|
||||
const simProfitFactor = document.getElementById('simProfitFactor');
|
||||
const equitySparkline = document.getElementById('equitySparkline');
|
||||
|
||||
if (simTrades) simTrades.textContent = '0';
|
||||
if (simWinRate) simWinRate.textContent = '0%';
|
||||
if (simPnL) {
|
||||
simPnL.textContent = '$0.00';
|
||||
simPnL.style.color = '';
|
||||
}
|
||||
if (simProfitFactor) simProfitFactor.textContent = '0';
|
||||
if (equitySparkline) equitySparkline.innerHTML = '';
|
||||
}
|
||||
|
||||
function getStrategyConfig() {
|
||||
const strategyId = window.currentStrategy;
|
||||
if (!strategyId) return null;
|
||||
|
||||
const params = {};
|
||||
const paramDefs = window.StrategyParams?.[strategyId] || [];
|
||||
|
||||
paramDefs.forEach(def => {
|
||||
const input = document.getElementById(`param_${def.name}`);
|
||||
if (input) {
|
||||
params[def.name] = def.type === 'number' ? parseFloat(input.value) : input.value;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
id: strategyId,
|
||||
params: params
|
||||
};
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
export const SimulationStorage = {
|
||||
STORAGE_KEY: 'btc_bot_simulations',
|
||||
|
||||
getAll() {
|
||||
try {
|
||||
const data = localStorage.getItem(this.STORAGE_KEY);
|
||||
return data ? JSON.parse(data) : [];
|
||||
} catch (e) {
|
||||
console.error('Error reading simulations:', e);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
save(simulation) {
|
||||
try {
|
||||
const simulations = this.getAll();
|
||||
simulation.id = simulation.id || 'sim_' + Date.now();
|
||||
simulation.createdAt = new Date().toISOString();
|
||||
simulations.push(simulation);
|
||||
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(simulations));
|
||||
return simulation.id;
|
||||
} catch (e) {
|
||||
console.error('Error saving simulation:', e);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
delete(id) {
|
||||
try {
|
||||
let simulations = this.getAll();
|
||||
simulations = simulations.filter(s => s.id !== id);
|
||||
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(simulations));
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Error deleting simulation:', e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
get(id) {
|
||||
return this.getAll().find(s => s.id === id);
|
||||
},
|
||||
|
||||
clear() {
|
||||
localStorage.removeItem(this.STORAGE_KEY);
|
||||
}
|
||||
};
|
||||
@ -1,318 +0,0 @@
|
||||
import { StrategyParams } from '../strategies/config.js';
|
||||
|
||||
let currentStrategy = null;
|
||||
|
||||
export function getCurrentStrategy() {
|
||||
return currentStrategy;
|
||||
}
|
||||
|
||||
export function setCurrentStrategy(strategyId) {
|
||||
currentStrategy = strategyId;
|
||||
window.currentStrategy = strategyId;
|
||||
}
|
||||
|
||||
export function renderStrategies(strategies) {
|
||||
const container = document.getElementById('strategyList');
|
||||
|
||||
if (!strategies || strategies.length === 0) {
|
||||
container.innerHTML = '<div style="text-align: center; color: var(--tv-text-secondary); padding: 20px;">No strategies available</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = strategies.map((s, index) => `
|
||||
<div class="strategy-item ${index === 0 ? 'selected' : ''}" data-strategy-id="${s.id}" onclick="selectStrategy('${s.id}')">
|
||||
<input type="radio" name="strategy" class="strategy-radio" ${index === 0 ? 'checked' : ''}>
|
||||
<span class="strategy-name">${s.name}</span>
|
||||
<span class="strategy-info" title="${s.description}">ⓘ</span>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
if (strategies.length > 0) {
|
||||
selectStrategy(strategies[0].id);
|
||||
}
|
||||
|
||||
document.getElementById('runSimBtn').disabled = false;
|
||||
}
|
||||
|
||||
export function selectStrategy(strategyId) {
|
||||
document.querySelectorAll('.strategy-item').forEach(item => {
|
||||
item.classList.toggle('selected', item.dataset.strategyId === strategyId);
|
||||
const radio = item.querySelector('input[type="radio"]');
|
||||
if (radio) radio.checked = item.dataset.strategyId === strategyId;
|
||||
});
|
||||
|
||||
setCurrentStrategy(strategyId);
|
||||
renderStrategyParams(strategyId);
|
||||
}
|
||||
|
||||
export function renderStrategyParams(strategyId) {
|
||||
const container = document.getElementById('strategyParams');
|
||||
const params = StrategyParams[strategyId] || [];
|
||||
|
||||
if (params.length === 0) {
|
||||
container.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = params.map(param => `
|
||||
<div class="config-group">
|
||||
<label class="config-label">${param.label}</label>
|
||||
<input type="${param.type}"
|
||||
id="param_${param.name}"
|
||||
class="config-input"
|
||||
value="${param.default}"
|
||||
${param.min !== undefined ? `min="${param.min}"` : ''}
|
||||
${param.max !== undefined ? `max="${param.max}"` : ''}
|
||||
${param.step !== undefined ? `step="${param.step}"` : ''}
|
||||
>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
export 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?_=' + Date.now(), {
|
||||
signal: controller.signal
|
||||
});
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
let data = await response.json();
|
||||
console.log('Strategies loaded:', data);
|
||||
|
||||
if (!data.strategies) {
|
||||
throw new Error('Invalid response format: missing strategies array');
|
||||
}
|
||||
|
||||
data.strategies = data.strategies.filter(s => s.id !== 'hts_trend');
|
||||
|
||||
data.strategies.push({
|
||||
id: 'hts_trend',
|
||||
name: 'HTS Trend Strategy',
|
||||
description: 'Higher Timeframe Trend System with Auto HTS, 1H filters, and channel-based entries/exits',
|
||||
required_indicators: []
|
||||
});
|
||||
|
||||
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>`;
|
||||
}
|
||||
}
|
||||
|
||||
export function saveSimulation() {
|
||||
const results = getLastResults();
|
||||
if (!results) {
|
||||
alert('Please run a simulation first');
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultName = generateSimulationName(results.config);
|
||||
const name = prompt('Save simulation as:', defaultName);
|
||||
|
||||
if (!name || name.trim() === '') return;
|
||||
|
||||
const simulation = {
|
||||
name: name.trim(),
|
||||
config: results.config,
|
||||
results: {
|
||||
total_trades: results.total_trades,
|
||||
win_rate: results.win_rate,
|
||||
total_pnl: results.total_pnl,
|
||||
trades: results.trades,
|
||||
equity_curve: results.equity_curve
|
||||
}
|
||||
};
|
||||
|
||||
const id = window.SimulationStorage?.save(simulation);
|
||||
if (id) {
|
||||
renderSavedSimulations();
|
||||
alert('Simulation saved successfully!');
|
||||
} else {
|
||||
alert('Error saving simulation');
|
||||
}
|
||||
}
|
||||
|
||||
function generateSimulationName(config) {
|
||||
if (!config) return 'Unnamed Simulation';
|
||||
|
||||
const start = new Date(config.startDate);
|
||||
const now = new Date();
|
||||
const duration = now - start;
|
||||
const oneDay = 24 * 60 * 60 * 1000;
|
||||
|
||||
let dateStr;
|
||||
if (duration < oneDay) {
|
||||
dateStr = start.toISOString().slice(0, 16).replace('T', ' ');
|
||||
} else {
|
||||
dateStr = start.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
return `${config.strategyName}_${config.timeframe}_${dateStr}`;
|
||||
}
|
||||
|
||||
export function renderSavedSimulations() {
|
||||
const container = document.getElementById('savedSimulations');
|
||||
const simulations = window.SimulationStorage?.getAll() || [];
|
||||
|
||||
if (simulations.length === 0) {
|
||||
container.innerHTML = '<div style="text-align: center; color: var(--tv-text-secondary); padding: 10px; font-size: 12px;">No saved simulations</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = simulations.map(sim => `
|
||||
<div class="saved-sim-item">
|
||||
<span class="saved-sim-name" onclick="loadSavedSimulation('${sim.id}')" title="${sim.name}">
|
||||
${sim.name.length > 25 ? sim.name.slice(0, 25) + '...' : sim.name}
|
||||
</span>
|
||||
<div class="saved-sim-actions">
|
||||
<button class="sim-action-btn" onclick="loadSavedSimulation('${sim.id}')" title="Load">📂</button>
|
||||
<button class="sim-action-btn" onclick="exportSavedSimulation('${sim.id}')" title="Export">📥</button>
|
||||
<button class="sim-action-btn" onclick="deleteSavedSimulation('${sim.id}')" title="Delete">🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
export function loadSavedSimulation(id) {
|
||||
const sim = window.SimulationStorage?.get(id);
|
||||
if (!sim) {
|
||||
alert('Simulation not found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (sim.config) {
|
||||
document.getElementById('simSecondaryTF').value = sim.config.secondaryTimeframe || '';
|
||||
document.getElementById('simStartDate').value = sim.config.startDate || '';
|
||||
document.getElementById('simRiskPercent').value = sim.config.riskPercent || 2;
|
||||
document.getElementById('simStopLoss').value = sim.config.stopLossPercent || 2;
|
||||
|
||||
if (sim.config.strategyId) {
|
||||
selectStrategy(sim.config.strategyId);
|
||||
|
||||
if (sim.config.params) {
|
||||
Object.entries(sim.config.params).forEach(([key, value]) => {
|
||||
const input = document.getElementById(`param_${key}`);
|
||||
if (input) input.value = value;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setLastResults(sim);
|
||||
displayEnhancedResults(sim.results);
|
||||
document.getElementById('resultsSection').style.display = 'block';
|
||||
}
|
||||
|
||||
export function deleteSavedSimulation(id) {
|
||||
if (!confirm('Are you sure you want to delete this simulation?')) return;
|
||||
|
||||
if (window.SimulationStorage?.delete(id)) {
|
||||
renderSavedSimulations();
|
||||
}
|
||||
}
|
||||
|
||||
function displayEnhancedResults(simulation) {
|
||||
const results = simulation.results || simulation;
|
||||
|
||||
document.getElementById('simTrades').textContent = results.total_trades || '0';
|
||||
document.getElementById('simWinRate').textContent = (results.win_rate || 0).toFixed(1) + '%';
|
||||
|
||||
const pnl = results.total_pnl || 0;
|
||||
const pnlElement = document.getElementById('simPnL');
|
||||
pnlElement.textContent = (pnl >= 0 ? '+' : '') + '$' + pnl.toFixed(2);
|
||||
pnlElement.style.color = pnl >= 0 ? '#4caf50' : '#f44336';
|
||||
|
||||
let grossProfit = 0;
|
||||
let grossLoss = 0;
|
||||
(results.trades || []).forEach(trade => {
|
||||
if (trade.pnl > 0) grossProfit += trade.pnl;
|
||||
else grossLoss += Math.abs(trade.pnl);
|
||||
});
|
||||
const profitFactor = grossLoss > 0 ? (grossProfit / grossLoss).toFixed(2) : grossProfit > 0 ? '∞' : '0';
|
||||
document.getElementById('simProfitFactor').textContent = profitFactor;
|
||||
|
||||
drawEquitySparkline(results);
|
||||
}
|
||||
|
||||
function drawEquitySparkline(results) {
|
||||
const container = document.getElementById('equitySparkline');
|
||||
if (!container || !results.trades || results.trades.length === 0) {
|
||||
container.innerHTML = '<div style="text-align: center; color: var(--tv-text-secondary); padding: 20px; font-size: 11px;">No trades</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let equity = 1000;
|
||||
const equityData = [{ time: results.trades[0].entryTime, equity: equity }];
|
||||
|
||||
results.trades.forEach(trade => {
|
||||
equity += trade.pnl;
|
||||
equityData.push({ time: trade.exitTime, equity: equity });
|
||||
});
|
||||
|
||||
const sim = getLastResults();
|
||||
if (sim) {
|
||||
sim.equity_curve = equityData;
|
||||
}
|
||||
|
||||
container.innerHTML = '<canvas id="sparklineCanvas" width="300" height="60"></canvas>';
|
||||
const canvas = document.getElementById('sparklineCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
const minEquity = Math.min(...equityData.map(d => d.equity));
|
||||
const maxEquity = Math.max(...equityData.map(d => d.equity));
|
||||
const range = maxEquity - minEquity || 1;
|
||||
|
||||
ctx.strokeStyle = equityData[equityData.length - 1].equity >= equityData[0].equity ? '#4caf50' : '#f44336';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
|
||||
equityData.forEach((point, i) => {
|
||||
const x = (i / (equityData.length - 1)) * canvas.width;
|
||||
const y = canvas.height - ((point.equity - minEquity) / range) * canvas.height;
|
||||
|
||||
if (i === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
});
|
||||
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = '#888';
|
||||
ctx.font = '9px sans-serif';
|
||||
ctx.fillText('$' + equityData[0].equity.toFixed(0), 2, canvas.height - 2);
|
||||
ctx.fillText('$' + equityData[equityData.length - 1].equity.toFixed(0), canvas.width - 30, 10);
|
||||
}
|
||||
|
||||
function getLastResults() {
|
||||
return window.lastSimulationResults;
|
||||
}
|
||||
|
||||
function setLastResults(results) {
|
||||
window.lastSimulationResults = results;
|
||||
}
|
||||
|
||||
window.selectStrategy = selectStrategy;
|
||||
window.loadSavedSimulation = loadSavedSimulation;
|
||||
window.deleteSavedSimulation = deleteSavedSimulation;
|
||||
window.renderSavedSimulations = renderSavedSimulations;
|
||||
Reference in New Issue
Block a user