diff --git a/SECURITY_INCIDENT_GITEA_CREDENTIALS.md b/SECURITY_INCIDENT_GITEA_CREDENTIALS.md index 958a3d2..e37f3f1 100644 --- a/SECURITY_INCIDENT_GITEA_CREDENTIALS.md +++ b/SECURITY_INCIDENT_GITEA_CREDENTIALS.md @@ -37,37 +37,22 @@ File này nằm trong repo private `hanlap/trading-portal` tại `git.dangloica. 5. Nếu bất kỳ ai trong 4 người dùng lại mật khẩu đã lộ ở nơi khác (email, hệ thống nội bộ khác...), **nhắc họ đổi luôn ở những nơi đó** — thói quen dùng lại mật khẩu là rủi ro thực tế phổ biến nhất sau một vụ lộ như thế này. 6. Nếu Gitea hỗ trợ, bật 2FA cho các tài khoản có quyền ghi vào repo. -### Bước 2 — Quyết định có rewrite lịch sử git hay không +### Bước 2 — Rewrite lịch sử git — ✅ Đã chuẩn bị xong, chỉ còn 1 bước của bạn -Hai lựa chọn, đánh đổi rõ ràng: +Đã dùng [`git filter-repo`](https://github.com/newren/git-filter-repo) để thay thế cả 4 mật khẩu bằng `***REMOVED***` trong **toàn bộ lịch sử git** (đã verify: 0 lần xuất hiện qua `git log --all -p`). Lịch sử đã rewrite được đẩy lên nhánh tạm `history-rewrite-2026-07-03` trên origin (không đụng tới `master`) để bạn kiểm tra trước: -**Lựa chọn A — Không rewrite (đơn giản, đủ dùng sau khi đã rotate)** -Giữ nguyên lịch sử, chấp nhận rằng commit cũ vẫn chứa mật khẩu *đã bị vô hiệu hoá*. Rủi ro còn lại gần như bằng 0 miễn là bước 1 đã hoàn tất và không ai dùng lại các mật khẩu đó ở nơi khác. +`https://git.dangloica.org/hanlap/trading-portal/src/branch/history-rewrite-2026-07-03` -**Lựa chọn B — Rewrite lịch sử (triệt để hơn, nhưng gây gián đoạn team)** -Dùng [`git filter-repo`](https://github.com/newren/git-filter-repo) (khuyến nghị hơn BFG vì được git chính thức giới thiệu): +> Lưu ý: nhánh này đã được cập nhật 2 lần — lần đầu dựa trên `dca7a49`, lần 2 (hiện tại) dựa trên `d9afca4` sau khi `hanlap` push thêm 1 commit hotfix, để không làm mất commit đó khi force-push. + +**Việc còn lại — chỉ bạn chạy được** (force-push vào `master` nằm ngoài phạm vi Claude được phép tự động thực hiện): ```bash -# Cài đặt (một lần) -pip install git-filter-repo - -# Backup trước khi làm bất cứ điều gì -git clone --mirror https://git.dangloica.org/hanlap/trading-portal.git trading-portal-backup.git - -# Trong bản clone đang làm việc, xoá nội dung nhạy cảm khỏi mọi commit -cd trading-portal -git filter-repo --path ARCHITECTURE.md --invert-paths --force # nếu chấp nhận xoá hẳn file khỏi lịch sử -# HOẶC — chỉ thay thế các chuỗi mật khẩu cụ thể bằng "***REMOVED***" trong mọi commit -# thay vì xoá cả file. Lấy nguyên văn 4 mật khẩu từ commit c734e3d (`git show c734e3d:ARCHITECTURE.md`) -# và điền vào file thay-thế cục bộ (KHÔNG commit file này!), mỗi dòng dạng `mật_khẩu==>***REMOVED***`: -git filter-repo --replace-text /path/to/local-only-passwords-to-redact.txt - -# Sau khi kiểm tra kỹ, force-push -git push origin --force --all -git push origin --force --tags +git push origin history-rewrite-2026-07-03:master --force +git push origin --delete history-rewrite-2026-07-03 # dọn nhánh tạm sau khi xong ``` -Sau khi force-push, **thông báo ngay cho cả 4 người còn lại**: họ phải chạy (không được `git pull` bình thường vì sẽ tạo merge conflict với history cũ): +Sau khi force-push, **thông báo ngay cho `hanlap`, `nguyendao`, `tulinh`, `namcunguyen`**: họ phải chạy (không được `git pull` bình thường vì sẽ tạo merge conflict với history cũ): ```bash git fetch origin @@ -88,5 +73,6 @@ hoặc đơn giản nhất là xoá bản clone cũ và clone lại từ đầu. | Gỡ bảng mật khẩu khỏi ARCHITECTURE.md (bản hiện tại) | ✅ Đã làm (2026-07-03, cùng đợt review này) | | Rotate mật khẩu `namcunguyen` | ✅ Đã đổi (2026-07-03) — credential mới đã cấu hình trong Git Credential Manager và dùng để push commit `210ccab..c609c2b` | | Rotate mật khẩu `hanlap`, `nguyendao`, `tulinh` | ⏳ Chờ bạn thực hiện trên Gitea | -| Quyết định rewrite lịch sử git | ⏳ Chờ bạn quyết định (Lựa chọn A hoặc B ở trên) | +| Rewrite lịch sử git (filter-repo, verify 0 mật khẩu còn sót) | ✅ Đã làm (2026-07-03), đẩy lên nhánh `history-rewrite-2026-07-03` | +| Force-push nhánh rewrite đè `master` + báo team resync | ⏳ Chờ bạn chạy (xem Bước 2 ở trên) | | Bật 2FA cho các tài khoản ghi | ⏳ Chờ bạn thực hiện | diff --git a/theo_doi_trading-portal_v4.md b/theo_doi_trading-portal_v4.md new file mode 100644 index 0000000..7eb18e8 --- /dev/null +++ b/theo_doi_trading-portal_v4.md @@ -0,0 +1,141 @@ +# Theo dõi đánh giá dự án Trading Portal — v4 + +> **Ngày đánh giá gốc:** 2026-07-03 +> **Cập nhật v1-v3:** xem các file `theo_doi_trading-portal_v1.md`…`v3.md` +> **Cập nhật v4 (lần này):** 2026-07-03 — rewrite lịch sử git xong (a), fix bug (p), test `indicator_service.py` + phần async của `signal_service.py`, phát hiện 2 quirk nhỏ mới (q, r), và ghi nhận 1 sự cố quy trình (s) +> **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_v5.md`, ... giữ nguyên các bản cũ làm lịch sử. + +--- + +## 1. Tổng quan dự án + +(Không đổi — xem [theo_doi_trading-portal_v2.md](theo_doi_trading-portal_v2.md) mục 1.) + +--- + +## 2. Ưu điểm + +Giữ nguyên danh sách ở v3. Test suite giờ đã lên **122 test** (từ 82 ở v3), bao phủ thêm toàn bộ thư viện chỉ báo kỹ thuật (`indicator_service.py`) và phần async quản lý vòng đời trade/signal (`close_stale_trades`, `expire_old_signals`). + +--- + +## 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** +**Trạng thái:** 🟢 **Redact lịch sử hoàn tất, chờ bước cuối từ bạn.** +- Đã rotate mật khẩu `namcunguyen` (v2/v3). +- Đã dùng `git filter-repo` redact cả 4 mật khẩu khỏi **toàn bộ lịch sử git** (verify: 0 lần xuất hiện trong `git log --all -p`), đẩy lên nhánh `history-rewrite-2026-07-03` trên origin — **2 lần**, vì lần đầu làm xong thì `hanlap` push thêm 1 commit hotfix lên `master` (xem mục (s) bên dưới) khiến nhánh rewrite lần 1 bị lỗi thời (thiếu commit đó); đã làm lại lần 2 để bao gồm đầy đủ. +- **Còn lại — cần bạn tự chạy** (force-push vào `master` là hành động mình không được phép tự động thực hiện): + ```bash + git push origin history-rewrite-2026-07-03:master --force + git push origin --delete history-rewrite-2026-07-03 + ``` + Sau đó báo `hanlap`, `nguyendao`, `tulinh` chạy `git fetch origin && git reset --hard origin/master` (hoặc clone lại). +- Vẫn cần bạn: rotate mật khẩu 3 tài khoản còn lại (`hanlap`, `nguyendao`, `tulinh`), bật 2FA. Xem [SECURITY_INCIDENT_GITEA_CREDENTIALS.md](SECURITY_INCIDENT_GITEA_CREDENTIALS.md). + +**b, c, d, f, g** — ✅ Đã xử lý (không đổi từ v2/v3). + +**e. Không có test suite / CI thực sự** +**Trạng thái:** 🟢 **Phần lớn đã xử lý.** Test suite tăng từ 82 → **122 test**: + +| File | Số test | Bao phủ | +|---|---:|---| +| (8 file cũ từ v3) | 82 | RBAC, order routing, mã hoá, CORS, Kelly/SL-TP, trade_executor, signal scoring | +| `tests/test_indicator_service.py` | 33 | sma, ema, rsi, bollinger_bands, macd, atr, vwap, obv, obv_signal, mfi, detect_market_regime | +| `tests/test_signal_service_async.py` | 7 | `close_stale_trades` (time limit, SL, TP, trailing stop) + `expire_old_signals` | +| **Tổng** | **122** | — | + +**Còn thiếu:** `analyse_and_generate_signals`/`_do_analysis`/`_manage_trades` (entry point chính, phụ thuộc nhiều side-effect: WS push, notification, DB candle) chưa test — chi phí mock cao, giá trị gia tăng thấp hơn so với phần đã làm; CI Gitea Actions vẫn chưa xác nhận có runner thật để chạy. + +### 🟡 Kiến trúc & chất lượng code + +Không đổi so với v3 (h, i, j, k, l, m). + +**p. Eviction dùng nhầm giá cross-symbol trong `trade_executor.py`** +**Trạng thái:** ✅ **Đã xử lý (2026-07-03)** — sửa để mỗi trade dùng đúng giá nến mới nhất theo symbol/exchange/timeframe của chính nó (batched lookup, cùng pattern đã dùng đúng trong `close_stale_trades`), thay vì dùng `current_price` của tín hiệu đang xử lý cho mọi trade. Có test [test_hybrid_eviction_uses_each_trades_own_symbol_price_not_incoming_signal_price](backend/tests/test_trade_executor.py) dựng kịch bản cụ thể: một trade lỗ thật ở giá riêng của nó nhưng sẽ bị tính nhầm thành lãi lớn nếu dùng giá tín hiệu khác — xác nhận sau khi sửa, hệ thống evict đúng trade lỗ, không phải trade cũ nhất theo FIFO. + +**q. [MỚI] RSI trả giá trị sai khi giá hoàn toàn đi ngang** +Trong `indicator_service.py::rsi()`, khi giá không đổi trong toàn bộ chu kỳ (avg_gain=0 và avg_loss=0), code gán `rs = 50.0 # no movement` nhưng vẫn đưa qua công thức `RSI = 100 - 100/(1+rs)`, cho ra RSI ≈ **98.04** thay vì 50 (trung tính) như comment code ngụ ý. Với cặp giao dịch kém thanh khoản/stablecoin đứng giá nhiều kỳ, hệ thống có thể hiểu nhầm là "quá mua" (overbought) trong khi thực chất không có biến động gì. +**Trạng thái:** ⏳ Chưa xử lý — mình chưa tự sửa vì đây là logic tính điểm ảnh hưởng trực tiếp hệ thống giao dịch đang chạy, cần bạn xác nhận trước. Đã có test ghi lại hành vi thật hiện tại ở `test_flat_prices_do_not_yield_neutral_50`. + +**r. [MỚI] MFI có lỗi bao quanh chỉ số (index wraparound) ở điểm dữ liệu đầu tiên** +Trong `indicator_service.py::mfi()`, ở điểm tính đầu tiên của một chuỗi nến, vòng lặp so sánh `typical_prices[j-1]` với `j=0` — theo cú pháp Python, `j-1 = -1` sẽ lấy **phần tử CUỐI CÙNG** của mảng thay vì "không có gì để so sánh". Điều này làm điểm MFI đầu tiên trong chuỗi bị lệch (ví dụ: đáng lẽ 100.0 lại ra 69.7). **Tác động thực tế rất thấp** vì `signal_service.py` chỉ đọc `mfi_data[-1]` (giá trị mới nhất, không phải giá trị đầu tiên của chuỗi) nên lỗi này gần như không ảnh hưởng production. +**Trạng thái:** ⏳ Ghi nhận, không ưu tiên sửa (tác động thực tế gần như bằng 0). Có test minh hoạ ở `test_first_computed_value_has_a_wraparound_indexing_quirk`. + +### 🔵 Sự cố quy trình + +**s. [MỚI] Lỗi build TypeScript do sửa `replace_all` không khớp hết 2 vị trí** +Khi fix (c) ở v1, mình sửa `DashboardPage.tsx` để truyền prop `exchange` xuống `OrderPanel` ở cả layout desktop và mobile bằng `Edit(replace_all=true)`. Hai khối `` có **thụt lề khác nhau** (desktop 16 spaces, mobile 18 spaces vì lồng sâu hơn trong overlay div) nên `old_string` chỉ khớp đúng 1 trong 2 khối — bản mobile bị bỏ sót, thiếu prop `exchange` bắt buộc → **lỗi build TypeScript**. `hanlap` đã tự phát hiện và vá đúng chỗ (commit `d9afca4` trên `master`, trước khi mình biết chuyện). +**Bài học áp dụng cho lần sau:** khi dùng `replace_all=true` cho các khối code lặp lại có thể khác nhau (thụt lề, biến, thứ tự prop...), phải tự kiểm tra `grep` toàn bộ số lần xuất hiện thực tế của pattern đích **sau khi sửa**, không chỉ tin vào thông báo "successfully replaced" của tool (thông báo đó chỉ đảm bảo ≥1 chỗ được sửa, không đảm bảo sửa hết mọi chỗ định sửa). +**Trạng thái:** ✅ Đã được `hanlap` vá kịp thời; đã đồng bộ lại local + rewrite lịch sử để bao gồm commit vá đó. + +### 🟢 Vận hành + +Không đổi so với v3 (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 | Force-push nhánh `history-rewrite-2026-07-03` đè `master`, xoá nhánh tạm, báo team resync (a) | 🟡 Chờ bạn chạy 2 lệnh | +| Ngay lập tức | Rotate mật khẩu `hanlap`, `nguyendao`, `tulinh` (a) | ⏳ | +| Đã xong | (b), (c), (d), (f), (g), (p) | ✅ | +| Ngắn hạn | Test trade_executor/risk_manager/signal_service/indicator_service/close_stale_trades (e) | 🟢 122 test | +| Ngắn hạn | Xác nhận Gitea Actions/runner đã bật | ⏳ | +| Trung hạn | Quyết định có sửa quirk RSI (q) hay không | ⏳ Chờ quyết định | +| Trung hạn | (h), (i), (j), (l) | ⏳ | +| Thấp | Quirk MFI (r) — tác động gần như 0 | ⏳ Không ưu tiên | +| Dài hạn | (m) backtest walk-forward | ⏳ | + +--- + +## 5. Chi tiết thay đổi trong v4 (2026-07-03) + +### File mới + +| File | Nội dung | +|---|---| +| `backend/tests/test_indicator_service.py` | 33 test cho thư viện chỉ báo kỹ thuật | +| `backend/tests/test_signal_service_async.py` | 7 test cho `close_stale_trades` + `expire_old_signals` | + +### File đã sửa + +| File | Thay đổi | +|---|---| +| `backend/app/services/trade_executor.py` | (p) Fix eviction dùng đúng giá per-symbol thay vì giá tín hiệu đang xử lý | +| `backend/tests/test_trade_executor.py` | Thêm test cho fix (p) | +| `backend/tests/conftest.py` | Thêm bảng `Symbol`/`Candle`; thêm fixture `session_factory` (trả về `async_sessionmaker` thay vì 1 session, để test các hàm tự mở session riêng như `close_stale_trades`); thêm `_UTCDateTime` TypeDecorator để SQLite giữ được tzinfo UTC qua round-trip (SQLite vốn không hỗ trợ lưu tz-aware datetime, khác Postgres) | +| `ARCHITECTURE.md`, `SECURITY_INCIDENT_GITEA_CREDENTIALS.md` | Không đổi nội dung, nhưng giờ nằm trong lịch sử git đã được redact | + +### Lịch sử git + +- Rewrite lần 1: dựa trên `dca7a49` (10 commit) → push lên `history-rewrite-2026-07-03`. +- Phát hiện `hanlap` đã push thêm `d9afca4` lên `master` (vá lỗi TypeScript từ sự cố (s)) trong lúc đó. +- Rewrite lần 2: dựa trên `d9afca4` (11 commit, bao gồm cả commit vá của hanlap) → force-push đè lên nhánh `history-rewrite-2026-07-03`. +- **Còn lại**: bạn tự force-push nhánh này vào `master` (xem mục (a) ở trên). + +### Cách chạy test (không đổi) + +```bash +cd backend +./.venv/Scripts/python -m pytest tests/ -v +``` + +Kết quả: **122/122 test pass**, ~4 giây. + +--- + +## 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), CI Gitea Actions, phát hiện (p) | +| v4 | 2026-07-03 | Rewrite lịch sử git (a) 2 lần, fix (p), test indicator_service + async signal_service (122 test), phát hiện (q)/(r), ghi nhận sự cố quy trình (s) |