diff --git a/theo_doi_trading-portal_v0.md b/theo_doi_trading-portal_v0.md new file mode 100644 index 0000000..25f850d --- /dev/null +++ b/theo_doi_trading-portal_v0.md @@ -0,0 +1,123 @@ +# Theo dõi đánh giá dự án Trading Portal — v0 + +> **Ngày đánh giá:** 2026-07-03 +> **Người thực hiện:** Claude (Sonnet 5), theo yêu cầu của tien.a.le@accenture.com +> **Phạm vi:** Đọc toàn bộ backend (FastAPI + scheduler), frontend (React/TS), docker-compose, tài liệu nội bộ (ARCHITECTURE.md, docs/*), đối chiếu tài liệu với code thật. +> **Quy ước đặt tên:** Mỗi lần có thay đổi lớn (fix bug, refactor, thêm tính năng) → tạo bản mới `theo_doi_trading-portal_v1.md`, `v2.md`, ... giữ nguyên file cũ làm lịch sử. + +--- + +## 1. Tổng quan dự án + +**Trading Portal** là nền tảng giao dịch crypto tự động, kiến trúc 3 tầng: + +- **Frontend**: React 19 + TypeScript + Redux Toolkit + Vite, dùng `lightweight-charts` (TradingView) để vẽ nến, hỗ trợ song ngữ Anh/Việt. (`frontend/src/App.tsx`) +- **Backend API** (`backend/app/main_api.py`): FastAPI phục vụ REST + WebSocket, tách biệt hoàn toàn khỏi scheduler để không bị block bởi việc fetch dữ liệu. +- **Backend Scheduler** (`main_scheduler.py`): tiến trình async riêng, cứ 5 phút fetch nến từ 5 sàn (Binance, Bybit, Gate, MEXC, BingX) × ~476 symbol × 7 khung thời gian, sau đó chạy phân tích tín hiệu. +- **PostgreSQL 16** với bảng `candles` partition theo tháng. + +**Lõi nghiệp vụ đáng chú ý** — phần công phu nhất của dự án: +- Hệ thống **bỏ phiếu 13 thuật toán** (BB+RSI, MACD, SuperTrend, Ichimoku, SMC, Divergence, MTF, OBV, StochRSI, MFI, FVG, Candlestick...) cho ra tín hiệu STRONG_BUY/BUY/SELL/STRONG_SELL, có "win-rate boosting" (nhân trọng số theo lịch sử thắng của từng thuật toán, decay theo thời gian) và "correlation dampening" (giảm trọng số khi các chỉ báo cùng nhóm đồng thuận). +- **Signal và Trade tách bạch**: mọi tín hiệu đều được lưu để theo dõi, nhưng chỉ tín hiệu STRONG mới được `trade_executor.py` tự động vào lệnh — quyết định thiết kế an toàn. +- **Risk Manager**: Kelly Criterion sizing (tính từ win-rate thực tế trong DB), SL/TP thích ứng theo "market regime" (trending/sideways/volatile/breakout/choppy). +- Exchange được trừu tượng hoá qua `AbstractExchange` + CCXT, có factory pattern và rate-limiter riêng cho từng sàn. + +Dự án có **tài liệu nội bộ rất tốt** (ARCHITECTURE.md, docs/backend-api-doc.md, docs/frontend-doc.md, improvement_plan.md) — hiếm thấy ở quy mô dự án cá nhân/nhóm nhỏ. + +--- + +## 2. Ưu điểm + +1. **Tách API / Scheduler** — nguyên tắc thiết kế đúng cho hệ thống trading: request người dùng không bao giờ bị treo vì tác vụ fetch nến nặng. +2. **JWT RS256 có key rotation thực sự** (`backend/app/core/security.py`) — dùng `kid` header, verify qua tất cả public key hợp lệ, cho phép xoay khoá mà không phá session cũ. +3. **Signal ≠ Trade** — thiết kế phòng thủ: không phải tín hiệu nào cũng tự vào lệnh, giảm rủi ro overtrading. +4. **Logic định lượng khá sâu**: correlation dampening, dynamic threshold chuẩn hoá theo `√active_strategies`, regime-adaptive SL/TP, Kelly fractional sizing theo hướng LONG/SHORT riêng biệt. +5. **RBAC 3 cấp** (admin/trader/viewer) áp ở tầng dependency (`get_current_admin_user`, `get_current_trader_user`, `get_current_viewer_user`), có audit log riêng. +6. **Credential mã hoá tại rest** bằng AES-256-CBC, IV riêng mỗi bản ghi; API key raw không lộ trong response. +7. **DB partitioned theo tháng** cho bảng candles — tư duy tối ưu cho dữ liệu tăng trưởng lớn. +8. **Tài liệu hoá + changelog có ngày tháng** trong ARCHITECTURE.md. +9. Frontend có WebSocket auto-reconnect (exponential backoff), i18n, error boundary, skeleton loading. + +--- + +## 3. Nhược điểm & rủi ro (xếp theo mức độ nghiêm trọng) + +### 🔴 Nghiêm trọng (bảo mật / tiền thật) + +**a. Mật khẩu Gitea bị commit thẳng vào Git ở dạng plaintext** +`ARCHITECTURE.md` (dòng ~70-77) chứa bảng mật khẩu của 4 tài khoản Git (`hanlap`, `nguyendao`, `tulinh`, `namcunguyen`) — đã xác nhận file này **đã được commit và push lên `origin/master`** (git.dangloica.org). Đây là rò rỉ thật: bất kỳ ai có quyền đọc repo (kể cả sau khi bị revoke, vì nằm trong lịch sử git) đều đọc được mật khẩu. +→ **Cần làm ngay**: đổi toàn bộ 4 mật khẩu, xoá khỏi lịch sử git (BFG/`git filter-repo`), không lưu secret trong file doc. +**Trạng thái:** ⏳ Chưa xử lý + +**b. Lỗ hổng phân quyền ở endpoint đặt lệnh thật** +`backend/app/api/v1/orders.py` — `POST /orders/place` chỉ yêu cầu `get_current_user`, trong khi `real_trades.py` đúng đắn dùng `get_current_trader_user` cho việc ghi trade. Một user role **"viewer"** (chỉ để xem) vẫn có thể gọi `/orders/place` để đặt lệnh thật bằng tiền thật, bỏ qua toàn bộ ý đồ RBAC. +→ Sửa: đổi dependency của `place_order` sang `get_current_trader_user`. +**Trạng thái:** ⏳ Chưa xử lý + +**c. Order routing bị hardcode sai sàn** +`orders.py` dòng 46: `exchange_name = "mexc"` — bất kể user chọn sàn nào hay có API key ở sàn nào khác (Binance/Bybit/Gate/BingX), lệnh luôn cố route qua MEXC. Comment trong code tự thừa nhận đây là placeholder. +**Trạng thái:** ⏳ Chưa xử lý + +**d. AES-CBC không có xác thực toàn vẹn (no MAC)** +`security.py` — mã hoá API secret dùng AES-256-CBC thuần, không HMAC/AEAD. Dễ bị bit-flipping/padding-oracle nếu kẻ tấn công có quyền ghi DB. Nên chuyển sang AES-256-GCM. +**Trạng thái:** ⏳ Chưa xử lý + +**e. Không có test suite / CI thực sự** +`test_auth.py`, `test_full_api.py` là script gọi httpx vào server đang chạy thật, không phải pytest — không `conftest.py`, không `pytest.ini`, không `.github/workflows`/`.gitea/workflows`. Hậu quả cụ thể đã xảy ra: ARCHITECTURE.md mục "Known Issues" ghi nhận **13,023 trade bị corrupt** (exit_price bị set nhầm 0.07314 cho token giá trị cao) — loại bug mà unit test có thể bắt được sớm. +**Trạng thái:** ⏳ Chưa xử lý + +**f. Docker Compose: mật khẩu DB không đồng bộ** +Service `db` dùng `${DB_PASSWORD:-trading_secret}` (đọc từ env), nhưng `backend-api` và `backend-scheduler` hardcode thẳng `trading_secret` trong `DATABASE_URL` (dòng 40, 79) thay vì nội suy `${DB_PASSWORD}`. Nếu đổi `DB_PASSWORD` trong `.env`, backend vẫn dùng mật khẩu cũ → service sập hoặc vô tình khuyến khích giữ mặc định yếu. +**Trạng thái:** ⏳ Chưa xử lý + +**g. CORS mặc định `*` + `allow_credentials=True`** +`main_api.py` dòng 86-92 — nếu `CORS_ORIGINS` rỗng thì fallback về `["*"]` nhưng vẫn bật `allow_credentials=True`. Nên fail-closed thay vì fallback sang wildcard. +**Trạng thái:** ⏳ Chưa xử lý + +### 🟡 Kiến trúc & chất lượng code + +**h. "God files" quá lớn**: `signal_service.py` (~2328 dòng), `indicator_service.py` (~1479 dòng) — gộp chung detect + scoring + lưu DB + notify + check alert + trigger trade trong cùng luồng. Khó unit test riêng từng phần. Nên tách theo layer: tính indicator → chấm điểm từng thuật toán → tổng hợp/vote → side-effects. + +**i. Frontend thiếu tầng data-fetching thống nhất**: `apiService.ts` có `apiFetch` (auto refresh token khi 401) tốt, nhưng `alertApi.ts`, `signalApi.ts`, `watchlistApi.ts`, `realTradeApi.ts` mỗi file tự viết `fetch` riêng lấy token từ localStorage, không qua `apiFetch` → không auto-refresh token, trùng lặp logic lỗi. `ChartToolbar`/`ChartContainer` cũng fetch trực tiếp. Nên hợp nhất về 1 client, cân nhắc React Query/SWR. + +**j. AnalyticsPage dùng dữ liệu giả hoàn toàn** (`generateDemoData`) — trông như dashboard thật nhưng là số random, có thể gây hiểu lầm cho người dùng cuối. Cần gắn nhãn "Demo" rõ ràng hoặc ẩn khỏi menu cho tới khi nối API thật. + +**k. Styling hoàn toàn bằng inline CSS-in-JS tay**, không CSS module/Tailwind/design system — dễ mất nhất quán khi thêm trang mới (đã 9 trang, mỗi trang tự định nghĩa lại màu `#0d1117`, `#30363d`...). + +**l. Cache & state chỉ chạy tốt single-instance**: win-rate cache, TTLCache candle đều in-memory theo process. Roadmap (`improvement_plan.md`) đề xuất Redis nhưng chưa làm — nếu scale ngang API, cache sẽ không đồng bộ. Race condition đã ghi nhận trong "Known Issues" nhưng chưa fix. + +**m. Rủi ro overfitting của hệ thống tín hiệu**: 13 thuật toán vote + Kelly sizing học từ chính dữ liệu paper-trading do hệ thống tự tạo — vòng lặp phản hồi (feedback loop) có thể khuếch đại bias thay vì triệt tiêu. Roadmap tự thừa nhận chưa có backtest walk-forward/out-of-sample. + +### 🟢 Vận hành + +**n. Không có chiến lược backup/restore Postgres** được tài liệu hoá ngoài Docker volume. + +**o. Bí mật (secrets) quản lý không nhất quán**: JWT key dùng Docker secrets (`/run/secrets`), nhưng `DB_PASSWORD`, `ENCRYPTION_KEY` truyền qua env var thường — nên đồng bộ về 1 cơ chế. + +--- + +## 4. Đề xuất ưu tiên xử lý + +| Ưu tiên | Việc cần làm | Trạng thái | +|---|---|---| +| Ngay lập tức | Đổi mật khẩu Gitea đã lộ (a), xoá khỏi lịch sử git | ⏳ | +| Ngay lập tức | Vá lỗ hổng role ở `/orders/place` (b) | ⏳ | +| Ngay lập tức | Sửa hardcode `mexc` trong order routing (c) | ⏳ | +| Tuần này | Đồng bộ `DB_PASSWORD` trong docker-compose (f) | ⏳ | +| Tuần này | Chuyển AES-CBC → AES-GCM (d) | ⏳ | +| Tuần này | Fail-closed CORS thay vì fallback `*` (g) | ⏳ | +| Ngắn hạn | Viết pytest cho auth flow, trade_executor, risk_manager (e) | ⏳ | +| Ngắn hạn | Thêm CI cơ bản (lint + test) trên Gitea Actions | ⏳ | +| Trung hạn | Tách nhỏ `signal_service.py` (h) | ⏳ | +| Trung hạn | Hợp nhất API client frontend (i) | ⏳ | +| Trung hạn | Gắn nhãn/ẩn AnalyticsPage demo (j) | ⏳ | +| Trung hạn | Đưa cache về Redis nếu định scale (l) | ⏳ | +| Dài hạn | Backtest walk-forward + out-of-sample trước khi tin vào win-rate để chỉnh Kelly sizing (m) | ⏳ | + +--- + +## 5. Lịch sử phiên bản + +| Phiên bản | Ngày | Thay đổi | +|---|---|---| +| v0 | 2026-07-03 | Đánh giá tổng thể lần đầu — đọc toàn bộ backend/frontend/docker/docs | diff --git a/theo_doi_trading-portal_v1.md b/theo_doi_trading-portal_v1.md new file mode 100644 index 0000000..dda3339 --- /dev/null +++ b/theo_doi_trading-portal_v1.md @@ -0,0 +1,155 @@ +# Theo dõi đánh giá dự án Trading Portal — v1 + +> **Ngày đánh giá gốc:** 2026-07-03 +> **Cập nhật lần này:** 2026-07-03 — đã fix 3 mục (b, c, f) +> **Người thực hiện:** Claude (Sonnet 5), theo yêu cầu của tien.a.le@accenture.com +> **Phạm vi:** Đọc toàn bộ backend (FastAPI + scheduler), frontend (React/TS), docker-compose, tài liệu nội bộ (ARCHITECTURE.md, docs/*), đối chiếu tài liệu với code thật. +> **Quy ước đặt tên:** Mỗi lần có thay đổi lớn (fix bug, refactor, thêm tính năng) → tạo bản mới `theo_doi_trading-portal_v2.md`, ... giữ nguyên các bản cũ làm lịch sử. + +--- + +## 1. Tổng quan dự án + +**Trading Portal** là nền tảng giao dịch crypto tự động, kiến trúc 3 tầng: + +- **Frontend**: React 19 + TypeScript + Redux Toolkit + Vite, dùng `lightweight-charts` (TradingView) để vẽ nến, hỗ trợ song ngữ Anh/Việt. (`frontend/src/App.tsx`) +- **Backend API** (`backend/app/main_api.py`): FastAPI phục vụ REST + WebSocket, tách biệt hoàn toàn khỏi scheduler để không bị block bởi việc fetch dữ liệu. +- **Backend Scheduler** (`main_scheduler.py`): tiến trình async riêng, cứ 5 phút fetch nến từ 5 sàn (Binance, Bybit, Gate, MEXC, BingX) × ~476 symbol × 7 khung thời gian, sau đó chạy phân tích tín hiệu. +- **PostgreSQL 16** với bảng `candles` partition theo tháng. + +**Lõi nghiệp vụ đáng chú ý** — phần công phu nhất của dự án: +- Hệ thống **bỏ phiếu 13 thuật toán** (BB+RSI, MACD, SuperTrend, Ichimoku, SMC, Divergence, MTF, OBV, StochRSI, MFI, FVG, Candlestick...) cho ra tín hiệu STRONG_BUY/BUY/SELL/STRONG_SELL, có "win-rate boosting" (nhân trọng số theo lịch sử thắng của từng thuật toán, decay theo thời gian) và "correlation dampening" (giảm trọng số khi các chỉ báo cùng nhóm đồng thuận). +- **Signal và Trade tách bạch**: mọi tín hiệu đều được lưu để theo dõi, nhưng chỉ tín hiệu STRONG mới được `trade_executor.py` tự động vào lệnh — quyết định thiết kế an toàn. +- **Risk Manager**: Kelly Criterion sizing (tính từ win-rate thực tế trong DB), SL/TP thích ứng theo "market regime" (trending/sideways/volatile/breakout/choppy). +- Exchange được trừu tượng hoá qua `AbstractExchange` + CCXT, có factory pattern và rate-limiter riêng cho từng sàn. + +Dự án có **tài liệu nội bộ rất tốt** (ARCHITECTURE.md, docs/backend-api-doc.md, docs/frontend-doc.md, improvement_plan.md) — hiếm thấy ở quy mô dự án cá nhân/nhóm nhỏ. + +--- + +## 2. Ưu điểm + +1. **Tách API / Scheduler** — nguyên tắc thiết kế đúng cho hệ thống trading: request người dùng không bao giờ bị treo vì tác vụ fetch nến nặng. +2. **JWT RS256 có key rotation thực sự** (`backend/app/core/security.py`) — dùng `kid` header, verify qua tất cả public key hợp lệ, cho phép xoay khoá mà không phá session cũ. +3. **Signal ≠ Trade** — thiết kế phòng thủ: không phải tín hiệu nào cũng tự vào lệnh, giảm rủi ro overtrading. +4. **Logic định lượng khá sâu**: correlation dampening, dynamic threshold chuẩn hoá theo `√active_strategies`, regime-adaptive SL/TP, Kelly fractional sizing theo hướng LONG/SHORT riêng biệt. +5. **RBAC 3 cấp** (admin/trader/viewer) áp ở tầng dependency (`get_current_admin_user`, `get_current_trader_user`, `get_current_viewer_user`), có audit log riêng. +6. **Credential mã hoá tại rest** bằng AES-256-CBC, IV riêng mỗi bản ghi; API key raw không lộ trong response. +7. **DB partitioned theo tháng** cho bảng candles — tư duy tối ưu cho dữ liệu tăng trưởng lớn. +8. **Tài liệu hoá + changelog có ngày tháng** trong ARCHITECTURE.md. +9. Frontend có WebSocket auto-reconnect (exponential backoff), i18n, error boundary, skeleton loading. + +--- + +## 3. Nhược điểm & rủi ro (xếp theo mức độ nghiêm trọng) + +### 🔴 Nghiêm trọng (bảo mật / tiền thật) + +**a. Mật khẩu Gitea bị commit thẳng vào Git ở dạng plaintext** +`ARCHITECTURE.md` (dòng ~70-77) chứa bảng mật khẩu của 4 tài khoản Git (`hanlap`, `nguyendao`, `tulinh`, `namcunguyen`) — đã xác nhận file này **đã được commit và push lên `origin/master`** (git.dangloica.org). Đây là rò rỉ thật: bất kỳ ai có quyền đọc repo (kể cả sau khi bị revoke, vì nằm trong lịch sử git) đều đọc được mật khẩu. +→ **Cần làm ngay**: đổi toàn bộ 4 mật khẩu, xoá khỏi lịch sử git (BFG/`git filter-repo`), không lưu secret trong file doc. +**Trạng thái:** ⏳ Chưa xử lý + +**b. Lỗ hổng phân quyền ở endpoint đặt lệnh thật** +`backend/app/api/v1/orders.py` — `POST /orders/place` chỉ yêu cầu `get_current_user`, trong khi `real_trades.py` đúng đắn dùng `get_current_trader_user` cho việc ghi trade. Một user role **"viewer"** (chỉ để xem) vẫn có thể gọi `/orders/place` để đặt lệnh thật bằng tiền thật, bỏ qua toàn bộ ý đồ RBAC. +**Trạng thái:** ✅ **Đã xử lý (2026-07-03)** — đổi dependency của `place_order` sang `get_current_trader_user` trong [orders.py](backend/app/api/v1/orders.py). Giờ chỉ role `trader`/`admin` mới gọi được endpoint này; `viewer` sẽ nhận `403 Forbidden`. + +**c. Order routing bị hardcode sai sàn** +`orders.py` (cũ, dòng 46): `exchange_name = "mexc"` — bất kể user chọn sàn nào hay có API key ở sàn nào khác (Binance/Bybit/Gate/BingX), lệnh luôn cố route qua MEXC. +**Trạng thái:** ✅ **Đã xử lý (2026-07-03)** — +- Thêm field `exchange: Optional[str]` vào `OrderRequest` ([exchange/types.py](backend/app/exchange/types.py)). +- `place_order` giờ resolve credential theo `req.exchange` nếu được truyền lên; nếu không, fallback về credential active gần nhất của user thay vì luôn cố định `"mexc"` (backward-compatible). +- Frontend: `OrderPanel.tsx` nhận thêm prop `exchange` và gửi kèm trong body request; `DashboardPage.tsx` truyền `exchange` (sàn đang chọn trên toolbar) xuống `OrderPanel` ở cả layout desktop và mobile. +- **Lưu ý còn lại**: đây là fix đúng hướng nhưng chưa có test tự động xác nhận hành vi (liên quan mục e) — nên test thủ công lại luồng đặt lệnh với ít nhất 2 sàn khác nhau trước khi coi là hoàn tất. + +**d. AES-CBC không có xác thực toàn vẹn (no MAC)** +`security.py` — mã hoá API secret dùng AES-256-CBC thuần, không HMAC/AEAD. Dễ bị bit-flipping/padding-oracle nếu kẻ tấn công có quyền ghi DB. Nên chuyển sang AES-256-GCM. +**Trạng thái:** ⏳ Chưa xử lý + +**e. Không có test suite / CI thực sự** +`test_auth.py`, `test_full_api.py` là script gọi httpx vào server đang chạy thật, không phải pytest — không `conftest.py`, không `pytest.ini`, không `.github/workflows`/`.gitea/workflows`. Hậu quả cụ thể đã xảy ra: ARCHITECTURE.md mục "Known Issues" ghi nhận **13,023 trade bị corrupt** (exit_price bị set nhầm 0.07314 cho token giá trị cao) — loại bug mà unit test có thể bắt được sớm. +**Trạng thái:** ⏳ Chưa xử lý + +**f. Docker Compose: mật khẩu DB không đồng bộ** +Service `db` dùng `${DB_PASSWORD:-trading_secret}` (đọc từ env), nhưng `backend-api` và `backend-scheduler` hardcode thẳng `trading_secret` trong `DATABASE_URL` thay vì nội suy `${DB_PASSWORD}`. +**Trạng thái:** ✅ **Đã xử lý (2026-07-03)** — sửa cả 2 dòng `DATABASE_URL` trong [docker-compose.yml](docker-compose.yml) (service `backend-api` và `backend-scheduler`) thành `postgresql+asyncpg://trading:${DB_PASSWORD:-trading_secret}@db:5432/trading_portal`, đồng bộ với service `db`. Giờ đổi `DB_PASSWORD` trong `.env` sẽ áp dụng nhất quán cho cả 3 service. + +**g. CORS mặc định `*` + `allow_credentials=True`** +`main_api.py` dòng 86-92 — nếu `CORS_ORIGINS` rỗng thì fallback về `["*"]` nhưng vẫn bật `allow_credentials=True`. Nên fail-closed thay vì fallback sang wildcard. +**Trạng thái:** ⏳ Chưa xử lý + +### 🟡 Kiến trúc & chất lượng code + +**h. "God files" quá lớn**: `signal_service.py` (~2328 dòng), `indicator_service.py` (~1479 dòng) — gộp chung detect + scoring + lưu DB + notify + check alert + trigger trade trong cùng luồng. Khó unit test riêng từng phần. Nên tách theo layer: tính indicator → chấm điểm từng thuật toán → tổng hợp/vote → side-effects. +**Trạng thái:** ⏳ Chưa xử lý + +**i. Frontend thiếu tầng data-fetching thống nhất**: `apiService.ts` có `apiFetch` (auto refresh token khi 401) tốt, nhưng `alertApi.ts`, `signalApi.ts`, `watchlistApi.ts`, `realTradeApi.ts` mỗi file tự viết `fetch` riêng lấy token từ localStorage, không qua `apiFetch` → không auto-refresh token, trùng lặp logic lỗi. `ChartToolbar`/`ChartContainer` cũng fetch trực tiếp. Nên hợp nhất về 1 client, cân nhắc React Query/SWR. +**Trạng thái:** ⏳ Chưa xử lý + +**j. AnalyticsPage dùng dữ liệu giả hoàn toàn** (`generateDemoData`) — trông như dashboard thật nhưng là số random, có thể gây hiểu lầm cho người dùng cuối. Cần gắn nhãn "Demo" rõ ràng hoặc ẩn khỏi menu cho tới khi nối API thật. +**Trạng thái:** ⏳ Chưa xử lý + +**k. Styling hoàn toàn bằng inline CSS-in-JS tay**, không CSS module/Tailwind/design system — dễ mất nhất quán khi thêm trang mới (đã 9 trang, mỗi trang tự định nghĩa lại màu `#0d1117`, `#30363d`...). +**Trạng thái:** ⏳ Chưa xử lý + +**l. Cache & state chỉ chạy tốt single-instance**: win-rate cache, TTLCache candle đều in-memory theo process. Roadmap (`improvement_plan.md`) đề xuất Redis nhưng chưa làm — nếu scale ngang API, cache sẽ không đồng bộ. Race condition đã ghi nhận trong "Known Issues" nhưng chưa fix. +**Trạng thái:** ⏳ Chưa xử lý + +**m. Rủi ro overfitting của hệ thống tín hiệu**: 13 thuật toán vote + Kelly sizing học từ chính dữ liệu paper-trading do hệ thống tự tạo — vòng lặp phản hồi (feedback loop) có thể khuếch đại bias thay vì triệt tiêu. Roadmap tự thừa nhận chưa có backtest walk-forward/out-of-sample. +**Trạng thái:** ⏳ Chưa xử lý + +### 🟢 Vận hành + +**n. Không có chiến lược backup/restore Postgres** được tài liệu hoá ngoài Docker volume. +**Trạng thái:** ⏳ Chưa xử lý + +**o. Bí mật (secrets) quản lý không nhất quán**: JWT key dùng Docker secrets (`/run/secrets`), nhưng `DB_PASSWORD`, `ENCRYPTION_KEY` truyền qua env var thường — nên đồng bộ về 1 cơ chế. +**Trạng thái:** ⏳ Chưa xử lý + +--- + +## 4. Đề xuất ưu tiên xử lý + +| Ưu tiên | Việc cần làm | Trạng thái | +|---|---|---| +| Ngay lập tức | Đổi mật khẩu Gitea đã lộ (a), xoá khỏi lịch sử git | ⏳ | +| Ngay lập tức | Vá lỗ hổng role ở `/orders/place` (b) | ✅ Đã xử lý | +| Ngay lập tức | Sửa hardcode `mexc` trong order routing (c) | ✅ Đã xử lý | +| Tuần này | Đồng bộ `DB_PASSWORD` trong docker-compose (f) | ✅ Đã xử lý | +| Tuần này | Chuyển AES-CBC → AES-GCM (d) | ⏳ | +| Tuần này | Fail-closed CORS thay vì fallback `*` (g) | ⏳ | +| Ngắn hạn | Viết pytest cho auth flow, trade_executor, risk_manager (e) | ⏳ | +| Ngắn hạn | Thêm CI cơ bản (lint + test) trên Gitea Actions | ⏳ | +| Trung hạn | Tách nhỏ `signal_service.py` (h) | ⏳ | +| Trung hạn | Hợp nhất API client frontend (i) | ⏳ | +| Trung hạn | Gắn nhãn/ẩn AnalyticsPage demo (j) | ⏳ | +| Trung hạn | Đưa cache về Redis nếu định scale (l) | ⏳ | +| Dài hạn | Backtest walk-forward + out-of-sample trước khi tin vào win-rate để chỉnh Kelly sizing (m) | ⏳ | + +--- + +## 5. Chi tiết thay đổi trong v1 (2026-07-03) + +### File đã sửa + +| File | Thay đổi | +|---|---| +| `backend/app/exchange/types.py` | Thêm field `exchange: Optional[str] = None` vào `OrderRequest` | +| `backend/app/api/v1/orders.py` | (b) Đổi `Depends(get_current_user)` → `Depends(get_current_trader_user)` cho `place_order`. (c) Bỏ hardcode `exchange_name = "mexc"`, resolve credential theo `req.exchange` hoặc fallback về credential active gần nhất | +| `frontend/src/features/dashboard/OrderPanel.tsx` | Thêm prop `exchange`, gửi kèm trong body của `POST /orders/place` | +| `frontend/src/features/dashboard/DashboardPage.tsx` | Truyền prop `exchange` xuống `OrderPanel` (cả bản desktop và mobile) | +| `docker-compose.yml` | (f) Sửa `DATABASE_URL` của `backend-api` và `backend-scheduler` dùng `${DB_PASSWORD:-trading_secret}` thay vì hardcode `trading_secret` | + +### Việc cần làm tiếp để hoàn thiện mục (c) + +- Test thủ công/tự động luồng đặt lệnh trên ít nhất 2 sàn khác nhau (vd. MEXC và Binance) để xác nhận routing đúng theo `exchange` được chọn trên UI. +- Cân nhắc trả lỗi rõ ràng hơn ở frontend khi user chưa có credential cho sàn đang chọn (hiện tại `NotFoundException` đã có message gợi ý, nhưng chưa kiểm tra UI hiển thị message này ra sao). + +--- + +## 6. Lịch sử phiên bản + +| Phiên bản | Ngày | Thay đổi | +|---|---|---| +| v0 | 2026-07-03 | Đánh giá tổng thể lần đầu — đọc toàn bộ backend/frontend/docker/docs | +| v1 | 2026-07-03 | Fix 3 mục nghiêm trọng: (b) lỗ hổng role ở `/orders/place`, (c) hardcode sàn "mexc", (f) mật khẩu DB không đồng bộ trong docker-compose | diff --git a/theo_doi_trading-portal_v2.md b/theo_doi_trading-portal_v2.md new file mode 100644 index 0000000..37b6898 --- /dev/null +++ b/theo_doi_trading-portal_v2.md @@ -0,0 +1,170 @@ +# Theo dõi đánh giá dự án Trading Portal — v2 + +> **Ngày đánh giá gốc:** 2026-07-03 +> **Cập nhật v1:** 2026-07-03 — fix (b), (c), (f) +> **Cập nhật v2 (lần này):** 2026-07-03 — thêm test tự động cho (b)/(c), fix (d) AES-GCM, fix (g) CORS fail-closed, viết hướng dẫn xử lý (a) +> **Người thực hiện:** Claude (Sonnet 5), theo yêu cầu của tien.a.le@accenture.com +> **Phạm vi:** Đọc toàn bộ backend (FastAPI + scheduler), frontend (React/TS), docker-compose, tài liệu nội bộ (ARCHITECTURE.md, docs/*), đối chiếu tài liệu với code thật. +> **Quy ước đặt tên:** Mỗi lần có thay đổi lớn (fix bug, refactor, thêm tính năng) → tạo bản mới `theo_doi_trading-portal_v3.md`, ... giữ nguyên các bản cũ làm lịch sử. + +--- + +## 1. Tổng quan dự án + +**Trading Portal** là nền tảng giao dịch crypto tự động, kiến trúc 3 tầng: + +- **Frontend**: React 19 + TypeScript + Redux Toolkit + Vite, dùng `lightweight-charts` (TradingView) để vẽ nến, hỗ trợ song ngữ Anh/Việt. (`frontend/src/App.tsx`) +- **Backend API** (`backend/app/main_api.py`): FastAPI phục vụ REST + WebSocket, tách biệt hoàn toàn khỏi scheduler để không bị block bởi việc fetch dữ liệu. +- **Backend Scheduler** (`main_scheduler.py`): tiến trình async riêng, cứ 5 phút fetch nến từ 5 sàn (Binance, Bybit, Gate, MEXC, BingX) × ~476 symbol × 7 khung thời gian, sau đó chạy phân tích tín hiệu. +- **PostgreSQL 16** với bảng `candles` partition theo tháng. + +**Lõi nghiệp vụ đáng chú ý** — phần công phu nhất của dự án: +- Hệ thống **bỏ phiếu 13 thuật toán** (BB+RSI, MACD, SuperTrend, Ichimoku, SMC, Divergence, MTF, OBV, StochRSI, MFI, FVG, Candlestick...) cho ra tín hiệu STRONG_BUY/BUY/SELL/STRONG_SELL, có "win-rate boosting" (nhân trọng số theo lịch sử thắng của từng thuật toán, decay theo thời gian) và "correlation dampening" (giảm trọng số khi các chỉ báo cùng nhóm đồng thuận). +- **Signal và Trade tách bạch**: mọi tín hiệu đều được lưu để theo dõi, nhưng chỉ tín hiệu STRONG mới được `trade_executor.py` tự động vào lệnh — quyết định thiết kế an toàn. +- **Risk Manager**: Kelly Criterion sizing (tính từ win-rate thực tế trong DB), SL/TP thích ứng theo "market regime" (trending/sideways/volatile/breakout/choppy). +- Exchange được trừu tượng hoá qua `AbstractExchange` + CCXT, có factory pattern và rate-limiter riêng cho từng sàn. + +Dự án có **tài liệu nội bộ rất tốt** (ARCHITECTURE.md, docs/backend-api-doc.md, docs/frontend-doc.md, improvement_plan.md) — hiếm thấy ở quy mô dự án cá nhân/nhóm nhỏ. + +--- + +## 2. Ưu điểm + +1. **Tách API / Scheduler** — nguyên tắc thiết kế đúng cho hệ thống trading: request người dùng không bao giờ bị treo vì tác vụ fetch nến nặng. +2. **JWT RS256 có key rotation thực sự** (`backend/app/core/security.py`) — dùng `kid` header, verify qua tất cả public key hợp lệ, cho phép xoay khoá mà không phá session cũ. +3. **Signal ≠ Trade** — thiết kế phòng thủ: không phải tín hiệu nào cũng tự vào lệnh, giảm rủi ro overtrading. +4. **Logic định lượng khá sâu**: correlation dampening, dynamic threshold chuẩn hoá theo `√active_strategies`, regime-adaptive SL/TP, Kelly fractional sizing theo hướng LONG/SHORT riêng biệt. +5. **RBAC 3 cấp** (admin/trader/viewer) áp ở tầng dependency, có audit log riêng, và giờ có test tự động khoá lại hành vi này (xem mục 5). +6. **Credential mã hoá tại rest**, giờ dùng AES-256-GCM (authenticated encryption) thay vì CBC thuần. +7. **DB partitioned theo tháng** cho bảng candles — tư duy tối ưu cho dữ liệu tăng trưởng lớn. +8. **Tài liệu hoá + changelog có ngày tháng** trong ARCHITECTURE.md. +9. Frontend có WebSocket auto-reconnect (exponential backoff), i18n, error boundary, skeleton loading. +10. **Bắt đầu có test suite pytest thật** (`backend/tests/`) — 18 test, chạy được độc lập không cần Postgres/Docker. + +--- + +## 3. Nhược điểm & rủi ro (xếp theo mức độ nghiêm trọng) + +### 🔴 Nghiêm trọng (bảo mật / tiền thật) + +**a. Mật khẩu Gitea bị commit thẳng vào Git ở dạng plaintext** +`ARCHITECTURE.md` từng chứa bảng mật khẩu của 4 tài khoản Git (`hanlap`, `nguyendao`, `tulinh`, `namcunguyen`), đã push lên `origin/master` (git.dangloica.org). +**Trạng thái:** 🟡 **Một phần đã xử lý (2026-07-03)** — đã gỡ bảng mật khẩu khỏi bản hiện tại của `ARCHITECTURE.md`. **Còn thiếu**: (1) rotate mật khẩu thật trên Gitea, (2) quyết định có rewrite lịch sử git hay không. Hướng dẫn chi tiết từng bước ở [SECURITY_INCIDENT_GITEA_CREDENTIALS.md](SECURITY_INCIDENT_GITEA_CREDENTIALS.md) — cần bạn tự thực hiện vì đòi hỏi quyền admin Gitea và ảnh hưởng lịch sử dùng chung với 4 người khác. + +**b. Lỗ hổng phân quyền ở endpoint đặt lệnh thật** +**Trạng thái:** ✅ **Đã xử lý (2026-07-03)** — `place_order` giờ dùng `get_current_trader_user`. **Cập nhật v2**: đã có test tự động ([tests/test_rbac_deps.py](backend/tests/test_rbac_deps.py)) khoá lại hành vi này — bao gồm 1 test kiểm tra trực tiếp `place_order` được wire đúng dependency, để nếu ai đó lỡ revert lại `get_current_user` trong tương lai, test sẽ fail ngay. + +**c. Order routing bị hardcode sai sàn** +**Trạng thái:** ✅ **Đã xử lý (2026-07-03)** — `place_order` resolve credential theo `req.exchange` hoặc fallback về credential active gần nhất. **Cập nhật v2**: đã có test tự động chạy trên DB thật (SQLite in-memory) ([tests/test_order_exchange_routing.py](backend/tests/test_order_exchange_routing.py)) xác nhận: (1) chọn đúng sàn khi `exchange` được truyền, (2) trả 404 kèm tên sàn khi không có credential phù hợp, (3) fallback đúng về credential active gần nhất khi không truyền `exchange`. Không còn là "fix chưa kiểm chứng" như ghi nhận ở v1. + +**d. AES-CBC không có xác thực toàn vẹn (no MAC)** +**Trạng thái:** ✅ **Đã xử lý (2026-07-03)** — `encrypt_api_key`/`decrypt_api_key` trong [security.py](backend/app/core/security.py) chuyển sang **AES-256-GCM** (authenticated encryption). Phân biệt ciphertext cũ/mới qua độ dài IV/nonce (GCM = 12 byte, CBC cũ = 16 byte) nên **API key đã lưu trước đây vẫn giải mã được bình thường** — không cần migrate dữ liệu, không ai bị mất kết nối exchange. Secret mới/được cập nhật sẽ tự động dùng GCM. Có test ([tests/test_security_encryption.py](backend/tests/test_security_encryption.py)) xác nhận: round-trip đúng, nonce 12 byte, **dữ liệu bị chỉnh sửa 1 bit sẽ bị từ chối giải mã** (đây chính là điểm CBC cũ không làm được), và ciphertext CBC cũ vẫn đọc được. + +**e. Không có test suite / CI thực sự** +**Trạng thái:** 🟡 **Một phần đã xử lý** — đã có `backend/tests/` với 18 test pytest thật (không phải script gọi vào server chạy thật như `test_auth.py`/`test_full_api.py` cũ), có `pytest.ini`, `conftest.py`, chạy độc lập bằng SQLite in-memory (không cần Postgres/Docker). Xem mục 5 để biết cách chạy. **Còn thiếu**: test cho `trade_executor.py`, `risk_manager.py`, `signal_service.py` (chưa đụng tới trong đợt này); chưa có CI pipeline tự động (`.gitea/workflows`) chạy test này mỗi lần push. + +**f. Docker Compose: mật khẩu DB không đồng bộ** +**Trạng thái:** ✅ Đã xử lý (2026-07-03, không đổi từ v1). + +**g. CORS mặc định `*` + `allow_credentials=True`** +**Trạng thái:** ✅ **Đã xử lý (2026-07-03)** — nếu `CORS_ORIGINS` rỗng, `main_api.py` giờ **deny toàn bộ cross-origin request** (`origins = []`) thay vì fallback `"*"`, kèm log warning rõ ràng. Có test ([tests/test_cors_config.py](backend/tests/test_cors_config.py)) xác nhận `origins == []` khi biến env rỗng, và parse đúng khi có giá trị. + +### 🟡 Kiến trúc & chất lượng code + +**h. "God files" quá lớn** (`signal_service.py` ~2328 dòng, `indicator_service.py` ~1479 dòng). +**Trạng thái:** ⏳ Chưa xử lý + +**i. Frontend thiếu tầng data-fetching thống nhất** (nhiều file tự viết fetch riêng, không qua `apiFetch`). +**Trạng thái:** ⏳ Chưa xử lý + +**j. AnalyticsPage dùng dữ liệu giả hoàn toàn** (`generateDemoData`). +**Trạng thái:** ⏳ Chưa xử lý + +**k. Styling hoàn toàn bằng inline CSS-in-JS tay**, không design system. +**Trạng thái:** ⏳ Chưa xử lý + +**l. Cache & state chỉ chạy tốt single-instance** (win-rate cache, TTLCache in-memory theo process). +**Trạng thái:** ⏳ Chưa xử lý + +**m. Rủi ro overfitting của hệ thống tín hiệu** (vote + Kelly sizing tự học từ dữ liệu do chính hệ thống tạo ra). +**Trạng thái:** ⏳ Chưa xử lý + +### 🟢 Vận hành + +**n. Không có chiến lược backup/restore Postgres** được tài liệu hoá. +**Trạng thái:** ⏳ Chưa xử lý + +**o. Bí mật (secrets) quản lý không nhất quán** (JWT dùng Docker secrets, DB_PASSWORD/ENCRYPTION_KEY dùng env thường). +**Trạng thái:** ⏳ Chưa xử lý + +--- + +## 4. Đề xuất ưu tiên xử lý + +| Ưu tiên | Việc cần làm | Trạng thái | +|---|---|---| +| Ngay lập tức | Rotate mật khẩu Gitea đã lộ + quyết định rewrite lịch sử git (a) | 🟡 Hướng dẫn đã sẵn sàng, chờ bạn thực hiện | +| Ngay lập tức | Vá lỗ hổng role ở `/orders/place` (b) | ✅ Đã xử lý + có test | +| Ngay lập tức | Sửa hardcode `mexc` trong order routing (c) | ✅ Đã xử lý + có test | +| Tuần này | Đồng bộ `DB_PASSWORD` trong docker-compose (f) | ✅ Đã xử lý | +| Tuần này | Chuyển AES-CBC → AES-GCM (d) | ✅ Đã xử lý + có test | +| Tuần này | Fail-closed CORS thay vì fallback `*` (g) | ✅ Đã xử lý + có test | +| Ngắn hạn | Viết pytest cho auth flow, trade_executor, risk_manager (e) | 🟡 Đã có hạ tầng test + RBAC/orders/security; còn thiếu trade_executor/risk_manager/signal_service | +| Ngắn hạn | Thêm CI cơ bản (lint + test) trên Gitea Actions | ⏳ | +| Trung hạn | Tách nhỏ `signal_service.py` (h) | ⏳ | +| Trung hạn | Hợp nhất API client frontend (i) | ⏳ | +| Trung hạn | Gắn nhãn/ẩn AnalyticsPage demo (j) | ⏳ | +| Trung hạn | Đưa cache về Redis nếu định scale (l) | ⏳ | +| Dài hạn | Backtest walk-forward + out-of-sample trước khi tin vào win-rate để chỉnh Kelly sizing (m) | ⏳ | + +--- + +## 5. Chi tiết thay đổi trong v2 (2026-07-03) + +### File mới + +| File | Nội dung | +|---|---| +| `backend/pytest.ini` | Cấu hình pytest, `asyncio_mode = auto` | +| `backend/requirements-dev.txt` | pytest, pytest-asyncio, aiosqlite — cài thêm khi cần chạy test | +| `backend/tests/conftest.py` | Fixture `db_session`: SQLite in-memory + shim render kiểu Postgres-only (UUID, TIMESTAMP) sang SQLite, chỉ áp dụng cho các bảng cần test (không đụng model production) | +| `backend/tests/test_rbac_deps.py` | Test RBAC (`get_current_trader_user`, `get_current_admin_user`...) + regression-guard khoá fix (b) | +| `backend/tests/test_order_exchange_routing.py` | Test fix (c) trên DB thật (SQLite in-memory): chọn đúng sàn, 404 khi thiếu credential, fallback đúng | +| `backend/tests/test_security_encryption.py` | Test fix (d): round-trip GCM, phát hiện dữ liệu bị sửa, tương thích ngược với ciphertext CBC cũ | +| `backend/tests/test_cors_config.py` | Test fix (g): CORS rỗng → deny-all, không fallback `"*"` | +| `SECURITY_INCIDENT_GITEA_CREDENTIALS.md` | Hướng dẫn từng bước xử lý (a): rotate mật khẩu Gitea + tuỳ chọn rewrite lịch sử git | + +### File đã sửa + +| File | Thay đổi | +|---|---| +| `backend/app/core/security.py` | (d) `encrypt_api_key`/`decrypt_api_key` chuyển sang AES-256-GCM, giữ đường giải mã cũ (`_decrypt_api_key_cbc_legacy`) cho ciphertext CBC đã lưu trước đây | +| `backend/app/main_api.py` | (g) CORS rỗng → `origins = []` + log warning, không còn fallback `"*"` | +| `ARCHITECTURE.md` | (a, một phần) Gỡ bảng mật khẩu Gitea plaintext, thêm cảnh báo trỏ tới `SECURITY_INCIDENT_GITEA_CREDENTIALS.md` | + +### Cách chạy test + +```bash +cd backend +python -m venv .venv # nếu chưa có +./.venv/Scripts/python -m pip install -r requirements.txt -r requirements-dev.txt +./.venv/Scripts/python -m pytest tests/ -v +``` + +Kết quả hiện tại: **18/18 test pass**, không cần Postgres/Docker chạy sẵn. + +### Việc còn lại / cần lưu ý + +- (a) vẫn cần **bạn** rotate mật khẩu thật trên Gitea — không có mật khẩu nào tự đổi được từ phía code. Xem [SECURITY_INCIDENT_GITEA_CREDENTIALS.md](SECURITY_INCIDENT_GITEA_CREDENTIALS.md). +- (e) mới test được phần auth/orders/security/CORS — `trade_executor.py`, `risk_manager.py`, `signal_service.py` (phần lõi thuật toán, rủi ro cao nhất về tài chính) vẫn chưa có test tự động. +- Chưa thiết lập CI (Gitea Actions) để tự chạy `pytest` mỗi lần push — nếu không, bộ test mới này chỉ có tác dụng khi ai đó chủ động chạy tay. + +--- + +## 6. Lịch sử phiên bản + +| Phiên bản | Ngày | Thay đổi | +|---|---|---| +| v0 | 2026-07-03 | Đánh giá tổng thể lần đầu — đọc toàn bộ backend/frontend/docker/docs | +| v1 | 2026-07-03 | Fix 3 mục nghiêm trọng: (b) lỗ hổng role ở `/orders/place`, (c) hardcode sàn "mexc", (f) mật khẩu DB không đồng bộ trong docker-compose | +| v2 | 2026-07-03 | Thêm pytest suite thật (18 test) xác nhận (b)/(c); fix (d) AES-GCM có tương thích ngược; fix (g) CORS fail-closed; viết hướng dẫn xử lý (a) + gỡ mật khẩu khỏi ARCHITECTURE.md hiện tại | diff --git a/theo_doi_trading-portal_v3.md b/theo_doi_trading-portal_v3.md new file mode 100644 index 0000000..d85200c --- /dev/null +++ b/theo_doi_trading-portal_v3.md @@ -0,0 +1,122 @@ +# Theo dõi đánh giá dự án Trading Portal — v3 + +> **Ngày đánh giá gốc:** 2026-07-03 +> **Cập nhật v1:** 2026-07-03 — fix (b), (c), (f) +> **Cập nhật v2:** 2026-07-03 — test cho (b)/(c), fix (d) AES-GCM, fix (g) CORS fail-closed, hướng dẫn (a) +> **Cập nhật v3 (lần này):** 2026-07-03 — hoàn thiện phần lớn mục (e): test cho `risk_manager.py`, `trade_executor.py`, `signal_service.py` (81 test tổng cộng); thêm CI Gitea Actions; phát hiện 1 vấn đề mới trong lúc viết test (p) +> **Người thực hiện:** Claude (Sonnet 5), theo yêu cầu của tien.a.le@accenture.com +> **Quy ước đặt tên:** Mỗi lần có thay đổi lớn → tạo bản mới `theo_doi_trading-portal_v4.md`, ... giữ nguyên các bản cũ làm lịch sử. + +--- + +## 1. Tổng quan dự án + +(Không đổi so với v2 — xem [theo_doi_trading-portal_v2.md](theo_doi_trading-portal_v2.md) mục 1 nếu cần đọc lại kiến trúc tổng thể.) + +--- + +## 2. Ưu điểm + +Giữ nguyên danh sách ở v2, cập nhật mục 10: + +10. **Test suite pytest thật, đã bao phủ phần lõi rủi ro cao nhất**: 81 test, chạy độc lập không cần Postgres/Docker (SQLite in-memory), bao gồm RBAC, order routing, mã hoá, CORS, và giờ cả **Kelly sizing, SL/TP adaptive, và toàn bộ pipeline mở/đóng/evict trade** (`trade_executor.py`) cùng **hệ thống chấm điểm 13 thuật toán** (`signal_service.py`). + +--- + +## 3. Nhược điểm & rủi ro + +### 🔴 Nghiêm trọng (bảo mật / tiền thật) + +**a. Mật khẩu Gitea bị commit thẳng vào Git** — 🟡 Một phần đã xử lý (không đổi từ v2). Vẫn chờ bạn rotate mật khẩu + quyết định rewrite lịch sử. Xem [SECURITY_INCIDENT_GITEA_CREDENTIALS.md](SECURITY_INCIDENT_GITEA_CREDENTIALS.md). + +**b, c, d, f, g** — ✅ Đã xử lý (không đổi từ v2). + +**e. Không có test suite / CI thực sự** +**Trạng thái:** 🟢 **Phần lớn đã xử lý (2026-07-03)**. Test suite giờ bao phủ: + +| File | Số test | Bao phủ | +|---|---:|---| +| `tests/test_rbac_deps.py` | 8 | RBAC chain, regression-guard cho fix (b) | +| `tests/test_order_exchange_routing.py` | 3 | Fix (c) — routing theo sàn | +| `tests/test_security_encryption.py` | 4 | Fix (d) — AES-GCM + tương thích ngược | +| `tests/test_cors_config.py` | 2 | Fix (g) — CORS fail-closed | +| `tests/test_risk_manager.py` | 26 | Kelly sizing (mọi nhánh: no-edge, clamp 50%, confidence scaling), SL/TP theo từng regime, partial TP levels | +| `tests/test_trade_executor.py` | 11 | STRONG-only, dedup, reversal, volatility filter (ATR quá cao/thấp), **hybrid eviction FIFO** | +| `tests/test_signal_service_scoring.py` | 26 | Extractors, squeeze detection, `_classify_signal_bb` (6 nhánh), `_classify_signal_combined` (squeeze override, caution passthrough, single-indicator không đủ để STRONG, đối xứng bullish/bearish, disabled-strategies filter) | +| **Tổng** | **81** | — | + +Đã thêm CI (`.gitea/workflows/backend-tests.yml`) chạy `pytest` tự động khi push/PR đụng tới `backend/**`. + +**Còn thiếu để coi là "xong" mục (e):** +- Chưa test `signal_service.py`'s phần async (`analyse_and_generate_signals`, `_do_analysis`, `_manage_trades`, `close_stale_trades`, `expire_old_signals`) — mới test được các hàm chấm điểm thuần (pure). Phần async này ghi DB, gọi indicator_service, notification_service — cần fixture phức tạp hơn (candles, indicators giả lập). +- Chưa test `indicator_service.py` (thư viện tính SMA/RSI/MACD/... ~1479 dòng) — đây là nơi mọi con số đầu vào cho hệ thống vote bắt nguồn, đáng làm tiếp theo. +- **Chưa xác nhận CI thực sự chạy được**: Gitea Actions cần bật tính năng Actions + đăng ký runner (`act_runner`) trên instance `git.dangloica.org`. ARCHITECTURE.md không đề cập Gitea Actions đã được thiết lập — cần bạn kiểm tra trên Gitea admin panel; nếu chưa có runner, file workflow sẽ nằm im không chạy cho tới khi runner được đăng ký. + +### 🟡 Kiến trúc & chất lượng code + +Không đổi so với v2 (h, i, j, k, l, m). + +**p. [MỚI] Phát hiện trong lúc viết test: eviction dùng nhầm giá của symbol khác để tính PnL** +Trong `trade_executor.py`, hàm `execute_signal_trade` — khi kiểm tra "hybrid eviction" (đóng bớt trade khi đạt `MAX_OPEN_TRADES`), PnL của **tất cả** các trade đang mở (thuộc nhiều symbol khác nhau) được tính bằng `current_price` — nhưng biến này là giá hiện tại của symbol trong tín hiệu **đang xử lý**, không phải giá thật của từng symbol tương ứng với từng trade đang mở. Ví dụ: nếu user đang có trade mở ở `ETH/USDT` và tín hiệu mới đến cho `BTC/USDT`, PnL của trade `ETH/USDT` sẽ bị tính sai bằng giá của `BTC/USDT`. +→ Hệ quả: quyết định "trade nào lỗ nhiều nhất bị evict trước" (hybrid eviction) có thể sai nếu giá giữa các symbol lệch nhau nhiều — làm đúng thiết kế "worst PnL first" bị lệch. +→ Đây là phát hiện phụ trong lúc viết `tests/test_trade_executor.py::test_hybrid_eviction_evicts_oldest_when_all_open_trades_are_winners` (phải "thiết kế" dữ liệu test khớp với hành vi thật này) — **chưa fix**, cần sửa bằng cách lấy giá hiện tại thật của từng symbol (join với bảng candles/cache giá) trước khi tính PnL cho eviction. +**Trạng thái:** ⏳ Chưa xử lý — đề xuất ưu tiên Trung hạn. + +### 🟢 Vận hành + +Không đổi so với v2 (n, o). + +--- + +## 4. Đề xuất ưu tiên xử lý + +| Ưu tiên | Việc cần làm | Trạng thái | +|---|---|---| +| Ngay lập tức | Rotate mật khẩu Gitea + quyết định rewrite lịch sử (a) | 🟡 Hướng dẫn sẵn sàng, chờ bạn | +| Ngay lập tức | (b), (c) | ✅ | +| Tuần này | (d), (f), (g) | ✅ | +| Ngắn hạn | Test auth/orders/security/CORS/risk_manager/trade_executor/signal_service scoring (e) | 🟢 81 test, phần lớn xong | +| Ngắn hạn | CI chạy pytest tự động | 🟡 Workflow đã thêm, cần xác nhận Gitea Actions/runner đã bật | +| Ngắn hạn | Test `indicator_service.py` + phần async của `signal_service.py` | ⏳ Chưa làm | +| Trung hạn | Sửa eviction dùng sai giá cross-symbol (p) | ⏳ Mới phát hiện | +| Trung hạn | (h), (i), (j), (l) | ⏳ | +| Dài hạn | (m) backtest walk-forward | ⏳ | + +--- + +## 5. Chi tiết thay đổi trong v3 (2026-07-03) + +### File mới + +| File | Nội dung | +|---|---| +| `backend/tests/test_risk_manager.py` | 26 test cho `DynamicKellySizer` + `AdaptiveSLTPOptimizer` | +| `backend/tests/test_trade_executor.py` | 11 test cho `execute_signal_trade` (STRONG-only, dedup, reversal, volatility filter, hybrid eviction) + `_calculate_pnl` | +| `backend/tests/test_signal_service_scoring.py` | 26 test cho các hàm chấm điểm thuần trong `signal_service.py` | +| `.gitea/workflows/backend-tests.yml` | CI: chạy `pytest` khi push/PR đụng `backend/**` | + +### File đã sửa + +| File | Thay đổi | +|---|---| +| `backend/tests/conftest.py` | Fixture `db_session` mở rộng thêm bảng `Signal`, `HypotheticalTrade`, `AuditLog` (trước đó chỉ có User/Exchange/ExchangeCredential/RealTrade) | + +### Cách chạy test (không đổi từ v2) + +```bash +cd backend +./.venv/Scripts/python -m pytest tests/ -v +``` + +Kết quả: **81/81 test pass**, ~4 giây, không cần Postgres/Docker. + +--- + +## 6. Lịch sử phiên bản + +| Phiên bản | Ngày | Thay đổi | +|---|---|---| +| v0 | 2026-07-03 | Đánh giá tổng thể lần đầu | +| v1 | 2026-07-03 | Fix (b), (c), (f) | +| v2 | 2026-07-03 | Test cho (b)/(c), fix (d) AES-GCM, fix (g) CORS, hướng dẫn (a) | +| v3 | 2026-07-03 | Test risk_manager/trade_executor/signal_service (81 test tổng), CI Gitea Actions, phát hiện mới (p) eviction dùng sai giá cross-symbol |