Hiện tượng khi đẩy tín hiệu từ amibroker lên tele "Có 4 mã nhưng chỉ đẩy được 3 mã" là một tình huống rất kinh điển khi kết nối phần mềm giao dịch với các nền tảng chat.
Nguyên nhân 100% không phải do code sai, mà là do Cơ chế chống Spam (Rate Limit) của Telegram! Cụ thể, Telegram có một luật rất nghiêm ngặt: Không được gửi quá 1 tin nhắn mỗi giây vào cùng một nhóm chat. Khi AmiBroker của bác quét ra 4 mã cổ phiếu cùng lúc, nó có tốc độ xử lý quá nhanh (chưa tới 0.2 giây là nó đẩy xong cả 4 mã).
Hệ quả là: Telegram nhận được 2-3 mã đầu tiên, nhưng đến mã thứ 4 nó tưởng bác đang cố tình "Spam" nên nó đã tự động chặn luôn mã đó lại.
Cách giải quyết là Bắn Telegram có độ trễ 1 giây để chống Spam. Để lách qua hệ thống tự vệ này của AmiBroker mà vẫn ép nó phải nghỉ 1 giây cho Telegram khỏi chặn, chúng ta sẽ dùng một "tiểu xảo" rất hay: Nhờ hệ điều hành Windows đếm nhịp bằng lệnh Ping thay vì bắt AmiBroker tự đếm.
Nếu mã đó đã báo trong ngày rồi thì ngày đó sẽ không báo lại nữa đúng không?
Đây là câu hỏi được nhiều nhà đầu tư hỏi nhất. Mỗi lần bạn bấm nút Explore, nó sẽ quét lại toàn bộ thị trường. Nếu lúc 10h sáng mã GAS báo MUA, nó sẽ bắn lên Telegram. Nếu 11h bạn rảnh tay bấm Explore thêm phát nữa và GAS vẫn giữ form đẹp (vẫn đạt điều kiện), AmiBroker sẽ lại ngây thơ bắn GAS thêm một lần nữa vì nó không nhớ là lúc 10h sáng nó đã báo rồi! Điều này sẽ làm loạn Group Telegram.
CÁCH GIẢI QUYẾT: DẠY AMIBROKER "GHI NHỚ". Đã báo MUA hôm nay rồi thì cấm báo lại, TRỪ KHI mã đó quay xe báo BÁN thì mới được báo tiếp. Để làm được điều này, chúng ta sử dụng biến StaticVar của AmiBroker để tạo ra một "Cuốn Sổ Ghi Nhớ" trong bộ nhớ RAM của máy tính.
// =========================================================================
// ROBOT LỌC TÍN HIỆU VÀ BẮN TELEGRAM
// =========================================================================
// -------------------------------------------------------------------------
// 1. BỘ LỌC TÍN HIỆU CỐT LÕI
// -------------------------------------------------------------------------
_V20 = MA(V, 20);
_C20 = MA(C, 20);
VolDownMax10 = HHV( IIf(C < Ref(C, -1), V, 0), 10 );
isPocketPivot = C > Ref(C, -1) AND V > VolDownMax10 AND C > _C20 AND Ref(C,-1) <= _C20*1.02;
isVCPBreak = Cross(C, HHV(Ref(H, -1), 20)) AND V > _V20 * 1.5 AND C == H;
isSpring = L < Ref(LLV(L, 20), -1) AND C > O AND C > (H+L)/2 AND V > _V20;
isUpthrust = H > Ref(HHV(H, 20), -1) AND C < (H+L)/2 AND V > _V20 * 1.5;
BuySig = isPocketPivot OR isVCPBreak OR isSpring;
SellSig = isUpthrust;
Filter = BuySig OR SellSig;
// -------------------------------------------------------------------------
// 2. TÍNH TOÁN & BẮN LÊN TELEGRAM (CÓ BỘ NHỚ CHỐNG LẶP)
// -------------------------------------------------------------------------
LB = BarCount - 1;
if (Filter[LB]) {
ActionStr = "BAN";
if (BuySig[LB]) ActionStr = "MUA";
// 🧠 TẠO MÃ GHI NHỚ TRÁNH BÁO CÙNG 1 MÃ NHIỀU LẦN TRONG NGÀY
TodayNum = Now(3);
MemKey = "SENT_TELE_" + NumToStr(TodayNum, 1.0, False) + "_" + Name() + "_" + ActionStr;
// 🛑 KIỂM TRA: Nếu mã này hôm nay chưa báo thì mới chạy tiếp
if (Nz(StaticVarGet(MemKey)) == 0) {
SigNameStr = "";
if (isPocketPivot[LB]) SigNameStr = "Pocket Pivot Chan Song";
else if (isVCPBreak[LB]) SigNameStr = "Breakout VCP Bung No Vol";
else if (isSpring[LB]) SigNameStr = "Spring Ru Bo (Wyckoff)";
else if (isUpthrust[LB]) SigNameStr = "Upthrust - Bay Tang Gia";
// --- LẤY NGÀY GIỜ CHUẨN ---
DateStrFull = NumToStr(TodayNum, 1.0, False);
DayStr = StrRight(DateStrFull, 2);
MonthStr = StrMid(DateStrFull, 4, 2);
YearStr = StrLeft(DateStrFull, 4);
DateDisplay = DayStr + "/" + MonthStr + "/" + YearStr;
vTimeNum = Now(4);
TimeStrFull = StrFormat("%06.0f", vTimeNum);
TimeDisplay = StrLeft(TimeStrFull, 2) + ":" + StrMid(TimeStrFull, 2, 2);
PriceStr = NumToStr(C[LB], 1.2);
Ticker = Name();
// --- TÍNH TOÁN THÔNG SỐ (%, VOL, GTGD) ---
PrevClose = C[LB - 1];
PctChange = (C[LB] - PrevClose) / PrevClose * 100;
PctStrTele = NumToStr(PctChange, 1.2) + "%25";
if (PctChange > 0) {
PctStrTele = "%2B" + PctStrTele;
}
VolRatio = V[LB] / _V20[LB];
VolRatioStr = NumToStr(VolRatio, 1.1) + "x";
ValBil = (C[LB] * V[LB]) / 1000000;
ValStr = NumToStr(ValBil, 1.1) + " Ty";
// --- CẤU HÌNH GỬI TELEGRAM ---
TeleToken = "xxxxx";
TeleChatID = "xxxxxxx";
TeleIcon = "%E2%9A%A0";
if (BuySig[LB]) TeleIcon = "%F0%9F%9A%80";
TeleMsg = "%F0%9F%9A%A8 TIN HIEU ROBOT UPTRADINGVN %F0%9F%9A%A8%0A%0A" +
"%E2%8F%B0 Thoi gian: " + TimeDisplay + " (" + DateDisplay + ")%0A" +
TeleIcon + " Hanh dong: " + ActionStr + "%0A" +
"%F0%9F%8F%B7 Ma: " + Ticker + " (" + PctStrTele + ")%0A" +
"%F0%9F%93%9D Mo hinh: " + SigNameStr + "%0A" +
"%F0%9F%92%B2 Vung gia: " + PriceStr + "%0A" +
"%F0%9F%93%8A Vol vs MA20: " + VolRatioStr + "%0A" +
"%F0%9F%92%B0 GTGD: " + ValStr;
TeleURL = "https://api.telegram.org/bot" + TeleToken + "/sendMessage?chat_id=" + TeleChatID + "&text=" + TeleMsg;
httpTele = CreateObject("MSXML2.XMLHTTP");
if(httpTele) {
httpTele.Open("GET", TeleURL, 0);
httpTele.Send();
_TRACE("SUCCESS: Da ban Telegram ma " + Ticker);
}
// 🧠 LƯU VÀO SỔ TAY ĐỂ KHÔNG BÁO LẶP
StaticVarSet(MemKey, 1);
// 🛑 NGHỈ 1 GIÂY CHỐNG SPAM TELEGRAM
wsh = CreateObject("WScript.Shell");
if(wsh) {
wsh.Run("ping 127.0.0.1 -n 2", 0, True);
}
}
}
// -------------------------------------------------------------------------
// 3. HIỂN THỊ BẢNG KẾT QUẢ AMIBROKER
// -------------------------------------------------------------------------
ActionCol = WriteIf(BuySig, "MUA", "BAN");
SigNameCol = WriteIf(isPocketPivot, "Pocket Pivot",
WriteIf(isVCPBreak, "Breakout VCP",
WriteIf(isSpring, "Spring Ru Bo",
WriteIf(isUpthrust, "Upthrust", ""))));
AddTextColumn(ActionCol, "Hanh Dong", 1.0, colorWhite, IIf(BuySig, colorDarkGreen, colorDarkRed));
AddTextColumn(SigNameCol, "Tin Hieu", 1.0);
AddColumn(V, "Khoi Luong", 1.0);
