1 Commits
working ... hts

Author SHA1 Message Date
38cdffc2b9 hts with only two SMAs 2025-07-21 21:20:39 +02:00
6 changed files with 98 additions and 167 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,98 +1,60 @@
/**
* HTS Indicator Definition
* Allows selecting average type and fast interval.
* HTS (High-Tech SMAs) - Combined Fast and Slow SMA Indicator
* This indicator displays both Fast SMA and Slow SMA on the same chart
*/
const HTS_INDICATOR = {
name: 'HTS',
label: 'HTS',
usesBaseData: false,
label: 'HTS (Fast & Slow SMA)',
usesBaseData: false, // This indicator uses the chart's currently displayed data
params: [
{ name: 'avgType', type: 'select', defaultValue: 'VWMA', options: ['RMA', 'EMA', 'SMA', 'WMA', 'VWMA'], label: 'Average Type' },
{ name: 'fast', type: 'number', defaultValue: 33, min: 1, label: 'Fast Interval' }
{ name: 'fastPeriod', type: 'number', defaultValue: 33, min: 2, label: 'Fast SMA Period' },
{ name: 'slowPeriod', type: 'number', defaultValue: 133, min: 2, label: 'Slow SMA Period' },
],
calculateFull: function(data, params) {
// determine data and period based on autoTF
let source = data;
const period = params.fast;
if (params.autoTF) {
// current timeframe in seconds between candles
const tfSec = (data.length > 1) ? (data[1].time - data[0].time) : 60;
const tfMin = tfSec / 60;
const autoInterval = Math.max(1, Math.floor(tfMin / 4));
// aggregate displayed data at autoInterval
source = aggregateCandles(data, autoInterval);
} else {
source = data;
}
const type = params.avgType;
if (!source || source.length < period) return { max: [], min: [] };
const maxSeries = [];
const minSeries = [];
let sumH, sumL, prevH, prevL, mult;
switch (type) {
case 'SMA':
sumH = 0; sumL = 0;
for (let i = 0; i < period; i++) { sumH += source[i].high; sumL += source[i].low; }
maxSeries.push({ time: source[period - 1].time, value: sumH / period });
minSeries.push({ time: source[period - 1].time, value: sumL / period });
for (let i = period; i < source.length; i++) {
sumH += source[i].high - source[i - period].high;
sumL += source[i].low - source[i - period].low;
maxSeries.push({ time: source[i].time, value: sumH / period });
minSeries.push({ time: source[i].time, value: sumL / period });
}
break;
case 'EMA':
case 'VWMA':
mult = 2 / (period + 1);
// initialize
sumH = 0; sumL = 0;
for (let i = 0; i < period; i++) { sumH += source[i].high; sumL += source[i].low; }
prevH = sumH / period; prevL = sumL / period;
maxSeries.push({ time: source[period - 1].time, value: prevH });
minSeries.push({ time: source[period - 1].time, value: prevL });
for (let i = period; i < source.length; i++) {
const h = source[i].high, l = source[i].low;
prevH = (h - prevH) * mult + prevH;
prevL = (l - prevL) * mult + prevL;
maxSeries.push({ time: source[i].time, value: prevH });
minSeries.push({ time: source[i].time, value: prevL });
}
break;
case 'RMA':
// Wilder's smoothing
sumH = 0; sumL = 0;
for (let i = 0; i < period; i++) { sumH += source[i].high; sumL += source[i].low; }
prevH = sumH / period; prevL = sumL / period;
maxSeries.push({ time: source[period - 1].time, value: prevH });
minSeries.push({ time: source[period - 1].time, value: prevL });
for (let i = period; i < source.length; i++) {
const h = source[i].high, l = source[i].low;
prevH = prevH + (h - prevH) / period;
prevL = prevL + (l - prevL) / period;
maxSeries.push({ time: source[i].time, value: prevH });
minSeries.push({ time: source[i].time, value: prevL });
}
break;
case 'WMA':
const denom = period * (period + 1) / 2;
for (let i = period - 1; i < source.length; i++) {
let wSumH = 0, wSumL = 0;
for (let j = 0; j < period; j++) {
const w = period - j;
wSumH += source[i - j].high * w;
wSumL += source[i - j].low * w;
}
maxSeries.push({ time: source[i].time, value: wSumH / denom });
minSeries.push({ time: source[i].time, value: wSumL / denom });
}
break;
default:
source.forEach(d => {
maxSeries.push({ time: d.time, value: d.high });
minSeries.push({ time: d.time, value: d.low });
});
}
return { max: maxSeries, min: minSeries };
}
calculateFull: calculateFullHTS,
};
function calculateFullHTS(data, params) {
const fastPeriod = params.fastPeriod;
const slowPeriod = params.slowPeriod;
if (!data || data.length < Math.max(fastPeriod, slowPeriod)) {
return {
fastSMA: [],
slowSMA: []
};
}
// Calculate Fast SMA
const fastSMA = calculateSMA(data, fastPeriod);
// Calculate Slow SMA
const slowSMA = calculateSMA(data, slowPeriod);
return {
fastSMA: fastSMA,
slowSMA: slowSMA
};
}
function calculateSMA(data, period) {
if (!data || data.length < period) return [];
let smaData = [];
let sum = 0;
// Calculate initial sum for the first period
for (let i = 0; i < period; i++) {
sum += data[i].close;
}
// Add the first SMA point
smaData.push({ time: data[period - 1].time, value: sum / period });
// Calculate remaining SMA points using sliding window
for (let i = period; i < data.length; i++) {
sum = sum - data[i - period].close + data[i].close;
smaData.push({ time: data[i].time, value: sum / period });
}
return smaData;
}

