📊 Giới thiệu
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).
⚙️ Tech Stack
| Layer | Công nghệ |
|---|---|
| Runtime | Node.js (CommonJS) |
| Language | TypeScript 5.6 |
| Database | SQLite via Prisma 5.22 |
| AI | Claude — Anthropic (claude-sonnet-4-6) |
| Market Data | TwelveData (default) / OANDA |
| Notification | Telegram Bot API |
| Logging | Winston |
| HTTP Client | axios |
| API Server | Express |
📁 Cấu trúc thư mục
🔄 Luồng dữ liệu
🏗️ Design Patterns
| Pattern | Nơi dùng | Mô tả |
|---|---|---|
| Factory | MarketDataProviderFactory | Chọn provider theo config |
| Strategy | TwelveData / OANDA | Cùng implement interface MarketDataProvider |
| Static Factory | Service.fromConfig() | Thay vì DI container |
| DTO | AnalysisResult | Bọc output từ AI |
| Retry + Backoff | Claude & TwelveData | 4 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ả symbol và timeframes đề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
| Param | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
limit | integer | 20 | Số 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.
| Field | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
symbol | string | Có | Mã symbol, tự động uppercase (vd: XAU/USD) |
name | string | Có | Tê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).
| Param | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
limit | integer | 20 | Số 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)
- Phân tích kỹ thuật độc lập từng khung (H1 → M15 → M5)
- Review lệnh cũ (nếu có)
- Quyết định tổng thể (BUY / SELL / NO_TRADE)
Cấu trúc prompt
| Mục | Nội dung |
|---|---|
1A | CẤU TRÚC THỊ TRƯỜNG — H1, M15, M5 |
1B | VÙNG CUNG CẦU & KEY LEVELS |
1C | CHỈ BÁO KỸ THUẬT |
2 | QUYẾT ĐỊNH GIAO DỊCH |
3 | KẾ 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
- Gửi signal card dạng HTML (không dùng Markdown)
- Auto-split tin nhắn >4000 ký tự
- Discussion thread: tự resolve DISCUSSION_ID nếu chưa set
- Chuyển đổi markdown AI → HTML Telegram (bảng, bold, italic, bullet)
- Thanh confidence:
████████░░(10 ký tự)
Format signal card
📈 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
| Script | Mô tả |
|---|---|
npm run dev | chạy tsx watch (development) |
npm start | chạy compiled JS (production) |
npm run build | biên dịch TypeScript → dist/ |
npm run analyze | chạy phân tích 1 lần (có check giờ) |
npm run analyze:force | chạy phân tích 1 lần (bỏ qua check giờ) |
npm run db:generate | generate Prisma client |
npm run db:push | sync schema → DB |
npm run db:migrate | chạy migration |
🗄️ Database Schema
Bảng trading_signals — Index: (instrument, created_at)
| Cột | Kiểu | Mô tả |
|---|---|---|
id | INT PK | Auto increment |
instrument | STRING | "XAU/USD" |
action | STRING | "BUY" / "SELL" / "NO_TRADE" |
timeframe | STRING | "M5" |
entry | FLOAT | Giá vào lệnh |
stop_loss | FLOAT | Điểm dừng lỗ |
take_profit | FLOAT | Chốt lời (TP1) |
risk_reward | FLOAT | Tỷ lệ R:R (vd: 2.0 = 1:2) |
confidence | INT | Độ tin cậy AI (0–100) |
current_price | FLOAT | Giá thị trường lúc phân tích |
reasoning | STRING | Lý luận AI |
trend_bias | STRING | "BULLISH" / "BEARISH" / "NEUTRAL" |
raw_ai_response | STRING | Toàn bộ JSON từ Claude |
indicators_snapshot | STRING | Nến + metadata JSON |
telegram_message_id | STRING | ID tin nhắn Telegram |
sent_at | DATETIME | Thời điểm gửi |
created_at | DATETIME | Thời điểm tạo |
📋 Quy tắc quan trọng khi sửa code
- 1Khung thời gian: Hệ thống hiện dùng H1, M15, M5 — KHÔNG thêm lại W1/D1/H4 trừ khi được yêu cầu rõ ràng.
- 2Ngôn ngữ prompt: Prompt AI viết bằng tiếng Việt — giữ nguyên.
- 3Database: Dùng Prisma — không dùng raw SQL.
- 4Telegram: Dùng HTML parse_mode — không dùng MarkdownV2.
- 5Config: Mọi giá trị cứng phải lấy từ
src/config/trading.ts, không hardcode. - 6Logging: Dùng
loggertừsrc/logger.ts, không dùngconsole.log.
↑ Lên đầu trang