📊 Giới thiệu

Đọc file này trước khi làm bất kỳ việc gì. Đây là nguồn sự thật duy nhất về kiến trúc hệ thống.

AI Trading Signal là hệ thống phân tích kỹ thuật và phát tín hiệu giao dịch cho cặp XAU/USD (vàng) sử dụng Claude AI (Anthropic). Hệ thống lấy dữ liệu nến từ API thị trường, gửi qua Claude để phân tích đa khung thời gian, lưu kết quả vào SQLite và gửi thông báo qua Telegram. Phân tích được trigger thủ công qua REST API (không dùng cron).

Ngôn ngữ AI
Tiếng Việt
Timeframes
H1, M15, M5
Múi giờ
Asia/Ho_Chi_Minh
Giờ hoạt động
06:00 – 22:00
Prompt AI
~400 dòng

⚙️ Tech Stack

LayerCông nghệ
RuntimeNode.js (CommonJS)
LanguageTypeScript 5.6
DatabaseSQLite via Prisma 5.22
AIClaude — Anthropic (claude-sonnet-4-6)
Market DataTwelveData (default) / OANDA
NotificationTelegram Bot API
LoggingWinston
HTTP Clientaxios
API ServerExpress

📁 Cấu trúc thư mục

D:/ai-trading-signal/ ├── CLAUDE.md ← nguồn sự thật kiến trúc ├── .env ← biến môi trường (không commit) ├── .env.example ← template cấu hình ├── package.json ├── tsconfig.json ├── prisma/ │ ├── schema.prisma ← schema SQLite │ └── data.db ← database thực tế ├── logs/ │ └── signal.log ← Winston, rotate 10MB×5 └── src/ ├── index.ts ← entry point (khởi động server) ├── server.ts ← Express REST API server ├── db.ts ← Prisma client singleton ├── logger.ts ← Winston logger ├── config/ │ └── trading.ts ← đọc toàn bộ config từ .env ├── commands/ │ └── analyzeSignal.ts ← CLI entry: check giờ → orchestrate └── services/ ├── SignalOrchestrator.ts ← pipeline chính ├── MarketHoursService.ts ← kiểm tra giờ giao dịch ├── ai/ │ ├── ClaudeAnalystService.ts ← Claude API, build prompt, parse JSON │ └── dto/ │ └── AnalysisResult.ts ← kiểu trả về từ AI ├── market/ │ ├── Candle.ts ← kiểu dữ liệu nến (OHLCV) │ ├── MarketDataProvider.ts ← interface │ ├── MarketDataProviderFactory.ts ← chọn provider │ ├── TwelveDataProvider.ts ← provider mặc định │ └── OandaProvider.ts ← provider thay thế └── telegram/ └── TelegramNotifier.ts ← format + gửi Telegram

🔄 Luồng dữ liệu

