Changed exports to use export { } syntax after function definitions
to avoid 'cannot access before initialization' error.
Moved export statements to end of file where all functions are defined.
720 lines
26 KiB
JavaScript
720 lines
26 KiB
JavaScript
import { getAvailableIndicators, IndicatorRegistry as IR } from '../indicators/index.js';
|
||
|
||
// State management
|
||
let activeIndicators = [];
|
||
let configuringId = null;
|
||
let searchQuery = '';
|
||
let selectedCategory = 'all';
|
||
let nextInstanceId = 1;
|
||
|
||
// Presets storage
|
||
let userPresets = JSON.parse(localStorage.getItem('indicator_presets') || '{}');
|
||
|
||
// Categories
|
||
const CATEGORIES = [
|
||
{ id: 'all', name: 'All Indicators', icon: '📊' },
|
||
{ id: 'trend', name: 'Trend', icon: '📊' },
|
||
{ id: 'momentum', name: 'Momentum', icon: '📈' },
|
||
{ id: 'volatility', name: 'Volatility', icon: '📉' },
|
||
{ id: 'volume', name: 'Volume', icon: '🔀' },
|
||
{ id: 'favorites', name: 'Favorites', icon: '★' }
|
||
];
|
||
|
||
const CATEGORY_MAP = {
|
||
sma: 'trend', ema: 'trend', hts: 'trend',
|
||
rsi: 'momentum', macd: 'momentum', stoch: 'momentum',
|
||
bb: 'volatility', atr: 'volatility',
|
||
others: 'volume'
|
||
};
|
||
|
||
const DEFAULT_COLORS = ['#2962ff', '#26a69a', '#ef5350', '#ff9800', '#9c27b0', '#00bcd4', '#ffeb3b', '#e91e63'];
|
||
const LINE_TYPES = ['solid', 'dotted', 'dashed'];
|
||
|
||
// Initialize
|
||
export function initIndicatorPanel() {
|
||
renderIndicatorPanel();
|
||
setupEventListeners();
|
||
}
|
||
|
||
function getDefaultColor(index) {
|
||
return DEFAULT_COLORS[index % DEFAULT_COLORS.length];
|
||
}
|
||
|
||
function getIndicatorCategory(indicator) {
|
||
return CATEGORY_MAP[indicator.type] || 'trend';
|
||
}
|
||
|
||
function getIndicatorLabel(indicator) {
|
||
const meta = getIndicatorMeta(indicator);
|
||
if (!meta) return indicator.name;
|
||
|
||
const paramParts = meta.inputs.map(input => {
|
||
const val = indicator.params[input.name];
|
||
if (val !== undefined && val !== input.default) return val;
|
||
return null;
|
||
}).filter(v => v !== null);
|
||
|
||
if (paramParts.length > 0) {
|
||
return `${indicator.name} (${paramParts.join(', ')})`;
|
||
}
|
||
return indicator.name;
|
||
}
|
||
|
||
function getIndicatorMeta(indicator) {
|
||
const IndicatorClass = IR?.[indicator.type];
|
||
if (!IndicatorClass) return null;
|
||
const instance = new IndicatorClass({ type: indicator.type, params: indicator.params, name: indicator.name });
|
||
return instance.getMetadata();
|
||
}
|
||
|
||
function groupPlotsByColor(plots) {
|
||
const groups = {};
|
||
plots.forEach((plot, idx) => {
|
||
const groupMap = {
|
||
'fast': 'Fast', 'slow': 'Slow', 'upper': 'Upper', 'lower': 'Lower',
|
||
'middle': 'Middle', 'basis': 'Middle', 'signal': 'Signal',
|
||
'histogram': 'Histogram', 'k': '%K', 'd': '%D'
|
||
};
|
||
const groupName = Object.entries(groupMap).find(([k, v]) => plot.id.toLowerCase().includes(k))?.[1] || plot.id;
|
||
if (!groups[groupName]) {
|
||
groups[groupName] = { name: groupName, indices: [], plots: [] };
|
||
}
|
||
groups[groupName].indices.push(idx);
|
||
groups[groupName].plots.push(plot);
|
||
});
|
||
return Object.values(groups);
|
||
}
|
||
|
||
export function getActiveIndicators() {
|
||
return activeIndicators;
|
||
}
|
||
|
||
export function setActiveIndicators(indicators) {
|
||
activeIndicators = indicators;
|
||
renderIndicatorPanel();
|
||
}
|
||
|
||
// Render main panel
|
||
export function renderIndicatorPanel() {
|
||
const container = document.getElementById('indicatorPanel');
|
||
if (!container) return;
|
||
|
||
const available = getAvailableIndicators();
|
||
const catalog = available.filter(ind => {
|
||
if (searchQuery && !ind.name.toLowerCase().includes(searchQuery.toLowerCase())) return false;
|
||
if (selectedCategory === 'all') return true;
|
||
if (selectedCategory === 'favorites') return false;
|
||
const cat = CATEGORY_MAP[ind.type] || 'trend';
|
||
return cat === selectedCategory;
|
||
});
|
||
|
||
const favoriteIds = new Set(userPresets.favorites || []);
|
||
|
||
container.innerHTML = `
|
||
<div class="indicator-panel">
|
||
<!-- Search Bar -->
|
||
<div class="indicator-search">
|
||
<span class="search-icon">🔍</span>
|
||
<input
|
||
type="text"
|
||
id="indicatorSearch"
|
||
placeholder="Search indicators..."
|
||
value="${searchQuery}"
|
||
autocomplete="off"
|
||
>
|
||
${searchQuery ? `<button class="search-clear" onclick="clearSearch()">×</button>` : ''}
|
||
</div>
|
||
|
||
<!-- Categories -->
|
||
<div class="category-tabs">
|
||
${CATEGORIES.map(cat => `
|
||
<button class="category-tab ${selectedCategory === cat.id ? 'active' : ''}" data-category="${cat.id}">
|
||
${cat.icon} ${cat.name}
|
||
</button>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<!-- Favorites (if any) -->
|
||
${favoriteIds.size > 0 ? `
|
||
<div class="indicator-section favorites">
|
||
<div class="section-title">★ Favorites</div>
|
||
${favoriteIds.map(id => {
|
||
const ind = available.find(a => {
|
||
// Find matching indicator by type
|
||
return a.type === id || (activeIndicators.find(ai => ai.id === id)?.type === '');
|
||
});
|
||
if (!ind) return '';
|
||
return renderIndicatorItem(ind, true);
|
||
}).join('')}
|
||
</div>
|
||
` : ''}
|
||
|
||
<!-- Active Indicators -->
|
||
${activeIndicators.length > 0 ? `
|
||
<div class="indicator-section active">
|
||
<div class="section-title">
|
||
${activeIndicators.length} Active
|
||
${activeIndicators.length > 0 ? `<button class="clear-all" onclick="clearAllIndicators()">Clear All</button>` : ''}
|
||
</div>
|
||
${activeIndicators.slice().reverse().map(ind => renderActiveIndicator(ind)).join('')}
|
||
</div>
|
||
` : ''}
|
||
|
||
<!-- Available Indicators -->
|
||
${catalog.length > 0 ? `
|
||
<div class="indicator-section catalog">
|
||
<div class="section-title">Available Indicators</div>
|
||
${catalog.map(ind => renderIndicatorItem(ind, false)).join('')}
|
||
</div>
|
||
` : `
|
||
<div class="no-results">
|
||
No indicators found
|
||
</div>
|
||
`}
|
||
</div>
|
||
`;
|
||
|
||
setupEventListeners();
|
||
}
|
||
|
||
function renderIndicatorItem(indicator, isFavorite) {
|
||
const colorDots = '';
|
||
|
||
return `
|
||
<div class="indicator-item ${isFavorite ? 'favorite' : ''}" data-type="${indicator.type}">
|
||
<div class="indicator-item-main">
|
||
<span class="indicator-name">${indicator.name}</span>
|
||
<span class="indicator-desc">${indicator.description || ''}</span>
|
||
</div>
|
||
<div class="indicator-actions">
|
||
<button class="indicator-btn add" title="Add to chart" onclick="window.addIndicator('${indicator.type}')">
|
||
+
|
||
</button>
|
||
${isFavorite ? '' : `
|
||
<button class="indicator-btn favorite" title="Add to favorites" onclick="window.toggleFavorite('${indicator.type}')">
|
||
${userPresets.favorites?.includes(indicator.type) ? '★' : '☆'}
|
||
</button>
|
||
`}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function renderActiveIndicator(indicator) {
|
||
const isExpanded = configuringId === indicator.id;
|
||
const meta = getIndicatorMeta(indicator);
|
||
const label = getIndicatorLabel(indicator);
|
||
const isFavorite = userPresets.favorites?.includes(indicator.type) || false;
|
||
|
||
return `
|
||
<div class="indicator-item active ${isExpanded ? 'expanded' : ''}" data-id="${indicator.id}">
|
||
<div class="indicator-item-main" onclick="window.toggleIndicatorExpand('${indicator.id}')">
|
||
<div class="drag-handle" title="Drag to reorder">⋮⋮</div>
|
||
<button class="indicator-btn visible" onclick="event.stopPropagation(); window.toggleIndicatorVisibility('${indicator.id}')" title="${indicator.visible !== false ? 'Hide' : 'Show'}">
|
||
${indicator.visible !== false ? '👁' : '👁🗨'}
|
||
</button>
|
||
<span class="indicator-name">${label}</span>
|
||
<div class="indicator-presets">
|
||
${meta.name && renderPresetIndicatorIndicator(meta, indicator)}
|
||
</div>
|
||
<button class="indicator-btn favorite" onclick="event.stopPropagation(); window.toggleFavorite('${indicator.type}')" title="Add to favorites">
|
||
${isFavorite ? '★' : '☆'}
|
||
</button>
|
||
<button class="indicator-btn expand ${isExpanded ? 'rotated' : ''}" title="Show settings">
|
||
${isExpanded ? '▼' : '▶'}
|
||
</button>
|
||
</div>
|
||
|
||
${isExpanded ? `
|
||
<div class="indicator-config">
|
||
${renderIndicatorConfig(indicator, meta)}
|
||
</div>
|
||
` : ''}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function renderPresetIndicatorIndicator(meta, indicator) {
|
||
const hasPresets = getPresetsForIndicator(meta.name);
|
||
if (!hasPresets || hasPresets.length === 0) return '';
|
||
|
||
return `<button class="preset-indicator" title="${hasPresets.length} saved presets" onclick="event.stopPropagation(); window.showPresets('${meta.name}')">💾</button>`;
|
||
}
|
||
|
||
function renderIndicatorConfig(indicator, meta) {
|
||
const plotGroups = groupPlotsByColor(meta?.plots || []);
|
||
|
||
return `
|
||
<div class="config-sections">
|
||
<!-- Colors -->
|
||
<div class="config-section">
|
||
<div class="section-subtitle">Visual Settings</div>
|
||
${plotGroups.map(group => {
|
||
const firstIdx = group.indices[0];
|
||
const color = indicator.params[`_color_${firstIdx}`] || meta.plots[firstIdx]?.color || getDefaultColor(activeIndicators.indexOf(indicator));
|
||
return `
|
||
<div class="config-row">
|
||
<label>${group.name} Color</label>
|
||
<div class="color-picker">
|
||
<input type="color" id="color_${indicator.id}_${firstIdx}" value="${color}" onchange="window.updateIndicatorColor('${indicator.id}', ${firstIdx}, this.value)">
|
||
<span class="color-preview" style="background: ${color};"></span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('')}
|
||
|
||
<div class="config-row">
|
||
<label>Line Type</label>
|
||
<select onchange="window.updateIndicatorSetting('${indicator.id}', '_lineType', this.value)">
|
||
${LINE_TYPES.map(lt => `<option value="${lt}" ${indicator.params._lineType === lt ? 'selected' : ''}>${lt.charAt(0).toUpperCase() + lt.slice(1)}</option>`).join('')}
|
||
</select>
|
||
</div>
|
||
|
||
<div class="config-row">
|
||
<label>Line Width</label>
|
||
<input type="range" min="1" max="5" value="${indicator.params._lineWidth || 2}" onchange="window.updateIndicatorSetting('${indicator.id}', '_lineWidth', parseInt(this.value))">
|
||
<span class="range-value">${indicator.params._lineWidth || 2}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Inputs -->
|
||
${meta?.inputs && meta.inputs.length > 0 ? `
|
||
<div class="config-section">
|
||
<div class="section-subtitle">Parameters</div>
|
||
${meta.inputs.map(input => `
|
||
<div class="config-row">
|
||
<label>${input.label}</label>
|
||
${input.type === 'select' ?
|
||
`<select onchange="window.updateIndicatorSetting('${indicator.id}', '${input.name}', this.value)">
|
||
${input.options.map(o => `<option value="${o}" ${indicator.params[input.name] === o ? 'selected' : ''}>${o}</option>`).join('')}
|
||
</select>` :
|
||
`<div class="input-with-preset">
|
||
<input
|
||
type="number"
|
||
value="${indicator.params[input.name]}"
|
||
${input.min !== undefined ? `min="${input.min}"` : ''}
|
||
${input.max !== undefined ? `max="${input.max}"` : ''}
|
||
${input.step !== undefined ? `step="${input.step}"` : ''}
|
||
onchange="window.updateIndicatorSetting('${indicator.id}', '${input.name}', parseFloat(this.value))"
|
||
>
|
||
<button class="presets-btn" onclick="window.showInputPresets('${indicator.id}', '${input.name}')">⋯</button>
|
||
</div>`
|
||
}
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
` : ''}
|
||
|
||
<!-- Presets -->
|
||
<div class="config-section">
|
||
<div class="section-subtitle">
|
||
Presets
|
||
<button class="preset-action-btn" onclick="window.savePreset('${indicator.id}')" title="Save current settings as preset">+ Save Preset</button>
|
||
</div>
|
||
${renderIndicatorPresets(indicator, meta)}
|
||
</div>
|
||
|
||
<!-- Actions -->
|
||
<div class="config-actions">
|
||
<button class="btn-secondary" onclick="window.resetIndicator('${indicator.id}')">Reset to Defaults</button>
|
||
<button class="btn-danger" onclick="window.removeIndicatorById('${indicator.id}')">Remove</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function renderIndicatorPresets(indicator, meta) {
|
||
const presets = getPresetsForIndicator(meta.name);
|
||
const instance = new IR[indicator.type]({ type: indicator.type, params: indicator.params, name: indicator.name });
|
||
const metadata = instance.getMetadata();
|
||
|
||
return presets.length > 0 ? `
|
||
<div class="presets-list">
|
||
${presets.map(preset => {
|
||
// Match values against current settings
|
||
const isApplied = metadata.inputs.every(input =>
|
||
preset.values[input.name] === indicator.params[input.name]
|
||
);
|
||
|
||
return `
|
||
<div class="preset-item ${isApplied ? 'applied' : ''}" data-preset="${preset.id}">
|
||
<span class="preset-label" onclick="window.applyPreset('${indicator.id}', '${preset.id}')">${preset.name}</span>
|
||
<button class="preset-delete" onclick="event.stopPropagation(); window.deletePreset('${preset.id}')" title="Delete preset">×</button>
|
||
</div>
|
||
`;
|
||
}).join('')}
|
||
</div>
|
||
` : '<div class="no-presets">No saved presets</div>';
|
||
}
|
||
|
||
// Event listeners
|
||
function setupEventListeners() {
|
||
// Search
|
||
const searchInput = document.getElementById('indicatorSearch');
|
||
if (searchInput) {
|
||
searchInput.addEventListener('input', (e) => {
|
||
searchQuery = e.target.value;
|
||
renderIndicatorPanel();
|
||
});
|
||
}
|
||
|
||
// Category tabs
|
||
document.querySelectorAll('.category-tab').forEach(tab => {
|
||
tab.addEventListener('click', (e) => {
|
||
selectedCategory = tab.dataset.category;
|
||
renderIndicatorPanel();
|
||
});
|
||
});
|
||
}
|
||
|
||
// Actions
|
||
window.clearSearch = function() {
|
||
searchQuery = '';
|
||
renderIndicatorPanel();
|
||
};
|
||
|
||
window.clearAllIndicators = function() {
|
||
activeIndicators.forEach(ind => {
|
||
ind.series?.forEach(s => {
|
||
try { window.dashboard?.chart?.removeSeries(s); } catch(e) {}
|
||
});
|
||
});
|
||
activeIndicators = [];
|
||
configuringId = null;
|
||
renderIndicatorPanel();
|
||
drawIndicatorsOnChart();
|
||
};
|
||
|
||
window.addIndicator = function(type) {
|
||
const IndicatorClass = IR?.[type];
|
||
if (!IndicatorClass) return;
|
||
|
||
const id = `${type}_${nextInstanceId++}`;
|
||
const instance = new IndicatorClass({ type, params: {}, name: '' });
|
||
const metadata = instance.getMetadata();
|
||
|
||
const params = {
|
||
_lineType: 'solid',
|
||
_lineWidth: 2
|
||
};
|
||
metadata.plots.forEach((plot, idx) => {
|
||
params[`_color_${idx}`] = plot.color || getDefaultColor(activeIndicators.length + idx);
|
||
});
|
||
metadata.inputs.forEach(input => {
|
||
params[input.name] = input.default;
|
||
});
|
||
|
||
activeIndicators.push({
|
||
id,
|
||
type,
|
||
name: metadata.name,
|
||
params,
|
||
plots: metadata.plots,
|
||
series: [],
|
||
visible: true
|
||
});
|
||
|
||
configuringId = id;
|
||
renderIndicatorPanel();
|
||
drawIndicatorsOnChart();
|
||
};
|
||
|
||
window.toggleIndicatorExpand = function(id) {
|
||
configuringId = configuringId === id ? null : id;
|
||
renderIndicatorPanel();
|
||
};
|
||
|
||
window.toggleIndicatorVisibility = function(id) {
|
||
const indicator = activeIndicators.find(a => a.id === id);
|
||
if (!indicator) return;
|
||
|
||
indicator.visible = indicator.visible === false ? true : false;
|
||
|
||
indicator.series?.forEach(s => {
|
||
try {
|
||
s.applyOptions({ visible: indicator.visible });
|
||
} catch(e) {}
|
||
});
|
||
|
||
renderIndicatorPanel();
|
||
};
|
||
|
||
window.toggleFavorite = function(type) {
|
||
const favorites = userPresets.favorites || [];
|
||
const idx = favorites.indexOf(type);
|
||
|
||
if (idx >= 0) {
|
||
favorites.splice(idx, 1);
|
||
} else {
|
||
favorites.push(type);
|
||
}
|
||
|
||
userPresets.favorites = favorites;
|
||
saveUserPresets();
|
||
renderIndicatorPanel();
|
||
};
|
||
|
||
window.updateIndicatorColor = function(id, index, color) {
|
||
const indicator = activeIndicators.find(a => a.id === id);
|
||
if (!indicator) return;
|
||
|
||
indicator.params[`_color_${index}`] = color;
|
||
|
||
const preview = document.querySelector(`#color_${id}_${index} + .color-preview`);
|
||
if (preview) {
|
||
preview.style.background = color;
|
||
}
|
||
|
||
drawIndicatorsOnChart();
|
||
};
|
||
|
||
window.updateIndicatorSetting = function(id, key, value) {
|
||
const indicator = activeIndicators.find(a => a.id === id);
|
||
if (!indicator) return;
|
||
|
||
indicator.params[key] = value;
|
||
|
||
const valueSpan = document.querySelector(`#color_${id}_${index} + .color-preview`);
|
||
if (valueSpan) {
|
||
valueSpan.textContent = value;
|
||
}
|
||
|
||
drawIndicatorsOnChart();
|
||
};
|
||
|
||
window.resetIndicator = function(id) {
|
||
const indicator = activeIndicators.find(a => a.id === id);
|
||
if (!indicator) return;
|
||
|
||
const IndicatorClass = IR?.[indicator.type];
|
||
if (!IndicatorClass) return;
|
||
|
||
const instance = new IndicatorClass({ type: indicator.type, params: {}, name: indicator.name });
|
||
const metadata = instance.getMetadata();
|
||
|
||
metadata.inputs.forEach(input => {
|
||
indicator.params[input.name] = input.default;
|
||
});
|
||
|
||
renderIndicatorPanel();
|
||
drawIndicatorsOnChart();
|
||
};
|
||
|
||
window.removeIndicatorById = function(id) {
|
||
const idx = activeIndicators.findIndex(a => a.id === id);
|
||
if (idx < 0) return;
|
||
|
||
activeIndicators[idx].series?.forEach(s => {
|
||
try { window.dashboard?.chart?.removeSeries(s); } catch(e) {}
|
||
});
|
||
|
||
activeIndicators.splice(idx, 1);
|
||
|
||
if (configuringId === id) {
|
||
configuringId = null;
|
||
}
|
||
|
||
renderIndicatorPanel();
|
||
drawIndicatorsOnChart();
|
||
};
|
||
|
||
// Presets
|
||
function getPresetsForIndicator(indicatorName) {
|
||
const allPresets = Object.values(userPresets).flat().filter(p => typeof p === 'object' && p.name);
|
||
return allPresets.filter(p => p.indicatorName === indicatorName);
|
||
}
|
||
|
||
window.savePreset = function(id) {
|
||
const indicator = activeIndicators.find(a => a.id === id);
|
||
if (!indicator) return;
|
||
|
||
const presetName = prompt('Enter preset name:');
|
||
if (!presetName) return;
|
||
|
||
const IndicatorClass = IR?.[indicator.type];
|
||
if (!IndicatorClass) return;
|
||
|
||
const instance = new IndicatorClass({ type: indicator.type, params: indicator.params, name: indicator.name });
|
||
const meta = instance.getMetadata();
|
||
|
||
const preset = {
|
||
id: `preset_${Date.now()}`,
|
||
name: presetName,
|
||
indicatorName: meta.name,
|
||
values: {}
|
||
};
|
||
|
||
meta.inputs.forEach(input => {
|
||
preset.values[input.name] = indicator.params[input.name];
|
||
});
|
||
|
||
if (!userPresets.presets) userPresets.presets = [];
|
||
userPresets.presets.push(preset);
|
||
saveUserPresets();
|
||
renderIndicatorPanel();
|
||
|
||
alert(`Preset "${presetName}" saved!`);
|
||
};
|
||
|
||
window.applyPreset = function(id, presetId) {
|
||
const allPresets = Object.values(userPresets).flat().filter(p => typeof p === 'object' && p.id);
|
||
const preset = allPresets.find(p => p.id === presetId);
|
||
if (!preset) return;
|
||
|
||
const indicator = activeIndicators.find(a => a.id === id);
|
||
if (!indicator) return;
|
||
|
||
Object.keys(preset.values).forEach(key => {
|
||
indicator.params[key] = preset.values[key];
|
||
});
|
||
|
||
renderIndicatorPanel();
|
||
drawIndicatorsOnChart();
|
||
};
|
||
|
||
window.deletePreset = function(presetId) {
|
||
if (!confirm('Delete this preset?')) return;
|
||
|
||
if (userPresets.presets) {
|
||
userPresets.presets = userPresets.presets.filter(p => p.id !== presetId);
|
||
saveUserPresets();
|
||
renderIndicatorPanel();
|
||
}
|
||
};
|
||
|
||
window.showPresets = function(indicatorName) {
|
||
const presets = getPresetsForIndicator(indicatorName);
|
||
if (presets.length === 0) {
|
||
alert('No saved presets for this indicator');
|
||
return;
|
||
}
|
||
|
||
const menu = window.open('', '_blank', 'width=400,height=500');
|
||
|
||
let htmlContent =
|
||
'<html><head><title>Presets - ' + indicatorName + '</title><style>' +
|
||
'body { font-family: sans-serif; padding: 20px; background: #1e222d; color: #d1d4dc; }' +
|
||
'.preset { padding: 10px; margin: 5px; background: #131722; border-radius: 4px; }' +
|
||
'.preset:hover { background: #2a2e39; cursor: pointer; }' +
|
||
'</style></head><body>' +
|
||
'<h3>' + indicatorName + ' Presets</h3>';
|
||
|
||
presets.forEach(p => {
|
||
htmlContent += '<div class="preset" onclick="opener.applyPresetFromWindow(' + "'" + p.id + "'" + ')">' + p.name + '</div>';
|
||
});
|
||
|
||
htmlContent += '</body></html>';
|
||
|
||
menu.document.write(htmlContent);
|
||
};
|
||
|
||
window.applyPresetFromWindow = function(presetId) {
|
||
const indicator = activeIndicators.find(a => a.id === configuringId);
|
||
if (!indicator) return;
|
||
applyPreset(indicator.id, presetId);
|
||
};
|
||
|
||
function saveUserPresets() {
|
||
localStorage.setItem('indicator_presets', JSON.stringify(userPresets));
|
||
}
|
||
|
||
// Chart drawing
|
||
export function drawIndicatorsOnChart() {
|
||
if (!window.dashboard || !window.dashboard.chart) return;
|
||
|
||
activeIndicators.forEach(ind => {
|
||
ind.series?.forEach(s => {
|
||
try { window.dashboard.chart.removeSeries(s); } catch(e) {}
|
||
});
|
||
});
|
||
|
||
const candles = window.dashboard.allData.get(window.dashboard.currentInterval);
|
||
if (!candles || candles.length === 0) return;
|
||
|
||
const lineStyleMap = {
|
||
'solid': LightweightCharts.LineStyle.Solid,
|
||
'dotted': LightweightCharts.LineStyle.Dotted,
|
||
'dashed': LightweightCharts.LineStyle.Dashed
|
||
};
|
||
|
||
indicatorPanes.clear();
|
||
nextPaneIndex = 1;
|
||
|
||
const overlayIndicators = [];
|
||
const paneIndicators = [];
|
||
|
||
activeIndicators.forEach(ind => {
|
||
const IndicatorClass = IR?.[ind.type];
|
||
if (!IndicatorClass) return;
|
||
|
||
const instance = new IndicatorClass({ type: ind.type, params: ind.params, name: ind.name });
|
||
const meta = instance.getMetadata();
|
||
|
||
if (meta.displayMode === 'pane') {
|
||
paneIndicators.push({ indicator: ind, meta, instance });
|
||
} else {
|
||
overlayIndicators.push({ indicator: ind, meta, instance });
|
||
}
|
||
});
|
||
|
||
const totalPanes = 1 + paneIndicators.length;
|
||
const mainPaneHeight = paneIndicators.length > 0 ? 60 : 100;
|
||
const paneHeight = paneIndicators.length > 0 ? Math.floor(40 / paneIndicators.length) : 0;
|
||
|
||
window.dashboard.chart.panes()[0]?.setHeight(mainPaneHeight);
|
||
|
||
overlayIndicators.forEach(({ indicator, meta, instance }) => {
|
||
if (indicator.visible === false) {
|
||
indicator.series = [];
|
||
return;
|
||
}
|
||
|
||
renderIndicatorOnPane(indicator, meta, instance, candles, 0, lineStyleMap);
|
||
});
|
||
|
||
paneIndicators.forEach(({ indicator, meta, instance }, idx) => {
|
||
if (indicator.visible === false) {
|
||
indicator.series = [];
|
||
return;
|
||
}
|
||
|
||
const paneIndex = nextPaneIndex++;
|
||
indicatorPanes.set(indicator.id, paneIndex);
|
||
|
||
renderIndicatorOnPane(indicator, meta, instance, candles, paneIndex, lineStyleMap);
|
||
|
||
const pane = window.dashboard.chart.panes()[paneIndex];
|
||
if (pane) {
|
||
pane.setHeight(paneHeight);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Legacy compatibility functions
|
||
window.renderIndicatorList = renderIndicatorPanel;
|
||
window.toggleIndicator = addIndicator;
|
||
window.showIndicatorConfig = function(id) {
|
||
const ind = activeIndicators.find(a => a.id === id);
|
||
if (ind) configuringId = id;
|
||
renderIndicatorPanel();
|
||
};
|
||
window.applyIndicatorConfig = function() {
|
||
// No-op - config is applied immediately
|
||
};
|
||
window.removeIndicator = function() {
|
||
if (!configuringId) return;
|
||
removeIndicatorById(configuringId);
|
||
};
|
||
|
||
// Assign to window for backward compatibility
|
||
window.toggleIndicator = window.addIndicator;
|
||
window.removeIndicatorByIndex = function(index) {
|
||
if (index < 0 || index >= activeIndicators.length) return;
|
||
removeIndicatorById(activeIndicators[index].id);
|
||
};
|
||
window.drawIndicatorsOnChart = drawIndicatorsOnChart;
|
||
|
||
// Export functions for module imports
|
||
export { window.addIndicator as addIndicator };
|
||
export { window.removeIndicatorById as removeIndicatorById };
|
||
export { window.removeIndicatorByIndex as removeIndicatorByIndex }; |