1. Mô tả chỉ báo Ranked Support & Resistance Zones (Zeiierman)
Chỉ báo biến mỗi đỉnh/đáy dao động (swing pivot) thành một vùng hỗ trợ/kháng cự được chấm điểm, xếp hạng lại mỗi nến, và chỉ hiển thị những vùng mạnh nhất trên biểu đồ
1.1 Concept của chỉ báo
Hầu hết các chỉ báo hỗ trợ/kháng cự (Support/Resistance - S/R) thông thường chỉ đơn giản đánh dấu mọi đỉnh/đáy dao động thành một đường kẻ, khiến biểu đồ bị lấp đầy bởi hàng chục đường mà không rõ đường nào thực sự quan trọng. Chỉ báo này được xây dựng trên một tư duy khác: không phải vùng S/R nào cũng đáng được chú ý như nhau. Một vùng hình thành trên khối lượng lớn, thuận theo xu hướng chính, sinh ra từ một cú swing sắc nét/rõ ràng, và đã tự chứng minh bằng việc bị giá chạm lại và giữ vững nhiều lần, thì có ý nghĩa cấu trúc cao hơn hẳn một pivot ngẫu nhiên chỉ xuất hiện một lần rồi không bao giờ bị giá quay lại kiểm tra.
- Vùng giá (zone), không phải đường kẻ: mỗi mức S/R được lưu dưới dạng một dải giá (biến số array — tức "mảng", một danh sách chứa nhiều đối tượng vùng giá cùng lúc) có độ dày tỉ lệ theo ATR (Average True Range - biên độ dao động trung bình), thay vì một mức giá duy nhất, vì trong thực tế giá hiếm khi phản ứng chính xác tại một con số tuyệt đối.
- Cỗ máy tính điểm: mỗi vùng liên tục được chấm điểm từ 0-100 dựa trên 5 yếu tố - kích thước vùng, khối lượng lúc hình thành, sự thuận theo xu hướng, độ sắc nét của swing, và số lần đã được kiểm tra lại - trừ đi các khoản phạt vì tuổi vùng đã cũ và vì bị giá "ăn sâu" vào bên trong (gọi là mitigation).
- Xếp hạng, không phải cộng dồn: mỗi nến, toàn bộ vùng đang lưu trữ được sắp xếp lại theo điểm số, và chỉ Top-N vùng cao điểm nhất mới thực sự được vẽ ra. Các vùng xếp hạng thấp hơn vẫn được giữ trong bộ nhớ (phòng khi điểm số tăng trở lại) nhưng bị ẩn khỏi biểu đồ, giúp biểu đồ luôn gọn gàng.
- Có vòng đời: một vùng không tồn tại mãi mãi. Nó có thể bị hấp thụ vào một vùng lân cận đã có sẵn (giảm trùng lặp), bị mitigation (giá đã lấp một phần vào bên trong, làm vùng yếu đi), bị phá vỡ (giá đóng cửa xuyên qua một cách dứt khoát, vô hiệu hoá vùng), hoặc đơn giản là hết hạn vì quá cũ.
1.2 Chức năng của chỉ báo
-
Hiển thị Box vùng giá (Zone Box):
- Ô hình chữ nhật màu xanh ngọc = vùng hỗ trợ (support - đáy giá), ô màu cam = vùng kháng cự (resistance - trần giá).
- Mỗi box trải dài theo một dải giá (đỉnh/đáy) và kéo dài liên tục sang bên phải (theo thời gian) khi vùng còn đang hoạt động.
- Chỉ những vùng xếp hạng cao nhất (điều chỉnh bằng "Show Top Zones") mới được tô màu đầy đủ; phần còn lại vẫn tồn tại trong bộ nhớ nhưng hoàn toàn trong suốt.
-
Thanh sức mạnh (Strength Bars):
- Bên trong mỗi vùng đang hiển thị, nửa trên có thể hiện thanh màu cam kèm % phe bán (bear), nửa dưới hiện thanh màu xanh ngọc kèm % phe mua (bull).
- Chiều dài thanh tỉ lệ thuận với phần trăm - thanh bull 90% sẽ kéo dài gần hết chiều rộng vùng, trong khi thanh 20% chỉ ngắn một đoạn nhỏ.
- Nhãn chữ trong vùng (Zone Text): một nhãn ngắn gọn như "Strong Support" (Hỗ trợ mạnh), "Support" (Hỗ trợ), "Weak Support" (Hỗ trợ yếu) hoặc "Mitigated Support" (Hỗ trợ đã bị ăn sâu) - và tương tự cho phía kháng cự - được in bên trong vùng, tóm tắt chất lượng hiện tại của vùng bằng lời.
- Dấu vết vùng đã vỡ: khi một vùng bị vô hiệu hoá, box của nó chuyển sang màu xám và đường tâm (centerline) trở thành một đường nét đứt màu xám đánh dấu chính xác nơi vùng cũ từng nằm. Một số lượng giới hạn các vùng vừa vỡ gần nhất được giữ lại trên biểu đồ trước khi bị xoá hẳn.
- Bộ lọc hướng (Direction Filter): chỉ báo có thể được đặt để chỉ hiện "All" (tất cả), "Support Only" (chỉ hỗ trợ), hoặc "Resistance Only" (chỉ kháng cự).
- Alert (cảnh báo): 12 điều kiện cảnh báo bao phủ việc tạo vùng mới, thay đổi vùng dẫn đầu bảng xếp hạng, giá chạm/ăn sâu vào vùng, và vùng bị phá vỡ - tách riêng cho từng phía hỗ trợ và kháng cự.
1.3 Cách dùng chỉ báo
- Ưu tiên giao dịch các vùng xếp hạng cao nhất: vì vùng được xếp hạng lại mỗi nến, những vùng đang được vẽ trên biểu đồ của bạn (theo chính hệ thống chấm điểm của chỉ báo) là các mức S/R quan trọng nhất tại thời điểm hiện tại - hãy coi đây là các vùng phản ứng chính để vào lệnh, chốt lời, hoặc đặt dừng lỗ.
- Đọc thanh sức mạnh trước khi phản ứng: một vùng hỗ trợ có thanh bull 85% là một vùng "mua khi giá điều chỉnh" có độ tin cậy cao hơn nhiều so với vùng chỉ có 30%. So sánh % bull vs % bear trong cùng một vùng để biết bên nào đang thắng thế tại khu vực giá đó.
- Theo dõi nhãn chữ để cảnh báo sớm: khi nhãn của một vùng chuyển thành "Weak Support" hoặc "Mitigated Support", nghĩa là giá đã ăn sâu vào phần lớn vùng đó rồi - hãy coi vùng này kém tin cậy hơn một vùng "Strong Support" còn nguyên vẹn.
- Dùng alert phá vỡ làm xác nhận breakout: alert "Resistance Broken" nghĩa là giá đã đóng cửa vượt dứt khoát qua một trần kháng cự (một cú breakout tăng), trong khi "Support Broken" nghĩa là một cú breakout giảm xuyên qua sàn hỗ trợ - cả hai đều có thể dùng làm tín hiệu vào lệnh theo breakout/momentum.
- Dùng alert top-rank để theo dõi vùng dẫn đầu: "New Top-Ranked S/R Zone" bắn ra mỗi khi vùng #1 điểm cao nhất đổi sang một vùng khác, giúp bạn biết mức giá quan trọng nhất trên biểu đồ vừa thay đổi.
- Điều chỉnh theo thị trường/khung thời gian: tăng "Pivot Length" và "Min Pivot ATR" trên các khung thời gian nhiễu/thấp để chỉ giữ lại các swing sạch, được xác nhận tốt; nới rộng "Zone Width ATR" trên các mã biến động mạnh để vùng không bị quá mỏng dẫn đến phản ứng kém.
1.4 Cách hoạt động của chỉ báo
Đầu vào & vai trò
- visibleLimit ("Show Top Zones", số nguyên, mặc định: 8, khoảng 1-30): số lượng vùng xếp hạng cao nhất thực sự được vẽ lên biểu đồ. Ví dụ: visibleLimit=8, nếu hệ thống đang lưu 40 vùng trong bộ nhớ, chỉ 8 vùng điểm cao nhất được tô màu - 32 vùng còn lại vẫn ẩn cho tới khi điểm của chúng đủ cao để lọt vào top 8. Tăng → nhiều vùng hiển thị cùng lúc hơn (biểu đồ rối hơn). Giảm → chỉ những vùng tốt nhất được hiện (biểu đồ sạch hơn, nhưng có thể bỏ lỡ các mức phụ).
- storedLimit ("Max Stored Zones", số nguyên, mặc định: 60, khoảng 10-250): số lượng vùng tối đa được giữ trong bộ nhớ (cả hiển thị lẫn ẩn). Khi vượt quá, vùng điểm thấp nhất bị xoá mỗi nến cho tới khi số lượng trở lại đúng giới hạn. Tăng → nhớ được nhiều vùng lịch sử hơn, tăng khả năng một vùng đang ẩn có thể leo trở lại top-N sau này. Giảm → tốn ít bộ nhớ hơn, nhưng các vùng cũ điểm thấp bị quên nhanh hơn.
- pivotSpan ("Pivot Length", số nguyên, mặc định: 5, khoảng 2-50): số nến cần có ở cả hai bên của một đỉnh/đáy dao động (swing) để nó được xác nhận là một pivot (dùng hàm ta.pivothigh/ta.pivotlow). Ví dụ: pivotSpan=5 nghĩa là một cây nến chỉ trở thành đỉnh dao động được xác nhận khi 5 nến trước VÀ 5 nến sau nó đều thấp hơn - do đó việc xác nhận luôn đến trễ 5 nến. Tăng → ít pivot hơn nhưng đáng tin cậy hơn (chỉ các swing lớn). Giảm → phát hiện nhiều pivot hơn, tạo nhiều vùng hơn (và nhiễu hơn), nhưng được xác nhận nhanh hơn.
- minSwingAtr ("Min Pivot ATR", số thập phân, mặc định: 0.15, khoảng 0-5): độ "sắc nét" tối thiểu mà một pivot phải có, tính theo bội số ATR, trước khi được phép trở thành một vùng. Độ sắc nét (swing quality) là mức độ pivot nhô ra cao/thấp hơn các cây nến ngay sát bên cạnh nó. Ví dụ: nếu ATR=$10 và minSwingAtr=0.15, một đỉnh dao động phải nhô cao hơn đỉnh của các nến lân cận ít nhất $1.5 mới đạt điều kiện; một pivot bằng phẳng, nhô ra rất ít sẽ bị bỏ qua. Tăng → chỉ những swing sắc nét, rõ ràng mới tạo vùng (ít vùng hơn, sạch hơn). Giảm (về gần 0) → ngay cả pivot rất nhẹ cũng tạo vùng (nhiều vùng hơn, nhiễu hơn).
- absorbAtr ("Absorb Similar Zones", số thập phân, mặc định: 0.55, khoảng 0-3): khoảng cách tối đa (tính theo ATR) giữa tâm của một pivot mới và tâm của một vùng đang có sẵn để pivot mới đó được gộp vào vùng cũ thay vì tạo vùng trùng lặp. Ví dụ: ATR=$10, absorbAtr=0.55 → bất kỳ pivot mới nào nằm trong $5.5 quanh tâm một vùng đang có sẵn sẽ bị hấp thụ (tái xác nhận/mở rộng vùng đó) thay vì vẽ một box hoàn toàn mới ngay bên cạnh. Tăng → gộp mạnh tay hơn, tổng số vùng ít hơn, mỗi vùng "được kiểm chứng" nhiều hơn. Giảm → tạo nhiều vùng riêng biệt hơn ngay cả khi chúng ở gần nhau.
- zoneAtrWidth ("Zone Width ATR", số thập phân, mặc định: 0.40, khoảng 0.05-2): độ dày của một box vùng, tính theo bội số ATR, chia đều lên trên và xuống dưới giá pivot. Ví dụ: ATR=$10, zoneAtrWidth=0.40 → nửa độ dày vùng = 0.40×10×0.5 = $2, nên một pivot tại $100 tạo ra vùng trải từ $98 đến $102. Tăng → vùng dày hơn (giá phản ứng "bên trong" một dải rộng hơn, nhưng các vùng dễ chồng lấn và hấp thụ nhau hơn). Giảm → vùng mỏng hơn, chính xác hơn, nhưng dễ bị bỏ lỡ hoặc bị phá vỡ ngay lập tức chỉ bởi một cây râu nến (wick).
- volLen ("Volume MA Length", số nguyên, mặc định: 20): số nến nhìn lại để tính đường trung bình khối lượng nền (ta.sma(volume, volLen)) mà khối lượng lúc tạo vùng mới sẽ được so sánh vào. Ví dụ: nếu khối lượng trung bình 20 nến là 1.000 và khối lượng của nến pivot là 2.000, điểm khối lượng của vùng = 2.0 (gấp đôi trung bình), giúp tăng thứ hạng của vùng. Tăng → đường nền mượt hơn, thay đổi chậm hơn. Giảm → đường nền phản ứng nhanh hơn với các đợt tăng/giảm khối lượng gần đây.
- trendLen ("Trend EMA Length", số nguyên, mặc định: 50): độ dài của đường EMA dùng để đánh giá một vùng có thuận theo xu hướng lớn hay không. Một vùng hỗ trợ hình thành phía trên EMA này, hoặc một vùng kháng cự hình thành phía dưới EMA này, sẽ được cộng điểm thưởng thuận xu hướng (ý tưởng: một cú điều chỉnh về hỗ trợ trong xu hướng tăng, hoặc một cú hồi bị từ chối tại kháng cự trong xu hướng giảm, mang nhiều ý nghĩa hơn). Tăng → bộ lọc xu hướng phản ứng theo thiên hướng dài hạn hơn. Giảm → phản ứng theo thiên hướng ngắn hạn hơn.
- breakAtr ("Break Close Buffer ATR", số thập phân, mặc định: 0.12, khoảng 0-3): giá phải đóng cửa vượt qua mép vùng bao xa (tính theo ATR) trước khi vùng đó bị coi là phá vỡ - một vùng đệm giúp tránh việc một vùng bị vô hiệu hoá chỉ vì một cú đóng cửa sát mép biên. Ví dụ: ATR=$10, breakAtr=0.12 → một vùng hỗ trợ có đáy tại $98 chỉ bị coi là vỡ khi close < 98 - (0.12×10) = $96.8. Tăng → vùng khó bị phá vỡ hơn (đệm rộng hơn, ít phá vỡ giả hơn). Giảm → dễ bị phá vỡ hơn, nhạy hơn với các cú đóng cửa sát biên.
- keepBrokenCount ("Keep Broken Zones", số nguyên, mặc định: 4, khoảng 0-30): số lượng vùng vừa bị phá vỡ gần nhất được giữ hiển thị (dưới dạng box xám + đường nét đứt) trên biểu đồ trước khi bị xoá hẳn, vùng cũ nhất bị xoá trước. Đặt về 0 → vùng vỡ biến mất ngay lập tức, không giữ lại dấu vết trực quan nào. Tăng → giữ lại nhiều lịch sử vùng đã vỡ hơn để tham chiếu (ví dụ: một kháng cự cũ giờ có thể đóng vai trò hỗ trợ mới).
- directionFilter ("Direction Filter", tuỳ chọn: All / Support Only / Resistance Only, mặc định: All): giới hạn những vùng nào đủ điều kiện được hiển thị. Ví dụ: chọn "Support Only" sẽ ẩn toàn bộ vùng kháng cự khỏi danh sách top-N hiển thị (chúng vẫn tồn tại và vẫn được chấm điểm ngầm, chỉ là không bao giờ được vẽ ra).
- showStrengthBars (boolean - kiểu bật/tắt đúng-sai, mặc định: true): hiện/ẩn các thanh sức mạnh bull%/bear% bên trong mỗi vùng đang hiển thị. Nếu tắt, vùng chỉ được vẽ dưới dạng box màu trơn, không có thanh bên trong.
- showZoneText (boolean, mặc định: true): hiện/ẩn nhãn chữ ("Strong Support", v.v.) bên trong mỗi vùng đang hiển thị.
- strengthTextSize / zoneTextSize (tuỳ chọn: auto/tiny/small/normal/large/huge, mặc định: auto): điều chỉnh cỡ chữ của nhãn % sức mạnh và nhãn chữ vùng. Thuần tuý về hình thức, không ảnh hưởng đến tính toán.
- supportColor / resistanceColor / brokenColor: màu tô/màu viền dùng cho vùng hỗ trợ, vùng kháng cự, và vùng đã vỡ. Thuần tuý về hình thức.
Các khối logic chính
🎯 Flow 1: Những gì bạn thấy — Hiển thị Top-N vùng xếp hạng cao nhất
- Mỗi nến, toàn bộ vùng đang lưu trong bộ nhớ được sắp xếp từ điểm score cao nhất tới thấp nhất (thang điểm 0-100, xem Flow 2).
- Hệ thống duyệt qua danh sách đã sắp xếp này và chỉ đánh dấu một vùng là "hiển thị" nếu vùng đó (a) vượt qua directionFilter và (b) vẫn còn nằm trong visibleLimit vùng đầu tiên đã đếm được. Ví dụ: visibleLimit=8 → chỉ 8 vùng điểm cao nhất thoả điều kiện được đánh dấu "hiển thị=true"; vùng xếp thứ 9 trở đi vẫn bị ẩn dù vẫn tồn tại trong bộ nhớ.
- Một vùng đang hiển thị được vẽ thành box có màu (xanh ngọc cho hỗ trợ, cam cho kháng cự) theo đúng dải giá đỉnh/đáy của nó. Bên trong: nửa trên có thể hiện thanh sức mạnh bên bán theo bearStrength%, nửa dưới hiện thanh sức mạnh bên mua theo bullStrength%, và (nếu bật) một dòng chữ tóm tắt như "Strong Support".
- Một vùng bị ẩn (rớt hạng) không bị xoá - box của nó chỉ đơn giản được tô hoàn toàn trong suốt và xoá chữ đi, để nó có thể âm thầm xuất hiện trở lại ngay khi điểm số leo lại vào top-N.
🔧 Flow 2: Cách tính điểm số và % sức mạnh của một vùng
-
Điểm số (0-100), được tính lại mỗi nến cho mọi vùng:
- 5 thành phần điểm cộng được kết hợp lại: kích thước vùng so với ATR (tối đa 20 điểm), khối lượng lúc tạo vùng so với trung bình (tối đa 18 điểm), sự thuận theo xu hướng (12 điểm cố định nếu thuận), độ sắc nét của swing (tối đa 28 điểm - yếu tố nặng ký nhất), và số lần đã bị chạm lại (tối đa 16 điểm) - cộng thêm 10 điểm nền cố định, tổng điểm thô tối đa khoảng 104.
- Sau đó trừ đi 2 khoản phạt: mitigation × 22 (giá đã ăn sâu vào vùng bao nhiêu) và một khoản phạt tuổi tối đa 10 điểm khi vùng tiến gần tới 450 nến tuổi.
- Ví dụ: một vùng có swing sắc nét 1.4-ATR (điểm swing gần tối đa ≈ 27/28), khối lượng gấp 2 lần trung bình (tối đa 18/18), thuận xu hướng (12/12), độ dày vùng đầy đủ (20/20), 2 lần chạm (8/16), cộng nền 10, chưa bị mitigation hay phạt tuổi, sẽ đạt khoảng 27+18+12+20+8+10 = 95/100 - một vùng có độ tin cậy rất cao.
-
% sức mạnh Bull/Bear, được suy ra từ điểm số:
- Điểm thô được nắn qua một đường cong kéo giãn các điểm số cao (≥75) tiến gần 100% và nén các điểm số thấp lại, sau đó cộng thêm điểm thưởng thuận xu hướng (+6) và trừ đi khoản phạt mitigation (tối đa -28), cho ra % "phe của chính vùng" (bull cho vùng hỗ trợ, bear cho vùng kháng cự).
- % của phe đối lập bắt đầu từ một mức sàn nhỏ 5% và tăng chủ yếu theo mitigation (tối đa +65 khi vùng bị ăn sâu dần) cộng thêm một phần nhỏ từ điểm số - thể hiện mức độ "phe sai" đang dần chiếm ưu thế khi vùng yếu đi.
- Ví dụ: một vùng hỗ trợ (dir=-1) có score=95, trendScore=1, mitigation=0 → zoneSide ≈ 72+(95-75)×1.12 = 94.4, cộng +6 điểm thưởng xu hướng, chặn trần ở 100 → bullStrength=100%; oppositeSide ≈ 5+0+95×0.08=12.6% → bearStrength=12%. Nếu sau đó vùng này bị mitigation 80%, bullStrength giảm mạnh (phạt mitigation -22.4) trong khi bearStrength tăng vọt lên khoảng 5+52+7.6≈64%.
- Nhãn chữ của vùng, được suy ra từ mitigation và % sức mạnh phe của vùng: "Mitigated" nếu mitigation ≥ 0.75, ngược lại "Strong" nếu sức mạnh ≥ 70%, ngược lại bình thường nếu sức mạnh ≥ 45%, còn lại là "Weak".
🌱 Flow 3: Vùng ra đời như thế nào — Nhận diện Pivot & Hấp thụ
- Bước 1 - Phát hiện pivot thô: hàm ta.pivothigh/ta.pivotlow xác nhận một đỉnh/đáy dao động sau khi đủ pivotSpan nến đã đóng cửa ở cả hai bên. Vì việc xác nhận cần các nến tương lai, nến pivot thực tế luôn nằm ở quá khứ pivotSpan nến tính từ thời điểm xử lý.
- Bước 2 - Lọc pivot yếu: "độ sắc nét" của pivot (mức nhô ra cao/thấp hơn 2 nến lân cận ngay sát cạnh, tính theo ATR) phải ≥ minSwingAtr, nếu không sẽ bị loại bỏ và không có vùng nào được tạo ra.
- Bước 3 - Dựng vùng ứng viên: một dải giá được dựng quanh giá pivot (± nửa của zoneAtrWidth×ATR), và điểm khối lượng, điểm xu hướng, điểm swing, và điểm tổng ban đầu đều được tính như ở Flow 2.
- Bước 4 - Hấp thụ hoặc tạo mới: vùng ứng viên được so sánh với mọi vùng đang có cùng phía (hỗ trợ so với hỗ trợ, kháng cự so với kháng cự). Nếu đủ gần (tâm nằm trong absorbAtr×ATR) hoặc chồng lấn ít nhất 35% chiều rộng của vùng nhỏ hơn, nó sẽ được gộp vào vùng đang có: vùng mở rộng ra để bao trùm cả hai, mitigation của nó reset về 0 (một lần retest mới sẽ "chữa lành" vùng), số lần chạm tăng thêm 1, và điểm số lấy giá trị cao hơn giữa hai điểm cộng thêm +3 điểm thưởng tái xác nhận. Nếu không tìm thấy vùng nào phù hợp, một vùng hoàn toàn mới sẽ được tạo và đẩy vào danh sách vùng đang hoạt động.
🔄 Flow 4: Vòng đời của một vùng — Chạm, Mitigation, Phá vỡ, Hết hạn
- Chạm & mitigation: mỗi nến, nếu dải giá (đỉnh-đáy) của cây nến hiện tại chồng lấn với dải giá của một vùng, số lần chạm của vùng đó tăng nhẹ thêm 0.20 và tỉ lệ mitigation được tính lại dựa trên mức giá đã đẩy xuyên qua vùng từ mép ngoài vào (0 = mới vừa chạm mép, 1 = đã đẩy xuyên hết qua phía bên kia). Lần đầu tiên vượt ngưỡng mitigation 0.75 sẽ bắn ra alert "Mitigated".
- Phá vỡ: một vùng hỗ trợ bị coi là vỡ khi close (giá đóng cửa) rơi xuống dưới mép đáy của vùng quá breakAtr×ATR (vùng kháng cự vỡ theo chiều ngược lại, phía trên mép đỉnh). Vùng bị vỡ sẽ được đánh dấu, box chuyển màu xám và đường tâm trở thành một đường nét đứt đóng băng tại điểm vỡ, các thanh bull/bear và chữ bị xoá, rồi vùng được chuyển ra khỏi danh sách đang hoạt động vào một bộ nhớ đệm "vừa vỡ gần đây" nhỏ, giới hạn bởi keepBrokenCount vùng (vùng cũ nhất bị xoá trước).
- Hết hạn: bất kể điểm số hay số lần chạm ra sao, một vùng sẽ bị âm thầm xoá ngay khi nó cũ hơn 450 nến tính từ nến tạo (hoặc gộp gần nhất) - các vùng cũ, không còn hoạt động sẽ tự dọn dẹp theo thời gian ngay cả khi chưa từng bị phá vỡ.
- Cắt bớt theo giới hạn lưu trữ: sau khi xếp hạng lại, nếu tổng số vùng đang hoạt động vượt quá storedLimit, các vùng điểm thấp nhất sẽ bị xoá lần lượt cho tới khi số lượng trở lại đúng giới hạn.
🔔 Flow 5: Alert (Cảnh báo)
- Tạo mới: "Support Created" / "Resistance Created" bắn ra ngay khi một vùng hoàn toàn mới (không bị hấp thụ) được thêm vào.
- Xếp hạng dẫn đầu: "Top-Ranked Support/Resistance Created" và "New Top-Ranked S/R Zone" bắn ra mỗi khi vùng #1 điểm cao nhất đổi sang danh tính khác (một vùng mới trở thành mức quan trọng nhất trên biểu đồ).
- Chạm/Mitigation: "Support/Resistance Mitigation Started" bắn ra mỗi khi có va chạm; "Support/Resistance Mitigated" bắn ra ngay khoảnh khắc mitigation lần đầu vượt 75%.
- Phá vỡ: "Support Broken" / "Resistance Broken" bắn ra khi một vùng bị vô hiệu hoá bởi giá đóng cửa, như mô tả ở Flow 4.
Đầu ra & vai trò trong sử dụng
- Box vùng xếp hạng cao nhất: đầu ra chính - các mức S/R tốt nhất hiện tại để phản ứng theo, đã được lọc và xếp hạng sẵn nên bạn không cần tự đánh giá chất lượng vùng.
- Thanh sức mạnh Bull%/Bear%: một cách đọc nhanh trực quan cho biết mỗi vùng hiện đang nghiêng về phe mua hay phe bán mạnh tới đâu, hữu ích để chấm điểm độ tin cậy trước khi vào lệnh.
- Nhãn chữ của vùng: tóm tắt bằng lời dễ hiểu ("Strong Support" → "Mitigated Support") về mức độ một vùng đã bị bào mòn, hữu ích như một hệ thống cảnh báo sớm chỉ cần liếc mắt.
- Dấu vết vùng đã vỡ: một trí nhớ trực quan ngắn hạn về các mức vừa bị vô hiệu hoá, hữu ích để nhận ra hiện tượng đảo vai trò hỗ trợ/kháng cự (kháng cự cũ trở thành hỗ trợ mới, và ngược lại).
- Alert: cho phép theo dõi 12 sự kiện nêu trên mà không cần ngồi canh biểu đồ trực tiếp.
// This work is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
// https://creativecommons.org/licenses/by-nc-sa/4.0/
// © Zeiierman {
//@version=6
indicator("Ranked Support & Resistance Zones (Zeiierman)", shorttitle = "Ranked S/R Zones (Zeiierman)", overlay = true, max_lines_count = 500, max_boxes_count = 300, max_labels_count = 200)
//}
// ~~ Tooltips (Đã Việt hóa) {
t1 = "Điều chỉnh số lượng vùng hỗ trợ/kháng cự xếp hạng cao nhất được hiển thị trên biểu đồ. Giá trị nhỏ hơn = biểu đồ gọn hơn nhưng có thể bỏ lỡ vùng phụ. VD: 8 = chỉ hiện 8 vùng điểm cao nhất."
t2 = "Số lượng vùng S/R tối đa được lưu trong bộ nhớ trước khi các vùng xếp hạng thấp nhất bị xoá bớt. Không ảnh hưởng số vùng hiển thị, chỉ ảnh hưởng vùng dự trữ ngầm."
t3 = "Độ dài xác nhận pivot (số nến 2 bên cần thấp/cao hơn để xác nhận đỉnh/đáy). Giá trị cao hơn = ít mức hơn nhưng được xác nhận chắc chắn hơn. Khuyến nghị: 5-10 cho khung H1-H4."
t4 = "Độ nhô tối thiểu của pivot, tính theo ATR. Giúp lọc bỏ các pivot yếu, không đủ rõ ràng. VD: 0.15 = pivot phải nhô ít nhất 0.15×ATR so với nến lân cận."
t5 = "Nếu vùng mới nằm gần vùng cũ hơn khoảng cách này (tính theo ATR), nó sẽ được gộp vào vùng cũ thay vì tạo vùng trùng lặp. Giá trị cao hơn = gộp mạnh tay hơn, ít vùng hơn."
t6 = "Độ dày của vùng giá, tính theo ATR. Giá trị cao hơn = vùng dày hơn, dễ phản ứng nhưng dễ chồng lấn nhau."
t7 = "Số nến dùng để tính khối lượng trung bình nền, làm cơ sở so sánh khi chấm điểm vùng."
t8 = "Độ dài EMA dùng để chấm điểm mức độ thuận xu hướng. Vùng hỗ trợ nằm trên EMA và vùng kháng cự nằm dưới EMA sẽ được cộng điểm mạnh hơn."
t9 = "Điều chỉnh mức giá phải đóng cửa vượt qua vùng bao xa (tính theo ATR) trước khi vùng đó bị coi là phá vỡ. Giá trị cao hơn = khó vỡ hơn, ít phá vỡ giả hơn."
t10 = "Hiện hoặc ẩn các thanh sức mạnh bên mua/bên bán bên trong mỗi vùng S/R."
t11 = "Hiện hoặc ẩn nhãn chữ thông minh (VD: Strong Support) bên trong mỗi vùng S/R."
t12 = "Bộ lọc tuỳ chọn cho các vùng được hiển thị (Tất cả / Chỉ hỗ trợ / Chỉ kháng cự)."
t13 = "Xoá vùng sau số nến này. Cũng góp phần vào việc suy giảm điểm xếp hạng theo tuổi." // Lưu ý: chưa gắn với input nào trong code gốc, đang cố định 450 nến — giữ nguyên như bản gốc, không bổ sung input mới.
t14 = "Hiện hoặc ẩn bảng dashboard thu gọn." // Lưu ý: tính năng dashboard chưa được cài đặt trong code gốc — giữ nguyên như bản gốc.
t15 = "Hiện hoặc ẩn nhãn khi vùng bị phá vỡ." // Lưu ý: tính năng nhãn phá vỡ chưa được cài đặt trong code gốc — giữ nguyên như bản gốc.
t16 = "Điều chỉnh cỡ chữ của nhãn phần trăm sức mạnh."
t17 = "Điều chỉnh cỡ chữ của nhãn chữ trong vùng."
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Inputs (Đã Việt hóa) {
gRank = "🏆 Xếp Hạng"
gDetect = "📐 Nhận Diện Swing"
gScore = "🧮 Cỗ Máy Tính Điểm"
gDisplay = "👁️ Hiển Thị"
gColors = "🎨 Màu Sắc"
gBreaks = "🧱 Phá Vỡ"
visibleLimit = input.int(8, "Hiện Top Bao Nhiêu Vùng", minval = 1, maxval = 30, tooltip = t1, group = gRank)
storedLimit = input.int(60, "Số Vùng Lưu Tối Đa", minval = 10, maxval = 250, tooltip = t2, group = gRank)
pivotSpan = input.int(5, "Độ Dài Pivot", minval = 2, maxval = 50, tooltip = t3, group = gDetect)
minSwingAtr = input.float(0.15, "ATR Pivot Tối Thiểu", minval = 0.0, maxval = 5.0, step = 0.05, tooltip = t4, group = gDetect)
absorbAtr = input.float(0.55, "Ngưỡng Hấp Thụ Vùng Gần", minval = 0.0, maxval = 3.0, step = 0.05, tooltip = t5, group = gDetect)
zoneAtrWidth = input.float(0.40, "Độ Dày Vùng (ATR)", minval = 0.05, maxval = 2.0, step = 0.05, tooltip = t6, group = gDetect)
volLen = input.int(20, "Độ Dài MA Khối Lượng", minval = 1, tooltip = t7, group = gScore)
trendLen = input.int(50, "Độ Dài EMA Xu Hướng", minval = 1, tooltip = t8, group = gScore)
breakAtr = input.float(0.12, "Vùng Đệm Phá Vỡ (ATR)", minval = 0.0, maxval = 3.0, step = 0.05, tooltip = t9, group = gBreaks)
keepBrokenCount = input.int(4, "Số Vùng Vỡ Giữ Lại", minval = 0, maxval = 30, group = gBreaks)
directionFilter = input.string("All", "Bộ Lọc Hướng", options = ["All", "Support Only", "Resistance Only"], tooltip = t12, group = gDisplay)
showStrengthBars = input.bool(true, "Hiện Thanh Sức Mạnh", tooltip = t10, group = gDisplay)
showZoneText = input.bool(true, "Hiện Chữ Trong Vùng", tooltip = t11, group = gDisplay)
strengthTextSize = input.string("auto", "Cỡ Chữ % Sức Mạnh", options = ["auto", "tiny", "small", "normal", "large", "huge"], tooltip = t16, group = gDisplay)
zoneTextSize = input.string("auto", "Cỡ Chữ Nhãn Vùng", options = ["auto", "tiny", "small", "normal", "large", "huge"], tooltip = t17, group = gDisplay)
supportColor = input.color(color.rgb(26, 216, 194, 50), "Hỗ Trợ", inline = "c", group = gColors)
resistanceColor = input.color(color.rgb(216, 76, 26, 50), "Kháng Cự", inline = "c", group = gColors)
brokenColor = input.color(color.rgb(148, 163, 184, 72), "Đã Vỡ", group = gColors)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Core calculations {
atrRaw = ta.atr(14)
atr = na(atrRaw) or atrRaw == 0 ? syminfo.mintick * 10.0 : atrRaw
volBase = ta.sma(volume, volLen)
trendBase = ta.ema(close, trendLen)
barMs = timeframe.in_seconds(timeframe.period) * 1000
pivotHigh = ta.pivothigh(high, pivotSpan, pivotSpan)
pivotLow = ta.pivotlow(low, pivotSpan, pivotSpan)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Zone type {
type RankedZone
float score
float top
float bottom
float mid
int dir
int bornBar
int leftTime
float width
float mitigation
float touchCount
float volScore
float trendScore
float swingScore
int bullStrength
int bearStrength
bool broken
line centerLine
box bodyBox
box bullBar
box bearBar
label eventLabel
var array srZones = array.new()
var array brokenZones = array.new()
var string previousLeaderKey = ""
var string lastBreakSide = "None"
var int lastBreakIndex = na
newTopRankAlert = false
supportCreatedAlert = false
resistanceCreatedAlert = false
topSupportAlert = false
topResistanceAlert = false
supportBreakAlert = false
resistanceBreakAlert = false
supportTouchAlert = false
resistanceTouchAlert = false
supportMitigatedAlert = false
resistanceMitigatedAlert = false
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Methods {
method canDisplay(RankedZone z) =>
directionFilter == "All" or directionFilter == "Support Only" and z.dir == -1 or directionFilter == "Resistance Only" and z.dir == 1
method overlaps(RankedZone z, float newTop, float newBottom, int newDir) =>
float overlapTop = math.min(z.top, newTop)
float overlapBottom = math.max(z.bottom, newBottom)
float overlap = math.max(overlapTop - overlapBottom, 0)
float smaller = math.min(math.max(z.top - z.bottom, syminfo.mintick), math.max(newTop - newBottom, syminfo.mintick))
bool sameSide = z.dir == newDir
bool closeMid = math.abs(z.mid - math.avg(newTop, newBottom)) <= absorbAtr * atr
bool meaningfulOverlap = overlap / smaller >= 0.35
sameSide and (closeMid or meaningfulOverlap)
method refreshGeometry(RankedZone z) =>
line.set_y1(z.centerLine, z.mid)
line.set_y2(z.centerLine, z.mid)
line.set_x2(z.centerLine, bar_index + 1)
box.set_top(z.bodyBox, z.top)
box.set_bottom(z.bodyBox, z.bottom)
box.set_right(z.bodyBox, time + barMs * 25)
method deleteZone(RankedZone z) =>
line.delete(z.centerLine)
box.delete(z.bodyBox)
box.delete(z.bullBar)
box.delete(z.bearBar)
if not na(z.eventLabel)
label.delete(z.eventLabel)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Scoring helpers {
swing_quality(float price, int dir) =>
float quality = 0.0
if dir == 1
float localMax = math.max(high[pivotSpan - 1], high[pivotSpan + 1])
quality := math.max((price - localMax) / atr, 0)
else
float localMin = math.min(low[pivotSpan - 1], low[pivotSpan + 1])
quality := math.max((localMin - price) / atr, 0)
quality
score(float width, float volScore, float trendScore, float swingScore, float mitigation, float touches, int age) =>
float sizeNorm = math.min(width / atr, 1.0)
float volNorm = math.min(volScore / 2.0, 1.0)
float swingNorm = math.min(swingScore / 1.5, 1.0)
float touchNorm = math.min(touches / 4.0, 1.0)
float ageNorm = math.min(age / math.max(450, 1), 1.0)
float raw = sizeNorm * 20.0 + volNorm * 18.0 + trendScore * 12.0 + swingNorm * 28.0 + touchNorm * 16.0 + 10.0
float penalty = mitigation * 22.0 + ageNorm * 10.0
math.max(math.min(raw - penalty, 100.0), 0.0)
strengths(int dir, float score, float trendScore, float mitigation) =>
float scoreNorm = math.max(math.min(score, 100.0), 0.0)
float zoneSide = scoreNorm
if scoreNorm >= 75
zoneSide := 72 + (scoreNorm - 75) * 1.12
else if scoreNorm >= 45
zoneSide := 48 + (scoreNorm - 45) * 0.75
else
zoneSide := scoreNorm * 1.05
zoneSide += trendScore * 6.0
zoneSide -= mitigation * 28.0
zoneSide := math.max(math.min(zoneSide, 100.0), 0.0)
float oppositeSide = 5.0 + mitigation * 65.0 + scoreNorm * 0.08
oppositeSide := math.max(math.min(oppositeSide, 100.0), 0.0)
int bull = dir == -1 ? int(zoneSide) : int(oppositeSide)
int bear = dir == 1 ? int(zoneSide) : int(oppositeSide)
[bull, bear]
zone_text(RankedZone z) =>
string txt = ""
if z.dir == -1
txt := z.mitigation >= 0.75 ? "Mitigated Support" : z.bullStrength >= 70 ? "Strong Support" : z.bullStrength >= 45 ? "Support" : "Weak Support"
else
txt := z.mitigation >= 0.75 ? "Mitigated Resistance" : z.bearStrength >= 70 ? "Strong Resistance" : z.bearStrength >= 45 ? "Resistance" : "Weak Resistance"
txt
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Create zones {
add_zone(float pivotPrice, int dir, int pivotBar, int pivotTime) =>
float half = zoneAtrWidth * atr * 0.5
float top = pivotPrice + half
float bottom = pivotPrice - half
float mid = pivotPrice
float width = math.max(top - bottom, syminfo.mintick)
float volScore = volBase != 0 ? volume[pivotSpan] / volBase : 1.0
float trendScore = dir == -1 and pivotPrice > trendBase ? 1.0 : dir == 1 and pivotPrice < trendBase ? 1.0 : 0.0
float swingScore = swing_quality(pivotPrice, dir)
float mitigation = 0.0
float touches = 0.0
int age = bar_index - pivotBar
float score = score(width, volScore, trendScore, swingScore, mitigation, touches, age)
[bullStrength, bearStrength] = strengths(dir, score, trendScore, mitigation)
bool absorbed = false
bool created = false
if srZones.size() > 0
for i = 0 to srZones.size() - 1
RankedZone old = srZones.get(i)
if old.overlaps(top, bottom, dir) and not absorbed
old.top := math.max(old.top, top)
old.bottom := math.min(old.bottom, bottom)
old.mid := math.avg(old.top, old.bottom)
old.width := math.max(old.top - old.bottom, syminfo.mintick)
old.bornBar := math.min(old.bornBar, pivotBar)
old.leftTime := math.min(old.leftTime, pivotTime)
old.volScore := math.max(old.volScore, volScore)
old.trendScore := math.max(old.trendScore, trendScore)
old.swingScore := math.max(old.swingScore, swingScore)
old.mitigation := 0.0
old.touchCount += 1.0
old.score := math.max(old.score, score) + 3.0
old.bullStrength := math.max(old.bullStrength, bullStrength)
old.bearStrength := math.max(old.bearStrength, bearStrength)
old.refreshGeometry()
srZones.set(i, old)
absorbed := true
if not absorbed
color baseColor = dir == -1 ? supportColor : resistanceColor
line ln = line.new(pivotBar, mid, bar_index + 1, mid, extend = extend.right, color = color.new(baseColor, 100), width = 1)
box bx = box.new(left = pivotTime, right = time + barMs * 5, top = top, bottom = bottom, xloc = xloc.bar_time, bgcolor = color.new(baseColor, 78), border_color = color.new(baseColor, 30))
float zoneMid = math.avg(top, bottom)
box bearBox = box.new(left = pivotTime, right = pivotTime, top = top, bottom = zoneMid, xloc = xloc.bar_time, bgcolor = resistanceColor, border_color = na)
box bullBox = box.new(left = pivotTime, right = pivotTime, top = zoneMid, bottom = bottom, xloc = xloc.bar_time, bgcolor = supportColor, border_color = na)
srZones.push(RankedZone.new(score, top, bottom, mid, dir, pivotBar, pivotTime, width, mitigation, touches, volScore, trendScore, swingScore, bullStrength, bearStrength, false, ln, bx, bullBox, bearBox, na))
created := true
created
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Add confirmed pivots {
if not na(pivotHigh)
float sq = swing_quality(pivotHigh, 1)
if sq >= minSwingAtr
bool createdResistance = add_zone(pivotHigh, 1, bar_index - pivotSpan, time[pivotSpan])
resistanceCreatedAlert := createdResistance
if not na(pivotLow)
float sq = swing_quality(pivotLow, -1)
if sq >= minSwingAtr
bool createdSupport = add_zone(pivotLow, -1, bar_index - pivotSpan, time[pivotSpan])
supportCreatedAlert := createdSupport
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Update zones {
if srZones.size() > 0
for i = srZones.size() - 1 to 0
RankedZone z = srZones.get(i)
int age = bar_index - z.bornBar
bool expired = age > 450
bool touched = high >= z.bottom and low <= z.top
bool brokenSupport = z.dir == -1 and close < z.bottom - breakAtr * atr
bool brokenResistance = z.dir == 1 and close > z.top + breakAtr * atr
if touched and not brokenSupport and not brokenResistance
bool wasUnderMitigationThreshold = z.mitigation < 0.75
supportTouchAlert := supportTouchAlert or z.dir == -1
resistanceTouchAlert := resistanceTouchAlert or z.dir == 1
z.touchCount += 0.20
float fillDistance = z.dir == -1 ? z.top - low : high - z.bottom
z.mitigation := math.min(math.max(fillDistance / math.max(z.width, syminfo.mintick), 0.0), 1.0)
supportMitigatedAlert := supportMitigatedAlert or z.dir == -1 and wasUnderMitigationThreshold and z.mitigation >= 0.75
resistanceMitigatedAlert := resistanceMitigatedAlert or z.dir == 1 and wasUnderMitigationThreshold and z.mitigation >= 0.75
z.score := score(z.width, z.volScore, z.trendScore, z.swingScore, z.mitigation, z.touchCount, age)
[newBull, newBear] = strengths(z.dir, z.score, z.trendScore, z.mitigation)
z.bullStrength := newBull
z.bearStrength := newBear
if expired
z.deleteZone()
srZones.remove(i)
else if brokenSupport or brokenResistance
z.broken := true
lastBreakSide := brokenResistance ? "Bullish Break" : "Bearish Break"
lastBreakIndex := bar_index
supportBreakAlert := supportBreakAlert or brokenSupport
resistanceBreakAlert := resistanceBreakAlert or brokenResistance
line.set_color(z.centerLine, color.new(brokenColor, 0))
line.set_style(z.centerLine, line.style_dotted)
line.set_extend(z.centerLine, extend.none)
line.set_x2(z.centerLine, bar_index)
box.set_bgcolor(z.bodyBox, color.new(brokenColor, 88))
box.set_border_color(z.bodyBox, color.new(brokenColor, 100))
box.set_right(z.bodyBox, time)
box.set_bgcolor(z.bullBar, color.new(color.gray, 100))
box.set_bgcolor(z.bearBar, color.new(color.gray, 100))
box.set_text(z.bodyBox, "")
box.set_text(z.bullBar, "")
box.set_text(z.bearBar, "")
if keepBrokenCount > 0
brokenZones.push(z)
while brokenZones.size() > keepBrokenCount
RankedZone oldBroken = brokenZones.shift()
oldBroken.deleteZone()
else
z.deleteZone()
srZones.remove(i)
else
z.refreshGeometry()
srZones.set(i, z)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Sort and limit zones {
srZones.sort(order.descending, sort_field = "score")
if srZones.size() > 0
RankedZone leader = srZones.get(0)
string leaderKey = str.tostring(leader.leftTime) + "_" + str.tostring(leader.dir)
bool newLeader = leaderKey != previousLeaderKey
newTopRankAlert := newLeader
topSupportAlert := newLeader and leader.dir == -1
topResistanceAlert := newLeader and leader.dir == 1
previousLeaderKey := leaderKey
while srZones.size() > storedLimit
RankedZone removed = srZones.pop()
removed.deleteZone()
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Display zones {
int visibleCount = 0
float nearestSupport = na
float nearestResistance = na
if srZones.size() > 0
for i = 0 to srZones.size() - 1
RankedZone z = srZones.get(i)
bool pass = z.canDisplay()
bool visible = pass and visibleCount < visibleLimit
if visible
visibleCount += 1
color baseColor = z.dir == -1 ? supportColor : resistanceColor
int left = z.leftTime
int right = time + barMs * 25
int maxBarWidth = int((right - left) * 0.82)
int bullWidth = int(maxBarWidth * z.bullStrength / 100)
int bearWidth = int(maxBarWidth * z.bearStrength / 100)
float middle = math.avg(z.top, z.bottom)
box.set_bgcolor(z.bodyBox, visible ? color.new(baseColor, 78) : color.new(color.gray, 100))
box.set_border_color(z.bodyBox, visible ? color.new(baseColor, 25) : color.new(color.gray, 100))
line.set_color(z.centerLine, color.new(baseColor, 100))
line.set_width(z.centerLine, 1)
if visible and showStrengthBars
box.set_left(z.bearBar, left)
box.set_right(z.bearBar, left + bearWidth)
box.set_top(z.bearBar, z.top)
box.set_bottom(z.bearBar, middle)
box.set_bgcolor(z.bearBar, color.new(resistanceColor, 10))
box.set_border_color(z.bearBar, na)
box.set_text(z.bearBar, str.tostring(z.bearStrength) + "%")
box.set_text_color(z.bearBar, color.white)
box.set_text_halign(z.bearBar, text.align_center)
box.set_text_valign(z.bearBar, text.align_center)
box.set_text_size(z.bearBar, strengthTextSize)
box.set_left(z.bullBar, left)
box.set_right(z.bullBar, left + bullWidth)
box.set_top(z.bullBar, middle)
box.set_bottom(z.bullBar, z.bottom)
box.set_bgcolor(z.bullBar, color.new(supportColor, 10))
box.set_border_color(z.bullBar, na)
box.set_text(z.bullBar, str.tostring(z.bullStrength) + "%")
box.set_text_color(z.bullBar, color.white)
box.set_text_halign(z.bullBar, text.align_center)
box.set_text_valign(z.bullBar, text.align_center)
box.set_text_size(z.bullBar, strengthTextSize)
box.set_text(z.bodyBox, showZoneText ? zone_text(z) : "")
box.set_text_halign(z.bodyBox, text.align_right)
box.set_text_valign(z.bodyBox, text.align_center)
box.set_text_color(z.bodyBox, chart.fg_color)
box.set_text_size(z.bodyBox, zoneTextSize)
else
box.set_bgcolor(z.bullBar, color.new(color.gray, 100))
box.set_bgcolor(z.bearBar, color.new(color.gray, 100))
box.set_border_color(z.bullBar, color.new(color.gray, 100))
box.set_border_color(z.bearBar, color.new(color.gray, 100))
box.set_text(z.bullBar, "")
box.set_text(z.bearBar, "")
box.set_text(z.bodyBox, "")
if z.dir == -1 and z.mid < close
nearestSupport := na(nearestSupport) or z.mid > nearestSupport ? z.mid : nearestSupport
if z.dir == 1 and z.mid > close
nearestResistance := na(nearestResistance) or z.mid < nearestResistance ? z.mid : nearestResistance
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Alerts (Đã Việt hóa) {
alertcondition(supportCreatedAlert, "Tạo Vùng Hỗ Trợ", "Một vùng hỗ trợ được xếp hạng mới vừa được tạo.")
alertcondition(resistanceCreatedAlert, "Tạo Vùng Kháng Cự", "Một vùng kháng cự được xếp hạng mới vừa được tạo.")
alertcondition(topSupportAlert, "Vùng Hỗ Trợ Dẫn Đầu Mới", "Một vùng hỗ trợ vừa trở thành vùng đang hoạt động có điểm xếp hạng cao nhất.")
alertcondition(topResistanceAlert, "Vùng Kháng Cự Dẫn Đầu Mới", "Một vùng kháng cự vừa trở thành vùng đang hoạt động có điểm xếp hạng cao nhất.")
alertcondition(newTopRankAlert, "Vùng S/R Dẫn Đầu Mới", "Một vùng hỗ trợ/kháng cự vừa trở thành vùng đang hoạt động có điểm xếp hạng cao nhất.")
alertcondition(supportBreakAlert, "Vỡ Vùng Hỗ Trợ", "Một vùng hỗ trợ được xếp hạng đã bị phá vỡ bởi giá đóng cửa.")
alertcondition(resistanceBreakAlert, "Vỡ Vùng Kháng Cự", "Một vùng kháng cự được xếp hạng đã bị phá vỡ bởi giá đóng cửa.")
alertcondition(supportTouchAlert, "Bắt Đầu Mitigation Vùng Hỗ Trợ", "Giá đã chạm hoặc bắt đầu ăn sâu vào một vùng hỗ trợ được xếp hạng.")
alertcondition(resistanceTouchAlert, "Bắt Đầu Mitigation Vùng Kháng Cự", "Giá đã chạm hoặc bắt đầu ăn sâu vào một vùng kháng cự được xếp hạng.")
alertcondition(supportMitigatedAlert, "Vùng Hỗ Trợ Đã Bị Mitigation", "Một vùng hỗ trợ được xếp hạng đã đạt ngưỡng mitigation.")
alertcondition(resistanceMitigatedAlert, "Vùng Kháng Cự Đã Bị Mitigation", "Một vùng kháng cự được xếp hạng đã đạt ngưỡng mitigation.")
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
Indicator Insider

Chỉ Báo TP RSI – Strategy
RSI mở rộng: vùng trung tính thích nghi theo biến động riêng của RSI, dải ribbon 5 lớp EMA đắp lên chính RSI, bộ phát hiện phân kỳ Regular/Hidden, và 1 dải Bollinger Bands thứ 2 hiển thị trực quan.

Chỉ Báo ICT Anchored Market Structures with Validation – Strategy
Chỉ báo theo phương pháp ICT (Inner Circle Trader) theo dõi cấu trúc thị trường ở 3 cấp độ lồng nhau (Ngắn/Trung/Dài hạn), xác định BoS/CHoCH tại mỗi cấp, và tự động phát hiện breakout giả (liquidity sweep) trước khi công nhận là phá vỡ cấu trúc thật.

Chỉ Báo Trend Duration Forecast – Strategy
Xác định xu hướng qua độ dốc đường Hull Moving Average (HMA), đo thời lượng thực tế của mỗi xu hướng, lưu lại lịch sử thời lượng theo từng hướng, rồi dùng trung bình lịch sử đó để “dự báo” xu hướng hiện tại có thể kéo dài thêm bao