View File

@ -24,6 +24,12 @@ function createIndicatorManager(chart, baseCandleDataRef, displayedCandleDataRef
bb3_lower: 'rgba(6, 95, 6, 1.0)',
},
hurst: { topBand: '#787b86', bottomBand: '#787b86', topBand_h: '#673ab7', bottomBand_h: '#673ab7' },
hts: {
fastSMA: '#00bcd4', // Cyan blue for Fast SMA
slowSMA: '#ff5252' // Red for Slow SMA
},
fast_sma: '#00bcd4',
slow_sma: '#ff5252',
default: ['#00BCD4', '#FFEB3B', '#4CAF50', '#E91E63']
};
@ -75,72 +81,27 @@ function createIndicatorManager(chart, baseCandleDataRef, displayedCandleDataRef
if (!definition) return;
slot.definition = definition;
// Special case for HTS: hide avgType/fast controls, show only Auto TF checkbox
if (indicatorName === 'HTS') {
const label = document.createElement('label');
label.textContent = 'Auto TF';
label.style.fontSize = '12px';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'input-field';
// initialize params
// default HTS params
slot.params.autoTF = false;
slot.params.avgType = definition.params.find(p => p.name === 'avgType').defaultValue;
slot.params.fast = definition.params.find(p => p.name === 'fast').defaultValue;
checkbox.addEventListener('change', () => {
slot.params.autoTF = checkbox.checked;
updateIndicator(slot.id, true);
});
const controlGroup = document.createElement('div');
controlGroup.style.display = 'flex';
controlGroup.style.flexDirection = 'column';
controlGroup.appendChild(label);
controlGroup.appendChild(checkbox);
controlsContainer.appendChild(controlGroup);
// initial draw
updateIndicator(slot.id, true);
return;
}
// Default controls for other indicators
definition.params.forEach(param => {
const label = document.createElement('label');
label.textContent = param.label || param.name;
label.style.fontSize = '12px';
// Create select for dropdowns, input for numbers
let input;
if (param.type === 'select') {
input = document.createElement('select');
input.className = 'input-field';
// populate options
param.options.forEach(opt => {
const option = document.createElement('option');
option.value = opt;
option.textContent = opt;
if (opt === param.defaultValue) option.selected = true;
input.appendChild(option);
});
slot.params[param.name] = input.value;
} else {
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] = parseFloat(input.value);
}
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;
input.addEventListener('input', () => {
// debounce param changes
// --- FIX: --- Use the slot's `debounceTimerId` property to manage the timeout.
clearTimeout(slot.debounceTimerId);
slot.debounceTimerId = setTimeout(() => {
// update param value
const val = input.value;
slot.params[param.name] = (param.type === 'number') ? parseFloat(val) : val;
slot.params[param.name] = input.type === 'number' ? parseFloat(input.value) : input.value;
updateIndicator(slot.id, true);
slot.debounceTimerId = null;
slot.debounceTimerId = null; // Clear the ID after the function has run.
}, 500);
});
const controlGroup = document.createElement('div');
@ -158,13 +119,7 @@ function createIndicatorManager(chart, baseCandleDataRef, displayedCandleDataRef
const slot = indicatorSlots.find(s => s.id === slotId);
if (!slot || !slot.definition) return;
// for HTS autoTF, always use base data for aggregation; else follow definition flag
let candleDataForCalc;
if (slot.definition.name === 'HTS' && slot.params.autoTF) {
candleDataForCalc = baseCandleDataRef;
} else {
candleDataForCalc = (slot.definition.usesBaseData) ? baseCandleDataRef : displayedCandleDataRef;
}
const candleDataForCalc = (slot.definition.usesBaseData) ? baseCandleDataRef : displayedCandleDataRef;
if (candleDataForCalc.length === 0) return;
@ -189,8 +144,10 @@ function createIndicatorManager(chart, baseCandleDataRef, displayedCandleDataRef
slot.series.push(series);
});
} else {
const indicatorNameLower = slot.definition.name.toLowerCase();
const indicatorColor = colors[indicatorNameLower] || slot.definition.color || colors.default[slot.id - 1];
const series = chart.addLineSeries({
color: colors.default[slot.id - 1],
color: indicatorColor,
lineWidth: 1,
title: '',
lastValueVisible: false,

View File

@ -6,10 +6,9 @@
* 3. Add the indicator's definition object (e.g., RSI_INDICATOR) to this array.
*/
const AVAILABLE_INDICATORS = [
SMA_INDICATOR,
HTS_INDICATOR,
EMA_INDICATOR,
BB_INDICATOR, // Added the new Bollinger Bands indicator
HURST_INDICATOR, // Added the new Hurst Bands indicator
HTS_INDICATOR // Added the new HTS indicator
HURST_INDICATOR // Added the new Hurst Bands indicator
// Add other indicators here as needed
];

View File

@ -1,14 +1,29 @@
/**
* Indicator Definition Object for SMA.
* Indicator Definition Object for Fast SMA.
*/
const SMA_INDICATOR = {
name: 'SMA',
label: 'Simple Moving Average',
const FAST_SMA_INDICATOR = {
name: 'FAST_SMA',
label: 'Fast SMA',
usesBaseData: false, // This simple indicator uses the chart's currently displayed data
params: [
{ name: 'period', type: 'number', defaultValue: 20, min: 2 },
{ name: 'period', type: 'number', defaultValue: 33, min: 2 },
],
calculateFull: calculateFullSMA,
color: '#00bcd4',
};
/**
* Indicator Definition Object for Slow SMA.
*/
const SLOW_SMA_INDICATOR = {
name: 'SLOW_SMA',
label: 'Slow SMA',
usesBaseData: false, // This simple indicator uses the chart's currently displayed data
params: [
{ name: 'period', type: 'number', defaultValue: 133, min: 2 },
],
calculateFull: calculateFullSMA,
color: '#ff5252',
};
function calculateFullSMA(data, params) {

View File

@ -8,11 +8,10 @@
<!-- NOTE: These 'url_for' will not work in a static HTML file. -->
<!-- They are placeholders for a Flask environment. For a standalone file, you would link directly to the JS files. -->
<script src="{{ url_for('static', filename='candle-aggregator.js') }}"></script>
<script src="{{ url_for('static', filename='sma.js') }}"></script>
<script src="{{ url_for('static', filename='hts.js') }}"></script>
<script src="{{ url_for('static', filename='ema.js') }}"></script>
<script src="{{ url_for('static',filename='bb.js') }}"></script>
<script src="{{ url_for('static', filename='hurst.js') }}"></script>
<script src="{{ url_for('static', filename='hts.js') }}"></script>
<script src="{{ url_for('static', filename='indicators.js') }}"></script>
<script src="{{ url_for('static', filename='indicator-manager.js') }}"></script>
@ -59,7 +58,6 @@
}
.action-button:hover, .control-cell select:hover { background-color: var(--button-hover-bg); }
.input-field { width: 60px; }
select.input-field { width: 100px; }
#candle-timer { font-size: 2rem; font-weight: 500; color: var(--accent-orange); }
#timeframe-display { margin-top: 10px; min-width: 60px; }
.progress-bar-container {