Chuyển tới nội dung chính

5 bài viết được gắn thẻ "python"

Xem tất cả thẻ

FIN3B – FIN BOT BYTE BIT

· 9 phút để đọc

FIN3B – FIN BOT BYTE BIT

Bot giao dịch định lượng theo xu hướng cho Crypto Futures Bitget​

https://miro.medium.com/1%2AkPMKD2UISQHd_nlSdpHw7A.png

https://www.bitget.com/baseasset/img/open_api/banner_img2.png

6

Fin3B – Fin Bot Byte Bit là bot giao dịch crypto futures được xây dựng theo tư duy định lượng, tập trung vào xác định xu hướng, kiểm định chiến lược và triển khai lệnh nhanh qua API Bitget , nhưng vẫn giữ nguyên tắc quản trị rủi ro trước khi trade thật.

Dự án hướng tới 3 mục tiêu chính:

  1. Thu thập và kích hoạt tín hiệu mua/bán theo đúng bối cảnh xu hướng
  2. Kiểm định lợi thế chiến lược bằng backtest
  3. Triển khai lệnh nhanh qua API Bitget nhưng không bỏ qua risk control

1. Tư duy cốt lõi khi xây bot​

Bot được thiết kế dựa trên nguyên tắc phân tích kỹ thuật chuyên nghiệp theo logic:

  • Trend First – Chỉ giao dịch khi xu hướng rõ ràng
  • Data Driven Decisions – Không vào lệnh theo cảm giác
  • Edge Validation – Chiến lược phải kiểm chứng được
  • Risk Managed Positioning – Không all-in, không quá đòn bẩy

Đây cũng là tư duy nền tảng mà CMT (Chartered Market Technician) hướng đến khi đào tạo trader chuyên nghiệp.


2. Kiến trúc vận hành của Fin3B​

Fin3B hoạt động theo pipeline 3 lớp:

a. Byte (Buy Signal Bot)​

  • Quét danh sách coin futures USDT trên Bitget
  • Đọc cấu trúc giá HH–HL hoặc LH–LL
  • Đánh giá xu hướng bằng MA6–10–20 và MACD
  • Tạo tín hiệu mua/bán khi đúng pha xu hướng

b. Test (Backtest Engine)​

  • Lấy dữ liệu lịch sử OHLCV từ API Bitget
  • Kiểm định quy tắc chiến lược
  • Đo lường hiệu suất: expectancy, RR, drawdown, streak
  • Lưu log ra CSV để trader đánh giá

c. Bit (Deploy nhanh qua Bitget API)​

  • Ký request REST trực tiếp vào API Bitget
  • Triển khai lệnh nhanh nhưng có tính toán vị thế theo risk %
  • Monitoring lệnh theo ngày: fee, funding, P&L thực
  • Cảnh báo khi xu hướng đổi pha hoặc drawdown cao

3. Các sai lầm mà Fin3B giúp tránh được​

  • Không phân biệt xu hướng → bot giao dịch sai pha
  • Không kiểm định chiến lược → bot tưởng edge nhưng không có
  • Tín hiệu trễ, mâu thuẫn khi dùng quá nhiều indicator
  • Không có SL/TP rõ ràng trước khi triển khai
  • Vào vị thế quá lớn so với tài khoản
  • Gồng lệnh ngược trend vì tin vào “indicator bất bại”

Fin3B không cố tìm chiến lược hoàn hảo, mà giúp bot:

Giao dịch đúng bối cảnh, đúng xu hướng, đúng khối lượng và có vòng kiểm định bắt buộc trước khi trade thật.


4. Mối liên hệ giữa Fin3B và khóa học sắp khai giảng​

Khóa học Phân Tích Kỹ Thuật – Chiến Lược Xu Hướng VIP của Hướng Nghiệp Dữ Liệu đang chuẩn bị khai giảng là môi trường phù hợp nhất để bạn:

  • Học cách xác định xu hướng bài bản bằng MA và MACD
  • Hiểu chu kỳ thị trường, sóng cung cầu và retest cấu trúc giá
  • Thực hành backtest chiến lược crypto đúng chuẩn
  • Xây hệ thống giao dịch có quy tắc rõ ràng, tái lập được
  • Có 6 buổi phụ đạo 1:1 để tối ưu chiến lược/bot cá nhân
  • Không đi theo đường tắt ảo tưởng “chén thánh”

Thông tin khóa học sắp khai giảng:


5. Chén thánh trong Fin3B không phải indicator​

Nếu có “chén thánh”, đó không phải:

  • một indicator,
  • một mô hình nến,
  • hay một công thức bí mật.

Mà là sự kết hợp có kiểm chứng giữa:

Thành phầnVai trò trong bot
Xu hướng (MA + cấu trúc giá)Định hướng lệnh
Động lượng (MACD + Volume)Đo cấp độ trend
Chu kỳ & nhịp sóngĐịnh thời điểm tham gia
Risk Control + Position SizeBảo vệ tài khoản
Kỷ luật triển khaiQuyết định hiệu quả

Khóa Xu Hướng VIP đang đào tạo học viên theo đúng khung tư duy này.


Kiến trúc Fin3B — ba khối và cách chúng nói chuyện với nhau​

Fin3B không phải một "bot thần kỳ". Nó là một hệ thống gồm ba khối rõ ràng, mỗi khối có trách nhiệm riêng và có thể kiểm thử độc lập.

flowchart TB
A[Raw Data Block - thu thap] --> B[Logic/AI Block - chuan hoa va sinh tin hieu]
B --> C[Risk Block - tinh khoi luong, SL/TP]
C --> D[Execution Block - dat lenh]
D --> E[Monitoring Block - giam sat va bao cao]
E -.->|phan hoi| B
KhốiTrách nhiệmNếu khối này hỏng thì sao?
Raw DataThu thập nến, tick, thông tin tài khoảnBot không có dữ liệu → dừng an toàn
Logic/AIChuẩn hoá, tính chỉ báo, sinh tín hiệuVào lệnh sai → nguy hiểm nhất
RiskTính khối lượng, SL/TP, giới hạn lỗRủi ro vượt kiểm soát → có thể cháy tài khoản
ExecutionGửi lệnh, xác nhận khớp, xử lý lỗiKhông vào được lệnh, hoặc vào lặp
MonitoringLog, cảnh báo, báo cáoBạn không biết bot đang làm gì

💡 Nguyên tắc thiết kế quan trọng nhất: mỗi khối phải thất bại an toàn. Nếu không lấy được dữ liệu, bot không được vào lệnh với dữ liệu cũ. Nếu tính khối lượng lỗi, bot phải từ chối giao dịch thay vì vào lệnh với số mặc định.

Khối Risk — phần quyết định bot sống hay chết​

from dataclasses import dataclass
from decimal import Decimal


@dataclass
class CauHinhRuiRo:
"""Toàn bộ tham số rủi ro ở một chỗ — dễ kiểm tra, dễ thay đổi."""

rui_ro_moi_lenh_pct: float = 1.0 # tối đa 1% số dư mỗi lệnh
so_lenh_toi_da_moi_ngay: int = 3
drawdown_dung_bot_pct: float = 10.0 # lỗ 10% thì tự dừng
he_so_atr_sl: float = 2.0
he_so_atr_tp: float = 4.0 # R:R = 1:2


class QuanTriRuiRo:
def __init__(self, cau_hinh: CauHinhRuiRo):
self.ch = cau_hinh
self.so_du_ban_dau: Decimal | None = None
self.so_lenh_hom_nay = 0
self.dang_dung = False

def ghi_nhan_so_du_ban_dau(self, so_du: Decimal) -> None:
if self.so_du_ban_dau is None:
self.so_du_ban_dau = so_du

def kiem_tra_drawdown(self, so_du_hien_tai: Decimal) -> bool:
"""Trả True nếu ĐƯỢC phép giao dịch tiếp."""
if self.so_du_ban_dau is None:
return True
drawdown = (1 - so_du_hien_tai / self.so_du_ban_dau) * 100
if drawdown >= self.ch.drawdown_dung_bot_pct:
self.dang_dung = True
return False
return True

def co_the_vao_lenh(self) -> tuple[bool, str]:
if self.dang_dung:
return False, "Bot đang tạm dừng do vượt ngưỡng drawdown"
if self.so_lenh_hom_nay >= self.ch.so_lenh_toi_da_moi_ngay:
return False, f"Đã đạt giới hạn {self.ch.so_lenh_toi_da_moi_ngay} lệnh/ngày"
return True, ""

def tinh_khoi_luong(self, so_du: Decimal, gia: float, atr: float) -> Decimal:
"""Khối lượng sao cho khi chạm SL chỉ mất đúng rui_ro_moi_lenh_pct."""
so_tien_rui_ro = so_du * Decimal(str(self.ch.rui_ro_moi_lenh_pct / 100))
khoang_cach_sl = atr * self.ch.he_so_atr_sl
if khoang_cach_sl <= 0:
raise ValueError("ATR không hợp lệ — không tính được khối lượng")
khoi_luong = so_tien_rui_ro / Decimal(str(gia)) / Decimal(str(khoang_cach_sl / gia))
return round(khoi_luong, 6)

Vì sao dùng Decimal thay float cho tiền: float có sai số dấu phẩy động. Cộng 0,1 + 0,2 trong Python cho ra 0.30000000000000004. Với hàng nghìn giao dịch, sai số này tích luỹ và có thể làm lệch báo cáo. Decimal chính xác tuyệt đối.

Khối Monitoring — biến bot từ "hộp đen" thành hệ thống kiểm soát được​

import csv
import logging
from datetime import datetime
from pathlib import Path

logger = logging.getLogger(__name__)

COT_NHAT_KY = [
"thoi_diem", "su_kien", "symbol", "huong", "khoi_luong",
"gia", "sl", "tp", "so_du", "ly_do",
]


class NhatKyGiaoDich:
def __init__(self, duong_dan: str = "nhat-ky-giao-dich.csv"):
self.path = Path(duong_dan)
if not self.path.exists():
with self.path.open("w", newline="", encoding="utf-8-sig") as f:
csv.writer(f).writerow(COT_NHAT_KY)

def ghi(self, su_kien: str, **thong_tin) -> None:
dong = {"thoi_diem": datetime.now().isoformat(timespec="seconds"),
"su_kien": su_kien}
dong.update({k: thong_tin.get(k, "") for k in COT_NHAT_KY if k not in dong})

with self.path.open("a", newline="", encoding="utf-8-sig") as f:
csv.DictWriter(f, fieldnames=COT_NHAT_KY).writerow(dong)

logger.info("[%s] %s", su_kien, {k: v for k, v in dong.items() if v != ""})

Dùng encoding="utf-8-sig" để Excel mở file CSV tiếng Việt không bị lỗi font — chi tiết nhỏ nhưng tiết kiệm rất nhiều thời gian.

Bốn câu hỏi kiểm định trước khi tin một chiến lược​

Fin3B hướng tới "kiểm định lợi thế". Đây là cách kiểm định cụ thể:

Câu hỏiCách trả lờiDấu hiệu cảnh báo
Chiến lược có lợi thế thật không?Backtest ≥ 500 lệnh, ≥ 2 năm dữ liệu< 100 lệnh → chưa đủ mẫu
Lợi thế có bền không?Chia dữ liệu thành 4 giai đoạn, xem từng giai đoạnChỉ 1 giai đoạn lãi → overfit
Có bị phụ thuộc tham số không?Đổi tham số ±20%, xem kết quảKết quả sụp hoàn toàn → quá nhạy
Có chịu được chi phí không?Trừ spread + phí vào mọi lệnhLãi biến mất sau khi trừ phí
import numpy as np
import pandas as pd


def kiem_dinh_on_dinh(ket_qua_lenh: pd.Series, so_phan: int = 4) -> pd.DataFrame:
"""Chia chuỗi lệnh thành nhiều giai đoạn — phát hiện overfit."""
doan = np.array_split(ket_qua_lenh.values, so_phan)

ban_ghi = []
for i, d in enumerate(doan, 1):
thang = (d > 0).sum()
ban_ghi.append({
"giai_doan": f"{i}/{so_phan}",
"so_lenh": len(d),
"ty_le_thang": round(thang / len(d) * 100, 1) if len(d) else 0,
"loi_nhuan": round(float(np.sum(d)), 4),
"dat": np.sum(d) > 0,
})
return pd.DataFrame(ban_ghi)


# Ví dụ minh hoạ: 400 lệnh mô phỏng
rng = np.random.default_rng(42)
lenh_mo_phong = pd.Series(rng.normal(0.004, 0.02, 400)) # kỳ vọng dương nhẹ
print(kiem_dinh_on_dinh(lenh_mo_phong))

⚠️ Đây là phép thử khiến nhiều chiến lược "đẹp" trên giấy bị loại. Nếu lợi nhuận chỉ đến từ một giai đoạn duy nhất, gần như chắc chắn đó là may mắn chứ không phải lợi thế.

Kết luận​

Fin3B – Fin Bot Byte Bit không hứa hẹn chiến lược hoàn hảo. Nó hướng tới mục tiêu thực tế hơn và bền vững hơn:

Xây bot giao dịch theo xu hướng có kiểm định lợi thế, triển khai nhanh nhưng không đánh mất quản trị rủi ro.

