Skip to main content
Glama
ta.js3.71 kB
// services/ta.js // Pure technical indicators: RSI (Wilder) and Bollinger Bands (SMA + population stdev). // Inputs: arrays of numeric closes. No network, no dates. // Returns: latest values only (your MCP builds series by calling these on growing slices). /** * Compute the latest RSI using Wilder's smoothing. * @param {number[]} values - Close prices (oldest → newest) * @param {number} period - RSI period (default 14) * @returns {number|null} - RSI in [0,100], or null if insufficient/invalid data */ export function rsi(values, period = 14) { const arr = normalize(values); if (arr.length < period + 1) return null; // Seed averages over the first `period` deltas let gains = 0, losses = 0; for (let i = 1; i <= period; i++) { const d = arr[i] - arr[i - 1]; if (d >= 0) gains += d; else losses -= d; } let avgGain = gains / period; let avgLoss = losses / period; // Wilder smoothing for the remaining deltas for (let i = period + 1; i < arr.length; i++) { const d = arr[i] - arr[i - 1]; const gain = d > 0 ? d : 0; const loss = d < 0 ? -d : 0; avgGain = (avgGain * (period - 1) + gain) / period; avgLoss = (avgLoss * (period - 1) + loss) / period; } if (!isFiniteNum(avgGain) || !isFiniteNum(avgLoss)) return null; // Handle flat / division-by-zero cases explicitly if (avgLoss === 0) { if (avgGain === 0) return 50; // perfectly flat return 100; // only gains } const rs = avgGain / avgLoss; if (!isFiniteNum(rs)) return null; const rsi = 100 - (100 / (1 + rs)); return isFiniteNum(rsi) ? clamp(rsi, 0, 100) : null; } /** * Compute latest Bollinger Bands. * Uses SMA over the window and population standard deviation. * @param {number[]} values - Close prices (oldest → newest) * @param {number} period - Window length (default 20) * @param {number} mult - Std-dev multiplier (default 2) * @returns {{ mean:number, upper:number, lower:number, last:number, percentB:number|null, bandwidth:number|null }|null} */ export function bollinger(values, period = 20, mult = 2) { const arr = normalize(values); if (arr.length < period) return null; const slice = arr.slice(-period); const mean = avg(slice); if (!isFiniteNum(mean)) return null; const variance = avg(slice.map(v => (v - mean) * (v - mean))); const stdev = Math.sqrt(variance); if (!isFiniteNum(stdev)) return null; const upper = mean + mult * stdev; const lower = mean - mult * stdev; const last = arr[arr.length - 1]; // Avoid division by zero in derived metrics const denomBands = upper - lower; const denomMean = mean; const percentB = isFiniteNum(denomBands) && denomBands !== 0 ? (last - lower) / denomBands : null; const bandwidth = isFiniteNum(denomMean) && denomMean !== 0 ? (upper - lower) / denomMean : null; return { mean, upper, lower, last, percentB: isFiniteNum(percentB) ? percentB : null, bandwidth: isFiniteNum(bandwidth) ? bandwidth : null }; } // ───────── helpers (kept local to this module) function normalize(values) { // Coerce to numbers and drop non-finite entries to keep math stable. if (!Array.isArray(values)) return []; const out = []; for (let i = 0; i < values.length; i++) { const x = Number(values[i]); if (Number.isFinite(x)) out.push(x); } return out; } function avg(arr) { if (!arr.length) return NaN; let s = 0; for (let i = 0; i < arr.length; i++) s += arr[i]; return s / arr.length; } function isFiniteNum(x) { return typeof x === 'number' && Number.isFinite(x); } function clamp(x, lo, hi) { return Math.max(lo, Math.min(hi, x)); }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JCF0/cg-alpha-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server