Skip to main content
Glama

Bybit MCP Server

by sammcj
knnAlgorithm.ts9.35 kB
/** * K-Nearest Neighbors algorithm implementation for ML-RSI * Based on the pinescript ML-RSI implementation */ import { euclideanDistance, normalize, FeatureVector, KlineData } from './mathUtils.js' export interface KNNNeighbor { index: number; distance: number; rsiValue: number; weight: number; } export interface KNNResult { enhancedRsi: number; knnDivergence: number; effectiveNeighbors: number; adaptiveOverbought: number; adaptiveOversold: number; confidence: number; } export interface KNNConfig { neighbors: number; lookbackPeriod: number; mlWeight: number; featureCount: number; } /** * Normalize feature vector for comparison */ function normalizeFeatureVector( features: FeatureVector, allFeatures: FeatureVector[], lookbackPeriod: number ): number[] { const normalized: number[] = [] // Extract all RSI values for normalization const rsiValues = allFeatures.map(f => f.rsi).filter(v => v !== undefined) const normalizedRsi = normalize(rsiValues, Math.min(lookbackPeriod, rsiValues.length)) normalized.push(normalizedRsi[normalizedRsi.length - 1] || 0.5) if (features.momentum !== undefined) { const momentumValues = allFeatures.map(f => f.momentum).filter(v => v !== undefined) as number[] if (momentumValues.length > 0) { const normalizedMomentum = normalize(momentumValues, Math.min(lookbackPeriod, momentumValues.length)) normalized.push(normalizedMomentum[normalizedMomentum.length - 1] || 0.5) } } if (features.volatility !== undefined) { const volatilityValues = allFeatures.map(f => f.volatility).filter(v => v !== undefined) as number[] if (volatilityValues.length > 0) { const normalizedVolatility = normalize(volatilityValues, Math.min(lookbackPeriod, volatilityValues.length)) normalized.push(normalizedVolatility[normalizedVolatility.length - 1] || 0.5) } } if (features.slope !== undefined) { const slopeValues = allFeatures.map(f => f.slope).filter(v => v !== undefined) as number[] if (slopeValues.length > 0) { const normalizedSlope = normalize(slopeValues, Math.min(lookbackPeriod, slopeValues.length)) normalized.push(normalizedSlope[normalizedSlope.length - 1] || 0.5) } } if (features.priceMomentum !== undefined) { const priceMomentumValues = allFeatures.map(f => f.priceMomentum).filter(v => v !== undefined) as number[] if (priceMomentumValues.length > 0) { const normalizedPriceMomentum = normalize(priceMomentumValues, Math.min(lookbackPeriod, priceMomentumValues.length)) normalized.push(normalizedPriceMomentum[normalizedPriceMomentum.length - 1] || 0.5) } } return normalized } /** * Find K nearest neighbors using feature similarity */ export function findKNearestNeighbors( currentFeatures: FeatureVector, historicalFeatures: FeatureVector[], rsiValues: number[], config: KNNConfig ): KNNNeighbor[] { if (historicalFeatures.length === 0 || rsiValues.length === 0) { return [] } const distances: { index: number; distance: number; rsiValue: number }[] = [] // Normalize current features const currentNormalized = normalizeFeatureVector(currentFeatures, historicalFeatures, config.lookbackPeriod) // Calculate distances to all historical points for (let i = 0; i < Math.min(historicalFeatures.length, config.lookbackPeriod); i++) { const historicalNormalized = normalizeFeatureVector(historicalFeatures[i], historicalFeatures, config.lookbackPeriod) if (currentNormalized.length === historicalNormalized.length) { const distance = euclideanDistance(currentNormalized, historicalNormalized) const rsiValue = rsiValues[i] if (!isNaN(distance) && !isNaN(rsiValue)) { distances.push({ index: i, distance, rsiValue }) } } } // Sort by distance (closest first) distances.sort((a, b) => a.distance - b.distance) // Take K nearest neighbors const kNearest = distances.slice(0, Math.min(config.neighbors, distances.length)) // Calculate weights (inverse distance weighting) const neighbors: KNNNeighbor[] = kNearest.map(neighbor => { const weight = neighbor.distance < 0.0001 ? 1.0 : 1.0 / neighbor.distance return { index: neighbor.index, distance: neighbor.distance, rsiValue: neighbor.rsiValue, weight } }) return neighbors } /** * Calculate adaptive thresholds based on historical RSI distribution */ function calculateAdaptiveThresholds( neighbors: KNNNeighbor[], klineData: KlineData[], defaultOverbought: number = 70, defaultOversold: number = 30 ): { overbought: number; oversold: number } { if (neighbors.length === 0) { return { overbought: defaultOverbought, oversold: defaultOversold } } const overboughtCandidates: number[] = [] const oversoldCandidates: number[] = [] // Analyze future returns for each neighbor to identify extreme zones for (const neighbor of neighbors) { const futureIndex = neighbor.index + 5 // Look 5 periods ahead if (futureIndex < klineData.length) { const currentPrice = klineData[neighbor.index].close const futurePrice = klineData[futureIndex].close const futureReturn = (futurePrice - currentPrice) / currentPrice // If significant positive return followed, this RSI level might be oversold if (futureReturn > 0.02) { // 2% positive return oversoldCandidates.push(neighbor.rsiValue) } // If significant negative return followed, this RSI level might be overbought if (futureReturn < -0.02) { // 2% negative return overboughtCandidates.push(neighbor.rsiValue) } } } // Calculate adaptive thresholds const overbought = overboughtCandidates.length > 0 ? overboughtCandidates.reduce((sum, val) => sum + val, 0) / overboughtCandidates.length : defaultOverbought const oversold = oversoldCandidates.length > 0 ? oversoldCandidates.reduce((sum, val) => sum + val, 0) / oversoldCandidates.length : defaultOversold return { overbought: Math.max(overbought, 60), // Ensure reasonable bounds oversold: Math.min(oversold, 40) } } /** * Apply KNN algorithm to enhance RSI */ export function applyKNNToRSI( currentRsi: number, currentFeatures: FeatureVector, historicalFeatures: FeatureVector[], rsiValues: number[], klineData: KlineData[], config: KNNConfig ): KNNResult { // Find nearest neighbors const neighbors = findKNearestNeighbors(currentFeatures, historicalFeatures, rsiValues, config) if (neighbors.length === 0) { return { enhancedRsi: currentRsi, knnDivergence: 0, effectiveNeighbors: 0, adaptiveOverbought: 70, adaptiveOversold: 30, confidence: 0 } } // Calculate weighted average RSI from neighbors const totalWeight = neighbors.reduce((sum, neighbor) => sum + neighbor.weight, 0) const weightedRsi = neighbors.reduce((sum, neighbor) => { return sum + (neighbor.rsiValue * neighbor.weight) }, 0) / totalWeight // Blend traditional RSI with ML-enhanced RSI const enhancedRsi = Math.max(0, Math.min(100, (1 - config.mlWeight) * currentRsi + config.mlWeight * weightedRsi )) // Calculate divergence (how different current RSI is from similar historical patterns) const avgDistance = neighbors.reduce((sum, neighbor) => sum + neighbor.distance, 0) / neighbors.length const knnDivergence = avgDistance * 100 // Scale for readability // Calculate adaptive thresholds const { overbought, oversold } = calculateAdaptiveThresholds(neighbors, klineData) // Calculate confidence based on neighbor similarity and count const maxDistance = Math.max(...neighbors.map(n => n.distance)) const avgSimilarity = maxDistance > 0 ? 1 - (avgDistance / maxDistance) : 1 const countFactor = Math.min(neighbors.length / config.neighbors, 1) const confidence = avgSimilarity * countFactor * 100 return { enhancedRsi, knnDivergence, effectiveNeighbors: neighbors.length, adaptiveOverbought: overbought, adaptiveOversold: oversold, confidence } } /** * Batch process multiple RSI values with KNN enhancement */ export function batchProcessKNN( rsiValues: number[], allFeatures: FeatureVector[], klineData: KlineData[], config: KNNConfig ): KNNResult[] { const results: KNNResult[] = [] for (let i = config.lookbackPeriod; i < rsiValues.length; i++) { const currentRsi = rsiValues[i] const currentFeatures = allFeatures[i] if (currentFeatures) { // Use historical features up to current point const historicalFeatures = allFeatures.slice(Math.max(0, i - config.lookbackPeriod), i) const historicalRsi = rsiValues.slice(Math.max(0, i - config.lookbackPeriod), i) const result = applyKNNToRSI( currentRsi, currentFeatures, historicalFeatures, historicalRsi, klineData, config ) results.push(result) } else { // Fallback for missing features results.push({ enhancedRsi: currentRsi, knnDivergence: 0, effectiveNeighbors: 0, adaptiveOverbought: 70, adaptiveOversold: 30, confidence: 0 }) } } return results }

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/sammcj/bybit-mcp'

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