POST /api/analyze
SignalOrchestrator.run(instrument?, timeframes?)
  • timeframes: dùng tham số nếu có, fallback về TRADING_TIMEFRAMES trong .env
  • MarketDataProvider.fetchCandles(XAU/USD, H1/M15/M5, 100 nến)
  • MarketDataProvider.fetchCurrentPrice()
  • ClaudeAnalystService.analyze()
  • Tính chỉ báo: RSI(14), EMA(200), HMA(200), BB(34,2.0)
  • Build system prompt (tiếng Việt, ICT/SMC methodology)
  • Build user prompt (bảng nến markdown)
  • POST Claude API (temperature=0.2, maxTokens=8192)
  • Parse JSON từ response (```json block hoặc fallback search)
  • Prisma → lưu TradingSignal vào SQLite
    TelegramNotifier
  • formatSignalCard() → HTML card
  • send() → kênh Telegram chính (auto-split nếu >4000 ký tự)
  • sendComment() → discussion thread (nếu có BUY/SELL)
  • 🏗️ Design Patterns

    PatternNơi dùngMô tả
    FactoryMarketDataProviderFactoryChọn provider theo config
    StrategyTwelveData / OANDACùng implement interface MarketDataProvider
    Static FactoryService.fromConfig()Thay vì DI container
    DTOAnalysisResultBọc output từ AI
    Retry + BackoffClaude & TwelveData4 lần, sleep 3s/6s/9s

    🔌 REST API Server

    File: src/server.ts — chạy cùng process với scheduler qua src/index.ts, lắng nghe port PORT (mặc định 3000).

    Authentication: Header x-api-key hoặc Authorization: Bearer <key>. Bỏ qua nếu API_SERVER_KEY không set.

    Tất cả endpoints

    POST /api/analyze

    Trigger phân tích thủ công. Cả symboltimeframes đều tùy chọn.

    Request body

    {
      "symbol":     "XAU/USD",        // tùy chọn — mặc định TRADING_INSTRUMENT
      "timeframes": ["H1", "M15"]   // tùy chọn — mặc định TRADING_TIMEFRAMES
    }

    timeframes chấp nhận cả array ["H1","M15"] lẫn string phân cách phẩy "H1,M15". Nếu không truyền, dùng giá trị TRADING_TIMEFRAMES trong .env.

    Response

    {
      "ok":          true,
      "symbol":      "XAU/USD",
      "duration_ms": 4200,
      "setup":       "<HTML signal card>",
      "reasoning":   "<HTML analysis>"
    }

    Ví dụ cURL

    # Dùng timeframe tùy chỉnh
    curl -X POST http://localhost:3000/api/analyze \
      -H "Content-Type: application/json" \
      -H "x-api-key: YOUR_KEY" \
      -d '{"symbol":"XAU/USD","timeframes":["H1","M15"]}'
    
    # Dùng string phân cách phẩy
    curl -X POST http://localhost:3000/api/analyze \
      -d '{"timeframes":"H1,M15"}'
    
    # Không truyền → dùng TRADING_TIMEFRAMES trong .env
    curl -X POST http://localhost:3000/api/analyze \
      -d '{}'

    📋 GET /api/signals

    Lấy danh sách tín hiệu trong ngày (tính theo giờ Việt Nam, từ 00:00 VN).

    Auth: Không yêu cầu.

    Query parameters

    ParamKiểuMặc địnhMô tả
    limitinteger20Số tín hiệu trả về (tối đa 100)

    Response

    [
      {
        "id":                   42,
        "instrument":           "XAU/USD",
        "action":               "BUY",
        "timeframe":            "M5",
        "entry":                2343.00,
        "stop_loss":            2340.00,
        "take_profit":          2350.00,
        "risk_reward":          2.33,
        "confidence":           82,
        "current_price":        2343.50,
        "trend_bias":           "BULLISH",
        "reasoning":            "...",
        "telegram_message_id":  "12345",
        "sent_at":              "2026-06-01T03:30:00.000Z",
        "created_at":           "2026-06-01T03:30:00.000Z",
        "market_structure":     { /* object từ raw_ai_response */ },
        "key_levels":           [ /* array */ ],
        "setups":               [ /* array */ ]
      }
    ]

    Ví dụ

    curl http://localhost:3000/api/signals?limit=5

    GET /api/signals/latest

    Lấy tín hiệu mới nhất (không giới hạn theo ngày). Dùng để hiển thị trên dashboard.

    Auth: Không yêu cầu.

    Response

    Trả về một object cùng cấu trúc với từng phần tử trong GET /api/signals.

    HTTP 200 — object tín hiệu mới nhất
    HTTP 404 — "No signals found"

    Ví dụ

    curl http://localhost:3000/api/signals/latest

    💱 Symbols

    CRUD danh sách symbol theo dõi. Auth: Yêu cầu API_SERVER_KEY nếu đã cấu hình.

    GET /api/symbols

    Lấy toàn bộ danh sách, sắp xếp: yêu thích trước, rồi theo tên ABC.

    curl http://localhost:3000/api/symbols \
      -H "x-api-key: YOUR_KEY"
    // Response
    [
      {
        "id":        1,
        "symbol":   "XAU/USD",
        "name":     "Gold",
        "favorite": true,
        "createdAt":"2026-06-01T00:00:00.000Z"
      }
    ]

    POST /api/symbols

    Thêm symbol mới. Trả về 409 nếu symbol đã tồn tại.

    FieldKiểuBắt buộcMô tả
    symbolstringMã symbol, tự động uppercase (vd: XAU/USD)
    namestringTên hiển thị (vd: Gold)
    curl -X POST http://localhost:3000/api/symbols \
      -H "x-api-key: YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{"symbol":"EUR/USD","name":"Euro"}'
    
    // Response 201
    { "id": 2, "symbol": "EUR/USD", "name": "Euro", "favorite": false }

    DELETE /api/symbols/:symbol

    curl -X DELETE http://localhost:3000/api/symbols/EUR%2FUSD \
      -H "x-api-key: YOUR_KEY"
    
    // Response 200
    { "ok": true, "deleted": "EUR/USD" }
    
    // Response 404 nếu không tìm thấy
    { "error": "Symbol 'EUR/USD' not found" }

    PATCH /api/symbols/:symbol/favorite

    Toggle trạng thái yêu thích. Mặc định favorite: true nếu không truyền body.

    curl -X PATCH http://localhost:3000/api/symbols/XAU%2FUSD/favorite \
      -H "x-api-key: YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{"favorite": false}'
    
    // Response — object symbol đã cập nhật

    GET /api/symbols/:symbol/signals

    Lấy analysis logs (kết quả từ /api/analyze) của một symbol trong ngày hôm nay (giờ VN).

    ParamKiểuMặc địnhMô tả
    limitinteger20Số bản ghi (tối đa 100)
    curl "http://localhost:3000/api/symbols/XAU%2FUSD/signals?limit=5" \
      -H "x-api-key: YOUR_KEY"
    
    // Response
    [
      {
        "id":          1,
        "symbol":      "XAU/USD",
        "analyzed_at": "2026-06-01T03:30:00.000Z",
        "duration_ms": 4200,
        "setup":       "<HTML signal card>",
        "reasoning":   "<HTML analysis>"
      }
    ]

    📁 Groups

    CRUD nhóm symbol. Auth: Yêu cầu API_SERVER_KEY nếu đã cấu hình.

    GET /api/groups

    Lấy tất cả nhóm, mỗi nhóm kèm danh sách symbols (mảng tên symbol).

    curl http://localhost:3000/api/groups \
      -H "x-api-key: YOUR_KEY"
    
    // Response
    [
      {
        "id":      1,
        "name":    "Metals",
        "symbols": ["XAU/USD", "XAG/USD"]
      }
    ]

    POST /api/groups

    Tạo nhóm mới. Trả về 409 nếu tên đã tồn tại.

    curl -X POST http://localhost:3000/api/groups \
      -H "x-api-key: YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{"name":"Metals"}'
    
    // Response 201
    { "id": 1, "name": "Metals", "symbols": [] }

    GET /api/groups/:id

    Chi tiết một nhóm kèm đầy đủ thông tin từng symbol (id, symbol, name, favorite).

    curl http://localhost:3000/api/groups/1 \
      -H "x-api-key: YOUR_KEY"
    
    // Response
    {
      "id":      1,
      "name":    "Metals",
      "symbols": [
        { "id": 1, "symbol": "XAU/USD", "name": "Gold", "favorite": true }
      ]
    }
    
    // Response 404
    { "error": "Group 99 not found" }

    DELETE /api/groups/:id

    curl -X DELETE http://localhost:3000/api/groups/1 \
      -H "x-api-key: YOUR_KEY"
    
    // Response 200
    { "ok": true, "deleted": 1 }

    POST /api/groups/:id/symbols

    Thêm symbol vào nhóm. Symbol phải tồn tại trong bảng symbols. Trả về 409 nếu đã có trong nhóm, 404 nếu group hoặc symbol không tồn tại.

    curl -X POST http://localhost:3000/api/groups/1/symbols \
      -H "x-api-key: YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{"symbol":"XAU/USD"}'
    
    // Response 201
    { "group_id": 1, "symbol": "XAU/USD" }

    DELETE /api/groups/:id/symbols/:symbol

    curl -X DELETE "http://localhost:3000/api/groups/1/symbols/XAU%2FUSD" \
      -H "x-api-key: YOUR_KEY"
    
    // Response 200
    { "ok": true, "group_id": 1, "removed": "XAU/USD" }

    🤖 ClaudeAnalystService

    File: src/services/ai/ClaudeAnalystService.ts

    Prompt strategy (3 bước)

    1. Phân tích kỹ thuật độc lập từng khung (H1 → M15 → M5)
    2. Review lệnh cũ (nếu có)
    3. Quyết định tổng thể (BUY / SELL / NO_TRADE)

    Cấu trúc prompt

    MụcNội dung
    1ACẤU TRÚC THỊ TRƯỜNG — H1, M15, M5
    1BVÙNG CUNG CẦU & KEY LEVELS
    1CCHỈ BÁO KỸ THUẬT
    2QUYẾT ĐỊNH GIAO DỊCH
    3KẾ HOẠCH QUẢN LÝ LỆNH

    JSON output

    {
      "action":      "BUY|SELL|NO_TRADE",
      "entry":       2343.00,
      "stop_loss":   2340.00,
      "take_profit": 2350.00,
      "risk_reward": 2.33,
      "confidence":  82,
      "trend_bias":  "BULLISH|BEARISH|NEUTRAL",
      "reasoning":   "..."
    }

    Retry logic

    4 lần, sleep 3s / 6s / 9s, trigger khi HTTP status 429 / 500 / 502 / 503 / 504.

    📨 TelegramNotifier

    File: src/services/telegram/TelegramNotifier.ts

    Format signal card

    ━━━━━━━━━━━━━━━━━━━━━ 📊 XAU/USD 🟢 MUA (BUY) ━━━━━━━━━━━━━━━━━━━━━ 🕐 14/05/2026 10:30 (Giờ VN) 💵 Giá hiện tại: 2343.50 📐 Xu hướng: 📈 Tăng ─ Thông số lệnh ───────────── 🎯 Entry: 2343.00 🛡 Stop Loss: 2340.00 💰 Take Profit: 2350.00 ⚖️ R:R: 1 : 2.33 ─ Đánh giá AI ─────────────── 🔎 Độ tin cậy: 82/100 ████████░░ ━━━━━━━━━━━━━━━━━━━━━ ⚠️ Tín hiệu tham khảo từ AI, không phải lời khuyên đầu tư.

    📈 Market Data Providers

    TwelveData default

    GET https://api.twelvedata.com/time_series
      ?symbol=XAU/USD&interval=5min&outputsize=100&order=ASC&apikey=...
    
    GET https://api.twelvedata.com/price?symbol=XAU/USD&apikey=...

    Retry: 2 lần, delay 500ms.

    OANDA thay thế

    GET https://api-fxpractice.oanda.com/v3/instruments/XAU_USD/candles
      ?granularity=M5&count=100&price=M
      Authorization: Bearer ...

    🔑 Cấu hình (.env)

    # Database
    DATABASE_URL="file:./prisma/data.db"
    
    # Logging
    LOG_LEVEL=info
    
    # Market Data
    MARKET_PROVIDER=twelvedata          # hoặc "oanda"
    TRADING_INSTRUMENT=XAU/USD
    TRADING_TIMEFRAMES=M5,M15,H1        # khung thời gian phân tích
    TRADING_CANDLES_COUNT=100
    TRADING_MIN_RR=2.0
    
    # Market Hours (Asia/Ho_Chi_Minh)
    MARKET_HOURS_OPEN=6
    MARKET_HOURS_CLOSE=22
    MARKET_HOURS_TIMEZONE=Asia/Ho_Chi_Minh
    
    # API Keys
    TWELVEDATA_API_KEY=...
    OANDA_API_TOKEN=...
    OANDA_ACCOUNT_ID=...
    OANDA_ENV=practice                  # hoặc "live"
    CLAUDE_API_KEY=...
    CLAUDE_MODEL=claude-sonnet-4-6       # hoặc claude-opus-4-8, claude-haiku-4-5
    TELEGRAM_BOT_TOKEN=...
    TELEGRAM_CHAT_ID=...
    TELEGRAM_DISCUSSION_ID=...           # auto-resolve nếu để trống
    
    # API Server
    PORT=3000
    API_SERVER_KEY=...                   # để trống = không yêu cầu auth

    📦 NPM Scripts

    ScriptMô tả
    npm run devchạy tsx watch (development)
    npm startchạy compiled JS (production)
    npm run buildbiên dịch TypeScript → dist/
    npm run analyzechạy phân tích 1 lần (có check giờ)
    npm run analyze:forcechạy phân tích 1 lần (bỏ qua check giờ)
    npm run db:generategenerate Prisma client
    npm run db:pushsync schema → DB
    npm run db:migratechạy migration

    🗄️ Database Schema

    Bảng trading_signals — Index: (instrument, created_at)

    CộtKiểuMô tả
    idINT PKAuto increment
    instrumentSTRING"XAU/USD"
    actionSTRING"BUY" / "SELL" / "NO_TRADE"
    timeframeSTRING"M5"
    entryFLOATGiá vào lệnh
    stop_lossFLOATĐiểm dừng lỗ
    take_profitFLOATChốt lời (TP1)
    risk_rewardFLOATTỷ lệ R:R (vd: 2.0 = 1:2)
    confidenceINTĐộ tin cậy AI (0–100)
    current_priceFLOATGiá thị trường lúc phân tích
    reasoningSTRINGLý luận AI
    trend_biasSTRING"BULLISH" / "BEARISH" / "NEUTRAL"
    raw_ai_responseSTRINGToàn bộ JSON từ Claude
    indicators_snapshotSTRINGNến + metadata JSON
    telegram_message_idSTRINGID tin nhắn Telegram
    sent_atDATETIMEThời điểm gửi
    created_atDATETIMEThời điểm tạo

    📋 Quy tắc quan trọng khi sửa code


    ↑ Lên đầu trang