Files
trading_bot_v4/.github/copilot-instructions.md
mindesbunister 3e2cf2eec2 feat: add Telegram bot for manual trade commands
- Added telegram_command_bot.py with slash commands (/buySOL, /sellBTC, etc)
- Docker compose setup with DNS configuration
- Sends trades as plain text to n8n webhook (same format as TradingView)
- Improved Telegram success message formatting
- Only responds to authorized chat ID (579304651)
- Commands: /buySOL, /sellSOL, /buyBTC, /sellBTC, /buyETH, /sellETH
2025-10-27 00:23:09 +01:00

9.5 KiB

AI Agent Instructions for Trading Bot v4

Architecture Overview

Type: Autonomous cryptocurrency trading bot with Next.js 15 frontend + Solana/Drift Protocol backend

Data Flow: TradingView → n8n webhook → Next.js API → Drift Protocol (Solana DEX) → Real-time monitoring → Auto-exit

Key Design Principle: Dual-layer redundancy - every trade has both on-chain orders (Drift) AND software monitoring (Position Manager) as backup.

Critical Components

1. Position Manager (lib/trading/position-manager.ts)

Purpose: Software-based monitoring loop that checks prices every 2 seconds and closes positions via market orders

Singleton pattern: Always use getPositionManager() - never instantiate directly

const positionManager = getPositionManager()
await positionManager.addTrade(activeTrade)

Key behaviors:

  • Tracks ActiveTrade objects in a Map
  • Dynamic SL adjustments: Moves to breakeven at +0.5%, locks profit at +1.2%
  • Closes positions via closePosition() market orders when targets hit
  • Acts as backup if on-chain orders don't fill

2. Drift Client (lib/drift/client.ts)

Purpose: Solana/Drift Protocol SDK wrapper for order execution

Singleton pattern: Use initializeDriftService() and getDriftService() - maintains single connection

const driftService = await initializeDriftService()
const health = await driftService.getAccountHealth()

Wallet handling: Supports both JSON array [91,24,...] and base58 string formats from Phantom wallet

3. Order Placement (lib/drift/orders.ts)

Critical function: placeExitOrders() - places TP/SL orders on-chain

Dual Stop System (USE_DUAL_STOPS=true):

// Soft stop: TRIGGER_LIMIT at -1.5% (avoids wicks)
// Hard stop: TRIGGER_MARKET at -2.5% (guarantees exit)

Order types:

  • Entry: MARKET (immediate execution)
  • TP1/TP2: LIMIT reduce-only orders
  • Soft SL: TRIGGER_LIMIT reduce-only
  • Hard SL: TRIGGER_MARKET reduce-only

4. Database (lib/database/trades.ts + prisma/schema.prisma)

Purpose: PostgreSQL via Prisma ORM for trade history and analytics

Models: Trade, PriceUpdate, SystemEvent, DailyStats

Singleton pattern: Use getPrismaClient() - never instantiate PrismaClient directly

Key functions:

  • createTrade() - Save trade after execution (includes dual stop TX signatures)
  • updateTradeExit() - Record exit with P&L
  • addPriceUpdate() - Track price movements (called by Position Manager)
  • getTradeStats() - Win rate, profit factor, avg win/loss

Configuration System

Three-layer merge:

  1. DEFAULT_TRADING_CONFIG (config/trading.ts)
  2. Environment variables (.env) via getConfigFromEnv()
  3. Runtime overrides via getMergedConfig(overrides)

Always use: getMergedConfig() to get final config - never read env vars directly in business logic

Symbol normalization: TradingView sends "SOLUSDT" → must convert to "SOL-PERP" for Drift

const driftSymbol = normalizeTradingViewSymbol(body.symbol)

API Endpoints Architecture

