Files
trading_bot_v4/lib/maintenance/data-cleanup.ts
mindesbunister 5773d7d36d feat: Extend 1-minute data retention from 4 weeks to 1 year
- Updated lib/maintenance/data-cleanup.ts retention period: 28 days → 365 days
- Storage requirements validated: 251 MB/year (negligible)
- Rationale: 13× more historical data for better pattern analysis
- Benefits: 260-390 blocked signals/year vs 20-30/month
- Cleanup cutoff: Now Dec 2, 2024 (vs Nov 4, 2025 previously)
- Deployment verified: Container restarted, cleanup scheduled for 3 AM daily
2025-12-02 11:55:36 +01:00

188 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Data Cleanup Service (Dec 2, 2025)
*
* Purpose: Automatic cleanup of old market data to prevent database bloat
* Retention: 1 year of 1-minute data (extended from 4 weeks - minimal storage ~251 MB/year)
*
* Schedule: Runs daily at 3 AM
* Impact: ~1,576,800 rows per year (1 row/min × 60 min × 24 hr × 365 days × 3 symbols)
*/
import { getPrismaClient } from '../database/trades'
export class DataCleanupService {
private prisma = getPrismaClient()
private cleanupInterval: NodeJS.Timeout | null = null
private isRunning = false
/**
* Start the automatic cleanup job
* Runs daily at 3 AM
*/
public start(): void {
if (this.isRunning) {
console.log('⚠️ Data cleanup service already running')
return
}
console.log('🧹 Starting data cleanup service...')
this.isRunning = true
// Run immediately on start
this.runCleanup().catch(error => {
console.error('❌ Error in initial cleanup:', error)
})
// Calculate time until next 3 AM
const now = new Date()
const next3AM = new Date()
next3AM.setHours(3, 0, 0, 0)
if (now.getHours() >= 3) {
// If it's already past 3 AM today, schedule for tomorrow
next3AM.setDate(next3AM.getDate() + 1)
}
const msUntil3AM = next3AM.getTime() - now.getTime()
// Schedule first run at 3 AM
setTimeout(() => {
this.runCleanup().catch(error => {
console.error('❌ Error in scheduled cleanup:', error)
})
// Then run every 24 hours
this.cleanupInterval = setInterval(() => {
this.runCleanup().catch(error => {
console.error('❌ Error in cleanup:', error)
})
}, 24 * 60 * 60 * 1000) // 24 hours
}, msUntil3AM)
const hoursUntil3AM = Math.round(msUntil3AM / (60 * 60 * 1000))
console.log(`✅ Data cleanup scheduled for 3 AM (in ${hoursUntil3AM} hours)`)
}
/**
* Stop the cleanup service
*/
public stop(): void {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval)
this.cleanupInterval = null
}
this.isRunning = false
console.log('⏹️ Data cleanup service stopped')
}
/**
* Run the cleanup job now
*/
public async runCleanup(): Promise<void> {
try {
console.log('🧹 Starting data cleanup...')
const startTime = Date.now()
// Delete market data older than 1 year (365 days)
const oneYearAgo = new Date()
oneYearAgo.setDate(oneYearAgo.getDate() - 365)
const deletedCount = await this.prisma.marketData.deleteMany({
where: {
createdAt: {
lt: oneYearAgo
}
}
})
const duration = Date.now() - startTime
console.log(
`✅ Data cleanup complete: Deleted ${deletedCount.count} old market data rows ` +
`(older than ${oneYearAgo.toISOString().split('T')[0]}) in ${duration}ms`
)
// Log statistics
const totalRows = await this.prisma.marketData.count()
const oldestRow = await this.prisma.marketData.findFirst({
orderBy: { createdAt: 'asc' }
})
const newestRow = await this.prisma.marketData.findFirst({
orderBy: { createdAt: 'desc' }
})
console.log(`📊 Database stats:`)
console.log(` Total rows: ${totalRows.toLocaleString()}`)
if (oldestRow) {
const oldestDays = Math.round(
(Date.now() - oldestRow.createdAt.getTime()) / (24 * 60 * 60 * 1000)
)
console.log(` Oldest data: ${oldestDays} days old`)
}
if (newestRow) {
const newestMinutes = Math.round(
(Date.now() - newestRow.createdAt.getTime()) / (60 * 1000)
)
console.log(` Newest data: ${newestMinutes} minutes old`)
}
} catch (error) {
console.error('❌ Error in data cleanup:', error)
throw error
}
}
/**
* Get cleanup statistics (for monitoring)
*/
public async getStats(): Promise<{
totalRows: number
oldestDate: Date | null
newestDate: Date | null
dataSpanDays: number
}> {
const totalRows = await this.prisma.marketData.count()
const oldestRow = await this.prisma.marketData.findFirst({
orderBy: { createdAt: 'asc' }
})
const newestRow = await this.prisma.marketData.findFirst({
orderBy: { createdAt: 'desc' }
})
let dataSpanDays = 0
if (oldestRow && newestRow) {
dataSpanDays = Math.round(
(newestRow.createdAt.getTime() - oldestRow.createdAt.getTime()) /
(24 * 60 * 60 * 1000)
)
}
return {
totalRows,
oldestDate: oldestRow?.createdAt || null,
newestDate: newestRow?.createdAt || null,
dataSpanDays
}
}
}
// Singleton instance
let cleanupService: DataCleanupService | null = null
export function getDataCleanupService(): DataCleanupService {
if (!cleanupService) {
cleanupService = new DataCleanupService()
console.log('🔧 Initialized Data Cleanup Service')
}
return cleanupService
}
/**
* Start the cleanup service (called from startup)
*/
export function startDataCleanup(): void {
const service = getDataCleanupService()
service.start()
}