152 lines
6.3 KiB
JavaScript
152 lines
6.3 KiB
JavaScript
/**
|
|
* Creates and manages all indicator-related logic for the chart.
|
|
* @param {Object} chart - The Lightweight Charts instance.
|
|
* @param {Array<Object>} baseCandleData - A reference to the array holding the chart's BASE 1m candle data.
|
|
* @param {Array<Object>} displayedCandleData - A reference to the array with currently visible candles.
|
|
* @returns {Object} A manager object with public methods to control indicators.
|
|
*/
|
|
function createIndicatorManager(chart, baseCandleData, displayedCandleData) {
|
|
const indicatorSlots = [
|
|
{ id: 1, cellId: 'indicator-cell-1', series: [], definition: null, params: {} },
|
|
{ id: 2, cellId: 'indicator-cell-2', series: [], definition: null, params: {} },
|
|
{ id: 3, cellId: 'indicator-cell-3', series: [], definition: null, params: {} },
|
|
{ id: 4, cellId: 'indicator-cell-4', series: [], definition: null, params: {} },
|
|
];
|
|
|
|
const colors = {
|
|
bb1: { upper: '#FF9800', lower: '#FF9800' },
|
|
bb2: { upper: '#2196F3', lower: '#2196F3' },
|
|
bb3: { upper: '#9C27B0', lower: '#9C27B0' },
|
|
hurst: { topBand: '#673ab7', bottomBand: '#673ab7' },
|
|
default: ['#FF5722', '#03A9F4', '#8BC34A', '#F44336']
|
|
};
|
|
|
|
function populateDropdowns() {
|
|
indicatorSlots.forEach(slot => {
|
|
const cell = document.getElementById(slot.cellId);
|
|
if (!cell) return;
|
|
|
|
const select = document.createElement('select');
|
|
select.innerHTML = `<option value="">-- Select Indicator --</option>`;
|
|
AVAILABLE_INDICATORS.forEach(ind => {
|
|
select.innerHTML += `<option value="${ind.name}">${ind.label}</option>`;
|
|
});
|
|
|
|
const controlsContainer = document.createElement('div');
|
|
controlsContainer.className = 'indicator-controls';
|
|
|
|
cell.innerHTML = '';
|
|
cell.appendChild(select);
|
|
cell.appendChild(controlsContainer);
|
|
|
|
select.addEventListener('change', (e) => loadIndicator(slot.id, e.target.value));
|
|
});
|
|
}
|
|
|
|
function loadIndicator(slotId, indicatorName) {
|
|
const slot = indicatorSlots.find(s => s.id === slotId);
|
|
if (!slot) return;
|
|
|
|
slot.series.forEach(s => chart.removeSeries(s));
|
|
slot.series = [];
|
|
slot.definition = null;
|
|
slot.params = {};
|
|
|
|
const controlsContainer = document.querySelector(`#${slot.cellId} .indicator-controls`);
|
|
controlsContainer.innerHTML = '';
|
|
|
|
if (!indicatorName) return;
|
|
|
|
const definition = AVAILABLE_INDICATORS.find(ind => ind.name === indicatorName);
|
|
if (!definition) return;
|
|
|
|
slot.definition = definition;
|
|
|
|
definition.params.forEach(param => {
|
|
const label = document.createElement('label');
|
|
label.textContent = param.label || param.name;
|
|
label.style.fontSize = '12px';
|
|
|
|
const input = document.createElement('input');
|
|
input.type = param.type;
|
|
input.value = param.defaultValue;
|
|
if (param.min !== undefined) input.min = param.min;
|
|
if (param.step !== undefined) input.step = param.step;
|
|
input.className = 'input-field';
|
|
slot.params[param.name] = input.type === 'number' ? parseFloat(input.value) : input.value;
|
|
|
|
let debounceTimer;
|
|
input.addEventListener('input', () => {
|
|
clearTimeout(debounceTimer);
|
|
debounceTimer = setTimeout(() => {
|
|
slot.params[param.name] = input.type === 'number' ? parseFloat(input.value) : input.value;
|
|
updateIndicator(slot.id);
|
|
}, 500);
|
|
});
|
|
const controlGroup = document.createElement('div');
|
|
controlGroup.style.display = 'flex';
|
|
controlGroup.style.flexDirection = 'column';
|
|
controlGroup.appendChild(label);
|
|
controlGroup.appendChild(input);
|
|
controlsContainer.appendChild(controlGroup);
|
|
});
|
|
|
|
updateIndicator(slot.id);
|
|
}
|
|
|
|
function updateIndicator(slotId) {
|
|
const slot = indicatorSlots.find(s => s.id === slotId);
|
|
const candleDataForCalc = (slot.definition.usesBaseData) ? baseCandleData : displayedCandleData;
|
|
|
|
if (!slot || !slot.definition || candleDataForCalc.length === 0) return;
|
|
|
|
slot.series.forEach(s => chart.removeSeries(s));
|
|
slot.series = [];
|
|
|
|
const indicatorResult = slot.definition.calculateFull(candleDataForCalc, slot.params);
|
|
|
|
if (typeof indicatorResult === 'object' && !Array.isArray(indicatorResult)) {
|
|
Object.keys(indicatorResult).forEach(key => {
|
|
const seriesData = indicatorResult[key];
|
|
const indicatorNameLower = slot.definition.name.toLowerCase();
|
|
|
|
const series = chart.addLineSeries({
|
|
color: (colors[indicatorNameLower] && colors[indicatorNameLower][key]) ? colors[indicatorNameLower][key] : colors.default[slot.id - 1],
|
|
lineWidth: indicatorNameLower === 'hurst' ? 1 : 2,
|
|
title: `${slot.definition.label} - ${key}`,
|
|
lastValueVisible: false,
|
|
priceLineVisible: false,
|
|
});
|
|
series.setData(seriesData);
|
|
slot.series.push(series);
|
|
});
|
|
} else {
|
|
const series = chart.addLineSeries({
|
|
color: colors.default[slot.id - 1],
|
|
lineWidth: 2,
|
|
title: slot.definition.label,
|
|
});
|
|
series.setData(indicatorResult);
|
|
slot.series.push(series);
|
|
}
|
|
}
|
|
|
|
function recalculateAllAfterHistory(newDisplayedCandleData) {
|
|
displayedCandleData = newDisplayedCandleData;
|
|
indicatorSlots.forEach(slot => {
|
|
if (slot.definition) updateIndicator(slot.id);
|
|
});
|
|
}
|
|
|
|
// This function is not currently used with the new model but is kept for potential future use.
|
|
function updateIndicatorsOnNewCandle(newCandle) {
|
|
// Real-time updates would go here.
|
|
}
|
|
|
|
return {
|
|
populateDropdowns,
|
|
recalculateAllAfterHistory,
|
|
updateIndicatorsOnNewCandle
|
|
};
|
|
}
|