Authentication: All /api/trading/* endpoints (except /test) require Authorization: Bearer API_SECRET_KEY

Pattern: Each endpoint follows same flow:

  1. Auth check
  2. Get config via getMergedConfig()
  3. Initialize Drift service
  4. Check account health
  5. Execute operation
  6. Save to database
  7. Add to Position Manager if applicable

Key endpoints:

  • /api/trading/execute - Main entry point from n8n (production)
  • /api/trading/test - Test trades from settings UI (no auth required)
  • /api/trading/close - Manual position closing
  • /api/trading/positions - Query open positions
  • /api/settings - Get/update config (writes to .env file)

Critical Workflows

Execute Trade (Production)

n8n webhook → /api/trading/execute
  ↓ normalize symbol (SOLUSDT → SOL-PERP)
  ↓ getMergedConfig()
  ↓ openPosition() [MARKET order]
  ↓ calculate dual stop prices if enabled
  ↓ placeExitOrders() [on-chain TP/SL orders]
  ↓ createTrade() [save to database]
  ↓ positionManager.addTrade() [start monitoring]

Position Monitoring Loop

Position Manager every 2s:
  ↓ getPythPriceMonitor().getLatestPrice()
  ↓ Calculate current P&L
  ↓ Check TP1 hit → closePosition(75%)
  ↓ Check TP2 hit → closePosition(100%)
  ↓ Check SL hit → closePosition(100%)
  ↓ Check dynamic adjustments (breakeven, profit lock)
  ↓ addPriceUpdate() [save to database]

Settings Update

Web UI → /api/settings POST
  ↓ Validate new settings
  ↓ Write to .env file using string replacement
  ↓ Return success
  ↓ User clicks "Restart Bot" → /api/restart
  ↓ Creates /tmp/trading-bot-restart.flag
  ↓ watch-restart.sh detects flag
  ↓ Executes: docker restart trading-bot-v4

Docker Context

Multi-stage build: deps → builder → runner (Node 20 Alpine)

Critical Dockerfile steps:

  1. Install deps with npm install --production
  2. Copy source and npx prisma generate (MUST happen before build)
  3. npm run build (Next.js standalone output)
  4. Runner stage copies standalone + static + node_modules + Prisma client

Container networking:

  • External: trading-bot-v4 on port 3001
  • Internal: Next.js on port 3000
  • Database: trading-bot-postgres on 172.28.0.0/16 network

DATABASE_URL caveat: Use trading-bot-postgres (container name) in .env for runtime, but localhost:5432 for Prisma CLI migrations from host

Project-Specific Patterns

1. Singleton Services

Never create multiple instances - always use getter functions:

const driftService = await initializeDriftService() // NOT: new DriftService()
const positionManager = getPositionManager()        // NOT: new PositionManager()
const prisma = getPrismaClient()                     // NOT: new PrismaClient()

2. Price Calculations

Direction matters for long vs short:

function calculatePrice(entry: number, percent: number, direction: 'long' | 'short') {
  if (direction === 'long') {
    return entry * (1 + percent / 100)  // Long: +1% = higher price
  } else {
    return entry * (1 - percent / 100)  // Short: +1% = lower price
  }
}

3. Error Handling

Database failures should not fail trades - always wrap in try/catch:

try {
  await createTrade(params)
  console.log('💾 Trade saved to database')
} catch (dbError) {
  console.error('❌ Failed to save trade:', dbError)
  // Don't fail the trade if database save fails
}

4. Reduce-Only Orders

All exit orders MUST be reduce-only (can only close, not open positions):

const orderParams = {
  reduceOnly: true,  // CRITICAL for TP/SL orders
  // ... other params
}

Testing Commands

# Local development
npm run dev

# Build production
npm run build && npm start

# Docker build and restart
docker compose build trading-bot
docker compose up -d --force-recreate trading-bot
docker logs -f trading-bot-v4

# Database operations
npx prisma generate                                    # Generate client
DATABASE_URL="postgresql://...@localhost:5432/..." npx prisma migrate dev
docker exec trading-bot-postgres psql -U postgres -d trading_bot_v4 -c "\dt"

# Test trade from UI
# Go to http://localhost:3001/settings
# Click "Test LONG" or "Test SHORT"

Common Pitfalls

  1. Prisma not generated in Docker: Must run npx prisma generate in Dockerfile BEFORE npm run build

  2. Wrong DATABASE_URL: Container runtime needs trading-bot-postgres, Prisma CLI from host needs localhost:5432

  3. Symbol format mismatch: Always normalize with normalizeTradingViewSymbol() before calling Drift

  4. Missing reduce-only flag: Exit orders without reduceOnly: true can accidentally open new positions

  5. Singleton violations: Creating multiple DriftClient or Position Manager instances causes connection/state issues

  6. Type errors with Prisma: The Trade type from Prisma is only available AFTER npx prisma generate - use explicit types or // @ts-ignore carefully

File Conventions

  • API routes: app/api/[feature]/[action]/route.ts (Next.js 15 App Router)
  • Services: lib/[service]/[module].ts (drift, pyth, trading, database)
  • Config: Single source in config/trading.ts with env merging
  • Types: Define interfaces in same file as implementation (not separate types directory)
  • Console logs: Use emojis for visual scanning: 🎯 🚀 💰 📊 🛡️

When Making Changes

  1. Adding new config: Update DEFAULT_TRADING_CONFIG + getConfigFromEnv() + .env file
  2. Adding database fields: Update prisma/schema.prisma → migrate → regenerate client → rebuild Docker
  3. Changing order logic: Test with DRY_RUN=true first, use small position sizes ($10)
  4. API endpoint changes: Update both endpoint + corresponding n8n workflow JSON
  5. Docker changes: Rebuild with docker compose build trading-bot then restart container

Integration Points

  • n8n: Expects exact response format from /api/trading/execute (see n8n-complete-workflow.json)
  • Drift Protocol: Uses SDK v2.75.0 - check docs at docs.drift.trade for API changes
  • Pyth Network: WebSocket + HTTP fallback for price feeds (handles reconnection)
  • PostgreSQL: Version 16-alpine, must be running before bot starts

Key Mental Model: Think of this as two parallel systems (on-chain orders + software monitoring) working together. The Position Manager is the "backup brain" that constantly watches and acts if on-chain orders fail. Both write to the same database for complete trade history.