Cryptocurrency Market Overheating and Cooling Detection System Built with Node.js + TypeScript
In cryptocurrency trading, accurately identifying market overheating and cooling timing is extremely important. This article explains how to build a price, volume, and open interest data analysis system using the KuCoin Futures API to detect market signals in real-time.
System Overview
This system analyzes three key data points of the cryptocurrency market:
- Price Data: Market trends and price volatility
- Volume Data: Trading activity levels
- Open Interest Data: Position accumulation status
From this data, we detect four types of signals and classify market overheating/cooling states into four levels.
Tech Stack
{
"dependencies": {
"express": "^4.18.2",
"typescript": "^5.0.0",
"node-fetch": "^3.3.0",
"@types/node": "^18.0.0"
},
"devDependencies": {
"vitest": "^0.34.0",
"@types/express": "^4.17.17"
}
}
Project Structure
src/
├── types/
│ ├── market.ts
│ └── signals.ts
├── services/
│ ├── kucoinApi.ts
│ ├── signalDetector.ts
│ └── coolingAnalyzer.ts
├── utils/
│ └── timeframe.ts
├── routes/
│ └── analysis.ts
└── app.ts
Type Definition Design
First, let's define types for market data and signals:
// types/market.ts
export interface MarketData {
timestamp: number;
price: number;
volume: number;
openInterest: number;
symbol: string;
}
export type TimeFrame = 'HTF' | 'LTF';
export type HTFInterval = '4h' | '1d';
export type LTFInterval = '15m' | '1h';
// types/signals.ts
export type SignalType = 'HIGH_ZONE' | 'PRICE_WEAK' | 'VOLUME_WEAK' | 'OI_SIGNAL';
export type CoolingLevel = 'confirmed' | 'watch' | 'mild' | 'none';
export interface DetectedSignal {
type: SignalType;
strength: number;
timestamp: number;
timeframe: TimeFrame;
}
export interface CoolingAnalysis {
level: CoolingLevel;
signals: DetectedSignal[];
confidence: number;
recommendation: string;
}
KuCoin API Connection Service
Let's implement a service to fetch data from the KuCoin Futures API:
// services/kucoinApi.ts
import fetch from 'node-fetch';
export class KuCoinApiService {
private readonly baseUrl = 'https://api-futures.kucoin.com';
async getKlineData(
symbol: string,
interval: string,
limit: number = 100
): Promise<MarketData[]> {
try {
const endpoint = `/api/v1/kline/query`;
const params = new URLSearchParams({
symbol,
granularity: this.convertInterval(interval),
from: (Date.now() - limit * this.getIntervalMs(interval)).toString(),
to: Date.now().toString()
});
const response = await fetch(`${this.baseUrl}${endpoint}?${params}`);
const data = await response.json();
return this.parseKlineData(data.data, symbol);
} catch (error) {
console.error('KuCoin API Error:', error);
throw new Error('Failed to fetch market data');
}
}
async getOpenInterest(symbol: string): Promise<number> {
const endpoint = `/api/v1/contracts/${symbol}`;
const response = await fetch(`${this.baseUrl}${endpoint}`);
const data = await response.json();
return parseFloat(data.data.openInterest);
}
private convertInterval(interval: string): string {
const mapping: { [key: string]: string } = {
'15m': '900',
'1h': '3600',
'4h': '14400',
'1d': '86400'
};
return mapping[interval] || '3600';
}
private getIntervalMs(interval: string): number {
const mapping: { [key: string]: number } = {
'15m': 15 * 60 * 1000,
'1h': 60 * 60 * 1000,
'4h': 4 * 60 * 60 * 1000,
'1d': 24 * 60 * 60 * 1000
};
return mapping[interval] || 60 * 60 * 1000;
}
private parseKlineData(rawData: any[], symbol: string): MarketData[] {
return rawData.map(item => ({
timestamp: parseInt(item[0]),
price: parseFloat(item[2]), // close price
volume: parseFloat(item[5]),
openInterest: 0, // fetched separately
symbol
}));
}
}
Signal Detection Logic
Let's implement a service that detects four types of signals:
// services/signalDetector.ts
export class SignalDetector {
detectHighZone(data: MarketData[]): DetectedSignal | null {
if (data.length < 20) return null;
const recent = data.slice(-5);
const historical = data.slice(-20, -5);
const recentHigh = Math.max(...recent.map(d => d.price));
const historicalAvg = historical.reduce((sum, d) => sum + d.price, 0) / historical.length;
const priceRatio = recentHigh / historicalAvg;
if (priceRatio > 1.15) { // 15% or more increase
return {
type: 'HIGH_ZONE',
strength: Math.min((priceRatio - 1) * 100, 100),
timestamp: Date.now(),
timeframe: 'HTF'
};
}
return null;
}
detectPriceWeak(data: MarketData[]): DetectedSignal | null {
if (data.length < 10) return null;
const recent = data.slice(-3);
const comparison = data.slice(-10, -3);
// Detect declining price momentum
const recentSlope = this.calculateSlope(recent.map(d => d.price));
const comparisonSlope = this.calculateSlope(comparison.map(d => d.price));
if (recentSlope < comparisonSlope * 0.5 && comparisonSlope > 0) {
return {
type: 'PRICE_WEAK',
strength: Math.abs(recentSlope - comparisonSlope) * 10,
timestamp: Date.now(),
timeframe: 'LTF'
};
}
return null;
}
detectVolumeWeak(data: MarketData[]): DetectedSignal | null {
if (data.length < 10) return null;
const recentVolume = data.slice(-3).reduce((sum, d) => sum + d.volume, 0) / 3;
const avgVolume = data.slice(-10).reduce((sum, d) => sum + d.volume, 0) / 10;
const volumeRatio = recentVolume / avgVolume;
if (volumeRatio < 0.7) { // 30% or more volume decrease
return {
type: 'VOLUME_WEAK',
strength: (1 - volumeRatio) * 100,
timestamp: Date.now(),
timeframe: 'LTF'
};
}
return null;
}
detectOISignal(data: MarketData[], currentOI: number): DetectedSignal | null {
if (data.length < 5) return null;
const recentPriceChange = (data[data.length - 1].price - data[data.length - 5].price) / data[data.length - 5].price;
const avgOI = data.reduce((sum, d) => sum + d.openInterest, 0) / data.length;
// Decreasing OI during price rise is an overheating signal
if (recentPriceChange > 0.05 && currentOI < avgOI * 0.9) {
return {
type: 'OI_SIGNAL',
strength: Math.abs(recentPriceChange) * 100,
timestamp: Date.now(),
timeframe: 'HTF'
};
}
return null;
}
private calculateSlope(values: number[]): number {
const n = values.length;
const xSum = (n * (n - 1)) / 2;
const ySum = values.reduce((sum, val) => sum + val, 0);
const xySum = values.reduce((sum, val, i) => sum + i * val, 0);
const xSquareSum = (n * (n - 1) * (2 * n - 1)) / 6;
return (n * xySum - xSum * ySum) / (n * xSquareSum - xSum * xSum);
}
}
Cooling Level Analysis
Integrate multiple signals to determine the cooling level:
// services/coolingAnalyzer.ts
export class CoolingAnalyzer {
analyzeCoolingLevel(signals: DetectedSignal[]): CoolingAnalysis {
const signalWeights = {
HIGH_ZONE: 0.4,
PRICE_WEAK: 0.2,
VOLUME_WEAK: 0.2,
OI_SIGNAL: 0.2
};
let totalScore = 0;
let weightSum = 0;
signals.forEach(signal => {
const weight = signalWeights[signal.type];
totalScore += signal.strength * weight;
weightSum += weight;
});
const confidence = weightSum > 0 ? totalScore / (weightSum * 100) : 0;
const level = this.determineCoolingLevel(confidence, signals);
return {
level,
signals,
confidence,
recommendation: this.generateRecommendation(level, confidence)
};
}
private determineCoolingLevel(confidence: number, signals: DetectedSignal[]): CoolingLevel {
const highZoneSignal = signals.find(s => s.type === 'HIGH_ZONE');
const weaknessSignals = signals.filter(s =>
s.type === 'PRICE_WEAK' || s.type === 'VOLUME_WEAK' || s.type === 'OI_SIGNAL'
);
if (highZoneSignal && weaknessSignals.length >= 2 && confidence > 0.7) {
return 'confirmed';
} else if (highZoneSignal && weaknessSignals.length >= 1 && confidence > 0.5) {
return 'watch';
} else if (weaknessSignals.length >= 1 && confidence > 0.3) {
return 'mild';
}
return 'none';
}
private generateRecommendation(level: CoolingLevel, confidence: number): string {
const recommendations = {
confirmed: 'Market overheating confirmed. Consider reducing positions.',
watch: 'Signs of overheating detected. Monitor closely and prepare for profit-taking.',
mild: 'Mild bearish signals detected. Watch for trend changes.',
none: 'No clear overheating signals currently detected.'
};
return recommendations[level];
}
}
Express API Endpoints
Implement API endpoints to provide analysis results:
// routes/analysis.ts
import { Router } from 'express';
import { KuCoinApiService } from '../services/kucoinApi';
import { SignalDetector } from '../services/signalDetector';
import { CoolingAnalyzer } from '../services/coolingAnalyzer';
const router = Router();
const apiService = new KuCoinApiService();
const signalDetector = new SignalDetector();
const coolingAnalyzer = new CoolingAnalyzer();
router.get('/analyze/:symbol', async (req, res) => {
try {
const { symbol } = req.params;
const { timeframe = 'HTF', interval = '4h' } = req.query;
// Fetch data
const marketData = await apiService.getKlineData(symbol, interval as string);
const currentOI = await apiService.getOpenInterest(symbol);
// Integrate open interest data
marketData.forEach(data => data.openInterest = currentOI);
// Detect signals
const signals = [
signalDetector.detectHighZone(marketData),
signalDetector.detectPriceWeak(marketData),
signalDetector.detectVolumeWeak(marketData),
signalDetector.detectOISignal(marketData, currentOI)
].filter(signal => signal !== null);
// Analyze cooling level
const analysis = coolingAnalyzer.analyzeCoolingLevel(signals);
res.json({
symbol,
timeframe,
analysis,
timestamp: Date.now()
});
} catch (error) {
console.error('Analysis error:', error);
res.status(500).json({ error: 'Analysis failed' });
}
});
export default router;
Test Implementation
Unit testing with Vitest is also important:
// tests/signalDetector.test.ts
import { describe, it, expect } from 'vitest';
import { SignalDetector } from '../src/services/signalDetector';
import type { MarketData } from '../src/types/market';
describe('SignalDetector', () => {
const detector = new SignalDetector();
it('should detect high zone signal', () => {
const mockData: MarketData[] = Array.from({ length: 20 }, (_, i) => ({
timestamp: Date.now() - (20 - i) * 60000,
price: i < 15 ? 100 : 120, // Last 5 candles show 20% increase
volume: 1000,
openInterest: 5000,
symbol: 'BTCUSDT'
}));
const signal = detector.detectHighZone(mockData);
expect(signal).toBeTruthy();
expect(signal?.type).toBe('HIGH_ZONE');
expect(signal?.strength).toBeGreaterThan(15);
});
it('should detect volume weakness', () => {
const mockData: MarketData[] = Array.from({ length: 10 }, (_, i) => ({
timestamp: Date.now() - (10 - i) * 60000,
price: 100,
volume: i < 7 ? 1000 : 500, // Last 3 candles show 50% volume drop
openInterest: 5000,
symbol: 'BTCUSDT'
}));
const signal = detector.detectVolumeWeak(mockData);
expect(signal).toBeTruthy();
expect(signal?.type).toBe('VOLUME_WEAK');
});
});
This system provides a comprehensive framework for detecting cryptocurrency market overheating and cooling
Top comments (0)