FEATURE: Validate signal quality before Smart Entry execution - Re-checks market conditions after pullback wait (2-4 min) - Cancels trade if conditions degraded significantly VALIDATION CHECKS (4): 1. ADX degradation: Cancel if drops >2 points (enhanced existing) 2. Volume collapse: Cancel if drops >40% (NEW - momentum fading) 3. RSI reversal: Cancel if LONG RSI <30 or SHORT RSI >70 (NEW) 4. MAGAP divergence: Cancel if wrong MA structure (NEW) EXPECTED IMPACT: - Block 5-10% of signals that degrade during Smart Entry wait - Save $300-800 in prevented losses over 100 trades - Prevent entries when ADX/volume/momentum weakens FILES CHANGED: - lib/trading/smart-entry-timer.ts (115 lines validation logic) - lib/trading/market-data-cache.ts (added maGap to interface) INTEGRATION: Works with Phase 7.1 Smart Entry Timer - Smart Entry waits for pullback (2-4 min) - Phase 7.2 validates quality before execution - Cancels if conditions degraded, executes if maintained
119 lines
3.3 KiB
TypeScript
119 lines
3.3 KiB
TypeScript
/**
|
|
* Market Data Cache Service
|
|
*
|
|
* Purpose: Stores real-time TradingView metrics for manual trade validation.
|
|
* Data flows: TradingView → /api/trading/market-data → Cache → Re-entry checks
|
|
*
|
|
* Cache expiry: 5 minutes (configurable)
|
|
*/
|
|
|
|
export interface MarketMetrics {
|
|
symbol: string // "SOL-PERP", "ETH-PERP", "BTC-PERP"
|
|
atr: number // Average True Range (volatility %)
|
|
adx: number // Average Directional Index (trend strength)
|
|
rsi: number // Relative Strength Index (momentum)
|
|
volumeRatio: number // Current volume / average volume
|
|
pricePosition: number // Position in recent range (0-100%)
|
|
maGap?: number // MA50-MA200 gap percentage (v9+)
|
|
currentPrice: number // Latest close price
|
|
timestamp: number // Unix timestamp (ms)
|
|
timeframe: string // "5" for 5min, "60" for 1h, etc.
|
|
}
|
|
|
|
class MarketDataCache {
|
|
private cache: Map<string, MarketMetrics> = new Map()
|
|
private readonly MAX_AGE_MS = 5 * 60 * 1000 // 5 minutes
|
|
|
|
/**
|
|
* Store fresh market data from TradingView
|
|
*/
|
|
set(symbol: string, metrics: MarketMetrics): void {
|
|
this.cache.set(symbol, metrics)
|
|
console.log(
|
|
`📊 Cached market data for ${symbol}: ` +
|
|
`ADX=${metrics.adx.toFixed(1)} ` +
|
|
`ATR=${metrics.atr.toFixed(2)}% ` +
|
|
`RSI=${metrics.rsi.toFixed(1)} ` +
|
|
`Vol=${metrics.volumeRatio.toFixed(2)}x`
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Retrieve cached data if still fresh (<5min old)
|
|
* Returns null if stale or missing
|
|
*/
|
|
get(symbol: string): MarketMetrics | null {
|
|
const data = this.cache.get(symbol)
|
|
|
|
if (!data) {
|
|
console.log(`⚠️ No cached data for ${symbol}`)
|
|
return null
|
|
}
|
|
|
|
const ageSeconds = Math.round((Date.now() - data.timestamp) / 1000)
|
|
|
|
if (Date.now() - data.timestamp > this.MAX_AGE_MS) {
|
|
console.log(`⏰ Cached data for ${symbol} is stale (${ageSeconds}s old, max 300s)`)
|
|
return null
|
|
}
|
|
|
|
console.log(`✅ Using fresh TradingView data for ${symbol} (${ageSeconds}s old)`)
|
|
return data
|
|
}
|
|
|
|
/**
|
|
* Check if fresh data exists without retrieving it
|
|
*/
|
|
has(symbol: string): boolean {
|
|
const data = this.cache.get(symbol)
|
|
if (!data) return false
|
|
|
|
return Date.now() - data.timestamp <= this.MAX_AGE_MS
|
|
}
|
|
|
|
/**
|
|
* Get all cached symbols with fresh data
|
|
*/
|
|
getAvailableSymbols(): string[] {
|
|
const now = Date.now()
|
|
const freshSymbols: string[] = []
|
|
|
|
for (const [symbol, data] of this.cache.entries()) {
|
|
if (now - data.timestamp <= this.MAX_AGE_MS) {
|
|
freshSymbols.push(symbol)
|
|
}
|
|
}
|
|
|
|
return freshSymbols
|
|
}
|
|
|
|
/**
|
|
* Get age of cached data in seconds (for debugging)
|
|
*/
|
|
getDataAge(symbol: string): number | null {
|
|
const data = this.cache.get(symbol)
|
|
if (!data) return null
|
|
|
|
return Math.round((Date.now() - data.timestamp) / 1000)
|
|
}
|
|
|
|
/**
|
|
* Clear all cached data (for testing)
|
|
*/
|
|
clear(): void {
|
|
this.cache.clear()
|
|
console.log('🗑️ Market data cache cleared')
|
|
}
|
|
}
|
|
|
|
// Singleton instance
|
|
let marketDataCache: MarketDataCache | null = null
|
|
|
|
export function getMarketDataCache(): MarketDataCache {
|
|
if (!marketDataCache) {
|
|
marketDataCache = new MarketDataCache()
|
|
console.log('🔧 Initialized Market Data Cache (5min expiry)')
|
|
}
|
|
return marketDataCache
|
|
}
|