learning_ai_invt_trdg/backend/src/strategies/directionTracker.ts

110 lines
3.3 KiB
TypeScript

import logger from '../utils/logger.js';
export enum SignalType {
BUY = 'BUY',
SELL = 'SELL',
NONE = 'NONE'
}
export interface StrategyResult {
signal: SignalType;
ema: number;
rsi: number;
changed: boolean;
}
export class DirectionTracker {
private lastSignal: SignalType = SignalType.NONE;
/**
* Advanced Strategy: EMA + RSI
* BUY: Price > EMA-20 AND RSI < 70 (not overbought)
* SELL: Price < EMA-20 AND RSI > 30 (not oversold)
*
* Includes a state machine to prevent duplicate signals.
*/
public calculateDirection(candles: any[]): StrategyResult {
// Need enough data for RSI (14) and EMA (20)
if (candles.length < 20) {
return { signal: SignalType.NONE, ema: 0, rsi: 0, changed: false };
}
const latestCandle = candles[candles.length - 1];
const latestClose = latestCandle.close;
// 1. Calculate EMA-20
const ema20 = this.calculateEMA(candles.map(c => c.close), 20);
// 2. Calculate RSI-14
const rsi14 = this.calculateRSI(candles.map(c => c.close), 14);
// 3. Signal Logic
let currentSignal = SignalType.NONE;
if (latestClose > ema20 && rsi14 < 70) {
currentSignal = SignalType.BUY;
} else if (latestClose < ema20 && rsi14 > 30) {
currentSignal = SignalType.SELL;
}
// 4. State Machine (Check if signal changed to avoid duplicates)
const changed = currentSignal !== SignalType.NONE && currentSignal !== this.lastSignal;
if (changed) {
logger.info(`🚨 New Strategy Signal: ${currentSignal} (EMA: ${ema20.toFixed(2)}, RSI: ${rsi14.toFixed(2)})`);
this.lastSignal = currentSignal;
}
return {
signal: currentSignal,
ema: ema20,
rsi: rsi14,
changed
};
}
private calculateEMA(data: number[], period: number): number {
if (data.length === 0) return 0;
const k = 2 / (period + 1);
let ema = data[0] || 0;
for (let i = 1; i < data.length; i++) {
const current = data[i] || 0;
ema = current * k + ema * (1 - k);
}
return ema;
}
private calculateRSI(data: number[], period: number): number {
if (data.length <= period) return 50; // Default if not enough data
let gains = 0;
let losses = 0;
// First average
for (let i = 1; i <= period; i++) {
const diff = data[i] - data[i - 1];
if (diff >= 0) gains += diff;
else losses -= diff;
}
let avgGain = gains / period;
let avgLoss = losses / period;
// Smoothing
for (let i = period + 1; i < data.length; i++) {
const diff = data[i] - data[i - 1];
if (diff >= 0) {
avgGain = (avgGain * (period - 1) + diff) / period;
avgLoss = (avgLoss * (period - 1)) / period;
} else {
avgGain = (avgGain * (period - 1)) / period;
avgLoss = (avgLoss * (period - 1) - diff) / period;
}
}
if (avgLoss === 0) return 100;
const rs = avgGain / avgLoss;
return 100 - (100 / (1 + rs));
}
}