NGUYÊN LÝ TRONG PHÂN TÍCH KỸ THUẬT TRIẾT LÝ THỊ TRƯỜNG

· 11 phút để đọc

NGUYÊN LÝ TRONG PHÂN TÍCH KỸ THUẬT & TRIẾT LÝ THỊ TRƯỜNG

HIỂU BẢN CHẤT GIÁ ĐỂ KHÔNG BAO GIỜ GIAO DỊCH BẰNG CẢM XÚC​

https://thetechnicaltraders.com/wp-content/uploads/2022/07/MarketCycles.png?utm_source=chatgpt.com

https://www.miraeassetmf.co.in/images/default-source/default-album/annotation-2020-08-17-171957808fe46a5302445f90090cfd980f4f9a.png?utm_source=chatgpt.com

6

Triết lý thị trường là nền tảng quan trọng giúp trader tránh những sai lầm lớn và đưa ra quyết định sáng suốt. Bài 4 giúp học viên hiểu rõ chu kỳ tâm lý , tác động của đám đông , và quy luật chu kỳ giá – những yếu tố không thể bỏ qua khi xây dựng chiến lược giao dịch xu hướng VIP.


1. Chu kỳ tâm lý của nhà đầu tư​

https://priceactionninja.com/wp-content/uploads/2023/04/image-1.jpg?utm_source=chatgpt.com

https://media.onedayinjuly.com/media/images/Investor_Emotion_3.png?utm_source=chatgpt.com

https://miro.medium.com/1%2A-iKFWkSRqRYlxmde3Uo57A.jpeg?utm_source=chatgpt.com

Tâm lý chi phối phần lớn biến động giá. Hầu hết nhà đầu tư đều trải qua các cảm xúc:

  • Tự tin
  • Hưng phấn
  • Tham lam
  • Lo lắng
  • Sợ hãi
  • Hoảng loạn
  • Hy vọng

Chu kỳ tâm lý này lặp lại liên tục và chính là nguyên nhân tạo ra:

  • Sóng tăng – sóng giảm
  • Breakout ảo
  • Những pha rũ bỏ mạnh
  • Các nhịp điều chỉnh 3–4 nhịp

Trader có hệ thống cần nhìn thị trường bằng logic , không phải bằng cảm xúc. Hiểu được chu kỳ tâm lý giúp:

  • Không fomo khi giá tăng quá mạnh
  • Không bắt đáy khi thị trường hoảng loạn
  • Luôn chờ điểm vào hợp lý thay vì phản ứng theo cảm xúc

2. Tác động của đám đông lên giá​

https://m.media-amazon.com/images/I/51DwuobFdcL.AC_UF1000%2C1000_QL80.jpg?utm_source=chatgpt.com

https://fastercapital.com/i/Herding-behavior--Exploring-Herding-Behavior-in-Behavioral-Economics--Herding-Behavior-in-Financial-Markets.webp?utm_source=chatgpt.com

Thị trường vận động dựa trên:

  • Niềm tin chung
  • Hành vi bầy đàn
  • Sự lan truyền cảm xúc

Đám đông thường:

  • Mua tại đỉnh
  • Bán tại đáy
  • Tin vào tin tức muộn
  • Bị cuốn vào những biến động nhất thời

Người giao dịch theo xu hướng VIP không đi cùng đám đông khi thị trường nhiễu loạn. Họ:

  • Xem dòng tiền thực sự đang đi đâu
  • Xác định xu hướng thật thay vì chạy theo tin tức
  • Muốn mua trong lúc tích lũy, không mua khi thị trường đã nóng

3. Giá chạy theo chu kỳ​

https://lh7-rt.googleusercontent.com/docsz/AD_4nXcxFIG3SO5UQJx3mavJ2_dULKY0XazihBhBjYdIMxOpVmUdsOjQfmr-S8NxCGgys3Jjfwd--qPdk4wtLDtadHbhgYH8MjJpIYYeAkepMh8ampxLI7TO_dwIA7TXshP8Xz59XobtWkE976bTdAOWAVcSG9Vg?key=iWiUV9zXMpNpeF9dXPlGJw&utm_source=chatgpt.com

https://cdn.britannica.com/26/238126-050-3CF21681/infographic-economic-cycle.jpg?utm_source=chatgpt.com

Mọi thị trường – cổ phiếu, crypto, forex – đều vận động theo chu kỳ:

  1. Tích lũy Giá đi ngang, biên độ hẹp, lực mua âm thầm xuất hiện.
  2. Tăng mạnh (Markup) Xu hướng rõ ràng, MA đồng pha, dòng tiền vào mạnh.
  3. Phân phối Giá đi ngang, biến động bất thường, dấu hiệu xu hướng yếu.
  4. Giảm mạnh (Markdown) Xu hướng giảm rõ nét, nhà đầu tư hoảng loạn bán tháo.

Chu kỳ có thể lặp lại trên nhiều khung thời gian, từ M15 đến W1.

Ứng dụng quan trọng trong chiến lược Xu Hướng VIP:

  • Mua ở pha tích lũy – điều chỉnh
  • Không mua trong pha phân phối
  • Không giữ lệnh khi xu hướng bước vào pha giảm rõ ràng

4. Tâm lý tham lam – sợ hãi và tác động đến quyết định giao dịch​

https://cdn.corporatefinanceinstitute.com/assets/fear-and-greed-index1.png?utm_source=chatgpt.com

https://www.equitypandit.com/wp-content/uploads/2025/08/iStock-1387454800-scaled-e1673289014746.jpg?utm_source=chatgpt.com

Hai cảm xúc lớn nhất dẫn đến sai lầm:

Tham lam​

  • Giữ lệnh quá lâu
  • Mua vào khi thị trường đã tăng nóng
  • Tăng khối lượng quá mức

Sợ hãi​

  • Bán sớm khi chưa có tín hiệu đảo chiều
  • Bỏ lỡ cơ hội tốt
  • Hoảng loạn khi thị trường điều chỉnh nhẹ

Trong khóa học, học viên được rèn luyện nguyên tắc:

  • Chỉ giao dịch theo kế hoạch
  • Không tăng vị thế khi không có tín hiệu
  • Đặt SL – TP rõ ràng trước khi vào lệnh
  • Không kỳ vọng cảm tính

5. Thị trường luôn đúng, chỉ trader sai​

https://s3.tradingview.com/0/0f7Ig5z7_big.png?utm_source=chatgpt.com

https://blog.elearnmarkets.com/wp-content/uploads/2023/09/778_ELM_Blog-Image_Infographic-3_1164.jpeg?utm_source=chatgpt.com

Một trong những triết lý quan trọng nhất:

  • Thị trường không sai
  • Trader sai vì kỳ vọng sai
  • Thua lỗ là một phần của trò chơi

Hiểu triết lý này giúp:

  • Không chống lại xu hướng
  • Không cố gồng lệnh thua
  • Không kỳ vọng thị trường quay lại theo ý mình

Trader giỏi là người:

  • Cắt lỗ nhanh
  • Giữ lệnh thắng theo xu hướng
  • Chỉ giao dịch khi xác suất cao

6. Ứng dụng triết lý này trong chiến lược Xu Hướng VIP​

https://www.quantifiedstrategies.com/wp-content/uploads/2024/05/what-is-a-trend-following-strategy.jpg?utm_source=chatgpt.com

https://tradeciety.com/hubfs/Imported_Blog_Media/Result_proces1-2.png?utm_source=chatgpt.com

Toàn bộ triết lý thị trường được chuyển hóa thành quy trình giao dịch:

1. Dựa vào xu hướng, không dựa vào tin tức​

MA, cấu trúc giá và MACD sẽ cho tín hiệu sớm hơn tin tức.

2. Chờ hoàn thành nhịp điều chỉnh trước khi vào lệnh​

Không mua khi đám đông mua, mà mua khi giá điều chỉnh về vùng an toàn.

3. Giữ kỷ luật tuyệt đối​

Không phá vỡ quy tắc giao dịch dù chỉ một lần.

4. Giao dịch theo xác suất, không theo cảm xúc​

Không có chiến lược nào đúng 100%, nhưng kỷ luật giúp tăng lợi nhuận dài hạn.


7. Kết luận​

Bài 4 giúp học viên hiểu bức tranh tâm lý – chu kỳ – đám đông đứng sau mọi biến động giá. Đây là nền móng để:

  • Không bị nhiễu bởi cảm xúc
  • Không bị cuốn theo thị trường
  • Xây dựng được hệ thống giao dịch xu hướng rõ ràng, logic

Đi sâu: tâm lý đám đông vận hành như thế nào​

Ba khái niệm trên chỉ có ích khi bạn biết cách nhận diện chúng trên biểu đồ. Phần này đi vào chi tiết.

1. Chu kỳ tâm lý — bốn giai đoạn và dấu hiệu nhận biết​

Mọi thị trường đều đi qua bốn giai đoạn, nhưng độ dài thay đổi không đoán trước được.

Giai đoạnTâm lý đám đôngDấu hiệu trên biểu đồHành động hợp lý
Tích luỹChán nản, bỏ cuộcBiên độ hẹp, khối lượng thấp, giá đi ngangTheo dõi, chờ phá vỡ
Tăng tốcHưng phấn dầnChuỗi đỉnh/đáy cao dần, khối lượng tăngGiao dịch theo xu hướng
Phân phốiTham lam cực độBiên độ rộng, khối lượng lớn, giá đi ngang ở đỉnhChốt lời dần, siết SL
GiảmSợ hãi, hoảng loạnChuỗi đỉnh/đáy thấp dần, khối lượng bán tăng vọtĐứng ngoài hoặc giao dịch xu hướng xuống

Ba dấu hiệu "phân phối" mà người mới luôn bỏ qua:

  1. Biên độ nến tăng nhưng giá không tăng tiếp — phe mua vẫn mua nhưng phe bán đã lớn hơn.
  2. Khối lượng tăng ở đỉnh sau, giảm ở đỉnh trước — dòng tiền thông minh đã bắt đầu rút.
  3. Tin tốt ra mà giá không tăng — thông tin đã được phản ánh hết vào giá.
import pandas as pd
import numpy as np


def phat_hien_phan_phoi(df: pd.DataFrame, nhiem_ky: int = 20) -> pd.Series:
"""Đánh dấu nến có dấu hiệu phân phối.

Điều kiện: giá ở gần đỉnh 20 nến, biên độ nến lớn,
nhưng giá đóng cửa yếu (đóng ở nửa dưới của nến).
"""
dinh = df["high"].rolling(nhiem_ky).max()
bien_do = df["high"] - df["low"]
bien_do_tb = bien_do.rolling(nhiem_ky).mean()

gan_dinh = df["high"] >= dinh * 0.995
nen_rong = bien_do > bien_do_tb * 1.5
dong_yeu = (df["close"] - df["low"]) / bien_do.replace(0, np.nan) < 0.4

return gan_dinh & nen_rong & dong_yeu


# Ví dụ minh hoạ
df = pd.DataFrame({
"high": [100, 101, 105, 104, 103, 102],
"low": [98, 99, 100, 99, 98, 97],
"close": [99, 100, 101, 100, 99, 98],
})
print(phat_hien_phan_phoi(df, nhiem_ky=4))

2. Đỉnh và đáy — cách xác nhận khách quan​

Nói "đây là đỉnh" rất dễ. Xác định bằng quy tắc thì khó. Đây là cách làm có thể lập trình được:

import pandas as pd


def tim_dinh_day(df: pd.DataFrame, trai: int = 2, phai: int = 2) -> pd.DataFrame:
"""Phát hiện pivot high/low theo quy tắc đối xứng.

Một đỉnh là nến cao hơn `trai` nến trước và `phai` nến sau.
"""
df = df.copy()
df["la_dinh"] = True
df["la_day"] = True

for i in range(1, trai + 1):
df["la_dinh"] &= df["high"] > df["high"].shift(i)
df["la_dinh"] &= df["high"] > df["high"].shift(-i)
df["la_day"] &= df["low"] < df["low"].shift(i)
df["la_day"] &= df["low"] < df["low"].shift(-i)

# Bỏ `phai` nến cuối vì chưa xác nhận được
df.loc[df.index[-phai:], ["la_dinh", "la_day"]] = False
return df


def danh_gia_cau_truc(df: pd.DataFrame) -> str:
"""Đánh giá cấu trúc xu hướng dựa trên đỉnh/đáy gần nhất."""
co_dinh = df[df["la_dinh"]]
co_day = df[df["la_day"]]

if len(co_dinh) < 2 or len(co_day) < 2:
return "Chưa đủ dữ liệu để kết luận"

dinh_tang = co_dinh["high"].iloc[-1] > co_dinh["high"].iloc[-2]
day_tang = co_day["low"].iloc[-1] > co_day["low"].iloc[-2]

if dinh_tang and day_tang:
return "📈 Xu hướng TĂNG (đỉnh và đáy đều cao dần)"
if not dinh_tang and not day_tang:
return "📉 Xu hướng GIẢM (đỉnh và đáy đều thấp dần)"
return "➡️ Đi ngang (cấu trúc mâu thuẫn — tín hiệu không rõ)"

Tham số trai và phai rất quan trọng: dùng số nhỏ (1–2) sẽ bắt được nhiều đỉnh nhỏ, dễ nhiễu. Dùng số lớn (5+) sẽ chỉ bắt các đỉnh lớn, nhưng xác nhận muộn — bạn biết đó là đỉnh sau khi giá đã đi 5 nến.

