first hurst

This commit is contained in:
2025-07-14 20:49:19 +02:00
parent 574dec7373
commit 666d5fb007
2 changed files with 167 additions and 2 deletions

165
static/hurst.js Normal file
View File

@ -0,0 +1,165 @@
/**
* Indicator Definition Object for Hurst Bands.
* This object is used by the indicator manager to create and control the indicator.
* It defines the parameters and the calculation functions.
*/
const HURST_INDICATOR = {
name: 'Hurst',
label: 'Hurst Bands',
params: [
{ name: 'cycle', type: 'number', defaultValue: 30, min: 2 },
{ name: 'atr_mult', type: 'number', defaultValue: 1.8, min: 0.1, step: 0.1 },
],
// This indicator returns multiple lines, so the manager will need to handle an object of arrays.
calculateFull: calculateFullHurst,
createRealtime: createRealtimeHurstCalculator,
};
// --- Helper Functions (private to this file) ---
/**
* Calculates RMA (Relative Moving Average), a type of EMA.
* @param {number[]} series - An array of numbers.
* @param {number} period - The smoothing period.
* @returns {number[]} The calculated RMA series.
*/
function _calculateRMA(series, period) {
const alpha = 1 / period;
let rma = [];
if (series.length < period) return rma;
// Initial SMA
let sum = 0;
for (let i = 0; i < period; i++) {
sum += series[i];
}
rma.push(sum / period);
// Subsequent RMAs
for (let i = period; i < series.length; i++) {
const val = alpha * series[i] + (1 - alpha) * rma[rma.length - 1];
rma.push(val);
}
return rma;
}
/**
* Calculates ATR (Average True Range).
* @param {Array<Object>} data - The full candle data.
* @param {number} period - The ATR period.
* @returns {number[]} The calculated ATR series.
*/
function _calculateATR(data, period) {
if (data.length < period) return [];
let tr_series = [];
tr_series.push(data[0].high - data[0].low); // First TR is just High - Low
for (let i = 1; i < data.length; i++) {
const h = data[i].high;
const l = data[i].low;
const prev_c = data[i - 1].close;
const tr = Math.max(h - l, Math.abs(h - prev_c), Math.abs(l - prev_c));
tr_series.push(tr);
}
// Smooth the True Range series with RMA to get ATR
return _calculateRMA(tr_series, period);
}
// --- Main Calculation Functions ---
/**
* Calculates the Hurst Bands for an entire dataset.
* @param {Array<Object>} data - An array of candle objects.
* @param {Object} params - An object with { cycle, atr_mult }.
* @returns {Object} An object containing two arrays: { topBand: [...], bottomBand: [...] }.
*/
function calculateFullHurst(data, params) {
const { cycle, atr_mult } = params;
const mcl = Math.floor(cycle / 2);
const mcl_2 = Math.floor(mcl / 2);
// Ensure there's enough data for all calculations, including the lookback.
if (data.length < cycle + mcl_2) {
return { topBand: [], bottomBand: [] };
}
const closePrices = data.map(d => d.close);
// 1. Calculate RMA of close prices
const ma_mcl_full = _calculateRMA(closePrices, mcl);
// 2. Calculate ATR
const atr_full = _calculateATR(data, mcl);
const topBand = [];
const bottomBand = [];
// Loop through the data to construct the bands.
// We start the loop where the first valid calculation can occur.
const startIndex = mcl - 1 + mcl_2;
for (let i = startIndex; i < data.length; i++) {
// Align indices: the result of RMA/ATR at index `j` corresponds to the original data at index `j + mcl - 1`.
const rma_atr_base_index = i - (mcl - 1);
const center_ma_index = rma_atr_base_index - mcl_2;
if (center_ma_index >= 0 && rma_atr_base_index >= 0) {
const center = ma_mcl_full[center_ma_index];
const offset = atr_full[rma_atr_base_index] * atr_mult;
topBand.push({
time: data[i].time,
value: center + offset
});
bottomBand.push({
time: data[i].time,
value: center - offset
});
}
}
return { topBand, bottomBand };
}
/**
* Creates a stateful Hurst calculator for real-time updates.
* Note: Due to the lookback (`[mcl_2]`), a truly efficient real-time version is complex.
* This version recalculates on a rolling buffer for simplicity and correctness.
* @param {Object} params - An object with { cycle, atr_mult }.
* @returns {Object} A calculator object with `update` and `prime` methods.
*/
function createRealtimeHurstCalculator(params) {
const bufferSize = params.cycle * 2; // Use a buffer to handle lookbacks
let buffer = [];
return {
update: function(candle) {
buffer.push(candle);
if (buffer.length > bufferSize) {
buffer.shift();
}
if (buffer.length < params.cycle + Math.floor(params.cycle / 4)) {
return null;
}
// Recalculate on the small buffer
const result = calculateFullHurst(buffer, params);
if (result.topBand.length > 0) {
// Return the last calculated point for each band
const lastTop = result.topBand[result.topBand.length - 1];
const lastBottom = result.bottomBand[result.bottomBand.length - 1];
// The manager will expect an object matching the keys from calculateFull
return { topBand: lastTop, bottomBand: lastBottom };
}
return null;
},
prime: function(historicalCandles) {
// Prime the buffer with the last N candles from history
buffer = historicalCandles.slice(-bufferSize);
}
};
}

View File

@ -9,5 +9,5 @@ const AVAILABLE_INDICATORS = [
SMA_INDICATOR,
EMA_INDICATOR,
BB_INDICATOR, // Added the new Bollinger Bands indicator
// Add other indicators here, e.g., RSI_INDICATOR
];
HURST_INDICATOR, // Added the new Hurst Bands indicator
// Add other indicators here as needed];