diff --git a/.env b/.env index e6b3b6f..c66b38b 100644 --- a/.env +++ b/.env @@ -452,4 +452,6 @@ WITHDRAWAL_PROFIT_PERCENT=10 MIN_WITHDRAWAL_AMOUNT=5 MIN_ACCOUNT_BALANCE=500 LAST_WITHDRAWAL_TIME=2025-11-19T19:34:47.185Z -TOTAL_WITHDRAWN=12.50 \ No newline at end of file +TOTAL_WITHDRAWN=12.50 +# Production Logging Configuration (Phase 1 Optimization - Dec 5, 2025) +DEBUG_LOGS=false diff --git a/cluster/exploration.db b/cluster/exploration.db index 4491318..284ab9a 100644 Binary files a/cluster/exploration.db and b/cluster/exploration.db differ diff --git a/docs/analysis/OPTIMIZATION_EXECUTION_PLAN.md b/docs/analysis/OPTIMIZATION_EXECUTION_PLAN.md new file mode 100644 index 0000000..48af48a --- /dev/null +++ b/docs/analysis/OPTIMIZATION_EXECUTION_PLAN.md @@ -0,0 +1,1491 @@ +# Trading Bot Optimization Execution Plan + +**Generated:** December 4, 2025 +**Based On:** Comprehensive system analysis (8 data collection commands) +**Status:** Ready for execution +**Duration:** 3 months (3 phases) + +--- + +## Quick Reference + +**Top 3 Priorities:** +1. πŸ”΄ **Console.log Gating** (4h, 90% impact, CRITICAL) +2. πŸ”΄ **Docker Image Size** (3h, 50% reduction, HIGH) +3. 🟑 **Position Manager Refactor** (11d, 59% complexity reduction, MEDIUM) + +**Current System Health:** βœ… EXCELLENT +- CPU: 10.88% (stable) +- Memory: 179.7MiB (8.77% of 2GB) +- Database: 20MB for 170+ trades (efficient) +- Trading: $540 capital, 57.1% WR, +$262.70 (v8) + +--- + +## Phase 1: Quick Wins (1-2 weeks) + +### Task 1.1: Console.log Production Gating πŸ”΄ CRITICAL + +**Problem:** 731 unguarded console statements causing production overhead + +**Files Affected:** 18 files across lib/ +``` +lib/trading/position-manager.ts: 244 statements +lib/drift/orders.ts: 89 statements +lib/database/trades.ts: 63 statements +lib/trading/smart-entry-timer.ts: 58 statements +lib/analysis/blocked-signal-tracker.ts: 54 statements +lib/trading/stop-hunt-tracker.ts: 50 statements +lib/drift/client.ts: 41 statements +lib/startup/init-position-manager.ts: 38 statements +lib/trading/smart-validation-queue.ts: 36 statements +lib/trading/signal-quality.ts: 28 statements +lib/pyth/price-monitor.ts: 13 statements +lib/notifications/telegram.ts: 7 statements +lib/trading/market-data-cache.ts: 4 statements +lib/monitoring/drift-health-monitor.ts: 2 statements +lib/trading/revenge-system.ts: 2 statements +lib/utils/persistent-logger.ts: 1 statement +lib/database/client.ts: 1 statement +lib/trading/ghost-detection.ts: 0 statements +``` + +**Solution: Environment-Gated Logging** + +**Step 1: Create Logger Utility (15 minutes)** +```typescript +// lib/utils/logger.ts +const isDev = process.env.NODE_ENV !== 'production' +const isDebug = process.env.DEBUG_LOGS === 'true' + +export const logger = { + log: (...args: any[]) => { + if (isDev || isDebug) console.log(...args) + }, + error: (...args: any[]) => { + // Errors always logged + console.error(...args) + }, + warn: (...args: any[]) => { + if (isDev || isDebug) console.warn(...args) + }, + debug: (...args: any[]) => { + if (isDebug) console.log('[DEBUG]', ...args) + } +} +``` + +**Step 2: Automated Replacement (3 hours)** +```bash +# Use codemod script (create scripts/replace-console-logs.js) +# Find all console.log β†’ logger.log +# Find all console.warn β†’ logger.warn +# Keep all console.error β†’ logger.error (always show) +# Add import { logger } from '@/lib/utils/logger' + +cd /home/icke/traderv4 +node scripts/replace-console-logs.js + +# Manual review high-priority files: +# - position-manager.ts (244 statements) +# - orders.ts (89 statements) +# - trades.ts (63 statements) +``` + +**Step 3: ENV Configuration (5 minutes)** +```bash +# .env additions +NODE_ENV=production +DEBUG_LOGS=false # Toggle for troubleshooting +``` + +**Step 4: Docker Rebuild (10 minutes)** +```bash +docker compose build trading-bot +docker compose up -d --force-recreate trading-bot +docker logs -f trading-bot-v4 | head -100 # Verify gating works +``` + +**Success Criteria:** +- βœ… Production logs: <10 entries per minute (was >100) +- βœ… 90% reduction in log volume +- βœ… DEBUG_LOGS=true restores full logging +- βœ… All trading functionality preserved + +**Effort:** 4 hours +**Risk:** LOW (fallback: revert git commit) +**Priority:** πŸ”΄ CRITICAL + +--- + +### Task 1.2: TypeScript Type-Only Imports ⚑ QUICK WIN + +**Problem:** 49 imports without `type` keyword causing compilation overhead + +**Solution: ESLint + Auto-Fix** + +**Step 1: ESLint Rule (10 minutes)** +```json +// .eslintrc.json additions +{ + "rules": { + "@typescript-eslint/consistent-type-imports": [ + "error", + { + "prefer": "type-imports", + "fixStyle": "separate-type-imports" + } + ] + } +} +``` + +**Step 2: Automated Fix (20 minutes)** +```bash +cd /home/icke/traderv4 +npx eslint lib/ --fix --ext .ts +npm run build # Verify no compilation errors +git add -A +git commit -m "optimize: Add type-only imports for TypeScript compilation speedup" +git push +``` + +**Success Criteria:** +- βœ… 0 missing type imports (was 49) +- βœ… Build time: 52-53s (5-10% faster from 54.74s) +- βœ… No runtime behavior changes + +**Effort:** 30 minutes +**Risk:** NONE (purely compilation optimization) +**Priority:** 🟒 HIGH + +--- + +### Task 1.3: Docker Image Size Investigation πŸ” + +**Problem:** 1.32GB image (5Γ— larger than postgres at 275MB) + +**Investigation Steps (3 hours)** + +**Step 1: Layer Analysis (1 hour)** +```bash +# Analyze layer sizes +docker history trading-bot-v4 --human --no-trunc | head -20 + +# Use dive tool for interactive inspection +docker run --rm -it \ + -v /var/run/docker.sock:/var/run/docker.sock \ + wagoodman/dive:latest trading-bot-v4 + +# Look for: +# - node_modules in multiple layers (duplication) +# - Dev dependencies in production +# - Large Solana/Drift SDK files +# - Unused build artifacts +``` + +**Step 2: Dockerfile Optimization (1.5 hours)** +```dockerfile +# Potential changes based on findings: + +# Multi-stage: Ensure dev dependencies NOT in final image +FROM node:20-alpine AS deps +WORKDIR /app +COPY package*.json ./ +RUN npm ci --only=production && npm cache clean --force + +# Builder stage: Keep build deps isolated +FROM node:20-alpine AS builder +WORKDIR /app +COPY package*.json ./ +RUN npm ci # Include dev deps for build +COPY . . +RUN npm run build + +# Final stage: Minimal runtime +FROM node:20-alpine AS runner +WORKDIR /app +ENV NODE_ENV=production +COPY --from=deps /app/node_modules ./node_modules +COPY --from=builder /app/.next ./.next +COPY --from=builder /app/public ./public +# ... rest of files +``` + +**Step 3: Build and Measure (30 minutes)** +```bash +docker compose build trading-bot +docker images | grep trading-bot + +# Target: 600-800MB (50% reduction from 1.32GB) +# If not achieved, investigate further: +# - npm dedupe to remove duplicates +# - Replace heavy dependencies +# - Use .dockerignore more aggressively +``` + +**Success Criteria:** +- βœ… Image size: 600-800MB (45-53% reduction) +- βœ… All functionality preserved +- βœ… Container starts successfully +- βœ… Test trade executes correctly + +**Effort:** 3 hours +**Risk:** LOW (can revert Dockerfile) +**Priority:** πŸ”΄ HIGH + +--- + +### Task 1.4: Export Tree-Shaking Audit 🌳 + +**Problem:** 93 exports, potential unused code in bundles + +**Solution: Automated Detection** + +**Step 1: Install Tool (5 minutes)** +```bash +cd /home/icke/traderv4 +npm install --save-dev ts-prune +``` + +**Step 2: Run Analysis (30 minutes)** +```bash +npx ts-prune | tee docs/analysis/unused-exports.txt + +# Review output, identify safe removals +# Focus on: +# - Unused helper functions +# - Legacy code exports +# - Over-exported types + +# Manual cleanup of confirmed unused exports +# Test after each removal: npm run build +``` + +**Step 3: Verification (15 minutes)** +```bash +npm run build +# Check bundle sizes: should be 5-10% smaller +ls -lh .next/static/chunks/app/*.js +``` + +**Success Criteria:** +- βœ… 5-10% bundle size reduction +- βœ… No broken imports +- βœ… Build successful + +**Effort:** 1 hour +**Risk:** LOW (TypeScript catches broken imports) +**Priority:** 🟑 MEDIUM + +--- + +## Phase 1 Summary + +**Duration:** 1-2 weeks +**Total Effort:** 8.5 hours +**Expected Results:** +- 90% log volume reduction +- 45-53% Docker image reduction +- 5-10% build time improvement +- 5-10% bundle size reduction +- 100% type import compliance + +**Deployment Checklist:** +- [ ] All changes committed to git +- [ ] Docker rebuilt with new optimizations +- [ ] Container restarted successfully +- [ ] Test trade executed (verify no regressions) +- [ ] Logs monitored for 24 hours +- [ ] Update OPTIMIZATION_MASTER_ROADMAP.md + +--- + +## Phase 2: Medium Initiatives (2-4 weeks) + +### Task 2.1: Database Query Batching πŸ“Š + +**Problem:** 32 trade queries (51.6% of all queries) concentrated in trades.ts + +**Solution: Prisma Include Optimization** + +**Step 1: Audit Current Queries (1 hour)** +```bash +# Identify N+1 patterns +grep -n "prisma.trade" lib/database/trades.ts + +# Common patterns needing batching: +# - getTradeStats() with multiple findMany +# - Individual trade fetches in loops +# - Separate queries for related data +``` + +**Step 2: Implement Batching (2 hours)** +```typescript +// Example: getTradeStats with include +export async function getTradeStats(filters?: TradeFilters) { + // BEFORE: Multiple queries + // const trades = await prisma.trade.findMany({ where }) + // const winningTrades = await prisma.trade.count({ where: { ...where, realizedPnL: { gt: 0 } } }) + // const losingTrades = await prisma.trade.count({ where: { ...where, realizedPnL: { lt: 0 } } }) + + // AFTER: Single query with aggregation + const [stats, trades] = await Promise.all([ + prisma.trade.aggregate({ + where, + _count: true, + _sum: { realizedPnL: true }, + _avg: { realizedPnL: true } + }), + prisma.trade.findMany({ + where, + select: { realizedPnL: true, exitReason: true } + }) + ]) + + // Calculate derived stats from single result set + const winningTrades = trades.filter(t => t.realizedPnL > 0).length + const losingTrades = trades.filter(t => t.realizedPnL < 0).length + // ... +} +``` + +**Step 3: Testing (30 minutes)** +```bash +# Run analytics queries, verify results match +curl http://localhost:3001/api/analytics/last-trade +curl http://localhost:3001/api/withdrawals/stats + +# Monitor query performance +docker logs trading-bot-v4 | grep -i "prisma" | head -20 +``` + +**Success Criteria:** +- βœ… Trade queries: 15-20 (50-70% reduction from 32) +- βœ… Same analytics results (correctness preserved) +- βœ… Response time: <100ms for dashboard + +**Effort:** 3.5 hours +**Risk:** LOW (compare old vs new results) +**Priority:** πŸ”΄ HIGH + +--- + +### Task 2.2: Database Indexing Audit πŸ” + +**Problem:** No systematic index audit, potential slow queries + +**Solution: Strategic Index Creation** + +**Step 1: Query Pattern Analysis (2 hours)** +```sql +-- Connect to database +docker exec -it trading-bot-postgres psql -U postgres -d trading_bot_v4 + +-- Analyze slow queries (if logging enabled) +SELECT query, calls, total_time, mean_time +FROM pg_stat_statements +ORDER BY mean_time DESC +LIMIT 20; + +-- Common filter patterns in codebase: +-- WHERE exitReason IS NULL (open positions) +-- WHERE symbol = 'SOL-PERP' (per-symbol queries) +-- WHERE signalQualityScore >= X (quality filtering) +-- WHERE createdAt > NOW() - INTERVAL '24 hours' (recent trades) +-- WHERE indicatorVersion = 'v8' (version comparison) +``` + +**Step 2: Index Creation (2 hours)** +```sql +-- Prisma migration file: prisma/migrations/YYYYMMDD_add_performance_indexes/migration.sql + +-- Index for open positions (frequent query) +CREATE INDEX idx_trade_open_positions ON "Trade"("exitReason") +WHERE "exitReason" IS NULL; + +-- Index for symbol filtering +CREATE INDEX idx_trade_symbol ON "Trade"("symbol"); + +-- Composite index for quality analysis +CREATE INDEX idx_trade_quality_version ON "Trade"("signalQualityScore", "indicatorVersion"); + +-- Index for time-based queries +CREATE INDEX idx_trade_created_at ON "Trade"("createdAt" DESC); + +-- Index for stop hunt tracking +CREATE INDEX idx_stophunt_active ON "StopHunt"("revengeExecuted", "revengeWindowExpired") +WHERE "revengeExecuted" = false AND "revengeWindowExpired" = false; +``` + +**Step 3: Migration and Verification (1 hour)** +```bash +# Create migration +npx prisma migrate dev --name add_performance_indexes + +# Apply to production +docker exec trading-bot-v4 npx prisma migrate deploy + +# Verify indexes created +docker exec -it trading-bot-postgres psql -U postgres -d trading_bot_v4 -c "\d+ \"Trade\"" + +# Benchmark queries before/after +# Should see 2-5Γ— speedup on filtered queries +``` + +**Success Criteria:** +- βœ… Query time: 2-5Γ— faster for common filters +- βœ… All migrations applied successfully +- βœ… No performance regressions + +**Effort:** 5 hours +**Risk:** LOW (indexes don't change data) +**Priority:** 🟑 MEDIUM + +--- + +### Task 2.3: Timer/Interval Consolidation ⏱️ + +**Problem:** 20 separate polling calls causing RPC overhead + +**Solution: Event-Driven Architecture** + +**Step 1: Audit Polling Patterns (4 hours)** +```bash +# Find all setInterval/setTimeout calls +grep -rn "setInterval\|setTimeout" lib/ --include="*.ts" + +# Document: +# - position-manager.ts: 2s price monitoring +# - stop-hunt-tracker.ts: 30s revenge checks +# - blocked-signal-tracker.ts: 5min price tracking +# - drift-health-monitor.ts: 2min health checks +# - smart-validation-queue.ts: 30s validation +``` + +**Step 2: Implement Event Bus (8 hours)** +```typescript +// lib/events/event-bus.ts +import { EventEmitter } from 'events' + +class TradingEventBus extends EventEmitter { + private static instance: TradingEventBus + + static getInstance() { + if (!this.instance) { + this.instance = new TradingEventBus() + } + return this.instance + } + + // Events: + // - 'price:update' - Pyth WebSocket price changes + // - 'trade:opened' - New position opened + // - 'trade:tp1' - TP1 hit + // - 'trade:closed' - Position closed +} + +// Example usage in position-manager.ts: +// Instead of 2s polling, listen to price updates +eventBus.on('price:update', ({ symbol, price }) => { + const trade = this.activeTrades.get(symbol) + if (trade) { + this.checkTradeConditions(trade, price) + } +}) +``` + +**Step 3: Adaptive Polling Fallback (4 hours)** +```typescript +// For systems that can't be fully event-driven +class AdaptivePoller { + private interval: NodeJS.Timeout | null = null + private currentRate: number = 30000 // Start slow + + adjustRate(activity: 'idle' | 'low' | 'high') { + const rates = { + idle: 30000, // 30s when no trades + low: 10000, // 10s with 1-2 trades + high: 2000 // 2s with 3+ trades + } + this.currentRate = rates[activity] + this.restart() + } +} +``` + +**Step 4: Testing (4 hours)** +```bash +# Shadow testing: Run old and new side-by-side +# Compare: Do same trades get detected? +# Measure: RPC call reduction (should be 50-70%) +# Monitor: CPU usage should drop 18-27% +``` + +**Success Criteria:** +- βœ… RPC calls: 50-70% reduction +- βœ… CPU usage: 8-9% (from 10.88%) +- βœ… Same trade detection accuracy + +**Effort:** 2 days +**Risk:** MEDIUM (core monitoring changes) +**Priority:** 🟑 MEDIUM + +--- + +### Task 2.4: Node Modules Audit πŸ“¦ + +**Problem:** 620MB node_modules (47.7% of disk) + +**Solution: Dependency Optimization** + +**Step 1: Analyze Dependencies (2 hours)** +```bash +# Size breakdown +npx npkgsize --output node_modules_sizes.txt + +# Identify large packages +du -sh node_modules/* | sort -rh | head -20 + +# Common culprits: +# - @drift-labs/sdk (Solana deps) +# - @solana/web3.js +# - @coral-xyz/anchor +# - next (framework) +``` + +**Step 2: Optimization Opportunities (2 hours)** +```json +// package.json changes: + +// 1. Remove unused dependencies +// Run: npx depcheck +// Remove packages not imported anywhere + +// 2. Replace heavy dependencies +// Example: moment β†’ date-fns (smaller bundle) +// Example: lodash β†’ native JS methods + +// 3. Move dev deps correctly +"devDependencies": { + "@types/*": "*", // Ensure all @types are dev-only + "eslint": "*", + "prettier": "*" +} + +// 4. Use npm ci for reproducible builds +// Already in Dockerfile, but verify +``` + +**Step 3: Rebuild and Test (30 minutes)** +```bash +rm -rf node_modules package-lock.json +npm install +npm run build +docker compose build trading-bot + +# Verify size reduction +du -sh node_modules +``` + +**Success Criteria:** +- βœ… Node modules: 480-500MB (20-23% reduction) +- βœ… All functionality preserved +- βœ… Build successful + +**Effort:** 4.5 hours +**Risk:** MEDIUM (dependency changes) +**Priority:** 🟑 MEDIUM + +--- + +### Task 2.5: RPC Call Pattern Optimization 🌐 + +**Problem:** 20.5GB received (high RPC volume) + +**Solution: Caching + Batching** + +**Step 1: Oracle Price Caching (4 hours)** +```typescript +// lib/drift/price-cache.ts +class OraclePriceCache { + private cache = new Map() + private TTL = 2000 // 2 second cache + + async getPrice(marketIndex: number): Promise { + const cached = this.cache.get(marketIndex.toString()) + const now = Date.now() + + if (cached && (now - cached.timestamp) < this.TTL) { + return cached.price + } + + // Fetch from Drift only if cache expired + const price = await driftService.getOraclePrice(marketIndex) + this.cache.set(marketIndex.toString(), { price, timestamp: now }) + return price + } +} +``` + +**Step 2: RPC Request Batching (4 hours)** +```typescript +// Batch multiple getOraclePrice calls into single RPC request +class BatchedRpcClient { + private queue: Array<{ marketIndex: number, resolve: Function }> = [] + private timeout: NodeJS.Timeout | null = null + + getPrice(marketIndex: number): Promise { + return new Promise((resolve) => { + this.queue.push({ marketIndex, resolve }) + + if (!this.timeout) { + this.timeout = setTimeout(() => this.flush(), 100) // 100ms batch window + } + }) + } + + private async flush() { + const batch = [...this.queue] + this.queue = [] + this.timeout = null + + // Single RPC call for all prices + const prices = await this.fetchMultiplePrices(batch.map(b => b.marketIndex)) + + batch.forEach((item, i) => item.resolve(prices[i])) + } +} +``` + +**Step 3: WebSocket Investigation (4 hours)** +```typescript +// Investigate if WebSocket subscriptions can replace polling +// Drift SDK may support WebSocket price feeds +// If yes, migrate from HTTP polling to WebSocket push +``` + +**Step 4: Monitoring (4 hours)** +```bash +# Track RPC call reduction +docker stats trading-bot-v4 --no-stream +# Network I/O should reduce by 30-50% + +# Verify no accuracy loss +# Price updates should still be timely (within 2s) +``` + +**Success Criteria:** +- βœ… RPC calls: 30-50% reduction +- βœ… Network received: <15GB/day (from 20.5GB) +- βœ… Price accuracy preserved (Β±0.01% tolerance) + +**Effort:** 2 days +**Risk:** LOW (caching is conservative) +**Priority:** 🟑 MEDIUM + +--- + +## Phase 2 Summary + +**Duration:** 2-4 weeks +**Total Effort:** 6 days +**Expected Results:** +- 38-53% database query reduction +- 2-5Γ— query speed improvement +- 50-70% RPC call reduction +- 20-23% node_modules size reduction +- 18-27% CPU usage reduction + +**Deployment Checklist:** +- [ ] Database migrations applied +- [ ] Shadow testing completed (old vs new behavior) +- [ ] Performance benchmarks documented +- [ ] Rollback plan prepared +- [ ] Gradual rollout: 10% β†’ 50% β†’ 100% over 2 weeks + +--- + +## Phase 3: Long-Term Projects (1-3 months) + +### Task 3.1: Winston Structured Logging πŸ“ + +**Problem:** Console.log doesn't provide queryable logs for production analysis + +**Solution: Professional Logging Framework** + +**Step 1: Install Winston (15 minutes)** +```bash +cd /home/icke/traderv4 +npm install winston winston-daily-rotate-file +``` + +**Step 2: Create Logger Service (3 hours)** +```typescript +// lib/utils/winston-logger.ts +import winston from 'winston' +import DailyRotateFile from 'winston-daily-rotate-file' + +const logger = winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + format: winston.format.combine( + winston.format.timestamp(), + winston.format.errors({ stack: true }), + winston.format.json() + ), + defaultMeta: { service: 'trading-bot' }, + transports: [ + // Console for Docker logs + new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + winston.format.simple() + ) + }), + + // File rotation for persistent logs + new DailyRotateFile({ + filename: '/app/logs/trading-%DATE%.log', + datePattern: 'YYYY-MM-DD', + maxSize: '20m', + maxFiles: '14d', + level: 'info' + }), + + // Separate error log + new DailyRotateFile({ + filename: '/app/logs/error-%DATE%.log', + datePattern: 'YYYY-MM-DD', + maxSize: '20m', + maxFiles: '30d', + level: 'error' + }) + ] +}) + +// Structured logging helpers +export const log = { + trade: (action: string, data: any) => + logger.info('TRADE', { action, ...data }), + + position: (action: string, data: any) => + logger.info('POSITION', { action, ...data }), + + error: (context: string, error: Error, data?: any) => + logger.error('ERROR', { context, error: error.message, stack: error.stack, ...data }) +} +``` + +**Step 3: Replace Logger Import (4 hours)** +```typescript +// Update all files to use Winston instead of simple logger +// Find: import { logger } from '@/lib/utils/logger' +// Replace: import { log } from '@/lib/utils/winston-logger' + +// Example conversions: +// logger.log('Trade opened') +// β†’ log.trade('opened', { symbol, entryPrice, size }) + +// logger.error('Failed to close position') +// β†’ log.error('position-close', error, { symbol, positionId }) +``` + +**Step 4: Log Analysis Setup (1 hour)** +```bash +# Query logs with jq +docker exec trading-bot-v4 cat /app/logs/trading-2025-12-04.log | jq '.action, .symbol, .realizedPnL' + +# Aggregate stats +cat logs/trading-*.log | jq -s 'group_by(.action) | map({action: .[0].action, count: length})' +``` + +**Success Criteria:** +- βœ… 100% console.log removed +- βœ… Queryable JSON logs +- βœ… 14-day retention working +- βœ… Error logs isolated + +**Effort:** 1 day +**Risk:** MEDIUM (logging changes) +**Priority:** 🟑 MEDIUM + +--- + +### Task 3.2: Position Manager Refactor πŸ”§ + +**Problem:** 1,945 lines causing maintainability issues + +**Solution: Modular Architecture** + +**Target Structure:** +``` +lib/trading/position-manager/ +β”œβ”€β”€ index.ts (200 lines) - Core orchestration +β”œβ”€β”€ price-monitor.ts (300 lines) - Price tracking & WebSocket +β”œβ”€β”€ trade-lifecycle.ts (400 lines) - State management +β”œβ”€β”€ exit-strategy.ts (500 lines) - TP/SL/trailing logic +β”œβ”€β”€ position-validator.ts (300 lines) - Ghost detection, external closure +└── types.ts (100 lines) - Shared interfaces +``` + +**Migration Strategy (11 days total):** + +**Week 1: Planning & Setup (2 days)** +- Day 1: Document current architecture (call graph, state flow) +- Day 2: Design module interfaces, define contracts + +**Week 2: Module Extraction (5 days)** +- Day 3-4: Extract price-monitor.ts (Pyth WebSocket, caching) +- Day 5: Extract position-validator.ts (ghost detection, external closure) +- Day 6-7: Extract exit-strategy.ts (TP1/TP2/trailing stop logic) + +**Week 3: Integration & Testing (4 days)** +- Day 8-9: Extract trade-lifecycle.ts (state transitions, DB updates) +- Day 10: Refactor index.ts as thin orchestrator +- Day 11: Integration testing, shadow deployment + +**Implementation Details:** + +**Step 1: Extract Price Monitor (2 days)** +```typescript +// lib/trading/position-manager/price-monitor.ts +export class PriceMonitor { + private pythMonitor: PythPriceMonitor + private subscriptions = new Map() + + constructor() { + this.pythMonitor = getPythPriceMonitor() + this.startMonitoring() + } + + subscribe(symbol: string, callback: (price: number) => void) { + this.subscriptions.set(symbol, callback) + } + + unsubscribe(symbol: string) { + this.subscriptions.delete(symbol) + } + + private startMonitoring() { + // WebSocket price updates trigger callbacks + this.pythMonitor.on('price', ({ symbol, price }) => { + const callback = this.subscriptions.get(symbol) + if (callback) callback(price) + }) + } +} +``` + +**Step 2: Extract Exit Strategy (2 days)** +```typescript +// lib/trading/position-manager/exit-strategy.ts +export class ExitStrategy { + shouldTakeProfit1(price: number, trade: ActiveTrade): boolean { + const profitPercent = this.calculateProfitPercent(trade.entryPrice, price, trade.direction) + return !trade.tp1Hit && profitPercent >= trade.tp1Percent + } + + shouldTakeProfit2(price: number, trade: ActiveTrade): boolean { + const profitPercent = this.calculateProfitPercent(trade.entryPrice, price, trade.direction) + return trade.tp1Hit && !trade.tp2Hit && profitPercent >= trade.tp2Percent + } + + shouldStopLoss(price: number, trade: ActiveTrade): boolean { + const profitPercent = this.calculateProfitPercent(trade.entryPrice, price, trade.direction) + return profitPercent <= trade.stopLossPercent + } + + calculateTrailingStop(trade: ActiveTrade): number { + // ATR-based trailing stop logic + const atrPercent = (trade.atrAtEntry / trade.entryPrice) * 100 + const multiplier = this.getTrailingMultiplier(trade) + return atrPercent * multiplier + } +} +``` + +**Step 3: Extract Position Validator (1 day)** +```typescript +// lib/trading/position-manager/position-validator.ts +export class PositionValidator { + async detectGhostPosition(trade: ActiveTrade): Promise { + const position = await this.getDriftPosition(trade.symbol) + if (!position || Math.abs(position.size) < 0.01) { + // Trade in memory but not on Drift = ghost + return true + } + return false + } + + async detectExternalClosure(trade: ActiveTrade): Promise { + const position = await this.getDriftPosition(trade.symbol) + if (!position && Date.now() - trade.lastUpdateTime > 30000) { + // Position gone and not recent = external closure + return true + } + return false + } +} +``` + +**Step 4: Refactor Core Index (2 days)** +```typescript +// lib/trading/position-manager/index.ts +export class PositionManager { + private priceMonitor: PriceMonitor + private exitStrategy: ExitStrategy + private validator: PositionValidator + private lifecycle: TradeLifecycle + + constructor(config: TradingConfig) { + this.priceMonitor = new PriceMonitor() + this.exitStrategy = new ExitStrategy(config) + this.validator = new PositionValidator() + this.lifecycle = new TradeLifecycle() + } + + async addTrade(trade: ActiveTrade) { + this.lifecycle.add(trade) + this.priceMonitor.subscribe(trade.symbol, (price) => { + this.handlePriceUpdate(trade, price) + }) + } + + private async handlePriceUpdate(trade: ActiveTrade, price: number) { + // Ghost detection + if (await this.validator.detectGhostPosition(trade)) { + return this.handleGhostDetection(trade) + } + + // Exit conditions + if (this.exitStrategy.shouldStopLoss(price, trade)) { + return this.executeExit(trade, 100, 'SL', price) + } + if (this.exitStrategy.shouldTakeProfit1(price, trade)) { + return this.executeExit(trade, 60, 'TP1', price) + } + // ... more conditions + } +} +``` + +**Step 5: Shadow Testing (2 days)** +```typescript +// Run both old and new implementations side-by-side +// Compare: Do they detect same exit conditions? +// Measure: Performance differences +// Validate: No missed signals or false triggers +``` + +**Step 6: Gradual Rollout (2 days)** +```typescript +// Feature flag for phased migration +if (process.env.USE_REFACTORED_POSITION_MANAGER === 'true') { + return new RefactoredPositionManager(config) +} else { + return new LegacyPositionManager(config) +} + +// Rollout plan: +// Week 1: 10% of trades (1-2 trades) +// Week 2: 50% of trades (monitor closely) +// Week 3: 100% (full migration) +``` + +**Success Criteria:** +- βœ… 1,945 lines β†’ ~800 lines per module (~59% complexity reduction) +- βœ… 100% test coverage on new modules +- βœ… No missed trades or false exits +- βœ… Same P&L results as legacy version + +**Effort:** 11 days +**Risk:** HIGH (core trading logic) +**Priority:** 🟑 MEDIUM + +--- + +### Task 3.3: Circular Dependency Resolution πŸ”„ + +**Problem:** 5 singleton patterns may have circular dependencies + +**Solution: Dependency Injection** + +**Step 1: Detect Circular Dependencies (2 hours)** +```bash +npm install --save-dev madge +npx madge --circular lib/ + +# Expected output: +# trades.ts β†’ position-manager.ts β†’ drift/client.ts β†’ trades.ts +# signal-quality.ts β†’ trades.ts β†’ signal-quality.ts +``` + +**Step 2: Refactor Singletons (1 day)** +```typescript +// BEFORE: Direct getInstance calls create circular deps +// drift/client.ts +export function getDriftService() { + if (!instance) { + const trades = require('../database/trades') // Circular! + instance = new DriftService(trades) + } + return instance +} + +// AFTER: Dependency injection +// drift/client.ts +export function createDriftService(dependencies: { + tradesRepo: TradesRepository +}) { + return new DriftService(dependencies.tradesRepo) +} + +// lib/startup/services.ts (central initialization) +export async function initializeServices() { + const tradesRepo = new TradesRepository(prisma) + const driftService = createDriftService({ tradesRepo }) + const positionManager = createPositionManager({ driftService, tradesRepo }) + return { driftService, positionManager, tradesRepo } +} +``` + +**Step 3: Update Call Sites (4 hours)** +```typescript +// BEFORE: +const driftService = getDriftService() + +// AFTER: +// In API routes, get from request context +const { driftService } = await getServices() +``` + +**Step 4: Verification (2 hours)** +```bash +npx madge --circular lib/ +# Should show 0 circular dependencies + +npm run build +# Should compile without issues +``` + +**Success Criteria:** +- βœ… 0 circular dependencies +- βœ… All services initialized correctly +- βœ… No runtime errors + +**Effort:** 2 days +**Risk:** MEDIUM (architectural change) +**Priority:** 🟒 LOW + +--- + +### Task 3.4: Build Time Optimization πŸš€ + +**Problem:** 54.74s build time could be faster + +**Solution: Incremental Builds + Caching** + +**Step 1: Enable Incremental TypeScript (30 minutes)** +```json +// tsconfig.json +{ + "compilerOptions": { + "incremental": true, + "tsBuildInfoFile": ".tsbuildinfo" + } +} + +// .gitignore +.tsbuildinfo +``` + +**Step 2: Parallel Build Processing (1 hour)** +```json +// next.config.js +module.exports = { + experimental: { + // Use SWC for minification (faster than Terser) + swcMinify: true, + + // Parallel build workers + workerThreads: true, + cpus: Math.max(1, require('os').cpus().length - 1) + } +} +``` + +**Step 3: Turborepo Caching (2 hours)** +```bash +# Install Turborepo +npm install -D turbo + +# Create turbo.json +{ + "pipeline": { + "build": { + "dependsOn": ["^build"], + "outputs": [".next/**", "!.next/cache/**"], + "cache": true + } + } +} + +# Update package.json scripts +"scripts": { + "build": "turbo run build" +} +``` + +**Step 4: Docker Layer Caching (1 hour)** +```dockerfile +# Dockerfile optimization +# Cache node_modules separately +FROM node:20-alpine AS deps +COPY package*.json ./ +RUN npm ci +# This layer is cached unless package.json changes + +FROM node:20-alpine AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN npm run build +# This layer rebuilds only when source changes +``` + +**Step 5: Benchmarking (30 minutes)** +```bash +# Cold build (no cache) +rm -rf .next .tsbuildinfo node_modules/.cache +time npm run build + +# Warm build (with cache) +touch lib/trading/position-manager.ts +time npm run build + +# Target: 25-30s (50% reduction from 54.74s) +``` + +**Success Criteria:** +- βœ… Cold build: <30s (from 54.74s) +- βœ… Warm build: <10s (incremental) +- βœ… Docker build: Layer caching working + +**Effort:** 5 hours +**Risk:** LOW (build tooling) +**Priority:** 🟒 LOW + +--- + +## Phase 3 Summary + +**Duration:** 1-3 months +**Total Effort:** 19.5 days +**Expected Results:** +- 100% console.log removal (Winston only) +- 59% position manager complexity reduction +- 0 circular dependencies +- 45-54% build time reduction +- Queryable structured logs + +**Deployment Checklist:** +- [ ] Winston logging tested in staging +- [ ] Position Manager shadow testing completed (1-2 weeks) +- [ ] Gradual rollout: 10% β†’ 50% β†’ 100% +- [ ] Rollback plan prepared +- [ ] Performance regression testing +- [ ] Update all documentation + +--- + +## Risk Mitigation + +### Trading System Constraints +- βœ… Real-money trading: $540 capital +- βœ… Win rate: Must maintain β‰₯60% +- βœ… Dual-layer redundancy: Preserve Position Manager + on-chain orders +- βœ… Database integrity: 170+ trades, critical for analytics +- βœ… Zero downtime: HA infrastructure must stay operational + +### Mitigation Strategies + +**1. Shadow Testing (All High-Risk Changes)** +```typescript +// Run old and new code side-by-side +if (process.env.SHADOW_MODE === 'true') { + const oldResult = await legacyFunction() + const newResult = await optimizedFunction() + + if (!deepEqual(oldResult, newResult)) { + log.error('Shadow test failed', { old: oldResult, new: newResult }) + } + + return oldResult // Use old result in production +} +``` + +**2. Feature Flags (Runtime Toggles)** +```typescript +// Environment-based toggles +const config = { + useEventDrivenMonitoring: process.env.USE_EVENT_DRIVEN === 'true', + useRefactoredPositionManager: process.env.USE_REFACTORED_PM === 'true', + useBatchedQueries: process.env.USE_BATCHED_QUERIES === 'true' +} + +// Easy rollback without deployment +``` + +**3. Rollback Plan** +```bash +# Git tags for each phase +git tag -a phase1-console-gating -m "Phase 1: Console.log gating" +git tag -a phase2-db-optimization -m "Phase 2: Database optimization" +git tag -a phase3-refactor -m "Phase 3: Position Manager refactor" + +# Docker image snapshots +docker tag trading-bot-v4 trading-bot-v4:phase1-backup +docker tag trading-bot-v4 trading-bot-v4:phase2-backup + +# Rollback procedure +git checkout phase1-console-gating +docker compose build trading-bot +docker compose up -d --force-recreate trading-bot +``` + +**4. Comprehensive Testing** +```bash +# Unit tests (target: 90%+ coverage) +npm test -- --coverage + +# Integration tests +npm run test:integration + +# Load testing (simulate 50-100 trades) +npm run test:load + +# Manual testing checklist: +# - Open position +# - Hit TP1 (verify 60% closes) +# - Monitor runner (verify trailing stop) +# - Hit SL (verify full close) +# - Database queries (verify correct results) +``` + +**5. Gradual Rollout** +| Week | Rollout % | Monitoring | +|------|-----------|------------| +| 1 | 10% | Watch every trade closely | +| 2 | 25% | Monitor daily aggregates | +| 3 | 50% | Compare old vs new metrics | +| 4 | 75% | Confidence growing | +| 5 | 100% | Full migration | + +**6. Monitoring Alerts** +```typescript +// Set up alerts for regressions +if (buildTime > previousBuildTime * 1.2) { + alert('Build time regression: ' + buildTime) +} + +if (queryTime > previousQueryTime * 1.5) { + alert('Query performance regression: ' + queryTime) +} + +if (memoryUsage > 250 * 1024 * 1024) { // 250MB + alert('Memory usage spike: ' + memoryUsage) +} +``` + +--- + +## Success Metrics Tracking + +### Baseline (Before Optimization) +| Metric | Current | Target After Phase 1 | Target After Phase 2 | Target After Phase 3 | +|--------|---------|---------------------|---------------------|---------------------| +| Console.log | 731 | 73 (90% gated) | 73 | 0 (Winston only) | +| Build Time | 54.74s | 52-53s | 52-53s | 25-30s | +| Docker Image | 1.32GB | 600-700MB | 600-700MB | 600-700MB | +| Node Modules | 620MB | 620MB | 480-500MB | 480-500MB | +| DB Queries (Trade) | 32 | 32 | 15-20 | 15-20 | +| Position Manager Lines | 1,945 | 1,945 | 1,945 | ~800 | +| Type Imports Missing | 49 | 0 | 0 | 0 | +| CPU Usage | 10.88% | 10.88% | 8-9% | 8-9% | +| Memory Usage | 179.7MiB | 175MiB | 150-160MiB | 140-150MiB | + +### Measurement Commands +```bash +# Console.log count +grep -r "console\.\(log\|error\|warn\)" --include="*.ts" lib/ | wc -l + +# Build time +time npm run build 2>&1 | grep "Compiled successfully" + +# Docker image size +docker images | grep trading-bot-v4 + +# Node modules size +du -sh node_modules + +# Database query count +grep -rn "prisma.trade" lib/database/trades.ts | wc -l + +# File lines +wc -l lib/trading/position-manager.ts + +# Type imports +grep -r "import.*{.*}" --include="*.ts" lib/ | grep -v "type {" | wc -l + +# Runtime metrics +docker stats trading-bot-v4 --no-stream +``` + +--- + +## Integration with Existing Roadmaps + +### OPTIMIZATION_MASTER_ROADMAP.md (Trading Strategy) +**Focus:** Signal quality, position scaling, ATR-based TP +**Status:** +- βœ… Signal Quality v8 complete (57.1% WR, +$262.70) +- πŸ”„ Data collection ongoing (8/20 blocked signals, 8/50 ATR trades) +- πŸ“‹ v9 development planned (directional filter, time-of-day) + +**This Plan (Infrastructure/Code Quality):** +**Focus:** Console.log, Docker size, Position Manager complexity, database queries +**Relationship:** Complementary (run in parallel, no conflicts) + +**Synergies:** +1. Console.log gating reduces noise during signal quality analysis +2. Database indexing speeds backtesting queries for position scaling +3. Position Manager refactor makes exit strategies easier to implement +4. Structured logging provides better data for trading performance analysis + +**No Conflicts:** +- Infrastructure optimizations don't touch trading logic +- Quality thresholds unchanged (91 for v8) +- Position sizing strategies unaffected +- Data collection systems continue running + +--- + +## Timeline Overview + +``` +December 2025 +Week 1-2: Phase 1 (Quick Wins) +β”œβ”€β”€ Console.log gating (4h) βœ“ +β”œβ”€β”€ Type imports (30m) βœ“ +β”œβ”€β”€ Docker investigation (3h) βœ“ +└── Export tree-shaking (1h) βœ“ + +Week 3-6: Phase 2 (Medium Initiatives) +β”œβ”€β”€ Database batching (3.5h) +β”œβ”€β”€ Database indexing (5h) +β”œβ”€β”€ Timer consolidation (2d) +β”œβ”€β”€ Node modules audit (4.5h) +└── RPC optimization (2d) + +January-March 2026: Phase 3 (Long-Term) +β”œβ”€β”€ Winston logging (1d) +β”œβ”€β”€ Position Manager refactor (11d) +β”‚ └── Shadow testing (1-2 weeks) +β”œβ”€β”€ Circular dependencies (2d) +└── Build optimization (5h) +``` + +--- + +## Execution Checklist + +### Pre-Phase 1 +- [ ] Backup database: `pg_dump trading_bot_v4 > backup_pre_optimization.sql` +- [ ] Tag git: `git tag -a pre-optimization -m "Before optimization plan"` +- [ ] Document baseline metrics (run measurement commands above) +- [ ] Create Nextcloud Deck cards for Phase 1 tasks +- [ ] Schedule maintenance window (if needed for risky changes) + +### During Each Phase +- [ ] Create feature branch: `git checkout -b optimize/phase-X-taskname` +- [ ] Implement changes +- [ ] Run tests: `npm test` +- [ ] Build: `npm run build` +- [ ] Measure improvement (document in git commit) +- [ ] Deploy to staging (if available) +- [ ] Shadow test (if high risk) +- [ ] Deploy to production with feature flag +- [ ] Monitor for 24-48 hours +- [ ] Commit: `git commit -m "optimize: [description]"` +- [ ] Push: `git push origin optimize/phase-X-taskname` +- [ ] Update Nextcloud Deck card status + +### Post-Phase +- [ ] Document actual vs expected results +- [ ] Update success metrics table +- [ ] Tag git: `git tag -a phase-X-complete` +- [ ] Update OPTIMIZATION_MASTER_ROADMAP.md +- [ ] Retrospective: What worked? What didn't? +- [ ] Adjust remaining phases based on learnings + +--- + +## Quick Reference Commands + +```bash +# Start Phase 1 +cd /home/icke/traderv4 +git checkout -b optimize/phase1-console-gating +# ... implement changes +npm run build +docker compose build trading-bot +docker compose up -d --force-recreate trading-bot +git add -A +git commit -m "optimize: Console.log production gating (90% reduction)" +git push +git checkout main +git merge optimize/phase1-console-gating +git tag -a phase1-complete + +# Measure improvements +grep -r "console\.\(log\|error\|warn\)" --include="*.ts" lib/ | wc -l +docker images | grep trading-bot-v4 +docker stats trading-bot-v4 --no-stream + +# Rollback if needed +git checkout phase1-backup +docker compose up -d --force-recreate trading-bot +``` + +--- + +## Documentation Updates + +After each phase, update: +1. **This file:** Mark tasks as complete, update success metrics +2. **OPTIMIZATION_MASTER_ROADMAP.md:** Add infrastructure notes +3. **README.md:** Update system requirements if changed +4. **.github/copilot-instructions.md:** Document new patterns learned +5. **Nextcloud Deck:** Move cards to "Complete" stack + +--- + +## Contact & Support + +**For Questions:** +- Review comprehensive analysis: `/home/icke/traderv4/docs/analysis/COMPREHENSIVE_IMPROVEMENT_PLAN_DEC2025.md` +- Check existing roadmap: `OPTIMIZATION_MASTER_ROADMAP.md` +- System architecture: `.github/copilot-instructions.md` + +**Best Practices:** +- Always test in shadow mode first for high-risk changes +- Document baseline before starting each task +- Use feature flags for easy rollback +- Measure twice, optimize once +- Trading system stability > optimization gains + +--- + +**Status:** βœ… READY FOR EXECUTION +**Next Action:** User reviews plan and approves Phase 1 start +**Estimated Total Duration:** 3 months +**Expected Total Impact:** 40-60% improvement across all metrics diff --git a/lib/analysis/blocked-signal-tracker.ts b/lib/analysis/blocked-signal-tracker.ts index bf1b7a0..c68db5f 100644 --- a/lib/analysis/blocked-signal-tracker.ts +++ b/lib/analysis/blocked-signal-tracker.ts @@ -20,6 +20,7 @@ */ import { getPrismaClient } from '../database/trades' +import { logger } from '../utils/logger' import { initializeDriftService } from '../drift/client' import { getMergedConfig, SUPPORTED_MARKETS } from '../../config/trading' @@ -60,11 +61,11 @@ export class BlockedSignalTracker { */ public start(): void { if (this.isRunning) { - console.log('⚠️ Blocked signal tracker already running') + logger.log('⚠️ Blocked signal tracker already running') return } - console.log('πŸ”¬ Starting blocked signal price tracker...') + logger.log('πŸ”¬ Starting blocked signal price tracker...') this.isRunning = true // Run immediately on start @@ -79,7 +80,7 @@ export class BlockedSignalTracker { }) }, 5 * 60 * 1000) // 5 minutes - console.log('βœ… Blocked signal tracker started (runs every 5 minutes)') + logger.log('βœ… Blocked signal tracker started (runs every 5 minutes)') } /** @@ -91,7 +92,7 @@ export class BlockedSignalTracker { this.intervalId = null } this.isRunning = false - console.log('⏹️ Blocked signal tracker stopped') + logger.log('⏹️ Blocked signal tracker stopped') } /** @@ -102,7 +103,7 @@ export class BlockedSignalTracker { // Initialize Drift service if needed const driftService = await initializeDriftService() if (!driftService) { - console.log('⚠️ Drift service not available, skipping price tracking') + logger.log('⚠️ Drift service not available, skipping price tracking') return } @@ -122,17 +123,17 @@ export class BlockedSignalTracker { }) if (signals.length === 0) { - console.log('πŸ“Š No blocked signals to track') + logger.log('πŸ“Š No blocked signals to track') return } - console.log(`πŸ“Š Tracking ${signals.length} blocked signals...`) + logger.log(`πŸ“Š Tracking ${signals.length} blocked signals...`) for (const signal of signals) { await this.trackSignal(signal as any) } - console.log(`βœ… Price tracking complete for ${signals.length} signals`) + logger.log(`βœ… Price tracking complete for ${signals.length} signals`) } catch (error) { console.error('❌ Error in trackPrices:', error) } @@ -152,7 +153,7 @@ export class BlockedSignalTracker { const marketConfig = SUPPORTED_MARKETS[signal.symbol] if (!marketConfig) { - console.log(`⚠️ No market config for ${signal.symbol}, skipping`) + logger.log(`⚠️ No market config for ${signal.symbol}, skipping`) return } @@ -160,7 +161,7 @@ export class BlockedSignalTracker { const entryPrice = Number(signal.entryPrice) if (entryPrice === 0) { - console.log(`⚠️ Entry price is 0 for ${signal.symbol}, skipping`) + logger.log(`⚠️ Entry price is 0 for ${signal.symbol}, skipping`) return } @@ -184,56 +185,56 @@ export class BlockedSignalTracker { if (elapsedMinutes >= 1 && !signal.priceAfter1Min) { updates.priceAfter1Min = currentPrice - console.log(` πŸ“ ${signal.symbol} ${signal.direction} @ 1min: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) + logger.log(` πŸ“ ${signal.symbol} ${signal.direction} @ 1min: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) } if (elapsedMinutes >= 5 && !signal.priceAfter5Min) { updates.priceAfter5Min = currentPrice - console.log(` πŸ“ ${signal.symbol} ${signal.direction} @ 5min: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) + logger.log(` πŸ“ ${signal.symbol} ${signal.direction} @ 5min: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) } if (elapsedMinutes >= 15 && !signal.priceAfter15Min) { updates.priceAfter15Min = currentPrice - console.log(` πŸ“ ${signal.symbol} ${signal.direction} @ 15min: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) + logger.log(` πŸ“ ${signal.symbol} ${signal.direction} @ 15min: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) } if (elapsedMinutes >= 30 && !signal.priceAfter30Min) { updates.priceAfter30Min = currentPrice - console.log(` πŸ“ ${signal.symbol} ${signal.direction} @ 30min: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) + logger.log(` πŸ“ ${signal.symbol} ${signal.direction} @ 30min: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) } // EXTENDED TRACKING (Dec 2, 2025): Track up to 8 hours for slow developers if (elapsedMinutes >= 60 && !signal.priceAfter1Hr) { updates.priceAfter1Hr = currentPrice - console.log(` πŸ“ ${signal.symbol} ${signal.direction} @ 1hr: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) + logger.log(` πŸ“ ${signal.symbol} ${signal.direction} @ 1hr: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) } if (elapsedMinutes >= 120 && !signal.priceAfter2Hr) { updates.priceAfter2Hr = currentPrice - console.log(` πŸ“ ${signal.symbol} ${signal.direction} @ 2hr: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) + logger.log(` πŸ“ ${signal.symbol} ${signal.direction} @ 2hr: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) } if (elapsedMinutes >= 240 && !signal.priceAfter4Hr) { updates.priceAfter4Hr = currentPrice - console.log(` πŸ“ ${signal.symbol} ${signal.direction} @ 4hr: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) + logger.log(` πŸ“ ${signal.symbol} ${signal.direction} @ 4hr: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) } if (elapsedMinutes >= 480 && !signal.priceAfter8Hr) { updates.priceAfter8Hr = currentPrice - console.log(` πŸ“ ${signal.symbol} ${signal.direction} @ 8hr: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) + logger.log(` πŸ“ ${signal.symbol} ${signal.direction} @ 8hr: $${currentPrice.toFixed(2)} (${profitPercent.toFixed(2)}%)`) } // Mark complete after 8 hours OR if TP/SL already hit if (elapsedMinutes >= 480 && !signal.analysisComplete) { updates.analysisComplete = true - console.log(` βœ… ${signal.symbol} ${signal.direction} @ 8hr: TRACKING COMPLETE`) + logger.log(` βœ… ${signal.symbol} ${signal.direction} @ 8hr: TRACKING COMPLETE`) } // Early completion if TP1/TP2/SL hit (no need to wait full 8 hours) if (!signal.analysisComplete && (signal.wouldHitTP1 || signal.wouldHitTP2 || signal.wouldHitSL)) { updates.analysisComplete = true const hitReason = signal.wouldHitTP1 ? 'TP1' : signal.wouldHitTP2 ? 'TP2' : 'SL' - console.log(` βœ… ${signal.symbol} ${signal.direction}: ${hitReason} hit at ${profitPercent.toFixed(2)}% - TRACKING COMPLETE`) + logger.log(` βœ… ${signal.symbol} ${signal.direction}: ${hitReason} hit at ${profitPercent.toFixed(2)}% - TRACKING COMPLETE`) } // Update max favorable/adverse excursion @@ -253,17 +254,17 @@ export class BlockedSignalTracker { // Check if TP1/TP2/SL would have been hit if (signal.wouldHitTP1 === null && Math.abs(profitPercent) >= tp1Percent) { updates.wouldHitTP1 = profitPercent > 0 - console.log(` 🎯 ${signal.symbol} ${signal.direction} hit ${profitPercent > 0 ? 'TP1' : 'SL'} (${profitPercent.toFixed(2)}%)`) + logger.log(` 🎯 ${signal.symbol} ${signal.direction} hit ${profitPercent > 0 ? 'TP1' : 'SL'} (${profitPercent.toFixed(2)}%)`) } if (signal.wouldHitTP2 === null && Math.abs(profitPercent) >= tp2Percent) { updates.wouldHitTP2 = profitPercent > 0 - console.log(` 🎯 ${signal.symbol} ${signal.direction} hit TP2 (${profitPercent.toFixed(2)}%)`) + logger.log(` 🎯 ${signal.symbol} ${signal.direction} hit TP2 (${profitPercent.toFixed(2)}%)`) } if (signal.wouldHitSL === null && profitPercent <= -slPercent) { updates.wouldHitSL = true - console.log(` πŸ›‘ ${signal.symbol} ${signal.direction} hit SL (${profitPercent.toFixed(2)}%)`) + logger.log(` πŸ›‘ ${signal.symbol} ${signal.direction} hit SL (${profitPercent.toFixed(2)}%)`) } // Update database if we have changes @@ -344,7 +345,7 @@ export class BlockedSignalTracker { } }) - console.log(`πŸ“Š Retrieved ${marketData.length} 1-minute data points for ${symbol}`) + logger.log(`πŸ“Š Retrieved ${marketData.length} 1-minute data points for ${symbol}`) return marketData } catch (error) { console.error('❌ Error querying historical prices:', error) @@ -388,7 +389,7 @@ export class BlockedSignalTracker { slPrice = entryPrice * (1 + targets.slPercent / 100) } - console.log(`🎯 Analyzing ${signal.symbol} ${direction}: Entry $${entryPrice.toFixed(2)}, TP1 $${tp1Price.toFixed(2)}, TP2 $${tp2Price.toFixed(2)}, SL $${slPrice.toFixed(2)}`) + logger.log(`🎯 Analyzing ${signal.symbol} ${direction}: Entry $${entryPrice.toFixed(2)}, TP1 $${tp1Price.toFixed(2)}, TP2 $${tp2Price.toFixed(2)}, SL $${slPrice.toFixed(2)}`) // Track hits (only record first occurrence) let tp1HitTime: Date | null = null @@ -439,7 +440,7 @@ export class BlockedSignalTracker { : currentPrice <= tp1Price if (tp1Hit) { tp1HitTime = timestamp - console.log(`βœ… TP1 hit at ${timestamp.toISOString()} (${minutesElapsed}min) - Price: $${currentPrice.toFixed(2)}`) + logger.log(`βœ… TP1 hit at ${timestamp.toISOString()} (${minutesElapsed}min) - Price: $${currentPrice.toFixed(2)}`) } } @@ -450,7 +451,7 @@ export class BlockedSignalTracker { : currentPrice <= tp2Price if (tp2Hit) { tp2HitTime = timestamp - console.log(`βœ… TP2 hit at ${timestamp.toISOString()} (${minutesElapsed}min) - Price: $${currentPrice.toFixed(2)}`) + logger.log(`βœ… TP2 hit at ${timestamp.toISOString()} (${minutesElapsed}min) - Price: $${currentPrice.toFixed(2)}`) } } @@ -461,7 +462,7 @@ export class BlockedSignalTracker { : currentPrice >= slPrice if (slHit) { slHitTime = timestamp - console.log(`❌ SL hit at ${timestamp.toISOString()} (${minutesElapsed}min) - Price: $${currentPrice.toFixed(2)}`) + logger.log(`❌ SL hit at ${timestamp.toISOString()} (${minutesElapsed}min) - Price: $${currentPrice.toFixed(2)}`) } } @@ -502,7 +503,7 @@ export class BlockedSignalTracker { if (checkpoints['4hr']) updates.priceAfter4Hr = checkpoints['4hr'] if (checkpoints['8hr']) updates.priceAfter8Hr = checkpoints['8hr'] - console.log(`πŸ“Š Analysis complete: TP1=${updates.wouldHitTP1}, TP2=${updates.wouldHitTP2}, SL=${updates.wouldHitSL}, MFE=${maxFavorableExcursion.toFixed(2)}%, MAE=${maxAdverseExcursion.toFixed(2)}%`) + logger.log(`πŸ“Š Analysis complete: TP1=${updates.wouldHitTP1}, TP2=${updates.wouldHitTP2}, SL=${updates.wouldHitSL}, MFE=${maxFavorableExcursion.toFixed(2)}%, MAE=${maxAdverseExcursion.toFixed(2)}%`) return updates } @@ -544,11 +545,11 @@ export class BlockedSignalTracker { }) if (signalsToProcess.length === 0) { - console.log('πŸ“Š No signals ready for batch processing') + logger.log('πŸ“Š No signals ready for batch processing') return } - console.log(`πŸ”„ Processing ${signalsToProcess.length} signals with historical data...`) + logger.log(`πŸ”„ Processing ${signalsToProcess.length} signals with historical data...`) let processed = 0 let skipped = 0 @@ -567,12 +568,12 @@ export class BlockedSignalTracker { ) if (historicalPrices.length === 0) { - console.log(`⏭️ Skipping ${signal.symbol} ${signal.direction} - no historical data`) + logger.log(`⏭️ Skipping ${signal.symbol} ${signal.direction} - no historical data`) skipped++ continue } - console.log(`πŸ“Š Processing ${signal.symbol} ${signal.direction} with ${historicalPrices.length} data points...`) + logger.log(`πŸ“Š Processing ${signal.symbol} ${signal.direction} with ${historicalPrices.length} data points...`) // Analyze minute-by-minute const updates = await this.analyzeHistoricalData( @@ -591,7 +592,7 @@ export class BlockedSignalTracker { }) processed++ - console.log(`βœ… ${signal.symbol} ${signal.direction} analyzed successfully`) + logger.log(`βœ… ${signal.symbol} ${signal.direction} analyzed successfully`) } catch (error) { console.error(`❌ Error processing signal ${signal.id}:`, error) @@ -599,7 +600,7 @@ export class BlockedSignalTracker { } } - console.log(`πŸŽ‰ Batch processing complete: ${processed} analyzed, ${skipped} skipped`) + logger.log(`πŸŽ‰ Batch processing complete: ${processed} analyzed, ${skipped} skipped`) } catch (error) { console.error('❌ Error in batch processing:', error) diff --git a/lib/database/trades.ts b/lib/database/trades.ts index e42162f..188abc8 100644 --- a/lib/database/trades.ts +++ b/lib/database/trades.ts @@ -3,6 +3,7 @@ */ import { PrismaClient } from '@prisma/client' +import { logger } from '../utils/logger' import { logCriticalError, logDatabaseOperation } from '../utils/persistent-logger' // Singleton Prisma client @@ -13,7 +14,7 @@ export function getPrismaClient(): PrismaClient { prisma = new PrismaClient({ log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'], }) - console.log('βœ… Prisma client initialized') + logger.log('βœ… Prisma client initialized') } return prisma } @@ -177,7 +178,7 @@ export async function createTrade(params: CreateTradeParams) { if (attempt < maxRetries) { const delay = baseDelay * Math.pow(2, attempt - 1) - console.log(`⏳ Verification failed, retrying in ${delay}ms (attempt ${attempt}/${maxRetries})...`) + logger.log(`⏳ Verification failed, retrying in ${delay}ms (attempt ${attempt}/${maxRetries})...`) await new Promise(resolve => setTimeout(resolve, delay)) continue // Retry } @@ -185,7 +186,7 @@ export async function createTrade(params: CreateTradeParams) { throw new Error(errorMsg) } - console.log(`πŸ“Š Trade record created & VERIFIED: ${trade.id}`) + logger.log(`πŸ“Š Trade record created & VERIFIED: ${trade.id}`) logDatabaseOperation('createTrade', true, { table: 'Trade', recordId: trade.id, @@ -206,7 +207,7 @@ export async function createTrade(params: CreateTradeParams) { if (attempt < maxRetries) { const delay = baseDelay * Math.pow(2, attempt - 1) - console.log(`⏳ Retrying in ${delay}ms...`) + logger.log(`⏳ Retrying in ${delay}ms...`) await new Promise(resolve => setTimeout(resolve, delay)) continue } @@ -267,7 +268,7 @@ export async function updateTradeExit(params: UpdateTradeExitParams) { }, }) - console.log(`πŸ“Š Trade closed: ${trade.id} | P&L: $${params.realizedPnL.toFixed(2)}`) + logger.log(`πŸ“Š Trade closed: ${trade.id} | P&L: $${params.realizedPnL.toFixed(2)}`) return trade } catch (error) { console.error('❌ Failed to update trade exit:', error) @@ -331,7 +332,7 @@ export async function getOpenTrades() { orderBy: { entryTime: 'asc' }, }) - console.log(`πŸ“Š Found ${trades.length} open trades to restore`) + logger.log(`πŸ“Š Found ${trades.length} open trades to restore`) return trades } catch (error) { console.error('❌ Failed to get open trades:', error) @@ -589,7 +590,7 @@ export async function createBlockedSignal(params: CreateBlockedSignalParams) { }, }) - console.log(`πŸ“ Blocked signal saved: ${params.symbol} ${params.direction} (score: ${params.signalQualityScore}/${params.minScoreRequired})`) + logger.log(`πŸ“ Blocked signal saved: ${params.symbol} ${params.direction} (score: ${params.signalQualityScore}/${params.minScoreRequired})`) return blockedSignal } catch (error) { console.error('❌ Failed to save blocked signal:', error) @@ -746,6 +747,6 @@ export async function disconnectPrisma() { if (prisma) { await prisma.$disconnect() prisma = null - console.log('βœ… Prisma client disconnected') + logger.log('βœ… Prisma client disconnected') } } diff --git a/lib/drift/client.ts b/lib/drift/client.ts index 8554923..7ccf6c0 100644 --- a/lib/drift/client.ts +++ b/lib/drift/client.ts @@ -5,6 +5,7 @@ */ import { Connection, PublicKey, Keypair } from '@solana/web3.js' +import { logger } from '../utils/logger' import { DriftClient, initialize, User, PerpMarkets } from '@drift-labs/sdk' import bs58 from 'bs58' import { getDriftHealthMonitor } from '../monitoring/drift-health-monitor' @@ -43,9 +44,9 @@ export class DriftService { : this.connection if (config.alchemyRpcUrl) { - console.log('πŸ”€ Hybrid RPC mode: Helius for init, Alchemy for trades') + logger.log('πŸ”€ Hybrid RPC mode: Helius for init, Alchemy for trades') } else { - console.log('πŸ“‘ Single RPC mode: Helius for all operations') + logger.log('πŸ“‘ Single RPC mode: Helius for all operations') } // Create wallet from private key @@ -88,7 +89,7 @@ export class DriftService { } } - console.log('βœ… Drift service created for wallet:', this.wallet.publicKey.toString()) + logger.log('βœ… Drift service created for wallet:', this.wallet.publicKey.toString()) } /** @@ -118,17 +119,17 @@ export class DriftService { error?.code === 'EAI_AGAIN' || error?.cause?.code === 'EAI_AGAIN' - console.log(`πŸ” Error detection: isTransient=${isTransient}, attempt=${attempt}/${maxRetries}`) - console.log(`πŸ” Error details: message="${error?.message}", code="${error?.code}", cause.code="${error?.cause?.code}"`) + logger.log(`πŸ” Error detection: isTransient=${isTransient}, attempt=${attempt}/${maxRetries}`) + logger.log(`πŸ” Error details: message="${error?.message}", code="${error?.code}", cause.code="${error?.cause?.code}"`) if (!isTransient || attempt === maxRetries) { // Non-transient error or max retries reached - fail immediately - console.log(`❌ Not retrying: isTransient=${isTransient}, maxed=${attempt === maxRetries}`) + logger.log(`❌ Not retrying: isTransient=${isTransient}, maxed=${attempt === maxRetries}`) throw error } - console.log(`⚠️ ${operationName} failed (attempt ${attempt}/${maxRetries}): ${error?.message || error}`) - console.log(`⏳ Retrying in ${delayMs}ms...`) + logger.log(`⚠️ ${operationName} failed (attempt ${attempt}/${maxRetries}): ${error?.message || error}`) + logger.log(`⏳ Retrying in ${delayMs}ms...`) // Wait before retry await new Promise(resolve => setTimeout(resolve, delayMs)) @@ -144,12 +145,12 @@ export class DriftService { */ async initialize(): Promise { if (this.isInitialized) { - console.log('⚠️ Drift service already initialized') + logger.log('⚠️ Drift service already initialized') return } try { - console.log('πŸš€ Initializing Drift Protocol client...') + logger.log('πŸš€ Initializing Drift Protocol client...') // Wrap initialization in retry logic to handle DNS failures await this.retryOperation(async () => { @@ -170,20 +171,20 @@ export class DriftService { // Subscribe to Drift account updates (this makes RPC calls) await this.driftClient.subscribe() - console.log('βœ… Drift client subscribed to account updates') + logger.log('βœ… Drift client subscribed to account updates') // Get user account this.user = this.driftClient.getUser() }, 3, 2000, 'Drift initialization') this.isInitialized = true - console.log('βœ… Drift service initialized successfully') + logger.log('βœ… Drift service initialized successfully') // CRITICAL FIX (Nov 25, 2025): Intercept errors BEFORE starting monitor // Without this, errors aren't recorded and auto-restart never triggers - console.log('πŸ”§ Setting up error interception for health monitoring...') + logger.log('πŸ”§ Setting up error interception for health monitoring...') this.interceptWebSocketErrors() - console.log('βœ… Error interception active') + logger.log('βœ… Error interception active') // Start health monitoring (error-based restart instead of blind timer) const monitor = getDriftHealthMonitor() @@ -427,7 +428,7 @@ export class DriftService { async disconnect(): Promise { if (this.driftClient) { await this.driftClient.unsubscribe() - console.log('βœ… Drift client disconnected') + logger.log('βœ… Drift client disconnected') } this.isInitialized = false } @@ -460,9 +461,9 @@ export function getDriftService(): DriftService { } driftServiceInstance = new DriftService(config) - console.log('πŸ”„ Created new Drift service singleton') + logger.log('πŸ”„ Created new Drift service singleton') } else { - console.log('♻️ Reusing existing Drift service instance') + logger.log('♻️ Reusing existing Drift service instance') } return driftServiceInstance @@ -471,7 +472,7 @@ export function getDriftService(): DriftService { export async function initializeDriftService(): Promise { // If already initializing, return the same promise to avoid multiple concurrent inits if (initializationPromise) { - console.log('⏳ Waiting for ongoing initialization...') + logger.log('⏳ Waiting for ongoing initialization...') return initializationPromise } @@ -479,7 +480,7 @@ export async function initializeDriftService(): Promise { // If already initialized, return immediately if (service['isInitialized']) { - console.log('βœ… Drift service already initialized') + logger.log('βœ… Drift service already initialized') return service } diff --git a/lib/drift/orders.ts b/lib/drift/orders.ts index 6e5ecbb..48042de 100644 --- a/lib/drift/orders.ts +++ b/lib/drift/orders.ts @@ -5,6 +5,7 @@ */ import { getDriftService, initializeDriftService } from './client' +import { logger } from '../utils/logger' import { getMarketConfig } from '../../config/trading' import BN from 'bn.js' import { @@ -81,7 +82,7 @@ export async function openPosition( params: OpenPositionParams ): Promise { try { - console.log('πŸ“Š Opening position:', params) + logger.log('πŸ“Š Opening position:', params) const driftService = getDriftService() const marketConfig = getMarketConfig(params.symbol) @@ -89,7 +90,7 @@ export async function openPosition( // Get current oracle price const oraclePrice = await driftService.getOraclePrice(marketConfig.driftMarketIndex) - console.log(`πŸ’° Current ${params.symbol} price: $${oraclePrice.toFixed(4)}`) + logger.log(`πŸ’° Current ${params.symbol} price: $${oraclePrice.toFixed(4)}`) // Calculate position size in base asset const baseAssetSize = params.sizeUSD / oraclePrice @@ -107,17 +108,17 @@ export async function openPosition( : 1 - (params.slippageTolerance / 100) const worstPrice = oraclePrice * slippageMultiplier - console.log(`πŸ“ Order details:`) - console.log(` Size: ${baseAssetSize.toFixed(4)} ${params.symbol.split('-')[0]}`) - console.log(` Notional: $${params.sizeUSD.toFixed(2)}`) - console.log(` Oracle price: $${oraclePrice.toFixed(4)}`) - console.log(` Worst price (${params.slippageTolerance}% slippage): $${worstPrice.toFixed(4)}`) + logger.log(`πŸ“ Order details:`) + logger.log(` Size: ${baseAssetSize.toFixed(4)} ${params.symbol.split('-')[0]}`) + logger.log(` Notional: $${params.sizeUSD.toFixed(2)}`) + logger.log(` Oracle price: $${oraclePrice.toFixed(4)}`) + logger.log(` Worst price (${params.slippageTolerance}% slippage): $${worstPrice.toFixed(4)}`) // Check DRY_RUN mode const isDryRun = process.env.DRY_RUN === 'true' if (isDryRun) { - console.log('πŸ§ͺ DRY RUN MODE: Simulating order (not executing on blockchain)') + logger.log('πŸ§ͺ DRY RUN MODE: Simulating order (not executing on blockchain)') const mockTxSig = `DRY_RUN_${Date.now()}_${Math.random().toString(36).substring(7)}` return { @@ -141,13 +142,13 @@ export async function openPosition( } // Place market order using simple placePerpOrder (like v3) - console.log('πŸš€ Placing REAL market order...') + logger.log('πŸš€ Placing REAL market order...') const txSig = await driftClient.placePerpOrder(orderParams) - console.log(`πŸ“ Transaction submitted: ${txSig}`) + logger.log(`πŸ“ Transaction submitted: ${txSig}`) // CRITICAL: Confirm transaction actually executed on-chain - console.log('⏳ Confirming transaction on-chain...') + logger.log('⏳ Confirming transaction on-chain...') const connection = driftService.getTradeConnection() // Use Alchemy for trade operations try { @@ -161,7 +162,7 @@ export async function openPosition( } } - console.log(`βœ… Transaction confirmed on-chain: ${txSig}`) + logger.log(`βœ… Transaction confirmed on-chain: ${txSig}`) } catch (confirmError) { console.error(`❌ Failed to confirm transaction:`, confirmError) @@ -172,7 +173,7 @@ export async function openPosition( } // Wait a moment for position to update - console.log('⏳ Waiting for position to update...') + logger.log('⏳ Waiting for position to update...') await new Promise(resolve => setTimeout(resolve, 2000)) // Get actual fill price from position @@ -188,12 +189,12 @@ export async function openPosition( const expectedSizeUSD = params.sizeUSD const sizeRatio = actualSizeUSD / expectedSizeUSD - console.log(`πŸ’° Fill details:`) - console.log(` Fill price: $${fillPrice.toFixed(4)}`) - console.log(` Slippage: ${slippage.toFixed(3)}%`) - console.log(` Expected size: $${expectedSizeUSD.toFixed(2)}`) - console.log(` Actual size: $${actualSizeUSD.toFixed(2)}`) - console.log(` Size ratio: ${(sizeRatio * 100).toFixed(1)}%`) + logger.log(`πŸ’° Fill details:`) + logger.log(` Fill price: $${fillPrice.toFixed(4)}`) + logger.log(` Slippage: ${slippage.toFixed(3)}%`) + logger.log(` Expected size: $${expectedSizeUSD.toFixed(2)}`) + logger.log(` Actual size: $${actualSizeUSD.toFixed(2)}`) + logger.log(` Size ratio: ${(sizeRatio * 100).toFixed(1)}%`) // Flag as phantom if actual size is less than 50% of expected const isPhantom = sizeRatio < 0.5 @@ -216,8 +217,8 @@ export async function openPosition( } } else { // Position not found yet (may be DRY_RUN mode) - console.log(`⚠️ Position not immediately visible (may be DRY_RUN mode)`) - console.log(` Using oracle price as estimate: $${oraclePrice.toFixed(4)}`) + logger.log(`⚠️ Position not immediately visible (may be DRY_RUN mode)`) + logger.log(` Using oracle price as estimate: $${oraclePrice.toFixed(4)}`) return { success: true, @@ -250,7 +251,7 @@ export async function openPosition( */ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise { try { - console.log('πŸ›‘οΈ Placing exit orders on-chain:', options.symbol) + logger.log('πŸ›‘οΈ Placing exit orders on-chain:', options.symbol) const driftService = getDriftService() const driftClient = driftService.getClient() @@ -258,7 +259,7 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise< const isDryRun = process.env.DRY_RUN === 'true' if (isDryRun) { - console.log('πŸ§ͺ DRY RUN: Simulating placement of exit orders') + logger.log('πŸ§ͺ DRY RUN: Simulating placement of exit orders') return { success: true, signatures: [ @@ -285,11 +286,11 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise< const remainingAfterTP1 = options.positionSizeUSD - tp1USD const tp2USD = (remainingAfterTP1 * options.tp2SizePercent) / 100 - console.log(`πŸ“Š Exit order sizes:`) - console.log(` TP1: ${options.tp1SizePercent}% of $${options.positionSizeUSD.toFixed(2)} = $${tp1USD.toFixed(2)}`) - console.log(` Remaining after TP1: $${remainingAfterTP1.toFixed(2)}`) - console.log(` TP2: ${options.tp2SizePercent}% of remaining = $${tp2USD.toFixed(2)}`) - console.log(` Runner (if any): $${(remainingAfterTP1 - tp2USD).toFixed(2)}`) + logger.log(`πŸ“Š Exit order sizes:`) + logger.log(` TP1: ${options.tp1SizePercent}% of $${options.positionSizeUSD.toFixed(2)} = $${tp1USD.toFixed(2)}`) + logger.log(` Remaining after TP1: $${remainingAfterTP1.toFixed(2)}`) + logger.log(` TP2: ${options.tp2SizePercent}% of remaining = $${tp2USD.toFixed(2)}`) + logger.log(` Runner (if any): $${(remainingAfterTP1 - tp2USD).toFixed(2)}`) // For orders that close a long, the order direction should be SHORT (sell) const orderDirection = options.direction === 'long' ? PositionDirection.SHORT : PositionDirection.LONG @@ -307,14 +308,14 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise< reduceOnly: true, } - console.log('🚧 Placing TP1 limit order (reduce-only)...') + logger.log('🚧 Placing TP1 limit order (reduce-only)...') const sig = await retryWithBackoff(async () => await (driftClient as any).placePerpOrder(orderParams) ) - console.log('βœ… TP1 order placed:', sig) + logger.log('βœ… TP1 order placed:', sig) signatures.push(sig) } else { - console.log('⚠️ TP1 size below market min, skipping on-chain TP1') + logger.log('⚠️ TP1 size below market min, skipping on-chain TP1') } } @@ -331,14 +332,14 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise< reduceOnly: true, } - console.log('🚧 Placing TP2 limit order (reduce-only)...') + logger.log('🚧 Placing TP2 limit order (reduce-only)...') const sig = await retryWithBackoff(async () => await (driftClient as any).placePerpOrder(orderParams) ) - console.log('βœ… TP2 order placed:', sig) + logger.log('βœ… TP2 order placed:', sig) signatures.push(sig) } else { - console.log('⚠️ TP2 size below market min, skipping on-chain TP2') + logger.log('⚠️ TP2 size below market min, skipping on-chain TP2') } } @@ -356,7 +357,7 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise< if (useDualStops && options.softStopPrice && options.hardStopPrice) { // ============== DUAL STOP SYSTEM ============== - console.log('πŸ›‘οΈπŸ›‘οΈ Placing DUAL STOP SYSTEM...') + logger.log('πŸ›‘οΈπŸ›‘οΈ Placing DUAL STOP SYSTEM...') // 1. Soft Stop (TRIGGER_LIMIT) - Avoids wicks const softStopBuffer = options.softStopBuffer ?? 0.4 @@ -377,15 +378,15 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise< reduceOnly: true, } - console.log(` 1️⃣ Soft Stop (TRIGGER_LIMIT):`) - console.log(` Trigger: $${options.softStopPrice.toFixed(4)}`) - console.log(` Limit: $${(options.softStopPrice * softStopMultiplier).toFixed(4)}`) - console.log(` Purpose: Avoid false breakouts/wicks`) + logger.log(` 1️⃣ Soft Stop (TRIGGER_LIMIT):`) + logger.log(` Trigger: $${options.softStopPrice.toFixed(4)}`) + logger.log(` Limit: $${(options.softStopPrice * softStopMultiplier).toFixed(4)}`) + logger.log(` Purpose: Avoid false breakouts/wicks`) const softStopSig = await retryWithBackoff(async () => await (driftClient as any).placePerpOrder(softStopParams) ) - console.log(` βœ… Soft stop placed: ${softStopSig}`) + logger.log(` βœ… Soft stop placed: ${softStopSig}`) signatures.push(softStopSig) // 2. Hard Stop (TRIGGER_MARKET) - Guarantees exit @@ -401,17 +402,17 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise< reduceOnly: true, } - console.log(` 2️⃣ Hard Stop (TRIGGER_MARKET):`) - console.log(` Trigger: $${options.hardStopPrice.toFixed(4)}`) - console.log(` Purpose: Guaranteed exit if soft stop doesn't fill`) + logger.log(` 2️⃣ Hard Stop (TRIGGER_MARKET):`) + logger.log(` Trigger: $${options.hardStopPrice.toFixed(4)}`) + logger.log(` Purpose: Guaranteed exit if soft stop doesn't fill`) const hardStopSig = await retryWithBackoff(async () => await (driftClient as any).placePerpOrder(hardStopParams) ) - console.log(` βœ… Hard stop placed: ${hardStopSig}`) + logger.log(` βœ… Hard stop placed: ${hardStopSig}`) signatures.push(hardStopSig) - console.log(`🎯 Dual stop system active: Soft @ $${options.softStopPrice.toFixed(2)} | Hard @ $${options.hardStopPrice.toFixed(2)}`) + logger.log(`🎯 Dual stop system active: Soft @ $${options.softStopPrice.toFixed(2)} | Hard @ $${options.hardStopPrice.toFixed(2)}`) } else { // ============== SINGLE STOP SYSTEM ============== @@ -437,15 +438,15 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise< reduceOnly: true, } - console.log(`πŸ›‘οΈ Placing SL as TRIGGER_LIMIT (${stopLimitBuffer}% buffer)...`) - console.log(` Trigger: ${options.direction === 'long' ? 'BELOW' : 'ABOVE'} $${options.stopLossPrice.toFixed(4)}`) - console.log(` Limit: $${(options.stopLossPrice * limitPriceMultiplier).toFixed(4)}`) - console.log(` ⚠️ May not fill during fast moves - use for liquid markets only!`) + logger.log(`πŸ›‘οΈ Placing SL as TRIGGER_LIMIT (${stopLimitBuffer}% buffer)...`) + logger.log(` Trigger: ${options.direction === 'long' ? 'BELOW' : 'ABOVE'} $${options.stopLossPrice.toFixed(4)}`) + logger.log(` Limit: $${(options.stopLossPrice * limitPriceMultiplier).toFixed(4)}`) + logger.log(` ⚠️ May not fill during fast moves - use for liquid markets only!`) const sig = await retryWithBackoff(async () => await (driftClient as any).placePerpOrder(orderParams) ) - console.log('βœ… SL trigger-limit order placed:', sig) + logger.log('βœ… SL trigger-limit order placed:', sig) signatures.push(sig) } else { // TRIGGER_MARKET: Default, guaranteed execution @@ -461,19 +462,19 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise< reduceOnly: true, } - console.log(`πŸ›‘οΈ Placing SL as TRIGGER_MARKET (guaranteed execution - RECOMMENDED)...`) - console.log(` Trigger: ${options.direction === 'long' ? 'BELOW' : 'ABOVE'} $${options.stopLossPrice.toFixed(4)}`) - console.log(` βœ… Will execute at market price when triggered (may slip but WILL fill)`) + logger.log(`πŸ›‘οΈ Placing SL as TRIGGER_MARKET (guaranteed execution - RECOMMENDED)...`) + logger.log(` Trigger: ${options.direction === 'long' ? 'BELOW' : 'ABOVE'} $${options.stopLossPrice.toFixed(4)}`) + logger.log(` βœ… Will execute at market price when triggered (may slip but WILL fill)`) const sig = await retryWithBackoff(async () => await (driftClient as any).placePerpOrder(orderParams) ) - console.log('βœ… SL trigger-market order placed:', sig) + logger.log('βœ… SL trigger-market order placed:', sig) signatures.push(sig) } } } else { - console.log('⚠️ SL size below market min, skipping on-chain SL') + logger.log('⚠️ SL size below market min, skipping on-chain SL') } return { success: true, signatures } @@ -490,7 +491,7 @@ export async function closePosition( params: ClosePositionParams ): Promise { try { - console.log('πŸ“Š Closing position:', params) + logger.log('πŸ“Š Closing position:', params) const driftService = getDriftService() const marketConfig = getMarketConfig(params.symbol) @@ -509,26 +510,26 @@ export async function closePosition( // CRITICAL FIX: If calculated size is below minimum, close 100% instead // This prevents "runner" positions from being too small to close if (sizeToClose < marketConfig.minOrderSize) { - console.log(`⚠️ Calculated close size ${sizeToClose.toFixed(4)} is below minimum ${marketConfig.minOrderSize}`) - console.log(` Forcing 100% close to avoid Drift rejection`) + logger.log(`⚠️ Calculated close size ${sizeToClose.toFixed(4)} is below minimum ${marketConfig.minOrderSize}`) + logger.log(` Forcing 100% close to avoid Drift rejection`) sizeToClose = position.size // Close entire position } - console.log(`πŸ“ Close order details:`) - console.log(` Current position: ${position.size.toFixed(4)} ${position.side}`) - console.log(` Closing: ${params.percentToClose}% (${sizeToClose.toFixed(4)})`) - console.log(` Entry price: $${position.entryPrice.toFixed(4)}`) - console.log(` Unrealized P&L: $${position.unrealizedPnL.toFixed(2)}`) + logger.log(`πŸ“ Close order details:`) + logger.log(` Current position: ${position.size.toFixed(4)} ${position.side}`) + logger.log(` Closing: ${params.percentToClose}% (${sizeToClose.toFixed(4)})`) + logger.log(` Entry price: $${position.entryPrice.toFixed(4)}`) + logger.log(` Unrealized P&L: $${position.unrealizedPnL.toFixed(2)}`) // Get current oracle price const oraclePrice = await driftService.getOraclePrice(marketConfig.driftMarketIndex) - console.log(` Current price: $${oraclePrice.toFixed(4)}`) + logger.log(` Current price: $${oraclePrice.toFixed(4)}`) // Check DRY_RUN mode const isDryRun = process.env.DRY_RUN === 'true' if (isDryRun) { - console.log('πŸ§ͺ DRY RUN MODE: Simulating close order (not executing on blockchain)') + logger.log('πŸ§ͺ DRY RUN MODE: Simulating close order (not executing on blockchain)') // Calculate realized P&L with leverage (default 10x in dry run) const profitPercent = ((oraclePrice - position.entryPrice) / position.entryPrice) * 100 * (position.side === 'long' ? 1 : -1) @@ -538,10 +539,10 @@ export async function closePosition( const mockTxSig = `DRY_RUN_CLOSE_${Date.now()}_${Math.random().toString(36).substring(7)}` - console.log(`πŸ’° Simulated close:`) - console.log(` Close price: $${oraclePrice.toFixed(4)}`) - console.log(` Profit %: ${profitPercent.toFixed(3)}% β†’ Account P&L (10x): ${accountPnLPercent.toFixed(2)}%`) - console.log(` Realized P&L: $${realizedPnL.toFixed(2)}`) + logger.log(`πŸ’° Simulated close:`) + logger.log(` Close price: $${oraclePrice.toFixed(4)}`) + logger.log(` Profit %: ${profitPercent.toFixed(3)}% β†’ Account P&L (10x): ${accountPnLPercent.toFixed(2)}%`) + logger.log(` Realized P&L: $${realizedPnL.toFixed(2)}`) return { success: true, @@ -565,16 +566,16 @@ export async function closePosition( // Place market close order using simple placePerpOrder (like v3) // CRITICAL: Wrap in retry logic for rate limit protection - console.log('πŸš€ Placing REAL market close order with retry protection...') + logger.log('πŸš€ Placing REAL market close order with retry protection...') const txSig = await retryWithBackoff(async () => { return await driftClient.placePerpOrder(orderParams) }, 3, 8000) // 8s base delay, 3 max retries - console.log(`βœ… Close order placed! Transaction: ${txSig}`) + logger.log(`βœ… Close order placed! Transaction: ${txSig}`) // CRITICAL: Confirm transaction on-chain to prevent phantom closes // BUT: Use timeout to prevent API hangs during network congestion - console.log('⏳ Confirming transaction on-chain (30s timeout)...') + logger.log('⏳ Confirming transaction on-chain (30s timeout)...') const connection = driftService.getTradeConnection() // Use Alchemy for trade operations try { @@ -590,7 +591,7 @@ export async function closePosition( throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`) } - console.log('βœ… Transaction confirmed on-chain') + logger.log('βœ… Transaction confirmed on-chain') } catch (timeoutError: any) { if (timeoutError.message === 'Transaction confirmation timeout') { console.warn('⚠️ Transaction confirmation timed out after 30s') @@ -615,7 +616,7 @@ export async function closePosition( leverage = 10000 / Number(userAccount.maxMarginRatio) } } catch (err) { - console.log('⚠️ Could not determine leverage from account, using 10x default') + logger.log('⚠️ Could not determine leverage from account, using 10x default') } // Calculate closed notional value (USD) @@ -623,24 +624,24 @@ export async function closePosition( const realizedPnL = (closedNotional * profitPercent) / 100 const accountPnLPercent = profitPercent * leverage - console.log(`πŸ’° Close details:`) - console.log(` Close price: $${oraclePrice.toFixed(4)}`) - console.log(` Profit %: ${profitPercent.toFixed(3)}% | Account P&L (${leverage}x): ${accountPnLPercent.toFixed(2)}%`) - console.log(` Closed notional: $${closedNotional.toFixed(2)}`) - console.log(` Realized P&L: $${realizedPnL.toFixed(2)}`) + logger.log(`πŸ’° Close details:`) + logger.log(` Close price: $${oraclePrice.toFixed(4)}`) + logger.log(` Profit %: ${profitPercent.toFixed(3)}% | Account P&L (${leverage}x): ${accountPnLPercent.toFixed(2)}%`) + logger.log(` Closed notional: $${closedNotional.toFixed(2)}`) + logger.log(` Realized P&L: $${realizedPnL.toFixed(2)}`) // If closing 100%, verify position actually closed and cancel remaining orders if (params.percentToClose === 100) { - console.log('πŸ—‘οΈ Position fully closed, cancelling remaining orders...') + logger.log('πŸ—‘οΈ Position fully closed, cancelling remaining orders...') const cancelResult = await cancelAllOrders(params.symbol) if (cancelResult.success && cancelResult.cancelledCount! > 0) { - console.log(`βœ… Cancelled ${cancelResult.cancelledCount} orders`) + logger.log(`βœ… Cancelled ${cancelResult.cancelledCount} orders`) } // CRITICAL: Verify position actually closed on Drift (Nov 16, 2025) // Transaction confirmed β‰  Drift state updated immediately // Wait 5 seconds for Drift internal state to propagate - console.log('⏳ Waiting 5s for Drift state to propagate...') + logger.log('⏳ Waiting 5s for Drift state to propagate...') await new Promise(resolve => setTimeout(resolve, 5000)) try { @@ -661,7 +662,7 @@ export async function closePosition( needsVerification: true, // Flag for Position Manager } } else { - console.log('βœ… Position verified closed on Drift') + logger.log('βœ… Position verified closed on Drift') } } catch (verifyError) { console.warn('⚠️ Could not verify position closure:', verifyError) @@ -712,7 +713,7 @@ async function retryWithBackoff( // Log successful execution time for rate limit monitoring if (attempt > 0) { const totalTime = Date.now() - startTime - console.log(`βœ… Retry successful after ${totalTime}ms (${attempt} retries)`) + logger.log(`βœ… Retry successful after ${totalTime}ms (${attempt} retries)`) // Log to database for analytics try { @@ -756,8 +757,8 @@ async function retryWithBackoff( } const delay = baseDelay * Math.pow(2, attempt) - console.log(`⏳ Rate limited (429), retrying in ${delay / 1000}s... (attempt ${attempt + 1}/${maxRetries})`) - console.log(` Error context: ${errorMessage.substring(0, 100)}`) + logger.log(`⏳ Rate limited (429), retrying in ${delay / 1000}s... (attempt ${attempt + 1}/${maxRetries})`) + logger.log(` Error context: ${errorMessage.substring(0, 100)}`) // Log rate limit hit to database try { @@ -783,12 +784,12 @@ export async function cancelAllOrders( symbol: string ): Promise<{ success: boolean; cancelledCount?: number; error?: string }> { try { - console.log(`πŸ—‘οΈ Cancelling all orders for ${symbol}...`) + logger.log(`πŸ—‘οΈ Cancelling all orders for ${symbol}...`) // Ensure Drift service is initialized let driftService = getDriftService() if (!driftService) { - console.log('⚠️ Drift service not initialized, initializing now...') + logger.log('⚠️ Drift service not initialized, initializing now...') driftService = await initializeDriftService() } @@ -797,7 +798,7 @@ export async function cancelAllOrders( const isDryRun = process.env.DRY_RUN === 'true' if (isDryRun) { - console.log('πŸ§ͺ DRY RUN: Simulating order cancellation') + logger.log('πŸ§ͺ DRY RUN: Simulating order cancellation') return { success: true, cancelledCount: 0 } } @@ -827,12 +828,12 @@ export async function cancelAllOrders( ) if (ordersToCancel.length === 0) { - console.log('βœ… No open orders to cancel') + logger.log('βœ… No open orders to cancel') return { success: true, cancelledCount: 0 } } - console.log(`πŸ“‹ Found ${ordersToCancel.length} open orders to cancel (including trigger orders)`) - console.log(` (checked ${userAccount.orders.length} total order slots)`) + logger.log(`πŸ“‹ Found ${ordersToCancel.length} open orders to cancel (including trigger orders)`) + logger.log(` (checked ${userAccount.orders.length} total order slots)`) // Cancel all orders with retry logic for rate limits const txSig = await retryWithBackoff(async () => { @@ -843,7 +844,7 @@ export async function cancelAllOrders( ) }) - console.log(`βœ… Orders cancelled! Transaction: ${txSig}`) + logger.log(`βœ… Orders cancelled! Transaction: ${txSig}`) return { success: true, @@ -883,21 +884,21 @@ export async function emergencyCloseAll(): Promise<{ result: ClosePositionResult }> }> { - console.log('🚨 EMERGENCY: Closing all positions') + logger.log('🚨 EMERGENCY: Closing all positions') try { const driftService = getDriftService() const positions = await driftService.getAllPositions() if (positions.length === 0) { - console.log('βœ… No positions to close') + logger.log('βœ… No positions to close') return { success: true, results: [] } } const results = [] for (const position of positions) { - console.log(`πŸ”΄ Emergency closing ${position.symbol}...`) + logger.log(`πŸ”΄ Emergency closing ${position.symbol}...`) const result = await closeEntirePosition(position.symbol, 2.0) // Allow 2% slippage results.push({ symbol: position.symbol, @@ -905,7 +906,7 @@ export async function emergencyCloseAll(): Promise<{ }) } - console.log('βœ… Emergency close complete') + logger.log('βœ… Emergency close complete') return { success: true, diff --git a/lib/monitoring/drift-health-monitor.ts b/lib/monitoring/drift-health-monitor.ts index 279dd82..e246b99 100644 --- a/lib/monitoring/drift-health-monitor.ts +++ b/lib/monitoring/drift-health-monitor.ts @@ -6,6 +6,7 @@ */ import fs from 'fs' +import { logger } from '../utils/logger' import path from 'path' class DriftHealthMonitor { @@ -20,13 +21,13 @@ class DriftHealthMonitor { */ start(): void { if (this.isMonitoring) { - console.log('⚠️ Drift health monitor already running') + logger.log('⚠️ Drift health monitor already running') return } this.isMonitoring = true - console.log('πŸ₯ Drift health monitor started') - console.log(` Threshold: ${this.errorThreshold} accountUnsubscribe errors in ${this.errorWindow/1000}s`) + logger.log('πŸ₯ Drift health monitor started') + logger.log(` Threshold: ${this.errorThreshold} accountUnsubscribe errors in ${this.errorWindow/1000}s`) // Check error counts every 3 seconds (was 10s - faster response to memory leak) this.checkInterval = setInterval(() => { @@ -43,7 +44,7 @@ class DriftHealthMonitor { this.checkInterval = null } this.isMonitoring = false - console.log('πŸ₯ Drift health monitor stopped') + logger.log('πŸ₯ Drift health monitor stopped') } /** @@ -103,8 +104,8 @@ class DriftHealthMonitor { `Drift SDK health check failed: ${this.errorCounts.size} accountUnsubscribe errors\nTimestamp: ${new Date().toISOString()}\n`, 'utf-8' ) - console.log(`βœ… Restart flag created at ${restartFlagPath}`) - console.log(' watch-restart.sh will restart container within 10 seconds') + logger.log(`βœ… Restart flag created at ${restartFlagPath}`) + logger.log(' watch-restart.sh will restart container within 10 seconds') } catch (error) { console.error('❌ Failed to create restart flag:', error) } diff --git a/lib/notifications/telegram.ts b/lib/notifications/telegram.ts index 96c680d..e3e7868 100644 --- a/lib/notifications/telegram.ts +++ b/lib/notifications/telegram.ts @@ -1,3 +1,5 @@ +import { logger } from '../utils/logger' + /** * Telegram notification utilities * @@ -36,7 +38,7 @@ export async function sendPositionClosedNotification(options: TelegramNotificati const chatId = process.env.TELEGRAM_CHAT_ID if (!token || !chatId) { - console.log('⚠️ Telegram credentials not configured, skipping notification') + logger.log('⚠️ Telegram credentials not configured, skipping notification') return } @@ -77,7 +79,7 @@ ${options.maxDrawdown ? `\nπŸ“‰ Max Drawdown: -${options.maxDrawdown.toFixed(2)} const errorData = await response.json() console.error('❌ Telegram notification failed:', errorData) } else { - console.log('βœ… Telegram notification sent') + logger.log('βœ… Telegram notification sent') } } catch (error) { console.error('❌ Error sending Telegram notification:', error) @@ -243,7 +245,7 @@ async function sendWithdrawalNotification(options: TelegramWithdrawalOptions): P const chatId = process.env.TELEGRAM_CHAT_ID if (!token || !chatId) { - console.log('⚠️ Telegram credentials not configured, skipping notification') + logger.log('⚠️ Telegram credentials not configured, skipping notification') return } @@ -273,7 +275,7 @@ async function sendWithdrawalNotification(options: TelegramWithdrawalOptions): P const errorData = await response.json() console.error('❌ Telegram notification failed:', errorData) } else { - console.log('βœ… Telegram withdrawal notification sent') + logger.log('βœ… Telegram withdrawal notification sent') } } catch (error) { console.error('❌ Error sending Telegram notification:', error) @@ -290,7 +292,7 @@ export async function sendTelegramMessage(message: string): Promise { const chatId = process.env.TELEGRAM_CHAT_ID if (!token || !chatId) { - console.log('⚠️ Telegram credentials not configured, skipping notification') + logger.log('⚠️ Telegram credentials not configured, skipping notification') return } diff --git a/lib/pyth/price-monitor.ts b/lib/pyth/price-monitor.ts index a2c943e..38b849d 100644 --- a/lib/pyth/price-monitor.ts +++ b/lib/pyth/price-monitor.ts @@ -5,6 +5,7 @@ */ import { Connection, PublicKey } from '@solana/web3.js' +import { logger } from '../utils/logger' import { PriceServiceConnection } from '@pythnetwork/price-service-client' import { getMarketConfig } from '../../config/trading' @@ -47,7 +48,7 @@ export class PythPriceMonitor { }, }) - console.log('βœ… Pyth price monitor created') + logger.log('βœ… Pyth price monitor created') } /** @@ -59,7 +60,7 @@ export class PythPriceMonitor { return } - console.log('πŸš€ Starting Pyth price monitor for:', config.symbols) + logger.log('πŸš€ Starting Pyth price monitor for:', config.symbols) try { // Get Pyth price feed IDs for all symbols @@ -68,7 +69,7 @@ export class PythPriceMonitor { return marketConfig.pythPriceFeedId }) - console.log('πŸ“‘ Subscribing to Pyth WebSocket...') + logger.log('πŸ“‘ Subscribing to Pyth WebSocket...') // Subscribe to Pyth WebSocket for real-time updates this.priceService.subscribePriceFeedUpdates(priceIds, (priceFeed) => { @@ -112,13 +113,13 @@ export class PythPriceMonitor { } }) - console.log('βœ… Pyth WebSocket subscribed') + logger.log('βœ… Pyth WebSocket subscribed') // Start polling fallback (every 2 seconds) in case WebSocket fails this.startPollingFallback(config) this.isMonitoring = true - console.log('βœ… Price monitoring active') + logger.log('βœ… Price monitoring active') } catch (error) { console.error('❌ Failed to start price monitor:', error) @@ -130,7 +131,7 @@ export class PythPriceMonitor { * Polling fallback - checks prices every 2 seconds via RPC */ private startPollingFallback(config: PriceMonitorConfig): void { - console.log('πŸ”„ Starting polling fallback (every 2s)...') + logger.log('πŸ”„ Starting polling fallback (every 2s)...') for (const symbol of config.symbols) { const interval = setInterval(async () => { @@ -140,7 +141,7 @@ export class PythPriceMonitor { const timeSinceUpdate = Date.now() - lastUpdate if (timeSinceUpdate > 5000) { - console.log(`⚠️ WebSocket stale for ${symbol}, using polling fallback`) + logger.log(`⚠️ WebSocket stale for ${symbol}, using polling fallback`) await this.fetchPriceViaRPC(symbol, config.onPriceUpdate) } } catch (error) { @@ -154,7 +155,7 @@ export class PythPriceMonitor { this.pollingIntervals.set(symbol, interval) } - console.log('βœ… Polling fallback active') + logger.log('βœ… Polling fallback active') } /** @@ -223,7 +224,7 @@ export class PythPriceMonitor { return } - console.log('πŸ›‘ Stopping price monitor...') + logger.log('πŸ›‘ Stopping price monitor...') // Clear polling intervals this.pollingIntervals.forEach(interval => clearInterval(interval)) @@ -237,7 +238,7 @@ export class PythPriceMonitor { this.lastUpdateTime.clear() this.isMonitoring = false - console.log('βœ… Price monitor stopped') + logger.log('βœ… Price monitor stopped') } } diff --git a/lib/startup/init-position-manager.ts b/lib/startup/init-position-manager.ts index 7d25dd2..3237966 100644 --- a/lib/startup/init-position-manager.ts +++ b/lib/startup/init-position-manager.ts @@ -6,6 +6,7 @@ */ import { getInitializedPositionManager } from '../trading/position-manager' +import { logger } from '../utils/logger' import { initializeDriftService } from '../drift/client' import { getPrismaClient, createTrade } from '../database/trades' import { getMarketConfig, getMergedConfig } from '../../config/trading' @@ -25,16 +26,16 @@ export async function initializePositionManagerOnStartup() { initStarted = true - console.log('πŸš€ Initializing Position Manager on startup...') + logger.log('πŸš€ Initializing Position Manager on startup...') try { // CRITICAL: Run database sync validator FIRST to clean up duplicates const { validateAllOpenTrades } = await import('../database/sync-validator') - console.log('πŸ” Running database sync validation before Position Manager init...') + logger.log('πŸ” Running database sync validation before Position Manager init...') const validationResult = await validateAllOpenTrades() if (validationResult.ghosts > 0) { - console.log(`βœ… Cleaned up ${validationResult.ghosts} ghost/duplicate trades`) + logger.log(`βœ… Cleaned up ${validationResult.ghosts} ghost/duplicate trades`) } // Then validate open trades against Drift positions @@ -46,28 +47,28 @@ export async function initializePositionManagerOnStartup() { const manager = await getInitializedPositionManager() const status = manager.getStatus() - console.log(`βœ… Position Manager ready - ${status.activeTradesCount} active trades`) + logger.log(`βœ… Position Manager ready - ${status.activeTradesCount} active trades`) if (status.activeTradesCount > 0) { - console.log(`πŸ“Š Monitoring: ${status.symbols.join(', ')}`) + logger.log(`πŸ“Š Monitoring: ${status.symbols.join(', ')}`) } // CRITICAL (Dec 2, 2025): Start data cleanup service for 4-week retention // User directive: "we want to store the data for 4 weeks" // Runs daily at 3 AM to delete MarketData records older than 28 days - console.log('🧹 Starting data cleanup service...') + logger.log('🧹 Starting data cleanup service...') startDataCleanup() // Start blocked signal price tracking - console.log('πŸ”¬ Starting blocked signal price tracker...') + logger.log('πŸ”¬ Starting blocked signal price tracker...') startBlockedSignalTracking() // Start stop hunt revenge tracker - console.log('🎯 Starting stop hunt revenge tracker...') + logger.log('🎯 Starting stop hunt revenge tracker...') await startStopHuntTracking() // Start smart entry validation queue (Nov 30, 2025) - console.log('🧠 Starting smart entry validation system...') + logger.log('🧠 Starting smart entry validation system...') await startSmartValidation() } catch (error) { console.error('❌ Failed to initialize Position Manager on startup:', error) @@ -107,11 +108,11 @@ async function validateOpenTrades() { const allTradesToCheck = [...openTrades, ...recentlyClosedTrades] if (allTradesToCheck.length === 0) { - console.log('βœ… No open trades to validate') + logger.log('βœ… No open trades to validate') return } - console.log(`πŸ” Validating ${openTrades.length} open + ${recentlyClosedTrades.length} recently closed trades against Drift...`) + logger.log(`πŸ” Validating ${openTrades.length} open + ${recentlyClosedTrades.length} recently closed trades against Drift...`) // CRITICAL: Group trades by symbol to handle multiple DB entries for same Drift position // This prevents reopening old closed trades when only the most recent should be restored @@ -135,7 +136,7 @@ async function validateOpenTrades() { // Close any older trades BEFORE validating the most recent for (const oldTrade of olderTrades) { if (oldTrade.exitReason === null) { - console.log(`πŸ—‘οΈ Closing duplicate old trade: ${oldTrade.id} (${symbol}, created ${oldTrade.createdAt.toISOString()})`) + logger.log(`πŸ—‘οΈ Closing duplicate old trade: ${oldTrade.id} (${symbol}, created ${oldTrade.createdAt.toISOString()})`) await prisma.trade.update({ where: { id: oldTrade.id }, data: { @@ -161,8 +162,8 @@ async function validateOpenTrades() { if (!position || position.size === 0) { // No position on Drift if (trade.status === 'open') { - console.log(`⚠️ PHANTOM TRADE: ${trade.symbol} marked open in DB but not found on Drift`) - console.log(` πŸ—‘οΈ Auto-closing phantom trade...`) + logger.log(`⚠️ PHANTOM TRADE: ${trade.symbol} marked open in DB but not found on Drift`) + logger.log(` πŸ—‘οΈ Auto-closing phantom trade...`) await prisma.trade.update({ where: { id: trade.id }, @@ -184,17 +185,17 @@ async function validateOpenTrades() { const driftDirection = position.side.toLowerCase() as 'long' | 'short' if (driftDirection !== trade.direction) { - console.log(`⚠️ DIRECTION MISMATCH: ${trade.symbol} DB=${trade.direction} Drift=${driftDirection}`) + logger.log(`⚠️ DIRECTION MISMATCH: ${trade.symbol} DB=${trade.direction} Drift=${driftDirection}`) continue } // CRITICAL: If DB says closed but Drift shows open, restore tracking! if (trade.exitReason !== null) { - console.log(`πŸ”΄ CRITICAL: ${trade.symbol} marked as CLOSED in DB but still OPEN on Drift!`) - console.log(` DB entry: $${trade.entryPrice.toFixed(2)} | Drift entry: $${position.entryPrice.toFixed(2)}`) - console.log(` DB exit: ${trade.exitReason} at ${trade.exitTime?.toISOString()}`) - console.log(` Drift: ${position.size} ${trade.symbol} ${driftDirection} @ $${position.entryPrice.toFixed(2)}`) - console.log(` πŸ”„ Reopening trade and correcting entry price to match Drift...`) + logger.log(`πŸ”΄ CRITICAL: ${trade.symbol} marked as CLOSED in DB but still OPEN on Drift!`) + logger.log(` DB entry: $${trade.entryPrice.toFixed(2)} | Drift entry: $${position.entryPrice.toFixed(2)}`) + logger.log(` DB exit: ${trade.exitReason} at ${trade.exitTime?.toISOString()}`) + logger.log(` Drift: ${position.size} ${trade.symbol} ${driftDirection} @ $${position.entryPrice.toFixed(2)}`) + logger.log(` πŸ”„ Reopening trade and correcting entry price to match Drift...`) // Calculate position size in USD using Drift's entry price const currentPrice = await driftService.getOraclePrice(marketConfig.driftMarketIndex) @@ -213,9 +214,9 @@ async function validateOpenTrades() { } }) - console.log(` βœ… Trade restored with corrected entry: $${position.entryPrice.toFixed(2)} (was $${trade.entryPrice.toFixed(2)})`) + logger.log(` βœ… Trade restored with corrected entry: $${position.entryPrice.toFixed(2)} (was $${trade.entryPrice.toFixed(2)})`) } else { - console.log(`βœ… ${trade.symbol} ${trade.direction}: Position verified on Drift`) + logger.log(`βœ… ${trade.symbol} ${trade.direction}: Position verified on Drift`) } // CRITICAL FIX (Nov 16, 2025): Restore missing on-chain orders @@ -255,11 +256,11 @@ async function restoreOrdersIfMissing( const hasOrders = position.orders && position.orders.length > 0 if (hasOrders) { - console.log(`βœ… ${trade.symbol} has ${position.orders.length} on-chain orders - protection active`) + logger.log(`βœ… ${trade.symbol} has ${position.orders.length} on-chain orders - protection active`) return // Orders exist, nothing to do } - console.log(`⚠️ ${trade.symbol} has NO on-chain orders - restoring TP/SL protection...`) + logger.log(`⚠️ ${trade.symbol} has NO on-chain orders - restoring TP/SL protection...`) // Import order placement function const { placeExitOrders } = await import('../drift/orders') @@ -278,11 +279,11 @@ async function restoreOrdersIfMissing( }) if (result.success) { - console.log(`βœ… Orders restored for ${trade.symbol}:`) - console.log(` TP1: $${trade.takeProfit1Price.toFixed(4)} (75%)`) - console.log(` TP2: $${trade.takeProfit2Price.toFixed(4)} (runner trigger)`) - console.log(` SL: $${trade.stopLossPrice.toFixed(4)}`) - console.log(` TX: ${result.signatures?.[0]?.slice(0, 8)}...`) + logger.log(`βœ… Orders restored for ${trade.symbol}:`) + logger.log(` TP1: $${trade.takeProfit1Price.toFixed(4)} (75%)`) + logger.log(` TP2: $${trade.takeProfit2Price.toFixed(4)} (runner trigger)`) + logger.log(` SL: $${trade.stopLossPrice.toFixed(4)}`) + logger.log(` TX: ${result.signatures?.[0]?.slice(0, 8)}...`) // Update database with order transaction signatures await prisma.trade.update({ @@ -324,17 +325,17 @@ async function detectOrphanedPositions() { const prisma = getPrismaClient() const driftService = await initializeDriftService() - console.log('πŸ” Checking for orphaned positions on Drift...') + logger.log('πŸ” Checking for orphaned positions on Drift...') // Get all open positions from Drift const driftPositions = await driftService.getAllPositions() if (driftPositions.length === 0) { - console.log('βœ… No positions on Drift') + logger.log('βœ… No positions on Drift') return } - console.log(`πŸ” Found ${driftPositions.length} positions on Drift, checking database...`) + logger.log(`πŸ” Found ${driftPositions.length} positions on Drift, checking database...`) // Get all open trades from database const openTrades = await prisma.trade.findMany({ @@ -349,7 +350,7 @@ async function detectOrphanedPositions() { const positionKey = `${position.symbol}-${position.side.toLowerCase()}` if (trackedSymbols.has(positionKey)) { - console.log(`βœ… ${position.symbol} ${position.side} tracked in database`) + logger.log(`βœ… ${position.symbol} ${position.side} tracked in database`) continue } @@ -359,12 +360,12 @@ async function detectOrphanedPositions() { const currentPrice = await driftService.getOraclePrice(marketConfig.driftMarketIndex) const positionSizeUSD = Math.abs(position.size) * currentPrice - console.log(`🚨 ORPHAN POSITION DETECTED!`) - console.log(` Symbol: ${position.symbol}`) - console.log(` Direction: ${position.side}`) - console.log(` Size: ${Math.abs(position.size)} (notional: $${positionSizeUSD.toFixed(2)})`) - console.log(` Entry: $${position.entryPrice.toFixed(4)}`) - console.log(` Current: $${currentPrice.toFixed(4)}`) + logger.log(`🚨 ORPHAN POSITION DETECTED!`) + logger.log(` Symbol: ${position.symbol}`) + logger.log(` Direction: ${position.side}`) + logger.log(` Size: ${Math.abs(position.size)} (notional: $${positionSizeUSD.toFixed(2)})`) + logger.log(` Entry: $${position.entryPrice.toFixed(4)}`) + logger.log(` Current: $${currentPrice.toFixed(4)}`) // Log to persistent file logCriticalError('ORPHAN POSITION DETECTED - Creating retroactive database record', { @@ -398,7 +399,7 @@ async function detectOrphanedPositions() { : entryPrice * (1 - config.takeProfit2Percent / 100) // Create retroactive database record - console.log(`πŸ”„ Creating retroactive database record...`) + logger.log(`πŸ”„ Creating retroactive database record...`) const trade = await createTrade({ positionId: `ORPHAN-${Date.now()}`, // Fake position ID since we don't have transaction symbol: position.symbol, @@ -418,7 +419,7 @@ async function detectOrphanedPositions() { status: 'open', }) - console.log(`βœ… Retroactive database record created: ${trade.id}`) + logger.log(`βœ… Retroactive database record created: ${trade.id}`) // Send Telegram notification try { @@ -438,7 +439,7 @@ async function detectOrphanedPositions() { console.error('Failed to send orphan notification:', telegramError) } - console.log(`🎯 Orphan position now tracked and monitored`) + logger.log(`🎯 Orphan position now tracked and monitored`) } catch (recoveryError) { console.error(`❌ Failed to recover orphan position ${position.symbol}:`, recoveryError) @@ -449,7 +450,7 @@ async function detectOrphanedPositions() { } } - console.log('βœ… Orphan position detection complete') + logger.log('βœ… Orphan position detection complete') } catch (error) { console.error('❌ Error detecting orphaned positions:', error) diff --git a/lib/trading/market-data-cache.ts b/lib/trading/market-data-cache.ts index 6c7554b..3a3f032 100644 --- a/lib/trading/market-data-cache.ts +++ b/lib/trading/market-data-cache.ts @@ -1,3 +1,5 @@ +import { logger } from '../utils/logger' + /** * Market Data Cache Service * @@ -29,7 +31,7 @@ class MarketDataCache { */ set(symbol: string, metrics: MarketMetrics): void { this.cache.set(symbol, metrics) - console.log( + logger.log( `πŸ“Š Cached market data for ${symbol}: ` + `ADX=${metrics.adx.toFixed(1)} ` + `ATR=${metrics.atr.toFixed(2)}% ` + @@ -46,18 +48,18 @@ class MarketDataCache { const data = this.cache.get(symbol) if (!data) { - console.log(`⚠️ No cached data for ${symbol}`) + logger.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)`) + logger.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)`) + logger.log(`βœ… Using fresh TradingView data for ${symbol} (${ageSeconds}s old)`) return data } @@ -102,7 +104,7 @@ class MarketDataCache { */ clear(): void { this.cache.clear() - console.log('πŸ—‘οΈ Market data cache cleared') + logger.log('πŸ—‘οΈ Market data cache cleared') } } @@ -112,7 +114,7 @@ let marketDataCache: MarketDataCache | null = null export function getMarketDataCache(): MarketDataCache { if (!marketDataCache) { marketDataCache = new MarketDataCache() - console.log('πŸ”§ Initialized Market Data Cache (5min expiry)') + logger.log('πŸ”§ Initialized Market Data Cache (5min expiry)') } return marketDataCache } diff --git a/lib/trading/position-manager.ts b/lib/trading/position-manager.ts index 5a02c12..faee57f 100644 --- a/lib/trading/position-manager.ts +++ b/lib/trading/position-manager.ts @@ -5,6 +5,7 @@ */ import { getDriftService, initializeDriftService } from '../drift/client' +import { logger } from '../utils/logger' import { closePosition } from '../drift/orders' import { getPythPriceMonitor, PriceUpdate } from '../pyth/price-monitor' import { getMergedConfig, TradingConfig, getMarketConfig } from '../../config/trading' @@ -92,7 +93,7 @@ export class PositionManager { constructor(config?: Partial) { this.config = getMergedConfig(config) - console.log('βœ… Position manager created') + logger.log('βœ… Position manager created') } /** @@ -103,7 +104,7 @@ export class PositionManager { return } - console.log('πŸ”„ Restoring active trades from database...') + logger.log('πŸ”„ Restoring active trades from database...') try { const openTrades = await getOpenTrades() @@ -149,14 +150,14 @@ export class PositionManager { } this.activeTrades.set(activeTrade.id, activeTrade) - console.log(`βœ… Restored trade: ${activeTrade.symbol} ${activeTrade.direction} at $${activeTrade.entryPrice}`) + logger.log(`βœ… Restored trade: ${activeTrade.symbol} ${activeTrade.direction} at $${activeTrade.entryPrice}`) } if (this.activeTrades.size > 0) { - console.log(`🎯 Restored ${this.activeTrades.size} active trades`) + logger.log(`🎯 Restored ${this.activeTrades.size} active trades`) await this.startMonitoring() } else { - console.log('βœ… No active trades to restore') + logger.log('βœ… No active trades to restore') } } catch (error) { @@ -175,7 +176,7 @@ export class PositionManager { currentPrice: number, remainingSize: number ): Promise { - console.log(`πŸ‘€ Processing manual closure for ${trade.symbol}`) + logger.log(`πŸ‘€ Processing manual closure for ${trade.symbol}`) // Determine exit reason based on price levels let exitReason: 'TP1' | 'TP2' | 'SL' | 'SOFT_SL' | 'HARD_SL' | 'TRAILING_SL' | 'manual' | 'emergency' = 'manual' @@ -187,24 +188,24 @@ export class PositionManager { if (isAtTP2 && trade.tp1Hit) { exitReason = 'TP2' - console.log(`βœ… Manual closure was TP2 (price at target)`) + logger.log(`βœ… Manual closure was TP2 (price at target)`) } else if (isAtSL) { // Check if trailing stop was active if (trade.trailingStopActive && trade.tp2Hit) { exitReason = 'TRAILING_SL' - console.log(`πŸƒ Manual closure was Trailing SL (price at trailing stop target)`) + logger.log(`πŸƒ Manual closure was Trailing SL (price at trailing stop target)`) } else { exitReason = 'SL' - console.log(`πŸ›‘ Manual closure was SL (price at target)`) + logger.log(`πŸ›‘ Manual closure was SL (price at target)`) } } else { - console.log(`πŸ‘€ Manual closure confirmed (price not at any target)`) - console.log(` Current: $${currentPrice.toFixed(4)}, TP1: $${trade.takeProfitPrice1?.toFixed(4)}, TP2: $${trade.takeProfitPrice2?.toFixed(4)}, SL: $${trade.stopLossPrice?.toFixed(4)}`) + logger.log(`πŸ‘€ Manual closure confirmed (price not at any target)`) + logger.log(` Current: $${currentPrice.toFixed(4)}, TP1: $${trade.takeProfitPrice1?.toFixed(4)}, TP2: $${trade.takeProfitPrice2?.toFixed(4)}, SL: $${trade.stopLossPrice?.toFixed(4)}`) } // CRITICAL: Calculate P&L using originalPositionSize for accuracy const realizedPnL = (trade.originalPositionSize * profitPercent) / 100 - console.log(`πŸ’° Manual close P&L: ${profitPercent.toFixed(2)}% on $${trade.originalPositionSize.toFixed(2)} = $${realizedPnL.toFixed(2)}`) + logger.log(`πŸ’° Manual close P&L: ${profitPercent.toFixed(2)}% on $${trade.originalPositionSize.toFixed(2)} = $${realizedPnL.toFixed(2)}`) // Remove from monitoring FIRST (prevent race conditions) this.activeTrades.delete(trade.id) @@ -226,7 +227,7 @@ export class PositionManager { maxAdversePrice: trade.maxAdversePrice, }) - console.log(`βœ… Manual closure recorded: ${trade.symbol} ${exitReason} P&L: $${realizedPnL.toFixed(2)}`) + logger.log(`βœ… Manual closure recorded: ${trade.symbol} ${exitReason} P&L: $${realizedPnL.toFixed(2)}`) // Send Telegram notification await sendPositionClosedNotification({ @@ -254,14 +255,14 @@ export class PositionManager { * Add a new trade to monitor */ async addTrade(trade: ActiveTrade): Promise { - console.log(`πŸ“Š Adding trade to monitor: ${trade.symbol} ${trade.direction}`) + logger.log(`πŸ“Š Adding trade to monitor: ${trade.symbol} ${trade.direction}`) this.activeTrades.set(trade.id, trade) // Note: Initial state is saved by the API endpoint that creates the trade // We don't save here to avoid race condition (trade may not be in DB yet) - console.log(`βœ… Trade added. Active trades: ${this.activeTrades.size}`) + logger.log(`βœ… Trade added. Active trades: ${this.activeTrades.size}`) // Start monitoring if not already running if (!this.isMonitoring && this.activeTrades.size > 0) { @@ -275,14 +276,14 @@ export class PositionManager { async removeTrade(tradeId: string): Promise { const trade = this.activeTrades.get(tradeId) if (trade) { - console.log(`πŸ—‘οΈ Removing trade: ${trade.symbol}`) + logger.log(`πŸ—‘οΈ Removing trade: ${trade.symbol}`) // Cancel all orders for this symbol (cleanup orphaned orders) try { const { cancelAllOrders } = await import('../drift/orders') const cancelResult = await cancelAllOrders(trade.symbol) if (cancelResult.success && cancelResult.cancelledCount! > 0) { - console.log(`βœ… Cancelled ${cancelResult.cancelledCount} orphaned orders`) + logger.log(`βœ… Cancelled ${cancelResult.cancelledCount} orphaned orders`) } } catch (error) { console.error('❌ Failed to cancel orders during trade removal:', error) @@ -313,7 +314,7 @@ export class PositionManager { await this.validatePositions() }, validationIntervalMs) - console.log('πŸ” Scheduled position validation every 5 minutes') + logger.log('πŸ” Scheduled position validation every 5 minutes') } /** @@ -335,16 +336,16 @@ export class PositionManager { return // Nothing to validate } - console.log('πŸ” Validating positions against Drift...') + logger.log('πŸ” Validating positions against Drift...') try { const driftService = getDriftService() // If Drift service not ready, skip this validation cycle if (!driftService || !(driftService as any).isInitialized) { - console.log('⚠️ Drift service not ready - skipping validation this cycle') - console.log(` Positions in memory: ${this.activeTrades.size}`) - console.log(` Will retry on next cycle (5 minutes) or during monitoring (40 seconds)`) + logger.log('⚠️ Drift service not ready - skipping validation this cycle') + logger.log(` Positions in memory: ${this.activeTrades.size}`) + logger.log(` Will retry on next cycle (5 minutes) or during monitoring (40 seconds)`) return } @@ -357,14 +358,14 @@ export class PositionManager { // Ghost detected: we're tracking it but Drift shows closed/missing if (!position || Math.abs(position.size) < 0.01) { - console.log(`πŸ”΄ Ghost position detected: ${trade.symbol} (${tradeId})`) - console.log(` Database: exitReason IS NULL (thinks it's open)`) - console.log(` Drift: Position ${position ? 'closed (size=' + position.size + ')' : 'missing'}`) - console.log(` Cause: Likely failed DB update during external closure`) + logger.log(`πŸ”΄ Ghost position detected: ${trade.symbol} (${tradeId})`) + logger.log(` Database: exitReason IS NULL (thinks it's open)`) + logger.log(` Drift: Position ${position ? 'closed (size=' + position.size + ')' : 'missing'}`) + logger.log(` Cause: Likely failed DB update during external closure`) // Auto-cleanup: Handle as external closure await this.handleExternalClosure(trade, 'Ghost position cleanup') - console.log(`βœ… Ghost position cleaned up: ${trade.symbol}`) + logger.log(`βœ… Ghost position cleaned up: ${trade.symbol}`) } } catch (posError) { console.error(`⚠️ Could not check ${trade.symbol} on Drift:`, posError) @@ -372,7 +373,7 @@ export class PositionManager { } } - console.log(`βœ… Validation complete: ${this.activeTrades.size} positions healthy`) + logger.log(`βœ… Validation complete: ${this.activeTrades.size} positions healthy`) } catch (error) { console.error('❌ Position validation failed:', error) @@ -388,7 +389,7 @@ export class PositionManager { * - Manual cleanup needed after failed database updates */ private async handleExternalClosure(trade: ActiveTrade, reason: string): Promise { - console.log(`🧹 Handling external closure: ${trade.symbol} (${reason})`) + logger.log(`🧹 Handling external closure: ${trade.symbol} (${reason})`) // CRITICAL FIX (Dec 2, 2025): Remove from activeTrades FIRST, then check if already removed // Bug: Multiple monitoring loops detect ghost simultaneously @@ -400,12 +401,12 @@ export class PositionManager { const wasInMap = this.activeTrades.delete(tradeId) if (!wasInMap) { - console.log(`⚠️ DUPLICATE PREVENTED: Trade ${tradeId} already processed, skipping`) - console.log(` This prevents duplicate Telegram notifications with compounding P&L`) + logger.log(`⚠️ DUPLICATE PREVENTED: Trade ${tradeId} already processed, skipping`) + logger.log(` This prevents duplicate Telegram notifications with compounding P&L`) return } - console.log(`πŸ—‘οΈ Removed ${trade.symbol} from monitoring (will not process duplicates)`) + logger.log(`πŸ—‘οΈ Removed ${trade.symbol} from monitoring (will not process duplicates)`) // CRITICAL: Calculate P&L using originalPositionSize for accuracy // currentSize may be stale if Drift propagation was interrupted @@ -417,7 +418,7 @@ export class PositionManager { const sizeForPnL = trade.originalPositionSize // Use original, not currentSize const estimatedPnL = (sizeForPnL * profitPercent) / 100 - console.log(`πŸ’° Estimated P&L: ${profitPercent.toFixed(2)}% on $${sizeForPnL.toFixed(2)} β†’ $${estimatedPnL.toFixed(2)}`) + logger.log(`πŸ’° Estimated P&L: ${profitPercent.toFixed(2)}% on $${sizeForPnL.toFixed(2)} β†’ $${estimatedPnL.toFixed(2)}`) // Update database try { @@ -436,7 +437,7 @@ export class PositionManager { maxFavorablePrice: trade.maxFavorablePrice, maxAdversePrice: trade.maxAdversePrice, }) - console.log(`πŸ’Ύ Ghost closure saved to database`) + logger.log(`πŸ’Ύ Ghost closure saved to database`) // Send Telegram notification for ghost closure await sendPositionClosedNotification({ @@ -492,7 +493,7 @@ export class PositionManager { return } - console.log('πŸš€ Starting price monitoring for:', symbols) + logger.log('πŸš€ Starting price monitoring for:', symbols) const priceMonitor = getPythPriceMonitor() @@ -507,7 +508,7 @@ export class PositionManager { }) this.isMonitoring = true - console.log('βœ… Position monitoring active') + logger.log('βœ… Position monitoring active') // Schedule periodic validation to detect and cleanup ghost positions this.scheduleValidation() @@ -521,7 +522,7 @@ export class PositionManager { return } - console.log('πŸ›‘ Stopping position monitoring...') + logger.log('πŸ›‘ Stopping position monitoring...') const priceMonitor = getPythPriceMonitor() await priceMonitor.stop() @@ -533,7 +534,7 @@ export class PositionManager { } this.isMonitoring = false - console.log('βœ… Position monitoring stopped') + logger.log('βœ… Position monitoring stopped') } /** @@ -563,7 +564,7 @@ export class PositionManager { // CRITICAL FIX (Nov 23, 2025): Check if trade still in monitoring // Prevents duplicate processing when async operations remove trade during loop if (!this.activeTrades.has(trade.id)) { - console.log(`⏭️ Skipping ${trade.symbol} - already removed from monitoring`) + logger.log(`⏭️ Skipping ${trade.symbol} - already removed from monitoring`) return } @@ -595,12 +596,12 @@ export class PositionManager { // IMPORTANT: Skip "external closure" detection for NEW trades (<30 seconds old) // Drift positions may not be immediately visible after opening due to blockchain delays if (tradeAgeSeconds < 30) { - console.log(`⏳ Trade ${trade.symbol} is new (${tradeAgeSeconds.toFixed(1)}s old) - skipping external closure check`) + logger.log(`⏳ Trade ${trade.symbol} is new (${tradeAgeSeconds.toFixed(1)}s old) - skipping external closure check`) return // Skip this check cycle, position might still be propagating } // Position closed externally (by on-chain TP/SL order or manual closure) - console.log(`⚠️ Position ${trade.symbol} was closed externally (by on-chain order)`) + logger.log(`⚠️ Position ${trade.symbol} was closed externally (by on-chain order)`) } else { // Position exists - check if size changed (TP1/TP2 filled) // CRITICAL FIX: position.size from Drift SDK is base asset tokens, must convert to USD @@ -608,11 +609,11 @@ export class PositionManager { const trackedSizeUSD = trade.currentSize const sizeDiffPercent = Math.abs(positionSizeUSD - trackedSizeUSD) / trackedSizeUSD * 100 - console.log(`πŸ“Š Position check: Drift=$${positionSizeUSD.toFixed(2)} Tracked=$${trackedSizeUSD.toFixed(2)} Diff=${sizeDiffPercent.toFixed(1)}%`) + logger.log(`πŸ“Š Position check: Drift=$${positionSizeUSD.toFixed(2)} Tracked=$${trackedSizeUSD.toFixed(2)} Diff=${sizeDiffPercent.toFixed(1)}%`) // If position size reduced significantly, TP orders likely filled if (positionSizeUSD < trackedSizeUSD * 0.9 && sizeDiffPercent > 10) { - console.log(`βœ… Position size reduced: tracking $${trackedSizeUSD.toFixed(2)} β†’ found $${positionSizeUSD.toFixed(2)}`) + logger.log(`βœ… Position size reduced: tracking $${trackedSizeUSD.toFixed(2)} β†’ found $${positionSizeUSD.toFixed(2)}`) // Detect which TP filled based on size reduction const reductionPercent = ((trackedSizeUSD - positionSizeUSD) / trade.positionSize) * 100 @@ -622,9 +623,9 @@ export class PositionManager { const isPriceAtTP1 = this.isPriceAtTarget(currentPrice, trade.takeProfitPrice1 || 0, 0.002) if (!isPriceAtTP1) { - console.log(`⚠️ Size reduction detected (${reductionPercent.toFixed(1)}%) but price NOT at TP1`) - console.log(` Current: $${currentPrice.toFixed(4)}, TP1: $${trade.takeProfitPrice1?.toFixed(4) || 'N/A'}`) - console.log(` This is likely a MANUAL CLOSE or external order, not TP1`) + logger.log(`⚠️ Size reduction detected (${reductionPercent.toFixed(1)}%) but price NOT at TP1`) + logger.log(` Current: $${currentPrice.toFixed(4)}, TP1: $${trade.takeProfitPrice1?.toFixed(4) || 'N/A'}`) + logger.log(` This is likely a MANUAL CLOSE or external order, not TP1`) // Handle as external closure with proper exit reason detection await this.handleManualClosure(trade, currentPrice, positionSizeUSD) @@ -632,7 +633,7 @@ export class PositionManager { } // TP1 fired (price validated at target) - console.log(`🎯 TP1 detected as filled! Reduction: ${reductionPercent.toFixed(1)}%, price at TP1 target`) + logger.log(`🎯 TP1 detected as filled! Reduction: ${reductionPercent.toFixed(1)}%, price at TP1 target`) trade.tp1Hit = true trade.currentSize = positionSizeUSD @@ -643,13 +644,13 @@ export class PositionManager { if (adx < 20) { runnerSlPercent = 0 // Weak trend: breakeven, preserve capital - console.log(`πŸ”’ ADX-based runner SL: ${adx.toFixed(1)} β†’ 0% (breakeven - weak trend)`) + logger.log(`πŸ”’ ADX-based runner SL: ${adx.toFixed(1)} β†’ 0% (breakeven - weak trend)`) } else if (adx < 25) { runnerSlPercent = -0.3 // Moderate trend: some room - console.log(`πŸ”’ ADX-based runner SL: ${adx.toFixed(1)} β†’ -0.3% (moderate trend)`) + logger.log(`πŸ”’ ADX-based runner SL: ${adx.toFixed(1)} β†’ -0.3% (moderate trend)`) } else { runnerSlPercent = -0.55 // Strong trend: full retracement room - console.log(`πŸ”’ ADX-based runner SL: ${adx.toFixed(1)} β†’ -0.55% (strong trend)`) + logger.log(`πŸ”’ ADX-based runner SL: ${adx.toFixed(1)} β†’ -0.55% (strong trend)`) } // CRITICAL: Use DATABASE entry price (Drift recalculates after partial closes) @@ -659,25 +660,25 @@ export class PositionManager { trade.direction ) - console.log(`πŸ“Š Runner SL calculation: Entry $${trade.entryPrice.toFixed(4)} ${runnerSlPercent >= 0 ? '+' : ''}${runnerSlPercent}% = $${newStopLossPrice.toFixed(4)}`) - console.log(` (${this.config.takeProfit1SizePercent}% closed, ${100 - this.config.takeProfit1SizePercent}% remaining)`) + logger.log(`πŸ“Š Runner SL calculation: Entry $${trade.entryPrice.toFixed(4)} ${runnerSlPercent >= 0 ? '+' : ''}${runnerSlPercent}% = $${newStopLossPrice.toFixed(4)}`) + logger.log(` (${this.config.takeProfit1SizePercent}% closed, ${100 - this.config.takeProfit1SizePercent}% remaining)`) // Move SL to ADX-based position after TP1 trade.stopLossPrice = newStopLossPrice trade.slMovedToBreakeven = true - console.log(`πŸ›‘οΈ Stop loss moved to: $${trade.stopLossPrice.toFixed(4)}`) + logger.log(`πŸ›‘οΈ Stop loss moved to: $${trade.stopLossPrice.toFixed(4)}`) // CRITICAL: Update on-chain orders to reflect new SL at breakeven try { const { cancelAllOrders, placeExitOrders } = await import('../drift/orders') - console.log(`πŸ”„ Cancelling old exit orders...`) + logger.log(`πŸ”„ Cancelling old exit orders...`) const cancelResult = await cancelAllOrders(trade.symbol) if (cancelResult.success) { - console.log(`βœ… Cancelled ${cancelResult.cancelledCount} old orders`) + logger.log(`βœ… Cancelled ${cancelResult.cancelledCount} old orders`) } - console.log(`πŸ›‘οΈ Placing new exit orders with SL at breakeven...`) + logger.log(`πŸ›‘οΈ Placing new exit orders with SL at breakeven...`) const orderResult = await placeExitOrders({ symbol: trade.symbol, direction: trade.direction, @@ -693,7 +694,7 @@ export class PositionManager { }) if (orderResult.success) { - console.log(`βœ… Exit orders updated with SL at breakeven`) + logger.log(`βœ… Exit orders updated with SL at breakeven`) } else { console.error(`❌ Failed to update exit orders:`, orderResult.error) } @@ -705,11 +706,11 @@ export class PositionManager { } else if (trade.tp1Hit && !trade.tp2Hit && reductionPercent >= 85) { // TP2 fired (total should be ~95% closed, 5% runner left) - console.log(`🎯 TP2 detected as filled! Reduction: ${reductionPercent.toFixed(1)}%`) + logger.log(`🎯 TP2 detected as filled! Reduction: ${reductionPercent.toFixed(1)}%`) trade.tp2Hit = true trade.currentSize = positionSizeUSD trade.trailingStopActive = true - console.log(`πŸƒ Runner active: $${positionSizeUSD.toFixed(2)} with ${this.config.trailingStopPercent}% trailing stop`) + logger.log(`πŸƒ Runner active: $${positionSizeUSD.toFixed(2)} with ${this.config.trailingStopPercent}% trailing stop`) await this.saveTradeState(trade) @@ -718,7 +719,7 @@ export class PositionManager { } else { // Partial fill detected but unclear which TP - just update size - console.log(`⚠️ Unknown partial fill detected - updating tracked size to $${positionSizeUSD.toFixed(2)}`) + logger.log(`⚠️ Unknown partial fill detected - updating tracked size to $${positionSizeUSD.toFixed(2)}`) trade.currentSize = positionSizeUSD await this.saveTradeState(trade) } @@ -733,8 +734,8 @@ export class PositionManager { if (entryPriceDiffPercent > 0.5) { // Entry prices differ by >0.5% - this is a DIFFERENT position - console.log(`⚠️ Position ${trade.symbol} entry mismatch: tracking $${trade.entryPrice.toFixed(4)} but found $${position.entryPrice.toFixed(4)}`) - console.log(`πŸ—‘οΈ This is a different/newer position - removing old trade from monitoring`) + logger.log(`⚠️ Position ${trade.symbol} entry mismatch: tracking $${trade.entryPrice.toFixed(4)} but found $${position.entryPrice.toFixed(4)}`) + logger.log(`πŸ—‘οΈ This is a different/newer position - removing old trade from monitoring`) // Mark the old trade as closed (we lost track of it) // Calculate approximate P&L using last known price @@ -746,7 +747,7 @@ export class PositionManager { const accountPnLPercent = profitPercent * trade.leverage const estimatedPnL = (trade.currentSize * profitPercent) / 100 - console.log(`πŸ’° Estimated P&L for lost trade: ${profitPercent.toFixed(2)}% price β†’ ${accountPnLPercent.toFixed(2)}% account β†’ $${estimatedPnL.toFixed(2)} realized`) + logger.log(`πŸ’° Estimated P&L for lost trade: ${profitPercent.toFixed(2)}% price β†’ ${accountPnLPercent.toFixed(2)}% account β†’ $${estimatedPnL.toFixed(2)} realized`) try { await updateTradeExit({ @@ -763,13 +764,13 @@ export class PositionManager { maxFavorablePrice: trade.maxFavorablePrice, maxAdversePrice: trade.maxAdversePrice, }) - console.log(`πŸ’Ύ Old trade marked as closed (lost tracking) with estimated P&L: $${estimatedPnL.toFixed(2)}`) + logger.log(`πŸ’Ύ Old trade marked as closed (lost tracking) with estimated P&L: $${estimatedPnL.toFixed(2)}`) } catch (dbError) { console.error('❌ Failed to save lost trade closure:', dbError) } // Remove from monitoring WITHOUT cancelling orders (they belong to the new position!) - console.log(`πŸ—‘οΈ Removing old trade WITHOUT cancelling orders`) + logger.log(`πŸ—‘οΈ Removing old trade WITHOUT cancelling orders`) this.activeTrades.delete(trade.id) if (this.activeTrades.size === 0 && this.isMonitoring) { @@ -787,12 +788,12 @@ export class PositionManager { // Check if close has been stuck for >60 seconds (abnormal) const timeInClosing = Date.now() - (trade.closeConfirmedAt || Date.now()) if (timeInClosing > 60000) { - console.log(`⚠️ Close stuck in progress for ${(timeInClosing / 1000).toFixed(0)}s - allowing external closure check`) + logger.log(`⚠️ Close stuck in progress for ${(timeInClosing / 1000).toFixed(0)}s - allowing external closure check`) trade.closingInProgress = false // Reset flag to allow cleanup } else { // Normal case: Close confirmed recently, waiting for Drift propagation (5-10s) // Skip external closure detection entirely to prevent duplicate P&L updates - console.log(`πŸ”’ Close in progress (${(timeInClosing / 1000).toFixed(0)}s) - skipping external closure check`) + logger.log(`πŸ”’ Close in progress (${(timeInClosing / 1000).toFixed(0)}s) - skipping external closure check`) // Continue to price calculations below (monitoring continues normally) } } @@ -809,8 +810,8 @@ export class PositionManager { trade.direction ) - console.log(`🎊 TP2 PRICE REACHED: ${trade.symbol} at ${profitPercent.toFixed(2)}%`) - console.log(` Activating trailing stop for runner protection`) + logger.log(`🎊 TP2 PRICE REACHED: ${trade.symbol} at ${profitPercent.toFixed(2)}%`) + logger.log(` Activating trailing stop for runner protection`) trade.tp2Hit = true trade.trailingStopActive = true @@ -832,16 +833,16 @@ export class PositionManager { // This prevents ANY duplicate processing before DB update completes trade.closingInProgress = true trade.closeConfirmedAt = Date.now() - console.log(`πŸ”’ Marked ${trade.symbol} as closingInProgress to prevent duplicate external closure processing`) + logger.log(`πŸ”’ Marked ${trade.symbol} as closingInProgress to prevent duplicate external closure processing`) // CRITICAL FIX (Nov 20, 2025): If TP1 already hit, this is RUNNER closure // We should have been monitoring with trailing stop active // Check if we should have had trailing stop protection if (trade.tp1Hit && !trade.tp2Hit) { - console.log(`⚠️ RUNNER CLOSED EXTERNALLY: ${trade.symbol}`) - console.log(` TP1 hit: true, TP2 hit: false`) - console.log(` This runner should have had trailing stop protection!`) - console.log(` Likely cause: Monitoring detected full closure before TP2 price check`) + logger.log(`⚠️ RUNNER CLOSED EXTERNALLY: ${trade.symbol}`) + logger.log(` TP1 hit: true, TP2 hit: false`) + logger.log(` This runner should have had trailing stop protection!`) + logger.log(` Likely cause: Monitoring detected full closure before TP2 price check`) // Check if price reached TP2 - if so, trailing should have been active const reachedTP2 = trade.direction === 'long' @@ -849,10 +850,10 @@ export class PositionManager { : currentPrice <= (trade.tp2Price || 0) if (reachedTP2) { - console.log(` ⚠️ Price reached TP2 ($${trade.tp2Price?.toFixed(4)}) but tp2Hit was false!`) - console.log(` Trailing stop should have been active but wasn't`) + logger.log(` ⚠️ Price reached TP2 ($${trade.tp2Price?.toFixed(4)}) but tp2Hit was false!`) + logger.log(` Trailing stop should have been active but wasn't`) } else { - console.log(` Runner hit SL before reaching TP2 ($${trade.tp2Price?.toFixed(4)})`) + logger.log(` Runner hit SL before reaching TP2 ($${trade.tp2Price?.toFixed(4)})`) } } @@ -871,12 +872,12 @@ export class PositionManager { // DO NOT flag runners after TP1 as phantom! const wasPhantom = !trade.tp1Hit && trade.currentSize > 0 && (trade.currentSize / trade.positionSize) < 0.5 - console.log(`πŸ“Š External closure detected - Position size tracking:`) - console.log(` Original size: $${trade.positionSize.toFixed(2)}`) - console.log(` Tracked current size: $${trade.currentSize.toFixed(2)}`) - console.log(` Using for P&L calc: $${sizeForPnL.toFixed(2)} (full position - exit reason will determine TP1 vs SL)`) + logger.log(`πŸ“Š External closure detected - Position size tracking:`) + logger.log(` Original size: $${trade.positionSize.toFixed(2)}`) + logger.log(` Tracked current size: $${trade.currentSize.toFixed(2)}`) + logger.log(` Using for P&L calc: $${sizeForPnL.toFixed(2)} (full position - exit reason will determine TP1 vs SL)`) if (wasPhantom) { - console.log(` ⚠️ PHANTOM TRADE: Setting P&L to 0 (size mismatch >50%)`) + logger.log(` ⚠️ PHANTOM TRADE: Setting P&L to 0 (size mismatch >50%)`) } // CRITICAL FIX (Nov 26, 2025): Calculate P&L from actual entry/exit prices @@ -898,13 +899,13 @@ export class PositionManager { // sizeForPnL is already in USD from above calculation totalRealizedPnL = (sizeForPnL * runnerProfitPercent) / 100 - console.log(` πŸ’° P&L calculation:`) - console.log(` Entry: $${trade.entryPrice.toFixed(4)} β†’ Exit: $${currentPrice.toFixed(4)}`) - console.log(` Profit %: ${runnerProfitPercent.toFixed(3)}%`) - console.log(` Position size: $${sizeForPnL.toFixed(2)}`) - console.log(` Realized P&L: $${totalRealizedPnL.toFixed(2)}`) + logger.log(` πŸ’° P&L calculation:`) + logger.log(` Entry: $${trade.entryPrice.toFixed(4)} β†’ Exit: $${currentPrice.toFixed(4)}`) + logger.log(` Profit %: ${runnerProfitPercent.toFixed(3)}%`) + logger.log(` Position size: $${sizeForPnL.toFixed(2)}`) + logger.log(` Realized P&L: $${totalRealizedPnL.toFixed(2)}`) } else { - console.log(` Phantom trade P&L: $0.00`) + logger.log(` Phantom trade P&L: $0.00`) } // Determine exit reason from P&L percentage and trade state @@ -914,8 +915,8 @@ export class PositionManager { // CRITICAL (Nov 20, 2025): Check if trailing stop was active // If so, this is a trailing stop exit, not regular SL if (trade.tp2Hit && trade.trailingStopActive) { - console.log(` πŸƒ Runner closed with TRAILING STOP active`) - console.log(` Peak price: $${trade.peakPrice.toFixed(4)}, Current: $${currentPrice.toFixed(4)}`) + logger.log(` πŸƒ Runner closed with TRAILING STOP active`) + logger.log(` Peak price: $${trade.peakPrice.toFixed(4)}, Current: $${currentPrice.toFixed(4)}`) // Check if price dropped from peak (trailing stop hit) const isPullback = trade.direction === 'long' @@ -924,11 +925,11 @@ export class PositionManager { if (isPullback) { exitReason = 'TRAILING_SL' // Distinguish from regular SL (Nov 24, 2025) - console.log(` βœ… Confirmed: Trailing stop hit (pulled back from peak)`) + logger.log(` βœ… Confirmed: Trailing stop hit (pulled back from peak)`) } else { // Very close to peak - might be emergency close or manual exitReason = 'TP2' // Give credit for reaching runner profit target - console.log(` βœ… Closed near peak - counting as TP2`) + logger.log(` βœ… Closed near peak - counting as TP2`) } } else if (runnerProfitPercent > 0.3) { // Positive profit - was a TP order @@ -953,24 +954,24 @@ export class PositionManager { // VERIFICATION: Check if already removed (would indicate duplicate processing attempt) if (!this.activeTrades.has(tradeId)) { - console.log(`⚠️ DUPLICATE PROCESSING PREVENTED: Trade ${tradeId} already removed from monitoring`) - console.log(` This is the bug fix working - without it, we'd update DB again with compounded P&L`) + logger.log(`⚠️ DUPLICATE PROCESSING PREVENTED: Trade ${tradeId} already removed from monitoring`) + logger.log(` This is the bug fix working - without it, we'd update DB again with compounded P&L`) return // Already processed, don't update DB again } this.activeTrades.delete(tradeId) - console.log(`πŸ—‘οΈ Removed trade ${tradeId} from monitoring (BEFORE DB update to prevent duplicates)`) - console.log(` Active trades remaining: ${this.activeTrades.size}`) + logger.log(`πŸ—‘οΈ Removed trade ${tradeId} from monitoring (BEFORE DB update to prevent duplicates)`) + logger.log(` Active trades remaining: ${this.activeTrades.size}`) // CRITICAL: Cancel all remaining orders for this position (ghost order cleanup) // When position closes externally (on-chain SL/TP), TP/SL orders may remain active // These ghost orders can trigger unintended positions if price moves to those levels - console.log(`πŸ—‘οΈ Cancelling remaining orders for ${trade.symbol}...`) + logger.log(`πŸ—‘οΈ Cancelling remaining orders for ${trade.symbol}...`) try { const { cancelAllOrders } = await import('../drift/orders') const cancelResult = await cancelAllOrders(trade.symbol) if (cancelResult.success) { - console.log(`βœ… Cancelled ${cancelResult.cancelledCount || 0} ghost orders`) + logger.log(`βœ… Cancelled ${cancelResult.cancelledCount || 0} ghost orders`) } else { console.error(`⚠️ Failed to cancel orders: ${cancelResult.error}`) } @@ -994,14 +995,14 @@ export class PositionManager { maxFavorablePrice: trade.maxFavorablePrice, maxAdversePrice: trade.maxAdversePrice, }) - console.log(`πŸ’Ύ External closure recorded: ${exitReason} at $${currentPrice} | P&L: $${totalRealizedPnL.toFixed(2)}`) + logger.log(`πŸ’Ύ External closure recorded: ${exitReason} at $${currentPrice} | P&L: $${totalRealizedPnL.toFixed(2)}`) // CRITICAL FIX (Dec 3, 2025): Check revenge eligibility for external closures // Bug Fix: External closures (on-chain SL orders) weren't checking if quality 85+ for revenge // Solution: After DB save, check if this was a quality 85+ SL stop-out and record for revenge const qualityScore = trade.signalQualityScore || 0 if (exitReason === 'SL' && qualityScore >= 85) { - console.log(`πŸ” Quality ${qualityScore} SL stop-out (external) - checking revenge eligibility...`) + logger.log(`πŸ” Quality ${qualityScore} SL stop-out (external) - checking revenge eligibility...`) try { const { getStopHuntTracker } = await import('./stop-hunt-tracker') const stopHuntTracker = getStopHuntTracker() @@ -1016,7 +1017,7 @@ export class PositionManager { originalATR: trade.atrAtEntry || 0, stopLossAmount: Math.abs(totalRealizedPnL) }) - console.log(`🎯 Stop hunt recorded (external closure) - revenge window active for 4 hours`) + logger.log(`🎯 Stop hunt recorded (external closure) - revenge window active for 4 hours`) } catch (revengeError) { console.error('⚠️ Failed to record stop hunt for revenge:', revengeError) // Don't fail external closure if revenge recording fails @@ -1041,13 +1042,13 @@ export class PositionManager { // Position exists but size mismatch (partial close by TP1?) if (positionSizeUSD < trade.currentSize * 0.95) { // 5% tolerance - console.log(`⚠️ Position size mismatch: expected $${trade.currentSize.toFixed(2)}, got $${positionSizeUSD.toFixed(2)}`) + logger.log(`⚠️ Position size mismatch: expected $${trade.currentSize.toFixed(2)}, got $${positionSizeUSD.toFixed(2)}`) // CRITICAL: Check if position direction changed (signal flip, not TP1!) const positionDirection = position.side === 'long' ? 'long' : 'short' if (positionDirection !== trade.direction) { - console.log(`πŸ”„ DIRECTION CHANGE DETECTED: ${trade.direction} β†’ ${positionDirection}`) - console.log(` This is a signal flip, not TP1! Closing old position as manual.`) + logger.log(`πŸ”„ DIRECTION CHANGE DETECTED: ${trade.direction} β†’ ${positionDirection}`) + logger.log(` This is a signal flip, not TP1! Closing old position as manual.`) // Calculate actual P&L on full position const profitPercent = this.calculateProfitPercent(trade.entryPrice, currentPrice, trade.direction) @@ -1069,7 +1070,7 @@ export class PositionManager { maxFavorablePrice: trade.maxFavorablePrice, maxAdversePrice: trade.maxAdversePrice, }) - console.log(`πŸ’Ύ Signal flip closure recorded: P&L $${actualPnL.toFixed(2)}`) + logger.log(`πŸ’Ύ Signal flip closure recorded: P&L $${actualPnL.toFixed(2)}`) } catch (dbError) { console.error('❌ Failed to save signal flip closure:', dbError) } @@ -1081,9 +1082,9 @@ export class PositionManager { // CRITICAL: If mismatch is extreme (>50%), this is a phantom trade const sizeRatio = positionSizeUSD / trade.currentSize if (sizeRatio < 0.5) { - console.log(`🚨 EXTREME SIZE MISMATCH (${(sizeRatio * 100).toFixed(1)}%) - Closing phantom trade`) - console.log(` Expected: $${trade.currentSize.toFixed(2)}`) - console.log(` Actual: $${positionSizeUSD.toFixed(2)}`) + logger.log(`🚨 EXTREME SIZE MISMATCH (${(sizeRatio * 100).toFixed(1)}%) - Closing phantom trade`) + logger.log(` Expected: $${trade.currentSize.toFixed(2)}`) + logger.log(` Actual: $${positionSizeUSD.toFixed(2)}`) // Close as phantom trade try { @@ -1102,7 +1103,7 @@ export class PositionManager { maxFavorablePrice: trade.maxFavorablePrice, maxAdversePrice: trade.maxAdversePrice, }) - console.log(`πŸ’Ύ Phantom trade closed`) + logger.log(`πŸ’Ύ Phantom trade closed`) } catch (dbError) { console.error('❌ Failed to close phantom trade:', dbError) } @@ -1119,21 +1120,21 @@ export class PositionManager { const tp1PriceReached = this.shouldTakeProfit1(currentPrice, trade) if (tp1PriceReached) { - console.log(`βœ… TP1 VERIFIED: Size mismatch + price target reached`) - console.log(` Size: $${trade.currentSize.toFixed(2)} β†’ $${positionSizeUSD.toFixed(2)} (${((positionSizeUSD / trade.currentSize) * 100).toFixed(1)}%)`) - console.log(` Price: ${currentPrice.toFixed(4)} crossed TP1 target ${trade.tp1Price.toFixed(4)}`) + logger.log(`βœ… TP1 VERIFIED: Size mismatch + price target reached`) + logger.log(` Size: $${trade.currentSize.toFixed(2)} β†’ $${positionSizeUSD.toFixed(2)} (${((positionSizeUSD / trade.currentSize) * 100).toFixed(1)}%)`) + logger.log(` Price: ${currentPrice.toFixed(4)} crossed TP1 target ${trade.tp1Price.toFixed(4)}`) // Update current size to match reality (already in USD) trade.currentSize = positionSizeUSD trade.tp1Hit = true await this.saveTradeState(trade) - console.log(`πŸŽ‰ TP1 HIT: ${trade.symbol} via on-chain order (detected by size reduction)`) + logger.log(`πŸŽ‰ TP1 HIT: ${trade.symbol} via on-chain order (detected by size reduction)`) } else { - console.log(`⚠️ Size reduced but TP1 price NOT reached yet - NOT triggering TP1 logic`) - console.log(` Current: ${currentPrice.toFixed(4)}, TP1 target: ${trade.tp1Price.toFixed(4)} (${trade.direction === 'long' ? 'need higher' : 'need lower'})`) - console.log(` Size: $${trade.currentSize.toFixed(2)} β†’ $${positionSizeUSD.toFixed(2)} (${((positionSizeUSD / trade.currentSize) * 100).toFixed(1)}%)`) - console.log(` Likely: Partial fill, slippage, or external action`) + logger.log(`⚠️ Size reduced but TP1 price NOT reached yet - NOT triggering TP1 logic`) + logger.log(` Current: ${currentPrice.toFixed(4)}, TP1 target: ${trade.tp1Price.toFixed(4)} (${trade.direction === 'long' ? 'need higher' : 'need lower'})`) + logger.log(` Size: $${trade.currentSize.toFixed(2)} β†’ $${positionSizeUSD.toFixed(2)} (${((positionSizeUSD / trade.currentSize) * 100).toFixed(1)}%)`) + logger.log(` Likely: Partial fill, slippage, or external action`) // Update tracked size but DON'T trigger TP1 logic trade.currentSize = positionSizeUSD @@ -1204,8 +1205,8 @@ export class PositionManager { // Position missing on Drift but we're still tracking it = ghost if (!position || Math.abs(position.size) < 0.01) { - console.log(`πŸ”΄ GHOST DETECTED in monitoring loop: ${trade.symbol}`) - console.log(` Position Manager thinks it's open, but Drift shows closed`) + logger.log(`πŸ”΄ GHOST DETECTED in monitoring loop: ${trade.symbol}`) + logger.log(` Position Manager thinks it's open, but Drift shows closed`) await this.handleExternalClosure(trade, 'Ghost detected during monitoring') return // Exit monitoring for this position } @@ -1217,7 +1218,7 @@ export class PositionManager { // Log status every 10 checks (~20 seconds) if (trade.priceCheckCount % 10 === 0) { - console.log( + logger.log( `πŸ“Š ${trade.symbol} | ` + `Price: ${currentPrice.toFixed(4)} | ` + `P&L: ${profitPercent.toFixed(2)}% | ` + @@ -1232,14 +1233,14 @@ export class PositionManager { // 1. Emergency stop (-2%) if (this.shouldEmergencyStop(currentPrice, trade)) { - console.log(`🚨 EMERGENCY STOP: ${trade.symbol}`) + logger.log(`🚨 EMERGENCY STOP: ${trade.symbol}`) await this.executeExit(trade, 100, 'emergency', currentPrice) return } // 2. Stop loss (BEFORE TP1) if (!trade.tp1Hit && this.shouldStopLoss(currentPrice, trade)) { - console.log(`πŸ”΄ STOP LOSS: ${trade.symbol} at ${profitPercent.toFixed(2)}%`) + logger.log(`πŸ”΄ STOP LOSS: ${trade.symbol} at ${profitPercent.toFixed(2)}%`) await this.executeExit(trade, 100, 'SL', currentPrice) return } @@ -1247,14 +1248,14 @@ export class PositionManager { // 2b. CRITICAL: Runner stop loss (AFTER TP1, BEFORE TP2) // This protects the runner position after TP1 closes main position if (trade.tp1Hit && !trade.tp2Hit && this.shouldStopLoss(currentPrice, trade)) { - console.log(`πŸ”΄ RUNNER STOP LOSS: ${trade.symbol} at ${profitPercent.toFixed(2)}% (profit lock triggered)`) + logger.log(`πŸ”΄ RUNNER STOP LOSS: ${trade.symbol} at ${profitPercent.toFixed(2)}% (profit lock triggered)`) await this.executeExit(trade, 100, 'SL', currentPrice) return } // 3. Take profit 1 (closes configured %) if (!trade.tp1Hit && this.shouldTakeProfit1(currentPrice, trade)) { - console.log(`πŸŽ‰ TP1 HIT: ${trade.symbol} at ${profitPercent.toFixed(2)}%`) + logger.log(`πŸŽ‰ TP1 HIT: ${trade.symbol} at ${profitPercent.toFixed(2)}%`) // CRITICAL: Set flag BEFORE async executeExit to prevent race condition // Multiple monitoring loops can trigger TP1 simultaneously if we wait until after @@ -1284,7 +1285,7 @@ export class PositionManager { trade.stopLossPrice = newStopLossPrice trade.slMovedToBreakeven = true - console.log(`πŸ”’ ADX-based runner SL: ${adx.toFixed(1)} β†’ ${runnerSlPercent}% (${this.config.takeProfit1SizePercent}% closed, ${100 - this.config.takeProfit1SizePercent}% remaining): ${newStopLossPrice.toFixed(4)}`) + logger.log(`πŸ”’ ADX-based runner SL: ${adx.toFixed(1)} β†’ ${runnerSlPercent}% (${this.config.takeProfit1SizePercent}% closed, ${100 - this.config.takeProfit1SizePercent}% remaining): ${newStopLossPrice.toFixed(4)}`) // CRITICAL: Cancel old on-chain SL orders and place new ones at updated price // BUT: Only if this is the ONLY active trade on this symbol @@ -1295,20 +1296,20 @@ export class PositionManager { ) if (otherTradesOnSymbol.length > 0) { - console.log(`⚠️ Multiple trades on ${trade.symbol} detected (${otherTradesOnSymbol.length + 1} total)`) - console.log(`⚠️ Skipping order cancellation to avoid wiping other positions' orders`) - console.log(`⚠️ Relying on Position Manager software monitoring for remaining ${100 - this.config.takeProfit1SizePercent}%`) + logger.log(`⚠️ Multiple trades on ${trade.symbol} detected (${otherTradesOnSymbol.length + 1} total)`) + logger.log(`⚠️ Skipping order cancellation to avoid wiping other positions' orders`) + logger.log(`⚠️ Relying on Position Manager software monitoring for remaining ${100 - this.config.takeProfit1SizePercent}%`) } else { - console.log('πŸ—‘οΈ Cancelling old stop loss orders...') + logger.log('πŸ—‘οΈ Cancelling old stop loss orders...') const { cancelAllOrders, placeExitOrders } = await import('../drift/orders') const cancelResult = await cancelAllOrders(trade.symbol) if (cancelResult.success) { - console.log(`βœ… Cancelled ${cancelResult.cancelledCount || 0} old orders`) + logger.log(`βœ… Cancelled ${cancelResult.cancelledCount || 0} old orders`) // Place ONLY new SL orders at breakeven/profit level for remaining position // DO NOT place TP2 order - trailing stop is software-only (Position Manager monitors) - console.log(`πŸ›‘οΈ Placing only SL orders at $${newStopLossPrice.toFixed(4)} for remaining position...`) - console.log(` TP2 at $${trade.tp2Price.toFixed(4)} is software-monitored only (activates trailing stop)`) + logger.log(`πŸ›‘οΈ Placing only SL orders at $${newStopLossPrice.toFixed(4)} for remaining position...`) + logger.log(` TP2 at $${trade.tp2Price.toFixed(4)} is software-monitored only (activates trailing stop)`) const exitOrdersResult = await placeExitOrders({ symbol: trade.symbol, positionSizeUSD: trade.currentSize, @@ -1327,7 +1328,7 @@ export class PositionManager { }) if (exitOrdersResult.success) { - console.log('βœ… New SL orders placed on-chain at updated price') + logger.log('βœ… New SL orders placed on-chain at updated price') } else { console.error('❌ Failed to place new SL orders:', exitOrdersResult.error) } @@ -1349,7 +1350,7 @@ export class PositionManager { !trade.slMovedToProfit && profitPercent >= this.config.profitLockTriggerPercent ) { - console.log(`πŸ” Profit lock trigger: ${trade.symbol}`) + logger.log(`πŸ” Profit lock trigger: ${trade.symbol}`) trade.stopLossPrice = this.calculatePrice( trade.entryPrice, @@ -1358,7 +1359,7 @@ export class PositionManager { ) trade.slMovedToProfit = true - console.log(`🎯 SL moved to +${this.config.profitLockPercent}%: ${trade.stopLossPrice.toFixed(4)}`) + logger.log(`🎯 SL moved to +${this.config.profitLockPercent}%: ${trade.stopLossPrice.toFixed(4)}`) // Save state after profit lock await this.saveTradeState(trade) @@ -1366,14 +1367,14 @@ export class PositionManager { // CRITICAL: Check stop loss for runner (after TP1, before TP2) if (trade.tp1Hit && !trade.tp2Hit && this.shouldStopLoss(currentPrice, trade)) { - console.log(`πŸ”΄ RUNNER STOP LOSS: ${trade.symbol} at ${profitPercent.toFixed(2)}% (profit lock triggered)`) + logger.log(`πŸ”΄ RUNNER STOP LOSS: ${trade.symbol} at ${profitPercent.toFixed(2)}% (profit lock triggered)`) await this.executeExit(trade, 100, 'SL', currentPrice) return } // 5. Take profit 2 (remaining position) if (trade.tp1Hit && !trade.tp2Hit && this.shouldTakeProfit2(currentPrice, trade)) { - console.log(`🎊 TP2 HIT: ${trade.symbol} at ${profitPercent.toFixed(2)}%`) + logger.log(`🎊 TP2 HIT: ${trade.symbol} at ${profitPercent.toFixed(2)}%`) // CRITICAL: Set flag BEFORE any async operations to prevent race condition trade.tp2Hit = true @@ -1386,8 +1387,8 @@ export class PositionManager { if (percentToClose === 0) { trade.trailingStopActive = true // Activate trailing stop immediately - console.log(`πŸƒ TP2-as-Runner activated: ${((trade.currentSize / trade.positionSize) * 100).toFixed(1)}% remaining with trailing stop`) - console.log(`πŸ“Š No position closed at TP2 - full ${trade.currentSize.toFixed(2)} USD remains as runner`) + logger.log(`πŸƒ TP2-as-Runner activated: ${((trade.currentSize / trade.positionSize) * 100).toFixed(1)}% remaining with trailing stop`) + logger.log(`πŸ“Š No position closed at TP2 - full ${trade.currentSize.toFixed(2)} USD remains as runner`) // Save state after TP2 await this.saveTradeState(trade) @@ -1402,7 +1403,7 @@ export class PositionManager { if (percentToClose < 100) { trade.currentSize = trade.currentSize * ((100 - percentToClose) / 100) - console.log(`πŸƒ Runner activated: ${((trade.currentSize / trade.positionSize) * 100).toFixed(1)}% remaining with trailing stop`) + logger.log(`πŸƒ Runner activated: ${((trade.currentSize / trade.positionSize) * 100).toFixed(1)}% remaining with trailing stop`) // Save state after TP2 await this.saveTradeState(trade) @@ -1416,7 +1417,7 @@ export class PositionManager { // Check if trailing stop should be activated if (!trade.trailingStopActive && profitPercent >= this.config.trailingStopActivation) { trade.trailingStopActive = true - console.log(`🎯 Trailing stop activated at +${profitPercent.toFixed(2)}%`) + logger.log(`🎯 Trailing stop activated at +${profitPercent.toFixed(2)}%`) } // If trailing stop is active, adjust SL dynamically @@ -1436,10 +1437,10 @@ export class PositionManager { adxChange = currentADX - (trade.adxAtEntry || 0) usingFreshData = true - console.log(`πŸ“Š 1-min ADX update: Entry ${(trade.adxAtEntry || 0).toFixed(1)} β†’ Current ${currentADX.toFixed(1)} (${adxChange >= 0 ? '+' : ''}${adxChange.toFixed(1)} change)`) + logger.log(`πŸ“Š 1-min ADX update: Entry ${(trade.adxAtEntry || 0).toFixed(1)} β†’ Current ${currentADX.toFixed(1)} (${adxChange >= 0 ? '+' : ''}${adxChange.toFixed(1)} change)`) } } catch (error) { - console.log(`⚠️ Could not fetch fresh ADX data, using entry ADX: ${error}`) + logger.log(`⚠️ Could not fetch fresh ADX data, using entry ADX: ${error}`) } // Calculate ATR-based trailing distance with ADAPTIVE ADX multiplier @@ -1455,11 +1456,11 @@ export class PositionManager { if (currentADX > 30) { // Very strong trend (ADX > 30): 50% wider trail trailMultiplier *= 1.5 - console.log(`πŸ“ˆ ${usingFreshData ? '1-min' : 'Entry'} ADX very strong (${currentADX.toFixed(1)}): Trail multiplier ${this.config.trailingStopAtrMultiplier}x β†’ ${trailMultiplier.toFixed(2)}x`) + logger.log(`πŸ“ˆ ${usingFreshData ? '1-min' : 'Entry'} ADX very strong (${currentADX.toFixed(1)}): Trail multiplier ${this.config.trailingStopAtrMultiplier}x β†’ ${trailMultiplier.toFixed(2)}x`) } else if (currentADX > 25) { // Strong trend (ADX 25-30): 25% wider trail trailMultiplier *= 1.25 - console.log(`πŸ“ˆ ${usingFreshData ? '1-min' : 'Entry'} ADX strong (${currentADX.toFixed(1)}): Trail multiplier ${this.config.trailingStopAtrMultiplier}x β†’ ${trailMultiplier.toFixed(2)}x`) + logger.log(`πŸ“ˆ ${usingFreshData ? '1-min' : 'Entry'} ADX strong (${currentADX.toFixed(1)}): Trail multiplier ${this.config.trailingStopAtrMultiplier}x β†’ ${trailMultiplier.toFixed(2)}x`) } // Else: weak/moderate trend, use base multiplier @@ -1467,14 +1468,14 @@ export class PositionManager { if (usingFreshData && adxChange > 5) { const oldMultiplier = trailMultiplier trailMultiplier *= 1.3 - console.log(`πŸš€ ADX acceleration (+${adxChange.toFixed(1)} points): Trail multiplier ${oldMultiplier.toFixed(2)}x β†’ ${trailMultiplier.toFixed(2)}x`) + logger.log(`πŸš€ ADX acceleration (+${adxChange.toFixed(1)} points): Trail multiplier ${oldMultiplier.toFixed(2)}x β†’ ${trailMultiplier.toFixed(2)}x`) } // DECELERATION PENALTY: If ADX decreased significantly, tighten trail if (usingFreshData && adxChange < -3) { const oldMultiplier = trailMultiplier trailMultiplier *= 0.7 - console.log(`⚠️ ADX deceleration (${adxChange.toFixed(1)} points): Trail multiplier ${oldMultiplier.toFixed(2)}x β†’ ${trailMultiplier.toFixed(2)}x (tighter to protect)`) + logger.log(`⚠️ ADX deceleration (${adxChange.toFixed(1)} points): Trail multiplier ${oldMultiplier.toFixed(2)}x β†’ ${trailMultiplier.toFixed(2)}x (tighter to protect)`) } } @@ -1482,7 +1483,7 @@ export class PositionManager { if (profitPercent > 2.0) { const oldMultiplier = trailMultiplier trailMultiplier *= 1.3 - console.log(`πŸ’° Large profit (${profitPercent.toFixed(2)}%): Trail multiplier ${oldMultiplier.toFixed(2)}x β†’ ${trailMultiplier.toFixed(2)}x`) + logger.log(`πŸ’° Large profit (${profitPercent.toFixed(2)}%): Trail multiplier ${oldMultiplier.toFixed(2)}x β†’ ${trailMultiplier.toFixed(2)}x`) } // ATR-based: Use ATR% * adjusted multiplier @@ -1495,7 +1496,7 @@ export class PositionManager { Math.min(this.config.trailingStopMaxPercent, rawDistance) ) - console.log(`πŸ“Š Adaptive trailing: ATR ${trade.atrAtEntry.toFixed(4)} (${atrPercent.toFixed(2)}%) Γ— ${trailMultiplier.toFixed(2)}x = ${trailingDistancePercent.toFixed(2)}%`) + logger.log(`πŸ“Š Adaptive trailing: ATR ${trade.atrAtEntry.toFixed(4)} (${atrPercent.toFixed(2)}%) Γ— ${trailMultiplier.toFixed(2)}x = ${trailingDistancePercent.toFixed(2)}%`) } else { // Fallback to configured legacy percent with min/max clamping trailingDistancePercent = Math.max( @@ -1503,7 +1504,7 @@ export class PositionManager { Math.min(this.config.trailingStopMaxPercent, this.config.trailingStopPercent) ) - console.log(`⚠️ No ATR data, using fallback: ${trailingDistancePercent.toFixed(2)}%`) + logger.log(`⚠️ No ATR data, using fallback: ${trailingDistancePercent.toFixed(2)}%`) } const trailingStopPrice = this.calculatePrice( @@ -1521,7 +1522,7 @@ export class PositionManager { const oldSL = trade.stopLossPrice trade.stopLossPrice = trailingStopPrice - console.log(`πŸ“ˆ Trailing SL updated: ${oldSL.toFixed(4)} β†’ ${trailingStopPrice.toFixed(4)} (${trailingDistancePercent.toFixed(2)}% below peak $${trade.peakPrice.toFixed(4)})`) + logger.log(`πŸ“ˆ Trailing SL updated: ${oldSL.toFixed(4)} β†’ ${trailingStopPrice.toFixed(4)} (${trailingDistancePercent.toFixed(2)}% below peak $${trade.peakPrice.toFixed(4)})`) // Save state after trailing SL update (every 10 updates to avoid spam) if (trade.priceCheckCount % 10 === 0) { @@ -1531,7 +1532,7 @@ export class PositionManager { // Check if trailing stop hit if (this.shouldStopLoss(currentPrice, trade)) { - console.log(`πŸ”΄ TRAILING STOP HIT: ${trade.symbol} at ${profitPercent.toFixed(2)}%`) + logger.log(`πŸ”΄ TRAILING STOP HIT: ${trade.symbol} at ${profitPercent.toFixed(2)}%`) await this.executeExit(trade, 100, 'TRAILING_SL', currentPrice) return } @@ -1570,16 +1571,16 @@ export class PositionManager { const wasInMap = this.activeTrades.delete(tradeId) if (!wasInMap) { - console.log(`⚠️ DUPLICATE EXIT PREVENTED: ${tradeId} already processing ${reason}`) - console.log(` This prevents duplicate Telegram notifications with compounding P&L`) + logger.log(`⚠️ DUPLICATE EXIT PREVENTED: ${tradeId} already processing ${reason}`) + logger.log(` This prevents duplicate Telegram notifications with compounding P&L`) return } - console.log(`πŸ—‘οΈ Removed ${trade.symbol} from monitoring (${reason}) - atomic deduplication applied`) + logger.log(`πŸ—‘οΈ Removed ${trade.symbol} from monitoring (${reason}) - atomic deduplication applied`) } try { - console.log(`πŸ”΄ Executing ${reason} for ${trade.symbol} (${percentToClose}%)`) + logger.log(`πŸ”΄ Executing ${reason} for ${trade.symbol} (${percentToClose}%)`) const result = await closePosition({ symbol: trade.symbol, @@ -1604,8 +1605,8 @@ export class PositionManager { // If position doesn't exist on Drift, it's a ghost - remove immediately if (!position || Math.abs(position.size) < 0.01) { - console.log(`πŸ”΄ LAYER 2: Ghost detected after ${trade.priceCheckCount} failures`) - console.log(` Drift shows position closed/missing - removing from monitoring`) + logger.log(`πŸ”΄ LAYER 2: Ghost detected after ${trade.priceCheckCount} failures`) + logger.log(` Drift shows position closed/missing - removing from monitoring`) // CRITICAL: Mark as closing to prevent duplicate processing trade.closingInProgress = true @@ -1614,7 +1615,7 @@ export class PositionManager { await this.handleExternalClosure(trade, 'Layer 2: Ghost detected via Drift API') return } else { - console.log(` Position verified on Drift (size: ${position.size}) - will keep retrying`) + logger.log(` Position verified on Drift (size: ${position.size}) - will keep retrying`) } } catch (checkError) { console.error(` Could not verify position on Drift:`, checkError) @@ -1635,16 +1636,16 @@ export class PositionManager { // If close transaction confirmed but Drift still shows position open, // DON'T mark as closed yet - keep monitoring until Drift confirms if ((result as any).needsVerification) { - console.log(`⚠️ Close transaction confirmed but position still exists on Drift`) - console.log(` Keeping ${trade.symbol} in monitoring until Drift confirms closure`) - console.log(` Ghost detection will handle final cleanup once Drift updates`) + logger.log(`⚠️ Close transaction confirmed but position still exists on Drift`) + logger.log(` Keeping ${trade.symbol} in monitoring until Drift confirms closure`) + logger.log(` Ghost detection will handle final cleanup once Drift updates`) // CRITICAL: Mark as "closing in progress" to prevent duplicate external closure detection // Without this flag, the monitoring loop detects position as "externally closed" // every 2 seconds and adds P&L repeatedly, causing 20x compounding bug trade.closingInProgress = true trade.closeConfirmedAt = Date.now() - console.log(`πŸ”’ Marked as closing in progress - external closure detection disabled`) + logger.log(`πŸ”’ Marked as closing in progress - external closure detection disabled`) // Keep monitoring - ghost detection will eventually see it's closed return @@ -1673,7 +1674,7 @@ export class PositionManager { maxFavorablePrice: trade.maxFavorablePrice, maxAdversePrice: trade.maxAdversePrice, }) - console.log('πŸ’Ύ Trade saved to database') + logger.log('πŸ’Ύ Trade saved to database') // πŸ”₯ REVENGE OUTCOME TRACKING (Enhancement #4 - Nov 27, 2025) // If this was a revenge trade, record the outcome in StopHunt table @@ -1688,7 +1689,7 @@ export class PositionManager { pnl: trade.realizedPnL, failedReason: reason === 'SL' ? 'stopped_again' : undefined }) - console.log(`πŸ”₯ Revenge outcome recorded: ${reason} (P&L: $${trade.realizedPnL.toFixed(2)})`) + logger.log(`πŸ”₯ Revenge outcome recorded: ${reason} (P&L: $${trade.realizedPnL.toFixed(2)})`) } catch (revengeError) { console.error('❌ Failed to record revenge outcome:', revengeError) // Don't fail trade closure if revenge tracking fails @@ -1706,7 +1707,7 @@ export class PositionManager { this.stopMonitoring() } - console.log(`βœ… Position closed | P&L: $${trade.realizedPnL.toFixed(2)} | Reason: ${reason}`) + logger.log(`βœ… Position closed | P&L: $${trade.realizedPnL.toFixed(2)} | Reason: ${reason}`) // Send Telegram notification await sendPositionClosedNotification({ @@ -1738,7 +1739,7 @@ export class PositionManager { originalATR: trade.atrAtEntry, stopLossAmount: Math.abs(trade.realizedPnL), // Loss amount (positive) }) - console.log(`🎯 Stop hunt recorded - revenge window activated`) + logger.log(`🎯 Stop hunt recorded - revenge window activated`) } catch (stopHuntError) { console.error('❌ Failed to record stop hunt:', stopHuntError) } @@ -1752,7 +1753,7 @@ export class PositionManager { const closedUSD = closedSizeBase * closePriceForCalc trade.currentSize = Math.max(0, trade.currentSize - closedUSD) - console.log(`βœ… Partial close executed | Realized: $${(result.realizedPnL || 0).toFixed(2)} | Closed (base): ${closedSizeBase.toFixed(6)} | Closed (USD): $${closedUSD.toFixed(2)} | Remaining USD: $${trade.currentSize.toFixed(2)}`) + logger.log(`βœ… Partial close executed | Realized: $${(result.realizedPnL || 0).toFixed(2)} | Closed (base): ${closedSizeBase.toFixed(6)} | Closed (USD): $${closedUSD.toFixed(2)} | Remaining USD: $${trade.currentSize.toFixed(2)}`) // Persist updated trade state so analytics reflect partial profits immediately await this.saveTradeState(trade) @@ -1856,7 +1857,7 @@ export class PositionManager { * Emergency close all positions */ async closeAll(): Promise { - console.log('🚨 EMERGENCY: Closing all positions') + logger.log('🚨 EMERGENCY: Closing all positions') const trades = Array.from(this.activeTrades.values()) @@ -1864,7 +1865,7 @@ export class PositionManager { await this.executeExit(trade, 100, 'emergency', trade.lastPrice) } - console.log('βœ… All positions closed') + logger.log('βœ… All positions closed') } /** @@ -1895,7 +1896,7 @@ export class PositionManager { */ refreshConfig(partial?: Partial): void { this.config = getMergedConfig(partial) - console.log('πŸ”„ Position Manager config refreshed') + logger.log('πŸ”„ Position Manager config refreshed') } /** diff --git a/lib/trading/signal-quality.ts b/lib/trading/signal-quality.ts index 1733f0d..ad98466 100644 --- a/lib/trading/signal-quality.ts +++ b/lib/trading/signal-quality.ts @@ -6,6 +6,7 @@ */ import { getRecentSignals } from '../database/trades' +import { logger } from '../utils/logger' export interface SignalQualityResult { score: number @@ -235,7 +236,7 @@ export async function scoreSignalQuality(params: { (params.currentPrice - recentSignals.oppositeDirectionPrice) / recentSignals.oppositeDirectionPrice * 100 ) - console.log(`πŸ” Flip-flop price check: $${recentSignals.oppositeDirectionPrice.toFixed(2)} β†’ $${params.currentPrice.toFixed(2)} = ${priceChangePercent.toFixed(2)}%`) + logger.log(`πŸ” Flip-flop price check: $${recentSignals.oppositeDirectionPrice.toFixed(2)} β†’ $${params.currentPrice.toFixed(2)} = ${priceChangePercent.toFixed(2)}%`) if (priceChangePercent < 2.0) { // Small price move = consolidation/chop = BAD diff --git a/lib/trading/smart-entry-timer.ts b/lib/trading/smart-entry-timer.ts index 558f444..e3a641c 100644 --- a/lib/trading/smart-entry-timer.ts +++ b/lib/trading/smart-entry-timer.ts @@ -14,6 +14,7 @@ */ import { getMarketDataCache } from './market-data-cache' +import { logger } from '../utils/logger' import { getPythPriceMonitor } from '../pyth/price-monitor' export interface QueuedSignal { @@ -85,7 +86,7 @@ export class SmartEntryTimer { monitorIntervalMs: 15000 // 15 seconds } - console.log('πŸ’‘ Smart Entry Timer initialized:', { + logger.log('πŸ’‘ Smart Entry Timer initialized:', { enabled: this.config.enabled, maxWait: `${this.config.maxWaitMs / 1000}s`, pullback: `${this.config.pullbackMin}-${this.config.pullbackMax}%`, @@ -137,10 +138,10 @@ export class SmartEntryTimer { this.queuedSignals.set(signal.id, signal) - console.log(`πŸ“₯ Smart Entry: Queued signal ${signal.id}`) - console.log(` ${signal.direction.toUpperCase()} ${signal.symbol} @ $${signal.signalPrice.toFixed(2)}`) - console.log(` Target pullback: ${this.config.pullbackMin}-${this.config.pullbackMax}%`) - console.log(` Max wait: ${this.config.maxWaitMs / 1000}s`) + logger.log(`πŸ“₯ Smart Entry: Queued signal ${signal.id}`) + logger.log(` ${signal.direction.toUpperCase()} ${signal.symbol} @ $${signal.signalPrice.toFixed(2)}`) + logger.log(` Target pullback: ${this.config.pullbackMin}-${this.config.pullbackMax}%`) + logger.log(` Max wait: ${this.config.maxWaitMs / 1000}s`) // Start monitoring if not already running if (!this.monitoringInterval) { @@ -156,7 +157,7 @@ export class SmartEntryTimer { private startMonitoring(): void { if (this.monitoringInterval) return - console.log(`πŸ‘οΈ Smart Entry: Starting monitoring loop (${this.config.monitorIntervalMs / 1000}s interval)`) + logger.log(`πŸ‘οΈ Smart Entry: Starting monitoring loop (${this.config.monitorIntervalMs / 1000}s interval)`) this.monitoringInterval = setInterval(() => { this.checkAllSignals() @@ -170,7 +171,7 @@ export class SmartEntryTimer { if (this.monitoringInterval) { clearInterval(this.monitoringInterval) this.monitoringInterval = null - console.log(`⏸️ Smart Entry: Monitoring stopped (no active signals)`) + logger.log(`⏸️ Smart Entry: Monitoring stopped (no active signals)`) } } @@ -187,7 +188,7 @@ export class SmartEntryTimer { // Check for timeout if (now >= signal.expiresAt) { - console.log(`⏰ Smart Entry: Timeout for ${signal.symbol} (waited ${this.config.maxWaitMs / 1000}s)`) + logger.log(`⏰ Smart Entry: Timeout for ${signal.symbol} (waited ${this.config.maxWaitMs / 1000}s)`) const priceMonitor = getPythPriceMonitor() const latestPrice = priceMonitor.getCachedPrice(signal.symbol) const currentPrice = latestPrice?.price || signal.signalPrice @@ -210,7 +211,7 @@ export class SmartEntryTimer { const latestPrice = priceMonitor.getCachedPrice(signal.symbol) if (!latestPrice || !latestPrice.price) { - console.log(`⚠️ Smart Entry: No price available for ${signal.symbol}, skipping check`) + logger.log(`⚠️ Smart Entry: No price available for ${signal.symbol}, skipping check`) return } @@ -234,18 +235,18 @@ export class SmartEntryTimer { } // Log check - console.log(`πŸ” Smart Entry: Checking ${signal.symbol} (check #${signal.checksPerformed})`) - console.log(` Signal: $${signal.signalPrice.toFixed(2)} β†’ Current: $${currentPrice.toFixed(2)}`) - console.log(` Pullback: ${pullbackMagnitude.toFixed(2)}% (target ${signal.targetPullbackMin}-${signal.targetPullbackMax}%)`) + logger.log(`πŸ” Smart Entry: Checking ${signal.symbol} (check #${signal.checksPerformed})`) + logger.log(` Signal: $${signal.signalPrice.toFixed(2)} β†’ Current: $${currentPrice.toFixed(2)}`) + logger.log(` Pullback: ${pullbackMagnitude.toFixed(2)}% (target ${signal.targetPullbackMin}-${signal.targetPullbackMax}%)`) // Check if pullback is in target range if (pullbackMagnitude < signal.targetPullbackMin) { - console.log(` ⏳ Waiting for pullback (${pullbackMagnitude.toFixed(2)}% < ${signal.targetPullbackMin}%)`) + logger.log(` ⏳ Waiting for pullback (${pullbackMagnitude.toFixed(2)}% < ${signal.targetPullbackMin}%)`) return } if (pullbackMagnitude > signal.targetPullbackMax) { - console.log(` ⚠️ Pullback too large (${pullbackMagnitude.toFixed(2)}% > ${signal.targetPullbackMax}%), might be reversal - waiting`) + logger.log(` ⚠️ Pullback too large (${pullbackMagnitude.toFixed(2)}% > ${signal.targetPullbackMax}%), might be reversal - waiting`) return } @@ -262,22 +263,22 @@ export class SmartEntryTimer { const now = Date.now() const dataAge = (now - latestMetrics.timestamp) / 1000 - console.log(` πŸ“Š Real-time validation (data age: ${dataAge.toFixed(0)}s):`) + logger.log(` πŸ“Š Real-time validation (data age: ${dataAge.toFixed(0)}s):`) // 1. ADX degradation check (original logic) if (latestMetrics.adx) { const adxDrop = signal.signalADX - latestMetrics.adx if (adxDrop > signal.adxTolerance) { - console.log(` ❌ ADX degraded: ${signal.signalADX.toFixed(1)} β†’ ${latestMetrics.adx.toFixed(1)} (dropped ${adxDrop.toFixed(1)} points, max ${signal.adxTolerance})`) + logger.log(` ❌ ADX degraded: ${signal.signalADX.toFixed(1)} β†’ ${latestMetrics.adx.toFixed(1)} (dropped ${adxDrop.toFixed(1)} points, max ${signal.adxTolerance})`) signal.status = 'cancelled' signal.executionReason = 'manual_override' this.queuedSignals.delete(signal.id) - console.log(` 🚫 Signal cancelled: ADX degradation exceeded tolerance`) + logger.log(` 🚫 Signal cancelled: ADX degradation exceeded tolerance`) return } - console.log(` βœ… ADX: ${signal.signalADX.toFixed(1)} β†’ ${latestMetrics.adx.toFixed(1)} (within tolerance)`) + logger.log(` βœ… ADX: ${signal.signalADX.toFixed(1)} β†’ ${latestMetrics.adx.toFixed(1)} (within tolerance)`) } // 2. Volume degradation check (NEW) @@ -289,15 +290,15 @@ export class SmartEntryTimer { // Cancel if volume dropped >40% if (volumeDrop > 40) { - console.log(` ❌ Volume collapsed: ${originalVolume.toFixed(2)}x β†’ ${currentVolume.toFixed(2)}x (${volumeDrop.toFixed(0)}% drop)`) + logger.log(` ❌ Volume collapsed: ${originalVolume.toFixed(2)}x β†’ ${currentVolume.toFixed(2)}x (${volumeDrop.toFixed(0)}% drop)`) signal.status = 'cancelled' signal.executionReason = 'manual_override' this.queuedSignals.delete(signal.id) - console.log(` 🚫 Signal cancelled: Volume degradation - momentum fading`) + logger.log(` 🚫 Signal cancelled: Volume degradation - momentum fading`) return } - console.log(` βœ… Volume: ${originalVolume.toFixed(2)}x β†’ ${currentVolume.toFixed(2)}x`) + logger.log(` βœ… Volume: ${originalVolume.toFixed(2)}x β†’ ${currentVolume.toFixed(2)}x`) } // 3. RSI reversal check (NEW) @@ -309,26 +310,26 @@ export class SmartEntryTimer { if (signal.direction === 'long') { // LONG: Cancel if RSI dropped into oversold (<30) if (originalRSI >= 40 && currentRSI < 30) { - console.log(` ❌ RSI collapsed: ${originalRSI.toFixed(1)} β†’ ${currentRSI.toFixed(1)} (now oversold)`) + logger.log(` ❌ RSI collapsed: ${originalRSI.toFixed(1)} β†’ ${currentRSI.toFixed(1)} (now oversold)`) signal.status = 'cancelled' signal.executionReason = 'manual_override' this.queuedSignals.delete(signal.id) - console.log(` 🚫 Signal cancelled: RSI reversal - trend weakening`) + logger.log(` 🚫 Signal cancelled: RSI reversal - trend weakening`) return } } else { // SHORT: Cancel if RSI rose into overbought (>70) if (originalRSI <= 60 && currentRSI > 70) { - console.log(` ❌ RSI spiked: ${originalRSI.toFixed(1)} β†’ ${currentRSI.toFixed(1)} (now overbought)`) + logger.log(` ❌ RSI spiked: ${originalRSI.toFixed(1)} β†’ ${currentRSI.toFixed(1)} (now overbought)`) signal.status = 'cancelled' signal.executionReason = 'manual_override' this.queuedSignals.delete(signal.id) - console.log(` 🚫 Signal cancelled: RSI reversal - trend weakening`) + logger.log(` 🚫 Signal cancelled: RSI reversal - trend weakening`) return } } - console.log(` βœ… RSI: ${originalRSI.toFixed(1)} β†’ ${currentRSI.toFixed(1)}`) + logger.log(` βœ… RSI: ${originalRSI.toFixed(1)} β†’ ${currentRSI.toFixed(1)}`) } // 4. MAGAP divergence check (NEW) @@ -338,36 +339,36 @@ export class SmartEntryTimer { if (signal.direction === 'long' && currentMAGap < -1.0) { // LONG but MAs now bearish diverging - console.log(` ❌ MA structure bearish: MAGAP ${currentMAGap.toFixed(2)}% (death cross accelerating)`) + logger.log(` ❌ MA structure bearish: MAGAP ${currentMAGap.toFixed(2)}% (death cross accelerating)`) signal.status = 'cancelled' signal.executionReason = 'manual_override' this.queuedSignals.delete(signal.id) - console.log(` 🚫 Signal cancelled: MA structure turned bearish`) + logger.log(` 🚫 Signal cancelled: MA structure turned bearish`) return } if (signal.direction === 'short' && currentMAGap > 1.0) { // SHORT but MAs now bullish diverging - console.log(` ❌ MA structure bullish: MAGAP ${currentMAGap.toFixed(2)}% (golden cross accelerating)`) + logger.log(` ❌ MA structure bullish: MAGAP ${currentMAGap.toFixed(2)}% (golden cross accelerating)`) signal.status = 'cancelled' signal.executionReason = 'manual_override' this.queuedSignals.delete(signal.id) - console.log(` 🚫 Signal cancelled: MA structure turned bullish`) + logger.log(` 🚫 Signal cancelled: MA structure turned bullish`) return } - console.log(` βœ… MAGAP: ${currentMAGap.toFixed(2)}%`) + logger.log(` βœ… MAGAP: ${currentMAGap.toFixed(2)}%`) } - console.log(` βœ… All real-time validations passed - signal quality maintained`) + logger.log(` βœ… All real-time validations passed - signal quality maintained`) } else { - console.log(` ⚠️ No fresh market data available - proceeding with original signal`) + logger.log(` ⚠️ No fresh market data available - proceeding with original signal`) } // All conditions met - execute! - console.log(`βœ… Smart Entry: OPTIMAL ENTRY CONFIRMED`) - console.log(` Pullback: ${pullbackMagnitude.toFixed(2)}% (target ${signal.targetPullbackMin}-${signal.targetPullbackMax}%)`) - console.log(` Price improvement: $${signal.signalPrice.toFixed(2)} β†’ $${currentPrice.toFixed(2)}`) + logger.log(`βœ… Smart Entry: OPTIMAL ENTRY CONFIRMED`) + logger.log(` Pullback: ${pullbackMagnitude.toFixed(2)}% (target ${signal.targetPullbackMin}-${signal.targetPullbackMax}%)`) + logger.log(` Price improvement: $${signal.signalPrice.toFixed(2)} β†’ $${currentPrice.toFixed(2)}`) await this.executeSignal(signal, currentPrice, 'pullback_confirmed') } @@ -388,10 +389,10 @@ export class SmartEntryTimer { const improvement = ((signal.signalPrice - entryPrice) / signal.signalPrice) * 100 const improvementDirection = signal.direction === 'long' ? improvement : -improvement - console.log(`🎯 Smart Entry: EXECUTING ${signal.direction.toUpperCase()} ${signal.symbol}`) - console.log(` Signal Price: $${signal.signalPrice.toFixed(2)}`) - console.log(` Entry Price: $${entryPrice.toFixed(2)}`) - console.log(` Improvement: ${improvementDirection >= 0 ? '+' : ''}${improvementDirection.toFixed(2)}%`) + logger.log(`🎯 Smart Entry: EXECUTING ${signal.direction.toUpperCase()} ${signal.symbol}`) + logger.log(` Signal Price: $${signal.signalPrice.toFixed(2)}`) + logger.log(` Entry Price: $${entryPrice.toFixed(2)}`) + logger.log(` Improvement: ${improvementDirection >= 0 ? '+' : ''}${improvementDirection.toFixed(2)}%`) // Execute the actual trade through Drift try { @@ -423,7 +424,7 @@ export class SmartEntryTimer { signal.qualityScore ) - console.log(` Opening position: $${positionSizeUSD.toFixed(2)} at ${leverage}x leverage`) + logger.log(` Opening position: $${positionSizeUSD.toFixed(2)} at ${leverage}x leverage`) // Open position const openResult = await openPosition({ @@ -439,7 +440,7 @@ export class SmartEntryTimer { } const fillPrice = openResult.fillPrice! - console.log(`βœ… Smart Entry: Position opened at $${fillPrice.toFixed(2)}`) + logger.log(`βœ… Smart Entry: Position opened at $${fillPrice.toFixed(2)}`) // Calculate TP/SL prices let tp1Percent = config.takeProfit1Percent @@ -505,7 +506,7 @@ export class SmartEntryTimer { if (exitRes.success) { exitOrderSignatures = exitRes.signatures || [] - console.log(`βœ… Smart Entry: Exit orders placed - ${exitOrderSignatures.length} orders`) + logger.log(`βœ… Smart Entry: Exit orders placed - ${exitOrderSignatures.length} orders`) } } catch (err) { console.error(`❌ Smart Entry: Error placing exit orders:`, err) @@ -551,7 +552,7 @@ export class SmartEntryTimer { } }) - console.log(`πŸ’Ύ Smart Entry: Trade saved to database`) + logger.log(`πŸ’Ύ Smart Entry: Trade saved to database`) } catch (dbError) { console.error(`❌ Smart Entry: Failed to save trade:`, dbError) const { logCriticalError } = await import('../utils/persistent-logger') @@ -616,14 +617,14 @@ export class SmartEntryTimer { } await positionManager.addTrade(activeTrade) - console.log(`πŸ“Š Smart Entry: Added to Position Manager`) + logger.log(`πŸ“Š Smart Entry: Added to Position Manager`) } catch (pmError) { console.error(`❌ Smart Entry: Failed to add to Position Manager:`, pmError) } - console.log(`βœ… Smart Entry: Execution complete for ${signal.symbol}`) - console.log(` Entry improvement: ${improvementDirection >= 0 ? '+' : ''}${improvementDirection.toFixed(2)}%`) - console.log(` Estimated value: $${(Math.abs(improvementDirection) / 100 * positionSizeUSD).toFixed(2)}`) + logger.log(`βœ… Smart Entry: Execution complete for ${signal.symbol}`) + logger.log(` Entry improvement: ${improvementDirection >= 0 ? '+' : ''}${improvementDirection.toFixed(2)}%`) + logger.log(` Estimated value: $${(Math.abs(improvementDirection) / 100 * positionSizeUSD).toFixed(2)}`) } catch (error) { console.error(`❌ Smart Entry: Execution error:`, error) @@ -632,7 +633,7 @@ export class SmartEntryTimer { // Remove from queue after brief delay (for logging) setTimeout(() => { this.queuedSignals.delete(signal.id) - console.log(`πŸ—‘οΈ Smart Entry: Cleaned up signal ${signal.id}`) + logger.log(`πŸ—‘οΈ Smart Entry: Cleaned up signal ${signal.id}`) if (this.queuedSignals.size === 0) { this.stopMonitoring() @@ -688,7 +689,7 @@ export class SmartEntryTimer { signal.status = 'cancelled' this.queuedSignals.delete(signalId) - console.log(`🚫 Smart Entry: Cancelled signal ${signalId}`) + logger.log(`🚫 Smart Entry: Cancelled signal ${signalId}`) return true } @@ -713,5 +714,5 @@ export function getSmartEntryTimer(): SmartEntryTimer { export function startSmartEntryTracking(): void { getSmartEntryTimer() - console.log('βœ… Smart Entry Timer service initialized') + logger.log('βœ… Smart Entry Timer service initialized') } diff --git a/lib/trading/smart-validation-queue.ts b/lib/trading/smart-validation-queue.ts index 67a930a..ec1d2db 100644 --- a/lib/trading/smart-validation-queue.ts +++ b/lib/trading/smart-validation-queue.ts @@ -12,6 +12,7 @@ */ import { getMarketDataCache } from './market-data-cache' +import { logger } from '../utils/logger' import { getMergedConfig } from '../../config/trading' import { sendValidationNotification } from '../notifications/telegram' @@ -49,7 +50,7 @@ class SmartValidationQueue { private isMonitoring = false constructor() { - console.log('🧠 Smart Validation Queue initialized') + logger.log('🧠 Smart Validation Queue initialized') } /** @@ -110,8 +111,8 @@ class SmartValidationQueue { } this.queue.set(signalId, queuedSignal) - console.log(`⏰ Smart validation queued: ${params.symbol} ${params.direction.toUpperCase()} @ $${params.originalPrice.toFixed(2)} (quality: ${params.qualityScore})`) - console.log(` Watching for ${queuedSignal.entryWindowMinutes}min: +${queuedSignal.confirmationThreshold}% confirms, ${queuedSignal.maxDrawdown}% abandons`) + logger.log(`⏰ Smart validation queued: ${params.symbol} ${params.direction.toUpperCase()} @ $${params.originalPrice.toFixed(2)} (quality: ${params.qualityScore})`) + logger.log(` Watching for ${queuedSignal.entryWindowMinutes}min: +${queuedSignal.confirmationThreshold}% confirms, ${queuedSignal.maxDrawdown}% abandons`) // Send Telegram notification await sendValidationNotification({ @@ -139,7 +140,7 @@ class SmartValidationQueue { } this.isMonitoring = true - console.log('πŸ‘οΈ Smart validation monitoring started (checks every 30s)') + logger.log('πŸ‘οΈ Smart validation monitoring started (checks every 30s)') // Check every 30 seconds this.monitoringInterval = setInterval(async () => { @@ -156,7 +157,7 @@ class SmartValidationQueue { this.monitoringInterval = undefined } this.isMonitoring = false - console.log('⏸️ Smart validation monitoring stopped') + logger.log('⏸️ Smart validation monitoring stopped') } /** @@ -171,7 +172,7 @@ class SmartValidationQueue { return } - console.log(`πŸ‘οΈ Smart validation check: ${pending.length} pending signals`) + logger.log(`πŸ‘οΈ Smart validation check: ${pending.length} pending signals`) for (const signal of pending) { try { @@ -195,7 +196,7 @@ class SmartValidationQueue { // Check if expired (beyond entry window) if (ageMinutes > signal.entryWindowMinutes) { signal.status = 'expired' - console.log(`⏰ Signal expired: ${signal.symbol} ${signal.direction} (${ageMinutes.toFixed(1)}min old)`) + logger.log(`⏰ Signal expired: ${signal.symbol} ${signal.direction} (${ageMinutes.toFixed(1)}min old)`) // Send Telegram notification await sendValidationNotification({ @@ -215,7 +216,7 @@ class SmartValidationQueue { const cachedData = marketDataCache.get(signal.symbol) if (!cachedData || !cachedData.currentPrice) { - console.log(`⚠️ No price data for ${signal.symbol}, skipping validation`) + logger.log(`⚠️ No price data for ${signal.symbol}, skipping validation`) return } @@ -237,8 +238,8 @@ class SmartValidationQueue { // Price moved up enough - CONFIRMED! signal.status = 'confirmed' signal.validatedAt = now - console.log(`βœ… LONG CONFIRMED: ${signal.symbol} moved +${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)} β†’ $${currentPrice.toFixed(2)})`) - console.log(` Validation time: ${ageMinutes.toFixed(1)} minutes, executing trade...`) + logger.log(`βœ… LONG CONFIRMED: ${signal.symbol} moved +${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)} β†’ $${currentPrice.toFixed(2)})`) + logger.log(` Validation time: ${ageMinutes.toFixed(1)} minutes, executing trade...`) // Send Telegram notification await sendValidationNotification({ @@ -257,8 +258,8 @@ class SmartValidationQueue { } else if (priceChange <= signal.maxDrawdown) { // Price moved down too much - ABANDON signal.status = 'abandoned' - console.log(`❌ LONG ABANDONED: ${signal.symbol} dropped ${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)} β†’ $${currentPrice.toFixed(2)})`) - console.log(` Saved from potential loser after ${ageMinutes.toFixed(1)} minutes`) + logger.log(`❌ LONG ABANDONED: ${signal.symbol} dropped ${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)} β†’ $${currentPrice.toFixed(2)})`) + logger.log(` Saved from potential loser after ${ageMinutes.toFixed(1)} minutes`) // Send Telegram notification await sendValidationNotification({ @@ -273,7 +274,7 @@ class SmartValidationQueue { }) } else { // Still pending, log progress - console.log(`⏳ LONG watching: ${signal.symbol} at ${priceChange.toFixed(2)}% (need +${signal.confirmationThreshold}%, abandon at ${signal.maxDrawdown}%) - ${ageMinutes.toFixed(1)}min`) + logger.log(`⏳ LONG watching: ${signal.symbol} at ${priceChange.toFixed(2)}% (need +${signal.confirmationThreshold}%, abandon at ${signal.maxDrawdown}%) - ${ageMinutes.toFixed(1)}min`) } } else { // SHORT: Need price to move DOWN to confirm @@ -281,8 +282,8 @@ class SmartValidationQueue { // Price moved down enough - CONFIRMED! signal.status = 'confirmed' signal.validatedAt = now - console.log(`βœ… SHORT CONFIRMED: ${signal.symbol} moved ${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)} β†’ $${currentPrice.toFixed(2)})`) - console.log(` Validation time: ${ageMinutes.toFixed(1)} minutes, executing trade...`) + logger.log(`βœ… SHORT CONFIRMED: ${signal.symbol} moved ${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)} β†’ $${currentPrice.toFixed(2)})`) + logger.log(` Validation time: ${ageMinutes.toFixed(1)} minutes, executing trade...`) // Send Telegram notification await sendValidationNotification({ @@ -301,8 +302,8 @@ class SmartValidationQueue { } else if (priceChange >= -signal.maxDrawdown) { // Price moved up too much - ABANDON signal.status = 'abandoned' - console.log(`❌ SHORT ABANDONED: ${signal.symbol} rose +${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)} β†’ $${currentPrice.toFixed(2)})`) - console.log(` Saved from potential loser after ${ageMinutes.toFixed(1)} minutes`) + logger.log(`❌ SHORT ABANDONED: ${signal.symbol} rose +${priceChange.toFixed(2)}% ($${signal.originalPrice.toFixed(2)} β†’ $${currentPrice.toFixed(2)})`) + logger.log(` Saved from potential loser after ${ageMinutes.toFixed(1)} minutes`) // Send Telegram notification await sendValidationNotification({ @@ -317,7 +318,7 @@ class SmartValidationQueue { }) } else { // Still pending, log progress - console.log(`⏳ SHORT watching: ${signal.symbol} at ${priceChange.toFixed(2)}% (need ${-signal.confirmationThreshold}%, abandon at +${-signal.maxDrawdown}%) - ${ageMinutes.toFixed(1)}min`) + logger.log(`⏳ SHORT watching: ${signal.symbol} at ${priceChange.toFixed(2)}% (need ${-signal.confirmationThreshold}%, abandon at +${-signal.maxDrawdown}%) - ${ageMinutes.toFixed(1)}min`) } } } @@ -347,9 +348,9 @@ class SmartValidationQueue { validationDelayMinutes: (Date.now() - signal.blockedAt) / (1000 * 60), } - console.log(`πŸš€ Executing validated trade: ${signal.symbol} ${signal.direction.toUpperCase()} @ $${currentPrice.toFixed(2)}`) - console.log(` Original signal: $${signal.originalPrice.toFixed(2)}, Quality: ${signal.qualityScore}`) - console.log(` Entry delay: ${payload.validationDelayMinutes.toFixed(1)} minutes`) + logger.log(`πŸš€ Executing validated trade: ${signal.symbol} ${signal.direction.toUpperCase()} @ $${currentPrice.toFixed(2)}`) + logger.log(` Original signal: $${signal.originalPrice.toFixed(2)}, Quality: ${signal.qualityScore}`) + logger.log(` Entry delay: ${payload.validationDelayMinutes.toFixed(1)} minutes`) const response = await fetch(executeUrl, { method: 'POST', @@ -367,9 +368,9 @@ class SmartValidationQueue { signal.executedAt = Date.now() signal.executionPrice = currentPrice signal.tradeId = result.trade?.id - console.log(`βœ… Trade executed successfully: ${signal.symbol} ${signal.direction}`) - console.log(` Trade ID: ${signal.tradeId}`) - console.log(` Entry: $${currentPrice.toFixed(2)}, Size: $${result.trade?.positionSizeUSD || 'unknown'}`) + logger.log(`βœ… Trade executed successfully: ${signal.symbol} ${signal.direction}`) + logger.log(` Trade ID: ${signal.tradeId}`) + logger.log(` Entry: $${currentPrice.toFixed(2)}, Size: $${result.trade?.positionSizeUSD || 'unknown'}`) // Send execution notification const slippage = ((currentPrice - signal.originalPrice) / signal.originalPrice) * 100 @@ -408,7 +409,7 @@ class SmartValidationQueue { } if (cleaned > 0) { - console.log(`🧹 Cleaned up ${cleaned} old validated signals`) + logger.log(`🧹 Cleaned up ${cleaned} old validated signals`) } } @@ -454,5 +455,5 @@ export function getSmartValidationQueue(): SmartValidationQueue { export function startSmartValidation(): void { const queue = getSmartValidationQueue() - console.log('🧠 Smart validation system ready') + logger.log('🧠 Smart validation system ready') } diff --git a/lib/trading/stop-hunt-tracker.ts b/lib/trading/stop-hunt-tracker.ts index bf6c943..2f45eee 100644 --- a/lib/trading/stop-hunt-tracker.ts +++ b/lib/trading/stop-hunt-tracker.ts @@ -14,6 +14,7 @@ */ import { getPrismaClient } from '../database/trades' +import { logger } from '../utils/logger' import { initializeDriftService } from '../drift/client' import { getPythPriceMonitor } from '../pyth/price-monitor' @@ -70,7 +71,7 @@ export class StopHuntTracker { }): Promise { // Only track quality 85+ stop-outs (high-confidence trades) if (params.originalQualityScore < 85) { - console.log(`⚠️ Stop hunt not tracked: Quality ${params.originalQualityScore} < 85 threshold`) + logger.log(`⚠️ Stop hunt not tracked: Quality ${params.originalQualityScore} < 85 threshold`) return } @@ -93,9 +94,9 @@ export class StopHuntTracker { } }) - console.log(`🎯 STOP HUNT RECORDED: ${params.symbol} ${params.direction.toUpperCase()}`) - console.log(` Quality: ${params.originalQualityScore}, Loss: $${params.stopLossAmount.toFixed(2)}`) - console.log(` Revenge window: 4 hours (expires ${revengeExpiresAt.toLocaleTimeString()})`) + logger.log(`🎯 STOP HUNT RECORDED: ${params.symbol} ${params.direction.toUpperCase()}`) + logger.log(` Quality: ${params.originalQualityScore}, Loss: $${params.stopLossAmount.toFixed(2)}`) + logger.log(` Revenge window: 4 hours (expires ${revengeExpiresAt.toLocaleTimeString()})`) // Start monitoring if not already running if (!this.isMonitoring) { @@ -113,7 +114,7 @@ export class StopHuntTracker { if (this.isMonitoring) return this.isMonitoring = true - console.log('πŸ” Stop Hunt Revenge Tracker: Monitoring started') + logger.log('πŸ” Stop Hunt Revenge Tracker: Monitoring started') // Check every 30 seconds monitoringInterval = setInterval(async () => { @@ -130,7 +131,7 @@ export class StopHuntTracker { monitoringInterval = null } this.isMonitoring = false - console.log('πŸ›‘ Stop Hunt Revenge Tracker: Monitoring stopped') + logger.log('πŸ›‘ Stop Hunt Revenge Tracker: Monitoring stopped') } /** @@ -152,13 +153,13 @@ export class StopHuntTracker { if (activeStopHunts.length === 0) { // No active stop hunts, stop monitoring to save resources if (this.isMonitoring) { - console.log('πŸ“Š No active stop hunts - pausing monitoring') + logger.log('πŸ“Š No active stop hunts - pausing monitoring') this.stopMonitoring() } return } - console.log(`πŸ” Checking ${activeStopHunts.length} active stop hunt(s)...`) + logger.log(`πŸ” Checking ${activeStopHunts.length} active stop hunt(s)...`) for (const stopHunt of activeStopHunts) { await this.checkStopHunt(stopHunt as StopHuntRecord) @@ -211,7 +212,7 @@ export class StopHuntTracker { const shouldRevenge = await this.shouldExecuteRevenge(stopHunt, currentPrice) if (shouldRevenge) { - console.log(`πŸ”₯ REVENGE CONDITIONS MET: ${stopHunt.symbol} ${stopHunt.direction.toUpperCase()}`) + logger.log(`πŸ”₯ REVENGE CONDITIONS MET: ${stopHunt.symbol} ${stopHunt.direction.toUpperCase()}`) await this.executeRevengeTrade(stopHunt, currentPrice) } @@ -260,7 +261,7 @@ export class StopHuntTracker { lowestInZone: currentPrice, } }) - console.log(` ⏱️ LONG revenge zone entered at ${currentPrice.toFixed(2)}, waiting for 90s confirmation...`) + logger.log(` ⏱️ LONG revenge zone entered at ${currentPrice.toFixed(2)}, waiting for 90s confirmation...`) return false } @@ -274,17 +275,17 @@ export class StopHuntTracker { // Check if we've been in zone for 90+ seconds (1.5 minutes) const timeInZone = now - stopHunt.firstCrossTime.getTime() if (timeInZone >= 90000) { // 90 seconds = 1.5 minutes - console.log(` βœ… LONG revenge: Price held below entry for ${(timeInZone/60000).toFixed(1)}min, confirmed!`) - console.log(` Entry ${originalEntryPrice.toFixed(2)} β†’ Current ${currentPrice.toFixed(2)}`) + logger.log(` βœ… LONG revenge: Price held below entry for ${(timeInZone/60000).toFixed(1)}min, confirmed!`) + logger.log(` Entry ${originalEntryPrice.toFixed(2)} β†’ Current ${currentPrice.toFixed(2)}`) return true } else { - console.log(` ⏱️ LONG revenge: ${(timeInZone/60000).toFixed(1)}min in zone (need 1.5min)`) + logger.log(` ⏱️ LONG revenge: ${(timeInZone/60000).toFixed(1)}min in zone (need 1.5min)`) return false } } else { // Price left revenge zone - reset timer and increment counter if (stopHunt.firstCrossTime) { - console.log(` ❌ LONG revenge: Price bounced back up to ${currentPrice.toFixed(2)}, resetting timer`) + logger.log(` ❌ LONG revenge: Price bounced back up to ${currentPrice.toFixed(2)}, resetting timer`) await this.prisma.stopHunt.update({ where: { id: stopHunt.id }, data: { @@ -310,7 +311,7 @@ export class StopHuntTracker { highestInZone: currentPrice, } }) - console.log(` ⏱️ SHORT revenge zone entered at ${currentPrice.toFixed(2)}, waiting for 90s confirmation...`) + logger.log(` ⏱️ SHORT revenge zone entered at ${currentPrice.toFixed(2)}, waiting for 90s confirmation...`) return false } @@ -324,17 +325,17 @@ export class StopHuntTracker { // Check if we've been in zone for 90+ seconds (1.5 minutes) const timeInZone = now - stopHunt.firstCrossTime.getTime() if (timeInZone >= 90000) { // 90 seconds = 1.5 minutes - console.log(` βœ… SHORT revenge: Price held above entry for ${(timeInZone/60000).toFixed(1)}min, confirmed!`) - console.log(` Entry ${originalEntryPrice.toFixed(2)} β†’ Current ${currentPrice.toFixed(2)}`) + logger.log(` βœ… SHORT revenge: Price held above entry for ${(timeInZone/60000).toFixed(1)}min, confirmed!`) + logger.log(` Entry ${originalEntryPrice.toFixed(2)} β†’ Current ${currentPrice.toFixed(2)}`) return true } else { - console.log(` ⏱️ SHORT revenge: ${(timeInZone/60000).toFixed(1)}min in zone (need 1.5min)`) + logger.log(` ⏱️ SHORT revenge: ${(timeInZone/60000).toFixed(1)}min in zone (need 1.5min)`) return false } } else { // Price left revenge zone - reset timer and increment counter if (stopHunt.firstCrossTime) { - console.log(` ❌ SHORT revenge: Price dropped back to ${currentPrice.toFixed(2)}, resetting timer`) + logger.log(` ❌ SHORT revenge: Price dropped back to ${currentPrice.toFixed(2)}, resetting timer`) await this.prisma.stopHunt.update({ where: { id: stopHunt.id }, data: { @@ -354,9 +355,9 @@ export class StopHuntTracker { */ private async executeRevengeTrade(stopHunt: StopHuntRecord, currentPrice: number): Promise { try { - console.log(`πŸ”₯ EXECUTING REVENGE TRADE: ${stopHunt.symbol} ${stopHunt.direction.toUpperCase()}`) - console.log(` Original loss: $${stopHunt.stopLossAmount.toFixed(2)}`) - console.log(` Revenge size: 1.2x (getting our money back!)`) + logger.log(`πŸ”₯ EXECUTING REVENGE TRADE: ${stopHunt.symbol} ${stopHunt.direction.toUpperCase()}`) + logger.log(` Original loss: $${stopHunt.stopLossAmount.toFixed(2)}`) + logger.log(` Revenge size: 1.2x (getting our money back!)`) // CRITICAL: Validate current ADX from 1-minute data cache // Block revenge if trend has weakened (ADX < 20) @@ -368,10 +369,10 @@ export class StopHuntTracker { const currentADX = cachedData.adx const dataAge = Date.now() - cachedData.timestamp - console.log(` πŸ“Š Fresh ADX check: ${currentADX.toFixed(1)} (${(dataAge/1000).toFixed(0)}s old)`) + logger.log(` πŸ“Š Fresh ADX check: ${currentADX.toFixed(1)} (${(dataAge/1000).toFixed(0)}s old)`) if (currentADX < 20) { - console.log(` ❌ REVENGE BLOCKED: ADX ${currentADX.toFixed(1)} < 20 (weak trend, not worth re-entry)`) + logger.log(` ❌ REVENGE BLOCKED: ADX ${currentADX.toFixed(1)} < 20 (weak trend, not worth re-entry)`) // Update database with failed reason await this.prisma.stopHunt.update({ @@ -397,10 +398,10 @@ export class StopHuntTracker { return } - console.log(` βœ… ADX validation passed: ${currentADX.toFixed(1)} β‰₯ 20 (strong trend)`) + logger.log(` βœ… ADX validation passed: ${currentADX.toFixed(1)} β‰₯ 20 (strong trend)`) } else { - console.log(` ⚠️ No fresh ADX data (cache age: ${cachedData ? (Date.now() - cachedData.timestamp)/1000 : 'N/A'}s)`) - console.log(` ⚠️ Proceeding with revenge but using original ADX ${stopHunt.originalADX}`) + logger.log(` ⚠️ No fresh ADX data (cache age: ${cachedData ? (Date.now() - cachedData.timestamp)/1000 : 'N/A'}s)`) + logger.log(` ⚠️ Proceeding with revenge but using original ADX ${stopHunt.originalADX}`) } // Call execute endpoint with revenge parameters @@ -454,9 +455,9 @@ export class StopHuntTracker { } }) - console.log(`βœ… REVENGE TRADE EXECUTED: ${result.trade?.id}`) - console.log(`πŸ“Š SL Distance: $${Math.abs(slDistance).toFixed(2)} (${stopHunt.originalATR ? `${(Math.abs(slDistance) / stopHunt.originalATR).toFixed(2)}Γ— ATR` : 'no ATR'})`) - console.log(`πŸ”₯ LET'S GET OUR MONEY BACK!`) + logger.log(`βœ… REVENGE TRADE EXECUTED: ${result.trade?.id}`) + logger.log(`πŸ“Š SL Distance: $${Math.abs(slDistance).toFixed(2)} (${stopHunt.originalATR ? `${(Math.abs(slDistance) / stopHunt.originalATR).toFixed(2)}Γ— ATR` : 'no ATR'})`) + logger.log(`πŸ”₯ LET'S GET OUR MONEY BACK!`) // Send special Telegram notification await this.sendRevengeNotification(stopHunt, result.trade) @@ -524,7 +525,7 @@ Reversal Confirmed: Price crossed back through entry }) if (!stopHunt) { - console.log(`⚠️ No stop hunt found for revenge trade ${params.revengeTradeId}`) + logger.log(`⚠️ No stop hunt found for revenge trade ${params.revengeTradeId}`) return } @@ -538,10 +539,10 @@ Reversal Confirmed: Price crossed back through entry }) const emoji = params.outcome.includes('TP') ? 'βœ…' : '❌' - console.log(`${emoji} REVENGE OUTCOME: ${params.outcome} (${params.pnl >= 0 ? '+' : ''}$${params.pnl.toFixed(2)})`) + logger.log(`${emoji} REVENGE OUTCOME: ${params.outcome} (${params.pnl >= 0 ? '+' : ''}$${params.pnl.toFixed(2)})`) if (params.failedReason) { - console.log(` Reason: ${params.failedReason}`) + logger.log(` Reason: ${params.failedReason}`) } } catch (error) { @@ -568,7 +569,7 @@ Reversal Confirmed: Price crossed back through entry }) if (expired.count > 0) { - console.log(`⏰ Expired ${expired.count} stop hunt revenge window(s)`) + logger.log(`⏰ Expired ${expired.count} stop hunt revenge window(s)`) } } catch (error) { console.error('❌ Error expiring stop hunts:', error) @@ -605,10 +606,10 @@ export async function startStopHuntTracking(): Promise { }) if (activeCount > 0) { - console.log(`🎯 Found ${activeCount} active stop hunt(s) - starting revenge tracker`) + logger.log(`🎯 Found ${activeCount} active stop hunt(s) - starting revenge tracker`) tracker.startMonitoring() } else { - console.log('πŸ“Š No active stop hunts - tracker will start when needed') + logger.log('πŸ“Š No active stop hunts - tracker will start when needed') } } catch (error) { console.error('❌ Error starting stop hunt tracker:', error) diff --git a/lib/utils/logger.ts b/lib/utils/logger.ts new file mode 100644 index 0000000..225f002 --- /dev/null +++ b/lib/utils/logger.ts @@ -0,0 +1,51 @@ +/** + * Production-safe logging utility + * Automatically gates console.log based on environment + * Always allows console.error for critical issues + */ + +const isDev = process.env.NODE_ENV !== 'production' +const debugEnabled = process.env.DEBUG_LOGS === 'true' + +export const logger = { + /** + * Debug logging - only in development or when DEBUG_LOGS=true + */ + log: (...args: any[]) => { + if (isDev || debugEnabled) { + console.log(...args) + } + }, + + /** + * Error logging - always enabled (critical for production issues) + */ + error: (...args: any[]) => { + console.error(...args) + }, + + /** + * Warning logging - always enabled (important for production monitoring) + */ + warn: (...args: any[]) => { + console.warn(...args) + }, + + /** + * Info logging - only in development or when DEBUG_LOGS=true + */ + info: (...args: any[]) => { + if (isDev || debugEnabled) { + console.info(...args) + } + }, + + /** + * Debug-specific logging - only when DEBUG_LOGS=true + */ + debug: (...args: any[]) => { + if (debugEnabled) { + console.log('[DEBUG]', ...args) + } + } +} diff --git a/lib/utils/persistent-logger.ts b/lib/utils/persistent-logger.ts index 7755af0..86af2a3 100644 --- a/lib/utils/persistent-logger.ts +++ b/lib/utils/persistent-logger.ts @@ -4,6 +4,7 @@ */ import * as fs from 'fs' +import { logger } from '../utils/logger' import * as path from 'path' const LOG_DIR = '/app/logs' @@ -31,7 +32,7 @@ function rotateLogIfNeeded(logPath: string) { const timestamp = new Date().toISOString().replace(/[:.]/g, '-') const rotatedPath = `${logPath}.${timestamp}` fs.renameSync(logPath, rotatedPath) - console.log(`πŸ“¦ Rotated log: ${rotatedPath}`) + logger.log(`πŸ“¦ Rotated log: ${rotatedPath}`) } } } catch (error) { @@ -86,7 +87,7 @@ export function logTradeExecution( : `Trade failed: ${tradeDetails.symbol} ${tradeDetails.direction} - ${tradeDetails.error}` const entry = formatLogEntry(level, message, tradeDetails) - console.log(success ? 'βœ…' : '❌', message) + logger.log(success ? 'βœ…' : '❌', message) appendToLog(TRADE_LOG, entry) } @@ -109,7 +110,7 @@ export function logDatabaseOperation( : `${operation} failed: ${details.error?.message || 'Unknown error'}` const entry = formatLogEntry(level, message, details) - console.log(success ? 'πŸ’Ύ' : '❌', message) + logger.log(success ? 'πŸ’Ύ' : '❌', message) appendToLog(ERROR_LOG, entry) } diff --git a/scripts/replace-console-logs.js b/scripts/replace-console-logs.js new file mode 100755 index 0000000..2828570 --- /dev/null +++ b/scripts/replace-console-logs.js @@ -0,0 +1,144 @@ +#!/usr/bin/env node + +/** + * Automated console.log replacement script + * Replaces console.log β†’ logger.log + * Preserves console.error and console.warn + * Adds logger import to files + */ + +const fs = require('fs') +const path = require('path') +const { execSync } = require('child_process') + +// Files to process (from grep analysis) +const targetFiles = [ + 'lib/trading/position-manager.ts', + 'lib/drift/orders.ts', + 'lib/database/trades.ts', + 'lib/trading/smart-entry-timer.ts', + 'lib/trading/signal-quality.ts', + 'lib/pyth/price-monitor.ts', + 'lib/drift/client.ts', + 'lib/startup/init-position-manager.ts', + 'lib/trading/market-data-cache.ts', + 'lib/analysis/blocked-signal-tracker.ts', + 'lib/trading/stop-hunt-tracker.ts', + 'lib/notifications/telegram.ts', + 'lib/trading/smart-validation-queue.ts', + 'lib/monitoring/drift-health-monitor.ts', + 'lib/database/client.ts', + 'lib/utils/persistent-logger.ts', + 'lib/drift/drift-service.ts', + 'lib/startup/index.ts' +] + +const loggerImport = "import { logger } from '../utils/logger'" +const loggerImportAlt = "import { logger } from './utils/logger'" // For root level files + +let totalReplacements = 0 +let filesModified = 0 + +function addLoggerImport(content, filePath) { + // Skip if logger already imported + if (content.includes("from '../utils/logger'") || content.includes("from './utils/logger'")) { + return content + } + + // Determine correct import path based on file location + const depth = filePath.split('/').length - 2 // Subtract 'lib/' and filename + const importPath = depth === 1 ? './utils/logger' : '../'.repeat(depth - 1) + 'utils/logger' + const importStatement = `import { logger } from '${importPath}'` + + // Find first import statement and add logger import after it + const lines = content.split('\n') + let insertIndex = 0 + + for (let i = 0; i < lines.length; i++) { + if (lines[i].startsWith('import ')) { + insertIndex = i + 1 + break + } + } + + // If no imports found, add at top after any comments + if (insertIndex === 0) { + for (let i = 0; i < lines.length; i++) { + if (!lines[i].trim().startsWith('//') && !lines[i].trim().startsWith('/*') && lines[i].trim() !== '') { + insertIndex = i + break + } + } + } + + lines.splice(insertIndex, 0, importStatement) + return lines.join('\n') +} + +function replaceConsoleLogs(content) { + let count = 0 + + // Replace console.log with logger.log + // Match: console.log(...) but not console.error or console.warn + const logRegex = /console\.log\(/g + const matches = content.match(logRegex) + if (matches) { + count = matches.length + content = content.replace(logRegex, 'logger.log(') + } + + // Replace console.info with logger.info + const infoRegex = /console\.info\(/g + const infoMatches = content.match(infoRegex) + if (infoMatches) { + count += infoMatches.length + content = content.replace(infoRegex, 'logger.info(') + } + + // Keep console.error and console.warn as-is (already correct) + + return { content, count } +} + +function processFile(filePath) { + const fullPath = path.join(process.cwd(), filePath) + + if (!fs.existsSync(fullPath)) { + console.log(`⚠️ File not found: ${filePath}`) + return + } + + let content = fs.readFileSync(fullPath, 'utf8') + const originalContent = content + + // Add logger import + content = addLoggerImport(content, filePath) + + // Replace console.log statements + const { content: newContent, count } = replaceConsoleLogs(content) + + if (count > 0) { + fs.writeFileSync(fullPath, newContent, 'utf8') + console.log(`βœ… ${filePath}: ${count} replacements`) + totalReplacements += count + filesModified++ + } else { + console.log(`⏭️ ${filePath}: No console.log found`) + } +} + +console.log('πŸš€ Starting console.log replacement...\n') + +targetFiles.forEach(processFile) + +console.log('\nπŸ“Š Summary:') +console.log(` Files modified: ${filesModified}`) +console.log(` Total replacements: ${totalReplacements}`) +console.log(` Estimated log reduction: ${Math.round((totalReplacements / 731) * 100)}%`) +console.log('\nβœ… Replacement complete!') +console.log('\nπŸ“ Next steps:') +console.log(' 1. Review changes: git diff') +console.log(' 2. Test build: npm run build') +console.log(' 3. Update .env: NODE_ENV=production, DEBUG_LOGS=false') +console.log(' 4. Rebuild Docker: docker compose build trading-bot') +console.log(' 5. Deploy: docker compose up -d --force-recreate trading-bot')