💡 Đây là bản chất của phân tích kỹ thuật: mọi tín hiệu đều là đánh đổi giữa độ nhạy và độ chính xác. Không có cấu hình nào tốt cho mọi thị trường. Đó là lý do bạn phải backtest trên chính thị trường mình giao dịch.

3. Vùng giá trị — nơi đám đông để lại dấu vết​

Khối lượng giao dịch cho biết đám đông đã hành động ở đâu. Đó là thông tin quý giá nhất mà giá không nói được.

import numpy as np
import pandas as pd


def volume_profile(df: pd.DataFrame, so_muc: int = 30) -> pd.DataFrame:
"""Xây dựng hồ sơ khối lượng theo mức giá (volume profile)."""
muc_gia, bien = np.linspace(
df["low"].min(), df["high"].max(), so_muc, retstep=True
)
chi_so = (df["close"] - muc_gia[0]) / bien
chi_so = chi_so.astype(int).clip(0, so_muc - 1)

khoi_luong_theo_muc = df.groupby(chi_so)["volume"].sum()
khoi_luong_theo_muc.index = muc_gia[khoi_luong_theo_muc.index]
khoi_luong_theo_muc.index.name = "muc_gia"

return khoi_luong_theo_muc.sort_index(ascending=False).to_frame("khoi_luong")


def vung_gia_tri(ho_so: pd.DataFrame, ty_le: float = 0.7) -> tuple[float, float]:
"""Tìm vùng giá chứa `ty_le` khối lượng — vùng giá trị thật."""
sap_xep = ho_so.sort_values("khoi_luong", ascending=False)
tich_luy = sap_xep["khoi_luong"].cumsum() / sap_xep["khoi_luong"].sum()
vung = sap_xep[tich_luy <= ty_le]
return float(vung.index.min()), float(vung.index.max())

Cách đọc kết quả: vùng giá có khối lượng lớn nhất là nơi đa số người đã mua. Khi giá quay lại vùng đó, những người này có xu hướng mua thêm để bảo vệ vị thế — tạo ra hỗ trợ tự nhiên. Ngược lại, khi giá phá xuống khỏi vùng đó, những người này bị lỗ và có thể bán tháo → tạo ra kháng cự.

4. Ba thiên kiến tâm lý bạn không thể loại bỏ — chỉ có thể chống lại có hệ thống​

Thiên kiếnBiểu hiệnCách chống lại
Sợ thua lỗ (loss aversion)Gồng lỗ quá lâu, chốt lời quá sớmSL/TP đặt trước khi vào lệnh, không sửa
Neo giá (anchoring)"Giá phải quay về 100"Chỉ dùng cấu trúc thị trường hiện tại
Thiên kiến xác nhậnChỉ đọc tin ủng hộ quan điểm của mìnhGhi ra 3 lý do mình có thể SAI
FOMOMua ở đỉnh vì sợ lỡKhông vào lệnh ngoài kế hoạch — bất kể thế nào
Trả thù thị trườngVào lệnh lớn hơn sau khi thuaQuy tắc cố định khối lượng, không đổi

💡 Cách duy nhất được chứng minh là hiệu quả: biến mọi quyết định thành quy tắc viết ra trước. Khi đã viết "chỉ vào lệnh khi HMA không chồng chéo và giá đóng trên đường giữa", bạn không còn phải quyết định trong lúc cảm xúc mạnh nhất. Đây là lý do bot giao dịch có lợi thế tâm lý rất lớn.

5. Ứng dụng vào thực tế​

Một hệ thống dựa trên các nguyên lý này cần trả lời được 5 câu hỏi:

  1. Xu hướng hiện tại là gì? (dùng cấu trúc đỉnh/đáy, không dùng cảm nhận)
  2. Chúng ta đang ở giai đoạn nào của chu kỳ? (tích luỹ / tăng tốc / phân phối / giảm)
  3. Vùng giá quan trọng nằm ở đâu? (volume profile, pivot)
  4. Điều gì sẽ khiến tôi nhận ra mình SAI? (mức giá cụ thể, không phải "cảm giác")
  5. Tôi sẽ làm gì nếu SAI? (thoát ở đâu, khối lượng bao nhiêu, mất bao nhiêu %)

Nếu hệ thống của bạn không trả lời được cả 5 câu, nó vẫn là giao dịch dựa trên cảm tính — dù bạn dùng bao nhiêu chỉ báo.

Thực Hành Với MetaTrader 5 Và TradingView

· 23 phút để đọc

Thực Hành Với MetaTrader 5 Và TradingView

Mục tiêu buổi học​

  • Kết nối MT5 với Python để mở/đóng lệnh mô phỏng
  • Viết chiến lược Pine Script xác định xu hướng
  • Nhận tín hiệu TradingView qua webhook về n8n
  • Vận hành bot bằng vòng polling TP/SL

Hình minh hoạ tổng quan​

https://media.b2broker.com/app/uploads/2024/08/MT5-terminal-interface-800x601.png

https://s3.tradingview.com/z/ZSyG0zv6_big.png

https://miro.medium.com/1%2ARBc1vx6-XUjbQRNvANYYEQ.png

4


Hình minh hoạ từng phần quan trọng​

1. Pine Script trên TradingView​

https://r2.pineify.app/assets/764d2eb58dde460fd519b4a177debd76.png

https://quantnomad.com/wp-content/uploads/2023/01/image-7.png

2. MT5 chạy tài khoản Demo​

https://www.metatrader5.com/i/help/android/en/new_account.png

https://help.markettraders.com/data/img/kA04V000000u0ThSAI_en_US_1_0.png

3. n8n Webhook nhận tín hiệu từ Alert​

https://hookdeck.com/_astro/untitled---2023-09-19t171122.355.Z8J-zXL9_mbWdB.webp

4. Monitoring dashboard bot giao dịch​

https://cdn.dribbble.com/userupload/43559129/file/original-b393b8636efcf48fd5c40b55c1e66aff.jpg?resize=2048x1536&vertical=center

https://coinrule.com/assets/webp/mobile_landing_bg_small2.webp


Ứng dụng đa khung (MTF)​

https://tradeciety.com/hubfs/XAUUSD_2023-04-28_12-08-50.png

https://forexbee.co/wp-content/uploads/2025/09/top-down-analysis-in-trading-768x1195.png


Script mẫu trong buổi học (giữ nguyên, không emoji)​

//@version=5
indicator("Fin3B – AMA Trend Demo", overlay=true)

ama6 = ta.hma(close, 6)
ama10 = ta.hma(close, 10)
ama20 = ta.hma(close, 20)

plot(ama6)
plot(ama10)
plot(ama20)

trendUp = ama6 > ama10 and ama10 > ama20
bgcolor(trendUp ? color.new(color.green, 85) : na)

import MetaTrader5 as mt5
import time

mt5.initialize()
symbol = "XAUUSD"
lot = 0.01
tp = 2700
sl = 2600

request = {
"action": mt5.TRADE_ACTION_DEAL,
"symbol": symbol,
"volume": lot,
"type": mt5.ORDER_TYPE_BUY,
"price": mt5.symbol_info_tick(symbol).ask,
"sl": sl,
"tp": tp,
}
order = mt5.order_send(request)
print("Entry demo:", order)

while True:
positions = mt5.positions_get(symbol=symbol)
if not positions:
print("Không còn position mở – dừng polling")
break

price = mt5.symbol_info_tick(symbol).bid
print("Giá hiện tại:", price)

if price >= tp:
print("→ Đạt TP, đóng lệnh demo")
mt5.Close(symbol)
break
if price <= sl:
print("→ Chạm SL, đóng lệnh demo")
mt5.Close(symbol)
break

time.sleep(10)


Kết luận buổi 20​

Fin3B ở buổi này giúp bạn hiểu:

  • Xu hướng khung lớn mới là bản đồ
  • Entry khung nhỏ chỉ là trigger
  • TP/SL có thể bot tự xử lý bằng polling nếu API không hỗ trợ
  • Không giao dịch khi AMA chồng chéo
  • Luôn giám sát bot để tránh gãy hệ thống

Phần mở rộng: Làm chủ MT5 + TradingView bằng Python từ A đến Z​

Phần trên là phần thực hành trong buổi học. Phần dưới đây đi sâu vào từng mảnh ghép, giải thích tại sao và làm thế nào để bạn tự xây dựng hệ thống tương tự từ đầu.

1. Vì sao chọn MT5 thay vì chỉ dùng API sàn?​

Hầu hết sàn crypto có REST API tốt. Nhưng với forex, vàng, chỉ số, hàng hoá, con đường phổ biến nhất vẫn là MetaTrader. Lý do rất cụ thể:

Tiêu chíMT5 + PythonREST API sàn
Thị trường hỗ trợForex, vàng, chỉ số, CFD, cryptoChủ yếu crypto
Broker phổ biếnHầu hết broker forexSàn crypto
Đòn bẩyCó (forex)Thường không
Backtest tích hợpCó (Strategy Tester)Không
Dữ liệu lịch sửSẵn theo brokerTuỳ sàn
Hạn chếPhải chạy trên Windows/VPS có MT5Chạy mọi nơi

Điểm yếu lớn nhất của MT5 là phải chạy trên Windows. Điều này nghĩa là bot của bạn luôn cần một máy Windows hoặc VPS Windows — không deploy lên Vercel/Lambda được.

💡 Lựa chọn thực tế: nếu bạn chỉ giao dịch crypto, hãy dùng API sàn (Binance, Bitget) — đơn giản hơn nhiều. Nếu bạn giao dịch forex/vàng, MT5 là lựa chọn gần như bắt buộc.

2. Cài đặt và kết nối an toàn​

pip install MetaTrader5 pandas numpy python-dotenv

Bạn phải cài MetaTrader 5 terminal trên cùng máy. Thư viện MetaTrader5 không kết nối qua mạng — nó nói chuyện trực tiếp với terminal đang chạy qua IPC.

import os
from dotenv import load_dotenv
import MetaTrader5 as mt5

load_dotenv()


def ket_noi_mt5() -> bool:
"""Kết nối tới MT5 terminal. Trả False nếu thất bại."""
if not mt5.initialize(
path=os.getenv("MT5_PATH"), # đường dẫn terminal64.exe
login=int(os.getenv("MT5_LOGIN", "0")),
password=os.getenv("MT5_PASSWORD"),
server=os.getenv("MT5_SERVER"),
timeout=10_000,
portable=False,
):
ma_loi, mo_ta = mt5.last_error()
print(f"❌ Khởi tạo thất bại: [{ma_loi}] {mo_ta}")
return False

thong_tin = mt5.terminal_info()
tai_khoan = mt5.account_info()

print(f"✅ MT5 {thong_tin.build} — kết nối: {thong_tin.connected}")
print(f" Tài khoản: {tai_khoan.login} | "
f"{tai_khoan.server} | "
f"{'DEMO' if tai_khoan.trade_mode == mt5.ACCOUNT_TRADE_MODE_DEMO else '⚠️ THẬT'}")
print(f" Số dư: {tai_khoan.balance:.2f} {tai_khoan.currency}")

return True


def ngat_ket_noi():
mt5.shutdown()


if __name__ == "__main__":
if ket_noi_mt5():
ngat_ket_noi()

File .env (không bao giờ commit):

MT5_PATH=C:\Program Files\MetaTrader 5\terminal64.exe
MT5_LOGIN=12345678
MT5_PASSWORD=mat_khau_cua_ban
MT5_SERVER=Broker-Demo

⚠️ Ba nguyên tắc an toàn với MT5:

  1. Luôn bắt đầu bằng tài khoản DEMO. Không có ngoại lệ.
  2. Không lưu mật khẩu trong code. Dùng .env + .gitignore.
  3. Giới hạn số lệnh tối đa trong bot để một lỗi logic không mở 1000 lệnh.

3. Các hàm MT5 bạn sẽ dùng 90% thời gian​

HàmCông dụng
mt5.initialize() / mt5.shutdown()Kết nối / ngắt kết nối
mt5.symbol_info(symbol)Thông số symbol (lot step, spread, digits)
mt5.symbol_info_tick(symbol)Giá bid/ask hiện tại
mt5.copy_rates_from_pos()Lấy nến lịch sử → DataFrame
mt5.order_send(request)Gửi lệnh (mở/đóng)
mt5.positions_get()Danh sách vị thế đang mở
mt5.orders_get()Danh sách lệnh chờ
mt5.history_deals_get()Lịch sử giao dịch
mt5.account_info()Số dư, equity, margin

Lấy dữ liệu nến — nền tảng của mọi chiến lược:

import pandas as pd
import MetaTrader5 as mt5


KHUNG_THOI_GIAN = {
"M1": mt5.TIMEFRAME_M1,
"M5": mt5.TIMEFRAME_M5,
"M15": mt5.TIMEFRAME_M15,
"H1": mt5.TIMEFRAME_H1,
"H4": mt5.TIMEFRAME_H4,
"D1": mt5.TIMEFRAME_D1,
}


def lay_nen(symbol: str, khung: str = "H1", so_nen: int = 500) -> pd.DataFrame:
"""Lấy nến từ MT5 và trả về DataFrame đã chuẩn hoá."""
tf = KHUNG_THOI_GIAN.get(khung)
if tf is None:
raise ValueError(f"Khung không hợp lệ: {khung}. Dùng: {list(KHUNG_THOI_GIAN)}")

# Đảm bảo symbol đã được chọn trong Market Watch
if not mt5.symbol_select(symbol, True):
raise RuntimeError(f"Không chọn được symbol {symbol}")

rates = mt5.copy_rates_from_pos(symbol, tf, 0, so_nen)
if rates is None or len(rates) == 0:
raise RuntimeError(f"Không lấy được dữ liệu {symbol} {khung}: {mt5.last_error()}")

df = pd.DataFrame(rates)
df["time"] = pd.to_datetime(df["time"], unit="s")
df = df.set_index("time")
df = df.rename(columns={
"open": "open", "high": "high", "low": "low",
"close": "close", "tick_volume": "volume",
})
return df[["open", "high", "low", "close", "volume"]]


df = lay_nen("XAUUSD", "H1", 500)
print(df.tail())

4. Chỉ báo AMA/HMA dùng trong buổi học — hiểu để dùng đúng​

Pine Script trong buổi học dùng ta.hma(close, n) — Hull Moving Average. Đây là biến thể làm mượt tốt hơn MA truyền thống và giảm độ trễ đáng kể.

Công thức HMA:

HMA(n) = WMA( 2 × WMA(price, n/2) − WMA(price, n), √n )

Cài đặt bằng Python (khớp với Pine Script):

import numpy as np
import pandas as pd


def wma(chuoi: pd.Series, chu_ky: int) -> pd.Series:
"""Weighted Moving Average — trọng số tuyến tính, mới nhất nặng nhất."""
trong_so = np.arange(1, chu_ky + 1)
return chuoi.rolling(chu_ky).apply(
lambda x: np.dot(x, trong_so) / trong_so.sum(), raw=True
)


def hma(chuoi: pd.Series, chu_ky: int) -> pd.Series:
"""Hull Moving Average — khớp với ta.hma() trong Pine Script."""
nua = max(1, chu_ky // 2)
can = max(1, int(np.sqrt(chu_ky)))
return wma(2 * wma(chuoi, nua) - wma(chuoi, chu_ky), can)


def them_tin_hieu_hma(df: pd.DataFrame, n6=6, n10=10, n20=20) -> pd.DataFrame:
df["hma6"] = hma(df["close"], n6)
df["hma10"] = hma(df["close"], n10)
df["hma20"] = hma(df["close"], n20)

# Điều kiện "xu hướng" giống hệt Pine Script
df["xu_huong_tang"] = (df["hma6"] > df["hma10"]) & (df["hma10"] > df["hma20"])
df["xu_huong_giam"] = (df["hma6"] < df["hma10"]) & (df["hma10"] < df["hma20"])

# Phát hiện lúc xu hướng ĐỔI (điểm vào lệnh tiềm năng)
df["doi_sang_tang"] = df["xu_huong_tang"] & ~df["xu_huong_tang"].shift(1).fillna(False)
df["doi_sang_giam"] = df["xu_huong_giam"] & ~df["xu_huong_giam"].shift(1).fillna(False)

# Đo mức "chồng chéo" — khi ba đường bó sát, không nên giao dịch
df["do_nen_chat"] = (df[["hma6", "hma10", "hma20"]].max(axis=1)
- df[["hma6", "hma10", "hma20"]].min(axis=1))

# Ngưỡng chồng chéo: nhỏ hơn 0.15% giá là quá chặt
df["chong_cheo"] = df["do_nen_chat"] < df["close"] * 0.0015

return df


df = them_tin_hieu_hma(lay_nen("XAUUSD", "H1", 500))
print(df[["close", "hma6", "hma10", "hma20", "xu_huong_tang", "chong_cheo"]].tail(10))

Ba đường HMA bó sát nhau nghĩa là thị trường đang tích luỹ, không có xu hướng. Vào lệnh lúc này gần như chắc chắn bị quét stop loss. Điều kiện chong_cheo chính là bộ lọc mà buổi học nhấn mạnh: "không giao dịch khi AMA chồng chéo".

5. Pine Script đầy đủ — có Alert để gửi webhook​

Script trong buổi học chỉ vẽ HMA. Để dùng cho bot, cần thêm alert có payload JSON:

//@version=5
indicator("Fin3B - HMA Trend + Alert", overlay=true)

// --- Tham số ---
n6 = input.int(6, "HMA nhanh")
n10 = input.int(10, "HMA trung")
n20 = input.int(20, "HMA cham")
nguong_chong_cheo = input.float(0.15, "Nguong chong cheo (% gia)") / 100

// --- Tính HMA ---
hma6 = ta.hma(close, n6)
hma10 = ta.hma(close, n10)
hma20 = ta.hma(close, n20)

plot(hma6, "HMA6", color=color.new(color.yellow, 0))
plot(hma10, "HMA10", color=color.new(color.orange, 0))
plot(hma20, "HMA20", color=color.new(color.red, 0))

// --- Xu hướng ---
tang = hma6 > hma10 and hma10 > hma20
giam = hma6 < hma10 and hma10 < hma20

// --- Do chong cheo ---
do_nen_chat = math.max(hma6, hma10, hma20) - math.min(hma6, hma10, hma20)
chong_cheo = do_nen_chat < close * nguong_chong_cheo

bgcolor(tang and not chong_cheo ? color.new(color.green, 85) : na)
bgcolor(giam and not chong_cheo ? color.new(color.red, 85) : na)

// --- Tin hieu doi xu huong (chi khi khong chong cheo) ---
doi_tang = tang and not tang[1] and not chong_cheo
doi_giam = giam and not giam[1] and not chong_cheo

plotshape(doi_tang, "MUA", shape.labelup, location.belowbar, color.green, text="BUY")
plotshape(doi_giam, "BAN", shape.labeldown, location.abovebar, color.red, text="SELL")

// --- Alert gui webhook dang JSON ---
if doi_tang
alert('{"hanh_dong":"MUA","symbol":"' + syminfo.ticker +
'","gia":' + str.tostring(close, "#.##") +
',"khung":"' + timeframe.period +
'","thoi_gian":"' + str.tostring(time, "yyyy-MM-dd HH:mm:ss") + '"}',
alert.freq_once_per_bar_close)

if doi_giam
alert('{"hanh_dong":"BAN","symbol":"' + syminfo.ticker +
'","gia":' + str.tostring(close, "#.##") +
',"khung":"' + timeframe.period +
'","thoi_gian":"' + str.tostring(time, "yyyy-MM-dd HH:mm:ss") + '"}',
alert.freq_once_per_bar_close)

Bốn chi tiết quan trọng trong script này:

Chi tiếtVì sao quan trọng
alert.freq_once_per_bar_closeNến chưa đóng mà báo tín hiệu → tín hiệu có thể biến mất. Chỉ báo khi nến đã đóng.
Payload là JSONn8n/API parse được ngay, không phải xử lý chuỗi tự do
Có symbol, khung, thoi_gianBot cần biết tín hiệu đến từ đâu, khung nào
Lọc chong_cheo ngay trong scriptGiảm tín hiệu rác trước khi tới bot

6. Webhook TradingView → n8n → MT5​

Kiến trúc đầy đủ:

flowchart LR
A[TradingView Alert] -->|POST JSON| B[n8n Webhook]
B --> C{Loc tin hieu}
C -->|Hop le| D[Goi API bot]
C -->|Khong hop le| E[Log & bo qua]
D --> F[MT5 order_send]
F --> G[Thong bao Telegram]

Bước 1 — Tạo webhook receiver trong n8n:

  1. Thêm node Webhook, method POST, path tradingview-signal.
  2. Thêm node IF kiểm tra {{ $json.hanh_dong }} là MUA hoặc BAN.
  3. Thêm node HTTP Request gọi API bot của bạn với header xác thực.
  4. Thêm node Telegram gửi thông báo.

⚠️ TradingView chỉ cho phép webhook tới URL công khai có HTTPS. Và URL webhook chính là mật khẩu — ai biết URL đều có thể gửi tín hiệu giả để bot của bạn vào lệnh. Hãy:

  • Dùng URL có chuỗi ngẫu nhiên dài (n8n tự sinh).
  • Kiểm tra thêm một secret trong body.
  • Đặt rate limit cho endpoint.

Bước 2 — Webhook receiver viết bằng Python (nếu không dùng n8n):

import hmac
import hashlib
import os
import logging
from flask import Flask, request, jsonify

app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

SECRET = os.environ["WEBHOOK_SECRET"]


def kiem_tra_secret(du_lieu: dict) -> bool:
"""So sánh secret theo cách chống timing attack."""
nhan_duoc = str(du_lieu.get("secret", ""))
return hmac.compare_digest(nhan_duoc, SECRET) # KHÔNG dùng ==


@app.post("/webhook/tradingview")
def nhan_tin_hieu():
du_lieu = request.get_json(silent=True)
if not du_lieu:
return jsonify({"loi": "Body không phải JSON hợp lệ"}), 400

if not kiem_tra_secret(du_lieu):
logger.warning("Có request với secret sai từ %s", request.remote_addr)
return jsonify({"loi": "Xác thực thất bại"}), 401

hanh_dong = du_lieu.get("hanh_dong")
symbol = du_lieu.get("symbol")

if hanh_dong not in ("MUA", "BAN"):
return jsonify({"loi": f"hanh_dong không hợp lệ: {hanh_dong}"}), 422
if not symbol:
return jsonify({"loi": "Thiếu symbol"}), 422

logger.info("📩 Tín hiệu: %s %s @ %s", hanh_dong, symbol, du_lieu.get("gia"))

ket_qua = xu_ly_tin_hieu(hanh_dong, symbol, du_lieu)
return jsonify(ket_qua), 200

Thư viện hmac.compare_digest quan trọng hơn vẻ ngoài của nó: so sánh chuỗi bằng == dừng ngay khi gặp ký tự khác nhau, và thời gian dừng tiết lộ thông tin về secret đúng.

7. Đặt lệnh MT5 đúng chuẩn​

Code trong buổi học gửi order_send trực tiếp. Trong thực tế cần kiểm tra kết quả, chuẩn hoá giá và khối lượng:

import MetaTrader5 as mt5


def chuan_hoa_khoi_luong(symbol: str, khoi_luong: float) -> float:
"""Làm tròn khối lượng theo bước (volume step) và giới hạn của broker."""
info = mt5.symbol_info(symbol)
if info is None:
raise RuntimeError(f"Không có thông tin symbol {symbol}")

buoc = info.volume_step
khoi_luong = round(khoi_luong / buoc) * buoc
khoi_luong = max(info.volume_min, min(info.volume_max, khoi_luong))
return round(khoi_luong, 8)


def chuan_hoa_gia(symbol: str, gia: float) -> float:
"""Làm tròn giá theo số chữ số thập phân mà broker yêu cầu."""
info = mt5.symbol_info(symbol)
return round(gia, info.digits)


def tinh_khoi_luong_theo_rui_ro(
symbol: str, so_du: float, rui_ro_pct: float, khoang_cach_sl: float
) -> float:
"""Khối lượng để khi chạm SL chỉ mất đúng rui_ro_pct % số dư."""
info = mt5.symbol_info(symbol)
so_tien_rui_ro = so_du * rui_ro_pct / 100

# Giá trị 1 điểm (point) cho 1 lot, quy đổi theo tick_value
gia_tri_moi_point = info.trade_tick_value / info.trade_tick_size
so_point_sl = khoang_cach_sl / info.point

if so_point_sl <= 0 or gia_tri_moi_point <= 0:
raise ValueError("Không tính được khối lượng — kiểm tra khoảng cách SL")

khoi_luong = so_tien_rui_ro / (so_point_sl * gia_tri_moi_point)
return chuan_hoa_khoi_luong(symbol, khoi_luong)


def mo_lenh(
symbol: str,
huong: str, # "MUA" hoặc "BAN"
khoi_luong: float,
sl: float | None = None,
tp: float | None = None,
magic: int = 20260101,
ghi_chu: str = "Fin3B bot",
) -> dict | None:
"""Mở lệnh thị trường với đầy đủ kiểm tra."""
info = mt5.symbol_info(symbol)
if info is None:
raise RuntimeError(f"Symbol không tồn tại: {symbol}")

if not info.visible:
mt5.symbol_select(symbol, True)

tick = mt5.symbol_info_tick(symbol)
if tick is None:
raise RuntimeError(f"Không lấy được giá {symbol}")

if huong == "MUA":
loai_lenh, gia = mt5.ORDER_TYPE_BUY, tick.ask
elif huong == "BAN":
loai_lenh, gia = mt5.ORDER_TYPE_SELL, tick.bid
else:
raise ValueError(f"Huong không hợp lệ: {huong}")

# Kiểm tra khoảng cách stop tối thiểu của broker
khoang_cach_min = info.trade_stops_level * info.point
if sl and abs(gia - sl) < khoang_cach_min:
raise ValueError(f"SL quá gần giá ({abs(gia - sl):.5f} < {khoang_cach_min:.5f})")
if tp and abs(gia - tp) < khoang_cach_min:
raise ValueError(f"TP quá gần giá ({abs(gia - tp):.5f} < {khoang_cach_min:.5f})")

request = {
"action": mt5.TRADE_ACTION_DEAL,
"symbol": symbol,
"volume": chuan_hoa_khoi_luong(symbol, khoi_luong),
"type": loai_lenh,
"price": chuan_hoa_gia(symbol, gia),
"sl": chuan_hoa_gia(symbol, sl) if sl else 0.0,
"tp": chuan_hoa_gia(symbol, tp) if tp else 0.0,
"deviation": 20, # trượt giá tối đa (point)
"magic": magic, # định danh bot — rất quan trọng
"comment": ghi_chu,
"type_time": mt5.ORDER_TIME_GTC,
"type_filling": mt5.ORDER_FILLING_IOC,
}

ket_qua = mt5.order_send(request)

if ket_qua is None:
raise RuntimeError(f"order_send trả None: {mt5.last_error()}")

if ket_qua.retcode != mt5.TRADE_RETCODE_DONE:
# Ví dụ: không đủ margin, thị trường đóng, giá lệch quá deviation
raise RuntimeError(
f"Lệnh thất bại [{ket_qua.retcode}] {ket_qua.comment}"
)

return {
"order": ket_qua.order,
"deal": ket_qua.deal,
"gia_khop": ket_qua.price,
"khoi_luong": ket_qua.volume,
"retcode": ket_qua.retcode,
}

magic là trường bắt buộc trong mọi bot thực tế. Nếu bạn chạy nhiều bot hoặc giao dịch tay trên cùng tài khoản, magic giúp bot chỉ quản lý lệnh của chính nó, không đóng nhầm lệnh tay của bạn.

Bảng mã lỗi MT5 hay gặp:

retcodeÝ nghĩaCách xử lý
10009Thành công—
10004RequoteTăng deviation, thử lại
10006Bị từ chốiKiểm tra thị trường có mở không
10013Request không hợp lệKiểm tra filling mode, stops_level
10014Khối lượng không hợp lệChạy chuan_hoa_khoi_luong
10016Stop không hợp lệSL/TP quá gần giá
10019Không đủ tiềnGiảm khối lượng
10027AutoTrading bị tắtBật nút AutoTrading trong terminal

8. Vòng polling quản lý TP/SL — viết lại cho đúng​

Đoạn code trong buổi học có một vấn đề nghiêm trọng: nó tự đóng lệnh khi giá chạm TP/SL thay vì để broker tự làm. Thực tế bạn nên đặt SL/TP vào lệnh và để broker xử lý. Polling chỉ dùng khi API không hỗ trợ, hoặc để làm việc khác (trailing stop, báo cáo).

import time
import logging
from datetime import datetime

import MetaTrader5 as mt5

logger = logging.getLogger(__name__)


def lay_vi_the_cua_bot(symbol: str, magic: int) -> list:
"""CHỈ lấy vị thế do bot này mở — lọc theo magic."""
tat_ca = mt5.positions_get(symbol=symbol) or []
return [p for p in tat_ca if p.magic == magic]


def quản_ly_trailing_stop(symbol: str, magic: int, he_so_atr: float = 2.0):
"""Dời SL theo ATR khi giá đi đúng hướng — khoá lợi nhuận dần."""
df = lay_nen(symbol, "H1", 100)
atr = (df["high"] - df["low"]).rolling(14).mean().iloc[-1]

for vt in lay_vi_the_cua_bot(symbol, magic):
tick = mt5.symbol_info_tick(symbol)
info = mt5.symbol_info(symbol)

if vt.type == mt5.POSITION_TYPE_BUY:
gia_hien_tai = tick.bid
sl_moi = gia_hien_tai - atr * he_so_atr

# Chỉ dời LÊN, không bao giờ dời xuống
if sl_moi <= vt.sl:
continue
if sl_moi <= vt.price_open:
continue # chưa tới điểm hoà vốn
else:
gia_hien_tai = tick.ask
sl_moi = gia_hien_tai + atr * he_so_atr

if vt.sl != 0 and sl_moi >= vt.sl:
continue
if sl_moi >= vt.price_open:
continue

request = {
"action": mt5.TRADE_ACTION_SLTP,
"symbol": symbol,
"position": vt.ticket,
"sl": chuan_hoa_gia(symbol, sl_moi),
"tp": vt.tp,
"magic": magic,
}
ket_qua = mt5.order_send(request)

if ket_qua and ket_qua.retcode == mt5.TRADE_RETCODE_DONE:
logger.info("📈 Dời SL vé %s → %.5f", vt.ticket, sl_moi)
else:
logger.warning("Dời SL thất bại vé %s: %s", vt.ticket,
ket_qua.comment if ket_qua else mt5.last_error())


def vong_lap_bot(symbol: str, khung: str = "H1", magic: int = 20260101,
poll_giay: int = 30, khoi_luong: float = 0.01):
"""Vòng lặp bot hoàn chỉnh."""
da_xu_ly_nen = None

while True:
try:
df = them_tin_hieu_hma(lay_nen(symbol, khung, 300))
nen_cuoi = df.index[-1]

# Chỉ xử lý một lần cho mỗi nến đã đóng
if nen_cuoi == da_xu_ly_nen:
quản_ly_trailing_stop(symbol, magic)
time.sleep(poll_giay)
continue

da_xu_ly_nen = nen_cuoi
dong = df.iloc[-1]

vi_the = lay_vi_the_cua_bot(symbol, magic)
co_lenh_mua = any(p.type == mt5.POSITION_TYPE_BUY for p in vi_the)
co_lenh_ban = any(p.type == mt5.POSITION_TYPE_SELL for p in vi_the)

tick = mt5.symbol_info_tick(symbol)
atr = (df["high"] - df["low"]).rolling(14).mean().iloc[-1]

logger.info("Nến %s | giá %.2f | tăng=%s | giảm=%s | chồng chéo=%s | vị thế=%d",
nen_cuoi, dong["close"], dong["xu_huong_tang"],
dong["xu_huong_giam"], dong["chong_cheo"], len(vi_the))

# Bộ lọc: không giao dịch khi HMA chồng chéo
if dong["chong_cheo"]:
logger.info("⏸ Bỏ qua — HMA chồng chéo")
time.sleep(poll_giay)
continue

# Giới hạn: tối đa 1 vị thế mỗi hướng
if dong["doi_sang_tang"] and not co_lenh_mua:
if co_lenh_ban:
dong_tat_ca(symbol, magic)
mo_lenh(symbol, "MUA", khoi_luong,
sl=tick.ask - atr * 2, tp=tick.ask + atr * 4,
magic=magic, ghi_chu="HMA tang")

elif dong["doi_sang_giam"] and not co_lenh_ban:
if co_lenh_mua:
dong_tat_ca(symbol, magic)
mo_lenh(symbol, "BAN", khoi_luong,
sl=tick.bid + atr * 2, tp=tick.bid - atr * 4,
magic=magic, ghi_chu="HMA giam")

except Exception:
logger.exception("Lỗi trong vòng lặp bot — tiếp tục chạy")

time.sleep(poll_giay)

Ba cải tiến so với code buổi học:

  1. Lọc theo magic — bot chỉ đụng vào lệnh của chính nó.
  2. Chỉ xử lý mỗi nến một lần (da_xu_ly_nen) — không đặt lệnh lặp lại trong cùng một nến.
  3. Ghi log có cấu trúc — biết bot đang nghĩ gì ở mỗi nến.

9. Backtest trước khi chạy tiền thật​

Không có backtest thì không có niềm tin. Dù đơn giản, một backtest vector hoá vẫn cho bạn con số đáng tham khảo:

import numpy as np
import pandas as pd


def backtest_hma(df: pd.DataFrame, phi_moi_lenh: float = 0.0002) -> dict:
"""Backtest đơn giản: vào lệnh khi xu hướng đổi, đóng khi đảo chiều."""
vi_the = 0 # 0: không, 1: mua, -1: bán
gia_vao = 0.0
von = 1.0
so_lenh = 0
thang = 0
duong_von = []

for i in range(1, len(df)):
dong = df.iloc[i]
gia = dong["close"]

if vi_the == 0:
if dong["doi_sang_tang"] and not dong["chong_cheo"]:
vi_the, gia_vao = 1, gia
so_lenh += 1
elif dong["doi_sang_giam"] and not dong["chong_cheo"]:
vi_the, gia_vao = -1, gia
so_lenh += 1
else:
doi_chieu = ((vi_the == 1 and dong["doi_sang_giam"]) or
(vi_the == -1 and dong["doi_sang_tang"]))

if doi_chieu:
loi_nhuan = (gia / gia_vao - 1) * vi_the - phi_moi_lenh
von *= (1 + loi_nhuan)
if loi_nhuan > 0:
thang += 1
vi_the = 0

duong_von.append(von)

duong_von = pd.Series(duong_von)
dinh = duong_von.cummax()
drawdown = (duong_von / dinh - 1)

return {
"so_lenh": so_lenh,
"ty_le_thang": round(thang / so_lenh * 100, 1) if so_lenh else 0,
"loi_nhuan_tong_pct": round((von - 1) * 100, 2),
"max_drawdown_pct": round(drawdown.min() * 100, 2),
"he_so_profit_factor": round(von, 4),
}


df = them_tin_hieu_hma(lay_nen("XAUUSD", "H1", 5000))
print(backtest_hma(df))

⚠️ Giới hạn của backtest này — phải nói rõ: nó bỏ qua spread, slippage, swap qua đêm, và giả định khớp lệnh ở giá đóng nến. Kết quả thực tế luôn xấu hơn. Hãy dùng nó để loại bỏ chiến lược tệ, không phải để tin vào con số đẹp.

10. Vận hành trên VPS​

MT5 yêu cầu Windows, nên bot cần VPS Windows:

Yêu cầuKhuyến nghị
RAM4 GB (MT5 + Python)
CPU2 vCPU
Ổ đĩa60 GB SSD
Hệ điều hànhWindows Server 2019/2022
Vị tríGần server broker để giảm độ trễ
Giá tham khảo10–25 USD/tháng

Bốn việc phải làm khi lên VPS:

  1. Chạy bot như Windows Service (dùng nssm) để tự khởi động lại sau khi VPS restart.
  2. Bật AutoTrading trong MT5 và không đóng cửa sổ terminal.
  3. Log ra file có xoay vòng — không để log làm đầy đĩa.
  4. Giám sát từ xa — gửi tin nhắn Telegram mỗi khi bot mở/đóng lệnh hoặc gặp lỗi.
import requests


def thong_bao_telegram(noi_dung: str):
"""Gửi thông báo — cách đơn giản nhất để biết bot còn sống."""
token = os.environ.get("TELEGRAM_BOT_TOKEN")
chat_id = os.environ.get("TELEGRAM_CHAT_ID")
if not token or not chat_id:
return

try:
requests.post(
f"https://api.telegram.org/bot{token}/sendMessage",
json={"chat_id": chat_id, "text": noi_dung, "parse_mode": "HTML"},
timeout=10,
)
except Exception as e:
logger.warning("Không gửi được Telegram: %s", e)

💡 Mẹo vận hành: hãy để bot gửi tin nhắn "còn sống" mỗi 6 giờ. Nếu bạn không nhận được tin nhắn, bot đã chết — và bạn biết trước khi mất tiền.

11. Sai lầm phổ biến khi xây bot MT5​

Sai lầmHậu quảCách tránh
Không dùng magicBot đóng nhầm lệnh tayLuôn đặt magic và lọc theo nó
Không chuẩn hoá khối lượngretcode 10014, lệnh bị từ chốiDùng volume_step từ symbol_info
Đặt SL/TP quá gầnretcode 10016Kiểm tra trade_stops_level
Chạy trên tài khoản thật ngayMất tiền do bug logicDemo tối thiểu 4 tuần
Không giới hạn số lệnhMột lỗi vòng lặp mở 1000 lệnhĐếm và chặn theo magic
Polling quá nhanhTốn CPU, bị broker giới hạnsleep ≥ 10 giây
Bỏ qua retcodeTưởng đã vào lệnh nhưng thất bạiLuôn kiểm tra retcode == 10009
Không logKhông biết vì sao bot mất tiềnLog mọi quyết định

12. Câu hỏi thường gặp​

MT5 có chạy được trên Linux/macOS không? Thư viện MetaTrader5 chính thức chỉ hỗ trợ Windows. Trên Linux cần Wine — chạy được nhưng không ổn định và không khuyến nghị cho tiền thật.

Có cần VPS không hay chạy máy nhà được? Máy nhà được nếu bạn không tắt máy và mạng ổn định. Nhưng một lần mất điện đúng lúc có vị thế mở có thể khiến bạn mất kiểm soát SL/TP. VPS đáng tiền.

TradingView có giới hạn số alert không? Có. Gói miễn phí giới hạn số alert và không cho webhook. Gói trả phí cho nhiều alert hơn. Đây là chi phí thật cần tính vào.

Bot có cần máy học không? Không, và phần lớn bot thành công không dùng ML. Quản lý rủi ro, kỷ luật thực thi, và chiến lược đơn giản nhưng được kiểm chứng quan trọng hơn nhiều so với model phức tạp.

Làm sao biết bot đang hoạt động đúng? Ba dấu hiệu: (1) log ghi đều mỗi nến, (2) tin nhắn Telegram "còn sống" đều đặn, (3) số vị thế mở khớp với kỳ vọng của chiến lược.

Nên dùng khung thời gian nào? Khung càng lớn, tín hiệu càng ít nhưng nhiễu càng thấp. Với người mới: H1 hoặc H4. Khung M1/M5 cần hạ tầng tốt hơn và chi phí giao dịch cao hơn nhiều.

13. Checklist trước khi chạy tiền thật​

  • Đã chạy demo tối thiểu 4 tuần liên tục
  • Đã backtest tối thiểu 2 năm dữ liệu, có tính spread và phí
  • Đã kiểm tra hành vi khi mất mạng và khi MT5 bị đóng
  • Có giới hạn số lệnh tối đa mỗi ngày
  • Có giới hạn drawdown — bot tự dừng nếu lỗ quá X%
  • SL luôn được đặt, không bao giờ có lệnh không SL
  • Rủi ro mỗi lệnh ≤ 1% số dư
  • Có thông báo Telegram cho mọi lệnh mở/đóng
  • Có log và biết cách đọc log
  • Đã tính chi phí: VPS + TradingView + spread + swap
  • Đã chuẩn bị tâm lý cho chuỗi thua 5–7 lệnh liên tiếp (chuyện bình thường)

⚖️ Miễn trừ trách nhiệm: Nội dung này chỉ nhằm mục đích giáo dục về lập trình và kỹ thuật. Giao dịch forex, CFD và tài sản số có rủi ro rất cao — phần lớn nhà đầu tư cá nhân thua lỗ. Không có chiến lược nào đảm bảo lợi nhuận. Hãy tự nghiên cứu và chỉ dùng số tiền bạn có thể chấp nhận mất hoàn toàn.

14. Tóm tắt​

Trong phần mở rộng này bạn đã đi qua:

✅ Kiến trúc MT5 + Python + TradingView + n8n ✅ Cài đặt và kết nối an toàn với .env ✅ HMA/AMA — cài đặt đúng để khớp Pine Script ✅ Pine Script có alert gửi JSON qua webhook ✅ Webhook receiver bảo mật bằng HMAC ✅ Đặt lệnh MT5 có chuẩn hoá khối lượng/giá và kiểm tra retcode ✅ Vòng polling đúng cách, lọc theo magic ✅ Backtest và giới hạn của nó ✅ Vận hành trên VPS có giám sát

Điều quan trọng nhất không nằm ở code: đó là hiểu rằng không giao dịch khi HMA chồng chéo, và luôn giới hạn rủi ro mỗi lệnh. Code chỉ thực thi những quyết định đó.


Bài viết liên quan​

Từ Dữ Liệu Thô Đến Thông Tin Hữu Ích – Khóa Học Phân Tích Dữ Liệu Thực Tế

· 10 phút để đọc

Từ Dữ Liệu Thô Đến Thông Tin Hữu Ích – Khóa Học Phân Tích Dữ Liệu Thực Tế

Bạn có biết rằng trong một hệ thống quản lý, có hàng nghìn bản ghi dữ liệu nhưng không biết cách kết nối chúng?

Ví dụ thực tế: Trong hệ thống PCCC, chúng ta có:

  • 702 thiết bị với địa chỉ lắp đặt
  • 221 cơ sở cần tọa độ địa lý
  • Làm sao để tự động match và cập nhật tọa độ?

Hình ảnh: Kết quả phân tích dữ liệu hiển thị trên bản đồ với các marker cho cơ sở và thiết bị

Khóa học Phân Tích Dữ Liệu sẽ dạy bạn: Kỹ thuật chuẩn hóa dữ liệu (normalize string) Fuzzy matching để tìm các bản ghi tương tự Tự động hóa quy trình cập nhật dữ liệu Xử lý dữ liệu lớn với Python và PostgreSQL

Hệ thống Phòng Cháy Chữa Cháy (PCCC)​

https://www.firstresponsecpr.com/wp-content/uploads/2023/03/Home-Fire-Safety-Tools-Checklist.png

https://upload.wikimedia.org/wikipedia/commons/8/8c/Map_Matching_Example_with_GraphHopper.png

https://www.pottersignal.com/product/images/PFC-6006.png

Trong 1 hệ thống quản lý PCCC điển hình, chúng ta có:

  • 702 thiết bị đã lắp đặt nhưng địa chỉ ghi không đồng nhất
  • 221 cơ sở chưa có tọa độ địa lý chính xác
  • Nhu cầu: Tự động đối chiếu địa chỉ lắp đặt và cập nhật tọa độ GPS mà không phải nhập tay

Câu hỏi đặt ra:

Làm sao để bot hoặc hệ thống có thể tự match địa chỉ và cập nhật tọa độ một cách tự động?


Fin3B và tư duy xử lý dữ liệu trong thực tế​

Đây chính là điểm mà các hệ thống thông minh như Fin3B trong mảng bot/trading hoặc monitoring đều phải đi qua: biến dữ liệu thô thành tín hiệu hoặc thông tin có thể hành động được.

Để làm được điều đó, trader hay kỹ sư dữ liệu cần làm chủ 3 khối:

  1. Thu thập dữ liệu thô (Raw Data Block)
  2. Chuẩn hóa và phân tích (Logic/AI Block)
  3. Giám sát và cập nhật liên tục (Monitoring Block)

Khóa học Phân Tích Dữ Liệu sẽ dạy bạn những gì?​

https://www.datameer.com/wp-content/uploads/2023/01/1.png

https://www.researchgate.net/publication/377386507/figure/fig1/AS%3A11431281217409492%401705204317835/Example-of-Fuzzy-String-Matching-GPM-Finding-the-similarity-between-the-terms-S1.png

https://www.altexsoft.com/static/blog-post/2023/11/a89125bc-af42-4bad-8cd6-256a680467c6.jpg

Trong khóa học, bạn sẽ thực hành trực tiếp các kỹ thuật quan trọng:

1. Chuẩn hóa dữ liệu​

  • Normalize chuỗi (loại bỏ ký tự thừa, viết hoa/viết thường, dấu câu sai lệch)

2. Fuzzy Matching​

  • Dùng thuật toán đo độ tương đồng chuỗi để tìm các bản ghi gần giống nhau
  • Nhận diện trùng lặp dữ liệu dù không khớp 100%

3. Tự động hóa quy trình cập nhật​

  • Xây dựng workflow tự đối chiếu – cập nhật mà không cần thao tác thủ công
  • Ứng dụng Python, n8n automation hoặc database triggers

4. Xử lý dữ liệu lớn​

  • Làm việc với tập dữ liệu hàng nghìn bản ghi
  • Dùng Python và PostgreSQL/MySQL để xử lý, lọc và tối ưu

Đi sâu: kỹ thuật match dữ liệu địa chỉ​

Bài toán "đối chiếu 702 thiết bị với 221 cơ sở" nghe có vẻ đơn giản, nhưng thực tế địa chỉ ghi trong hệ thống luôn khác nhau:

Cơ sở trong hệ thốngĐịa chỉ thiết bị ghiVấn đề
Công ty TNHH ABCCty TNHH ABCViết tắt
123 Nguyễn Trãi, P.5, Q.1123 Nguyễn Trãi Phường 5 Quận 1Dấu câu
Số 45 Lê Lợi45 Lê LợiTừ "Số"
KCN Tân Bình, Lô C3KCN Tan Binh Lo C3Mất dấu
Trường THCS Nguyễn DuTrường Trung học cơ sở Nguyễn DuViết đầy đủ

Không có thuật toán nào so khớp chính xác được những trường hợp này. Đó là lý do cần chuẩn hoá + fuzzy matching.

Bước 1 — Chuẩn hoá chuỗi (normalization)​

import re
import unicodedata
from functools import lru_cache

TU_VIET_TAT = {
"cty": "cong ty", "tnhh": "trach nhiem huu han",
"cp": "co phan", "tp": "thanh pho", "q": "quan",
"p": "phuong", "p.": "phuong", "qh": "quan huyen",
"tt": "thi tran", "xa": "xa", "phong": "phuong",
"kcn": "khu cong nghiep", "kcx": "khu che xuat",
"thcs": "trung hoc co so", "thpt": "trung hoc pho thong",
"so": "", "số": "",
}


@lru_cache(maxsize=10_000)
def chuan_hoa(dia_chi: str) -> str:
"""Chuẩn hoá địa chỉ thành dạng so sánh được."""
if not dia_chi:
return ""

# 1. Chuẩn hoá Unicode (quan trọng: NFC vs NFD gây lỗi so khớp)
s = unicodedata.normalize("NFC", str(dia_chi)).lower().strip()

# 2. Bỏ dấu tiếng Việt
s = "".join(
c for c in unicodedata.normalize("NFD", s)
if unicodedata.category(c) != "Mn"
).replace("đ", "d")

# 3. Bỏ mọi dấu câu, chuẩn hoá khoảng trắng
s = re.sub(r"[^a-z0-9\s/]", " ", s)
s = re.sub(r"\s+", " ", s).strip()

# 4. Mở rộng từ viết tắt (so khớp theo từ, không theo chuỗi con)
tu = s.split()
tu = [TU_VIET_TAT.get(t, t) for t in tu]
return " ".join(t for t in tu if t)

Chi tiết dễ bị bỏ qua nhưng gây lỗi: unicodedata.normalize("NFC", ...). macOS lưu tên file ở dạng NFD, Windows ở dạng NFC. Hai chuỗi nhìn giống hệt nhau có thể khác nhau ở mức byte. Nếu bạn không chuẩn hoá, việc so sánh sẽ thất bại một cách khó hiểu.

Bước 2 — Fuzzy matching​

from difflib import SequenceMatcher
from collections import defaultdict


def do_tuong_dong(a: str, b: str) -> float:
"""Độ tương đồng 0..1 giữa hai chuỗi đã chuẩn hoá."""
return SequenceMatcher(None, a, b).ratio()


def token_set_ratio(a: str, b: str) -> float:
"""So khớp theo tập từ — bỏ qua thứ tự từ và từ lặp.

Tốt hơn SequenceMatcher khi thứ tự từ khác nhau:
'nguyen trai quan 1' vs 'quan 1 nguyen trai' → 1.0
"""
ta, tb = set(a.split()), set(b.split())
if not ta or not tb:
return 0.0
giao = len(ta & tb)
return giao / max(len(ta), len(tb))


def diem_ket_hop(a: str, b: str, trong_so: float = 0.6) -> float:
"""Kết hợp hai thước đo."""
return trong_so * token_set_ratio(a, b) + (1 - trong_so) * do_tuong_dong(a, b)

Hai thước đo cho hai mục đích khác nhau:

Thước đoMạnh ởYếu ở
SequenceMatcherSai chính tả nhỏ, tên viết lệch ký tựThứ tự từ khác nhau
token_set_ratioThứ tự từ khác, có từ thừaSai chính tả
Kết hợpCả hai trường hợp—

Bước 3 — Đối chiếu và cập nhật​

import pandas as pd
from typing import NamedTuple


class KetQuaMatch(NamedTuple):
ma_thiet_bi: str
ma_co_so: str | None
diem: float
can_kiem_tra: bool
lat: float | None
lon: float | None


NGUONG_TU_DONG = 0.90 # >= ngưỡng này: tự động gán
NGUONG_NGHI_NGO = 0.75 # giữa hai ngưỡng: cần người kiểm tra


def doi_chieu(thiet_bi: pd.DataFrame, co_so: pd.DataFrame) -> pd.DataFrame:
"""Đối chiếu từng thiết bị với cơ sở phù hợp nhất."""
# Chuẩn hoá một lần cho toàn bộ (không chuẩn hoá lại trong vòng lặp)
thiet_bi = thiet_bi.copy()
co_so = co_so.copy()
thiet_bi["chuan_hoa"] = thiet_bi["dia_chi"].map(chuan_hoa)
co_so["chuan_hoa"] = co_so["dia_chi"].map(chuan_hoa)

ket_qua = []
for _, tb in thiet_bi.iterrows():
diem_tot_nhat, co_so_tot_nhat = 0.0, None

for _, cs in co_so.iterrows():
d = diem_ket_hop(tb["chuan_hoa"], cs["chuan_hoa"])
if d > diem_tot_nhat:
diem_tot_nhat, co_so_tot_nhat = d, cs

if diem_tot_nhat >= NGUONG_TU_DONG:
ket_qua.append(KetQuaMatch(
tb["ma_thiet_bi"], co_so_tot_nhat["ma_co_so"], diem_tot_nhat,
False, co_so_tot_nhat["lat"], co_so_tot_nhat["lon"],
))
elif diem_tot_nhat >= NGUONG_NGHI_NGO:
ket_qua.append(KetQuaMatch(
tb["ma_thiet_bi"], co_so_tot_nhat["ma_co_so"], diem_tot_nhat,
True, co_so_tot_nhat["lat"], co_so_tot_nhat["lon"],
))
else:
ket_qua.append(KetQuaMatch(tb["ma_thiet_bi"], None, diem_tot_nhat, True, None, None))

return pd.DataFrame(ket_qua)

⚠️ Nguyên tắc quan trọng nhất trong bài toán này: không bao giờ tự động gán khi điểm nằm giữa hai ngưỡng. Việc đưa vào danh sách "cần người kiểm tra" tốn ít thời gian hơn rất nhiều so với việc sửa một tọa độ GPS sai đã bị ghi vào hệ thống. Thiết kế hệ thống phải thừa nhận giới hạn của thuật toán, không giả vờ nó luôn đúng.

Bước 4 — Geocoding: từ địa chỉ suy ra tọa độ​

Khi đã match được cơ sở, tọa độ có thể đến từ ba nguồn:

NguồnĐộ chính xácChi phíGhi chú
Tọa độ có sẵn trong hệ thốngCao nhất0Ưu tiên dùng
Geocoding API (Google, Mapbox)CaoTheo requestCần API key, có giới hạn
Nominatim (OpenStreetMap)Trung bìnhMiễn phíGiới hạn 1 request/giây, phải có User-Agent
import time
import requests


def geocode_nominatim(dia_chi: str, cache: dict) -> tuple[float, float] | None:
"""Tra tọa độ qua Nominatim. BẮT BUỘC: tôn trọng rate limit."""
khoa = chuan_hoa(dia_chi)
if khoa in cache:
return cache[khoa]

try:
r = requests.get(
"https://nominatim.openstreetmap.org/search",
params={"q": dia_chi, "format": "json", "limit": 1, "countrycodes": "vn"},
headers={"User-Agent": "NoiBo-PCCC-Matching/1.0 (lien-he@congty.vn)"},
timeout=10,
)
r.raise_for_status()
du_lieu = r.json()

if not du_lieu:
cache[khoa] = None
return None

ket_qua = (float(du_lieu[0]["lat"]), float(du_lieu[0]["lon"]))
cache[khoa] = ket_qua
return ket_qua

except (requests.RequestException, ValueError, KeyError, IndexError):
cache[khoa] = None
return None
finally:
time.sleep(1.1) # TÔN TRỌNG rate limit 1 req/s

⚠️ Nominatim là dịch vụ miễn phí do cộng đồng OpenStreetMap tài trợ. Vi phạm giới hạn 1 request/giây có thể khiến IP của bạn bị chặn. Ngoài ra, dùng khối lượng lớn cho mục đích thương mại vi phạm điều khoản sử dụng — hãy dùng Google/Mapbox có trả phí trong trường hợp đó.

Kiểm tra kết quả — bước cuối không được bỏ qua​

def kiem_tra_toa_do(lat: float, lon: float) -> bool:
"""Kiểm tra tọa độ có nằm trong lãnh thổ Việt Nam không."""
return bool(lat and lon and 8.0 <= lat <= 23.5 and 102.0 <= lon <= 110.0)


def bao_cao_chat_luong(kq: pd.DataFrame) -> None:
tong = len(kq)
tu_dong = (~kq["can_kiem_tra"]).sum()
co_toa_do = kq["lat"].notna().sum()

print(f"Tổng thiết bị: {tong}")
print(f"Tự động gán được: {tu_dong} ({tu_dong / tong:.1%})")
print(f"Cần kiểm tra tay: {tong - tu_dong} ({(tong - tu_dong) / tong:.1%})")
print(f"Có tọa độ hợp lệ: {co_toa_do} ({co_toa_do / tong:.1%})")
print(f"Điểm trung bình: {kq['diem'].mean():.3f}")
print(f" - Trên 0.90: {(kq['diem'] >= 0.90).sum()}")
print(f" - 0.75 đến 0.90: {((kq['diem'] >= 0.75) & (kq['diem'] < 0.90)).sum()}")
print(f" - Dưới 0.75: {(kq['diem'] < 0.75).sum()}")

Điều cần kỳ vọng: với dữ liệu thực tế chưa được làm sạch, tỷ lệ tự động gán đúng thường chỉ đạt 60–80%. Con số 20–40% còn lại cần người kiểm tra. Đây là kết quả bình thường — đừng cố ép thuật toán đạt 100% bằng cách hạ ngưỡng, vì bạn sẽ đưa dữ liệu sai vào hệ thống.

Năm sai lầm phổ biến​

  1. Hạ ngưỡng để đạt tỷ lệ tự động cao — đổi lấy dữ liệu sai trong hệ thống.
  2. Không chuẩn hoá Unicode — lỗi so khớp khó hiểu giữa các máy khác hệ điều hành.
  3. Không cache kết quả geocoding — chạy lại tốn thời gian và có thể vượt rate limit.
  4. Dùng thuật toán O(n×m) với dữ liệu lớn — 702 × 221 = 155.000 phép so sánh, chấp nhận được; nhưng 100.000 × 50.000 là bất khả thi.
  5. Không kiểm tra tọa độ sau khi lấy — geocoding có thể trả về địa điểm cùng tên ở tỉnh khác.

💡 Khi dữ liệu lớn hơn nhiều, hãy dùng blocking: chia dữ liệu thành nhóm theo tiêu chí chắc chắn (quận/huyện, mã bưu chính) rồi chỉ so sánh trong từng nhóm. Điều này giảm số phép so sánh từ O(n×m) xuống gần O(n+m). Với 100.000 bản ghi, đây là khác biệt giữa vài giây và vài giờ.

TƯ DUY KHOA HỌC TRONG PHÂN TÍCH KỸ THUẬT (THEO CHUẨN CMT)

· 13 phút để đọc

TƯ DUY KHOA HỌC TRONG PHÂN TÍCH KỸ THUẬT (THEO CHUẨN CMT)

Biến phân tích kỹ thuật từ “cảm giác” thành “hệ thống có thể kiểm chứng”​

https://todotrader.com/wp-content/uploads/2019/04/Bases-of-the-Scientific-Trading-Systems-1-724x1024.jpg?x75585=&utm_source=chatgpt.com

https://d1rwhvwstyk9gu.cloudfront.net/2023/09/Steps-to-do-systematic-trading.png?utm_source=chatgpt.com

https://m.media-amazon.com/images/I/51OYQSO5LcL.AC_UF1000%2C1000_QL80.jpg?utm_source=chatgpt.com

6

Phần lớn người mới giao dịch theo cảm giác, còn trader cấp độ CMT giao dịch theo hệ thống tư duy khoa học. Phân tích kỹ thuật không phải nghệ thuật vẽ vời mô hình, mà là phương pháp định lượng hành vi giá dựa trên xác suất – thống kê – kiểm định thực nghiệm.

Đây chính là tư duy mà chương trình CMT truyền tải, và cũng là tư duy được áp dụng xuyên suốt trong khóa học Phân Tích Kỹ Thuật – Chiến Lược Xu Hướng VIP tại Hướng Nghiệp Dữ Liệu (link: https://www.huongnghiepdulieu.com/phan-tich-ky-thuat-chien-luoc-xu-huong-vip/).


1. Phân tích kỹ thuật không phải dự đoán – mà là đo lường và xác suất​

https://www.quantifiedstrategies.com/wp-content/uploads/2024/04/Trading-Probability-The-Odds-of-Winning.jpg?utm_source=chatgpt.com

https://s3.tradingview.com/b/BvhR1wGs_mid.webp?utm_source=chatgpt.com

https://www.questionpro.com/blog/wp-content/uploads/2017/10/2-Quantitative-Market-Research-1.jpg?utm_source=chatgpt.com

Sự khác biệt giữa người mới và người có tư duy CMT nằm ở đây:

  • Người mới cố “đoán đỉnh – đáy”.
  • Người tư duy khoa học hiểu rằng không thể dự đoán tuyệt đối , mà chỉ có thể ra quyết định dựa trên xác suất xảy ra cao nhất.

Phân tích kỹ thuật khoa học tập trung vào:

  • Xác suất tiếp diễn xu hướng
  • Xác suất phá vỡ mô hình
  • Xác suất retest thành công
  • Xác suất thành công của chiến lược theo chu kỳ

CMT không dạy “đúng – sai”, mà dạy kỳ vọng dương (positive expectancy).


2. Mọi chiến lược đều phải kiểm định được (Backtest & Forward Test)​

https://www.investopedia.com/thmb/WNk7Lq_aOElz_9Fiw1QmyieVwlU%3D/1500x0/filters%3Ano_upscale%28%29%3Amax_bytes%28150000%29%3Astrip_icc%28%29/Backtesting_final-cbc367cefc6f4553b7118a398b5845c2.png?utm_source=chatgpt.com

https://www.aihr.com/wp-content/uploads/Performance-Management-Strategies-13-Examples-To-Implement-Blog.png?utm_source=chatgpt.com

https://www.investopedia.com/thmb/WzvytassWcbDBxwd8DFDmQ8ftNc%3D/1500x0/filters%3Ano_upscale%28%29%3Amax_bytes%28150000%29%3Astrip_icc%28%29/dotdash_Final_The_Vital_Importance_Of_Defining_Your_Trading_Edge_IYR_Jul_2020-01-20482b8d84984d509abb186209c7ffb0.jpg?utm_source=chatgpt.com

Một chiến lược chỉ có giá trị khi:

  1. Có quy tắc rõ ràng
  2. Có thể kiểm định lại trên dữ liệu quá khứ
  3. Có thể tái lập kết quả trong nhiều điều kiện thị trường

Không kiểm định = không phải chiến lược. Đó là lý do tại sao trader theo chuẩn CMT:

  • Không tin vào chiến lược nghe “hay ho”
  • Không tin vào mô hình không kiểm chứng
  • Không tin vào indicator không rõ logic

Khóa Xu Hướng VIP áp dụng nguyên tắc này khi hướng dẫn học viên xây dựng – kiểm định – cải tiến chiến lược MA6–10–20, MACD, xu hướng và chu kỳ thị trường.


3. Tư duy mô hình – bối cảnh – chu kỳ (Model–Context–Cycle)​

https://static.seekingalpha.com/uploads/2020/4/7/saupload_understanding-market-structure.jpg?utm_source=chatgpt.com

https://tradethatswing.com/wp-content/uploads/2024/09/range-breakout-and-hold.jpg?utm_source=chatgpt.com

https://images.squarespace-cdn.com/content/v1/606db47af1026038e2fd5c6c/6ae301b1-d581-47bb-b1cd-f7398e45f464/The%2BTrend%2BCycle%2BGraph.png?utm_source=chatgpt.com

CMT không nhìn biểu đồ theo kiểu “thấy mô hình là vào lệnh”. Mà là:

1. Mô hình (Model)​

Mô hình nến, mô hình giá, MA, MACD…

2. Bối cảnh (Context)​

Xu hướng? Chu kỳ? Volume? Dòng tiền? Thị trường đang tăng mạnh, đi ngang, hay phân phối?

3. Chu kỳ (Cycle)​

Thị trường đang trong:

  • Tích lũy
  • Tăng
  • Phân phối
  • Giảm

Chỉ khi mô hình khớp với bối cảnh và chu kỳ , chiến lược mới có xác suất cao. Đây là một trong những tư duy cốt lõi của phân tích kỹ thuật cấp độ chuyên nghiệp.

Khóa học Xu Hướng VIP cũng dạy bối cảnh theo:

  • Cấu trúc HH–HL
  • MA6–10–20
  • MACD phân kỳ – hội tụ
  • Kênh xu hướng
  • Chu kỳ 3–4 nhịp

… đúng chuẩn tư duy Model–Context–Cycle.


4. Loại bỏ cảm xúc bằng hệ thống – nền tảng của tư duy khoa học​

https://www.quantifiedstrategies.com/wp-content/uploads/2024/05/Mastering-Trading-Rules-for-Success.jpg?utm_source=chatgpt.com

https://www.writofinance.com/wp-content/uploads/2024/09/Emotionless-option-trading.png?utm_source=chatgpt.com

https://cdn.corporatefinanceinstitute.com/assets/objective-vs-subjective-trading.png?utm_source=chatgpt.com

Người mới thua vì:

  • Vào lệnh theo cảm xúc
  • Thấy nến đẹp là vào
  • Thấy tín hiệu mơ hồ là fomo
  • Không có quy trình rõ ràng

Người theo chuẩn CMT thì ngược lại:

  • Có hệ thống
  • Có điều kiện vào lệnh rõ
  • Có stop-loss định nghĩa sẵn
  • Có kế hoạch thoát lệnh trước khi vào
  • Không đưa cảm xúc vào giao dịch

Khóa học Xu Hướng VIP nhấn mạnh điều này bằng quy trình:

  • Bộ 5 bước phân tích
  • Xác định điểm vào theo MA–xu hướng
  • Xác nhận bằng MACD
  • Quản trị vốn 30–70
  • TP/SL ≥ 2

Một hệ thống rõ ràng giúp loại bỏ 70% sai lầm của trader.


5. Định lượng hóa xu hướng – trái tim của phân tích kỹ thuật​

https://tradeciety.com/hubfs/Imported_Blog_Media/2-depth_3.png?utm_source=chatgpt.com

https://c.mql5.com/6/710/200-day-moving-average-trend-filter__6.png?utm_source=chatgpt.com

Tư duy khoa học biến “xu hướng” thành thứ đo được:

  • Độ dốc MA
  • Khoảng cách MA
  • HL–HH được xác nhận
  • MACD xác định cấp độ trend
  • Volume xác nhận nhịp tăng/giảm
  • Giá có nằm trong kênh xu hướng hay không

Không còn khái niệm: “Chart trông có vẻ tăng.”

Mà là: “Xu hướng tăng mạnh vì MA đồng pha, MACD mở rộng, giá giữ trên MA20.”

Khóa Xu Hướng VIP dành 10 buổi liên tục chỉ để:

  • Định lượng xu hướng
  • Đếm nhịp 3–4 nhịp
  • Phân biệt điều chỉnh thật và giả
  • Tìm điểm vào an toàn theo trend

Đây chính là khoa học trong phân tích kỹ thuật.


6. Hành vi giá phải được đặt trong mô hình xác suất – không phải cảm xúc​

https://i.ytimg.com/vi/3aIeyLrFGF8/hq720.jpg?rs=AOn4CLCAi5fR7tdyEhS5Gi24il66uZJb0g&sqp=-oaymwEhCK4FEIIDSFryq4qpAxMIARUAAAAAGAElAADIQj0AgKJD&utm_source=chatgpt.com

https://lh7-us.googleusercontent.com/FAkZJr7JSmjIKzbBV5l9VxK7bUPJt1LwD0Ezduz_JZhNcXAENUw8AgaxNMbWbEvdFeOcEivzp58Ho864lQsymGXje5S7U14KFEVYl159hOf8SUfrTQFNoSu2JkUT-_5NZmOH_Iteoktx9oOZpjkPypE?utm_source=chatgpt.com

https://www.investopedia.com/thmb/bkV1sKQtyQQ0x0LrBttyJu9gvtw%3D/1500x0/filters%3Ano_upscale%28%29%3Amax_bytes%28150000%29%3Astrip_icc%28%29/BuildaProfitableTradingModelIn7EasySteps2-93ba242cb2e3443a8a846ed36c92867f.png?utm_source=chatgpt.com

Cùng một mô hình:

  • Có lúc thắng
  • Có lúc thất bại
  • Không phải vì mô hình sai
  • Mà vì xác suất trong bối cảnh đó thấp

Tư duy khoa học của CMT là:

Không đánh giá mô hình đúng – sai. Mà đánh giá xác suất cao – thấp.

Khi xác suất thấp → đứng ngoài. Đó chính là cách trader bền vững.


7. Khóa “Phân Tích Kỹ Thuật – Chiến Lược Xu Hướng VIP” áp dụng đúng tư duy CMT​

https://s3.amazonaws.com/coursesity-blog/2021/06/Technical-analysis_App_Classes.png?utm_source=chatgpt.com

https://opentradez.com/wp-content/uploads/2021/06/live_2_day.jpeg?utm_source=chatgpt.com

https://m.media-amazon.com/images/I/6112I2cuLqL.AC_UF1000%2C1000_QL80.jpg?utm_source=chatgpt.com

Khóa học tại Hướng Nghiệp Dữ Liệu không dạy mẹo – không dạy bí kíp , mà dạy đúng tư duy khoa học của CMT:

  • Hiểu xu hướng
  • Hiểu chu kỳ
  • Hiểu hành vi giá
  • Xây dựng chiến lược dựa trên dữ liệu
  • Rèn luyện qua bài tập thực tế
  • Học 1:1 để sửa tư duy và hệ thống

Tất cả nhằm giúp học viên:

Không giao dịch theo cảm xúc Không dựa vào “đoán”, Chỉ giao dịch theo xác suất và hệ thống.

Xem thông tin khóa học: https://www.huongnghiepdulieu.com/phan-tich-ky-thuat-chien-luoc-xu-huong-vip/


Kết luận​

Phân tích kỹ thuật không phải bói toán , cũng không phải tìm “chén thánh”. Nó là khoa học của hành vi giá , và trader phải tiếp cận nó như một nhà nghiên cứu:

  • Đưa ra giả thuyết
  • Xác định quy tắc
  • Kiểm định dữ liệu
  • Loại bỏ cảm xúc
  • Theo xu hướng có xác suất cao nhất

Tư duy khoa học là nền tảng để đi xa. Đó cũng là nền tảng của mọi nội dung trong khóa Xu Hướng VIP.

Đi sâu: biến tư duy khoa học thành quy trình kiểm định​

"Tư duy khoa học" nghe trừu tượng. Phần này biến nó thành các bước cụ thể, có thể lập trình được.

Bước 1 — Phát biểu giả thuyết ở dạng kiểm định được​

Một giả thuyết kém: "Chỉ báo RSI hoạt động tốt."

Một giả thuyết khoa học:

Trên EURUSD khung H1, khi RSI(14) cắt lên dưới 30 và giá đóng cửa trên EMA(50), thì xác suất giá tăng ít nhất 1×ATR(14) trong 20 nến tiếp theo cao hơn đáng kể so với tỷ lệ cơ bản của thị trường.

Yếu tốGiả thuyết kémGiả thuyết khoa học
Thị trườngKhông nêuEURUSD
Khung thời gianKhông nêuH1
Điều kiện vàoMơ hồRSI cắt lên 30 + giá > EMA50
Định nghĩa "thành công"Không có+1×ATR trong 20 nến
Đối chứngKhông cóSo với tỷ lệ cơ bản

Có đối chứng là điều kiện bắt buộc. Nếu thị trường tăng 55% thời gian, một chiến lược đạt 52% không có lợi thế — nó kém thị trường.

Bước 2 — Tính tỷ lệ cơ bản trước​

import numpy as np
import pandas as pd


def ty_le_co_ban(df: pd.DataFrame, so_nen_truoc: int = 20,
he_so_atr: float = 1.0) -> float:
"""Tỷ lệ thị trường tự tăng 1×ATR trong 20 nến — không cần tín hiệu nào.

Đây là mốc so sánh. Chiến lược phải VƯỢT con số này mới có giá trị.
"""
atr = (df["high"] - df["low"]).rolling(14).mean()
ket_qua = []

for i in range(len(df) - so_nen_truoc):
gia_vao = df["close"].iloc[i]
muc_tieu = gia_vao + he_so_atr * atr.iloc[i]
tuong_lai = df["high"].iloc[i + 1 : i + 1 + so_nen_truoc]
ket_qua.append(bool((tuong_lai >= muc_tieu).any()))

return float(np.mean(ket_qua))


def ty_le_voi_dieu_kien(df: pd.DataFrame, dieu_kien: pd.Series,
so_nen_truoc: int = 20, he_so_atr: float = 1.0) -> float:
"""Tỷ lệ tăng của các lần tín hiệu xuất hiện."""
atr = (df["high"] - df["low"]).rolling(14).mean()
chi_so = np.where(dieu_kien.fillna(False).values)[0]
ket_qua = []

for i in chi_so:
if i + so_nen_truoc >= len(df) or pd.isna(atr.iloc[i]):
continue
muc_tieu = df["close"].iloc[i] + he_so_atr * atr.iloc[i]
tuong_lai = df["high"].iloc[i + 1 : i + 1 + so_nen_truoc]
ket_qua.append(bool((tuong_lai >= muc_tieu).any()))

return float(np.mean(ket_qua)) if ket_qua else 0.0

Bước 3 — Kiểm định ý nghĩa thống kê​

Điều quan trọng nhất: sự khác biệt có thật hay chỉ là may mắn?

from scipy import stats


def kiem_dinh_lien_quan(ds_co_tin_hieu: list[bool], ds_tat_ca: list[bool]) -> dict:
"""Kiểm định tỷ lệ thành công giữa hai nhóm có khác nhau không."""
thanh_cong_a = sum(ds_co_tin_hieu)
thanh_cong_b = sum(ds_tat_ca)

bang = [
[thanh_cong_a, len(ds_co_tin_hieu) - thanh_cong_a],
[thanh_cong_b, len(ds_tat_ca) - thanh_cong_b],
]
_, p_value = stats.fisher_exact(bang)

ty_le_a = thanh_cong_a / len(ds_co_tin_hieu)
ty_le_b = thanh_cong_b / len(ds_tat_ca)

return {
"ty_le_co_tin_hieu": f"{ty_le_a:.2%}",
"ty_le_co_ban": f"{ty_le_b:.2%}",
"chenh_lech": f"{(ty_le_a - ty_le_b) * 100:+.2f}%",
"so_mau": len(ds_co_tin_hieu),
"p_value": round(p_value, 4),
"ket_luan": "✅ Có ý nghĩa" if p_value < 0.05 else "❌ Chưa đủ bằng chứng",
}

Cách đọc kết quả đúng:

Kết quảDiễn giải
p < 0,05 và chênh lệch > 5%Có bằng chứng về lợi thế
p < 0,05 nhưng chênh lệch 1–2%Có ý nghĩa thống kê nhưng không bù được phí giao dịch
p ≥ 0,05Chưa đủ bằng chứng — không phải là "không có", chỉ là chưa chứng minh được
Số mẫu < 30Kết quả không đáng tin bất kể p-value

⚠️ Sai lầm nghiêm trọng nhất: p-hacking. Nếu bạn thử 20 tổ hợp chỉ báo và báo cáo tổ hợp có p < 0,05, bạn đã tự lừa mình. Với 20 phép thử, xác suất có ít nhất một kết quả p < 0,05 do ngẫu nhiên là khoảng 64%. Hãy đặt giả thuyết trước, rồi kiểm định một lần.

Bước 4 — Kiểm tra tính bền vững​

Một lợi thế thật phải tồn tại qua các điều kiện khác nhau.

def kiem_tra_ben_vung(df: pd.DataFrame, ham_tin_hieu, tham_so_list: list[dict]) -> pd.DataFrame:
"""Chạy cùng chiến lược với nhiều bộ tham số.

Nếu kết quả tốt chỉ ở MỘT bộ tham số → overfit.
Nếu kết quả tốt ở MỘT VÙNG tham số → đáng tin.
"""
ban_ghi = []
for ts in tham_so_list:
tin_hieu = ham_tin_hieu(df, **ts)
ty_le = ty_le_voi_dieu_kien(df, tin_hieu)
ban_ghi.append({**ts, "ty_le": round(ty_le * 100, 2)})
return pd.DataFrame(ban_ghi)


# Ví dụ
# print(kiem_tra_ben_vung(df, ham_tin_hieu,
# [{"nguong_rsi": n} for n in (25, 28, 30, 32, 35)]))
Mẫu kết quảKết luận
52%, 51%, 53%, 52%, 51%✅ Bền vững — lợi thế không phụ thuộc tham số
48%, 49%, 71%, 50%, 47%❌ Overfit — chỉ 1 tham số may mắn
45%, 47%, 49%, 50%, 51%⚠️ Không có lợi thế — chỉ là nhiễu

Bước 5 — Kiểm định ngoài mẫu (out-of-sample)​

Đây là bước cuối và quan trọng nhất:

Dữ liệu 2018–2024  →  70% dùng để PHÁT HIỆN (in-sample)
30% giữ riêng để KIỂM ĐỊNH (out-of-sample)

Quy tắc: chỉ chạm vào 30% đó MỘT LẦN DUY NHẤT.
Nếu kết quả tốt → có cơ sở để tin.
Nếu kết quả kém → chiến lược thất bại, KHÔNG được chỉnh lại rồi thử lại.

⚠️ Cám dỗ chết người: nhìn kết quả out-of-sample kém, chỉnh tham số, chạy lại. Làm vậy thì tập "out-of-sample" đã trở thành in-sample — và bạn không còn cách nào kiểm định nữa. Nếu chiến lược thất bại, hãy ghi nhận và chuyển sang giả thuyết khác.

Sáu nguyên tắc của tư duy khoa học trong giao dịch​

  1. Đặt giả thuyết trước, xem dữ liệu sau. Không tìm mẫu hình rồi mới nghĩ ra lý do.
  2. Luôn có đối chứng. Luôn so với tỷ lệ cơ bản hoặc chiến lược đơn giản nhất.
  3. Đo lường, đừng đoán. "Cảm giác" không phải bằng chứng.
  4. Chấp nhận bằng chứng phủ định. Một giả thuyết bị bác bỏ là kết quả tốt — nó tiết kiệm tiền của bạn.
  5. Ghi lại mọi thử nghiệm. Kể cả thất bại, để không thử lại điều đã biết là sai.
  6. Cẩn thận với số mẫu nhỏ. 20 lệnh thắng không chứng minh được điều gì.

Nhật ký kiểm định mẫu​

## Giả thuyết #7: RSI oversold + EMA filter

- **Ngày kiểm định:** 2026-09-15
- **Dữ liệu:** EURUSD H1, 2019-01-01 → 2024-12-31
- **Số mẫu:** 412 tín hiệu

| Chỉ số | Giá trị |
|--------|---------|
| Tỷ lệ cơ bản | 51,2% |
| Tỷ lệ có tín hiệu | 56,4% |
| Chênh lệch | +5,2% |
| p-value | 0,031 |
| Sau khi trừ phí 0,2% | +1,8% (dương) |

- **Kiểm định bền vững:** ngưỡng RSI 25–35 đều cho 55–57% ✅
- **Out-of-sample (2025):** 54,1% ✅

- **Kết luận:** Có lợi thế nhỏ nhưng bền vững. Cần thêm lọc
để giảm số lệnh (hiện 412 lệnh/6 năm = ~5,7 lệnh/tháng).
- **Bước tiếp theo:** thử thêm bộ lọc theo ATR để loại giai đoạn biến động thấp.

Viết nhật ký như thế này mất 10 phút. Nhưng nó giúp bạn nhớ chính xác mình đã thử gì, tránh thử lại điều đã biết là sai, và quan trọng nhất — nó biến giao dịch từ phỏng đoán thành quy trình có thể cải tiến.