# -*- coding: utf-8 -*-
"""THEM_DIEN_GIAI.PY — viết nốt 194 diễn giải còn thiếu vào dien_giai.json.

Chạy MỘT LẦN rồi giữ lại để đối chiếu về sau. Chạy lại cũng an toàn:
chỉ ghi những khoá CHƯA có, không đè lên phần đã viết tay trước đó.

    python them_dien_giai.py

HAI HỌ THAM SỐ, viết theo hai cách khác nhau:

1. 138 tham số của 27 bộ lọc XII.2..XII.28 — viết TAY từng cái, vì mỗi
   chỉ báo đo một thứ khác nhau. Hướng chặn đã đối chiếu với mã trong
   include/filters/, KHÔNG suy từ tên tham số:
     - Nhóm dao động (Stoch, CCI, WPR, MFI, DeMarker, UO, BB%B, RCI,
       RVI): chặn BUY khi quá mua, chặn SELL khi quá bán.
     - Nhóm xu hướng (ADX, Choppiness, Aroon, DMI, VIDYA, Elder, MACD,
       SAR, Supertrend, ATR, ADR): xu hướng mạnh thì chặn CẢ HAI chiều —
       DCA cần thị trường đi ngang.
     - Nhóm kênh giá (Pivot, BB Channel, Keltner, Donchian): chặn khi
       giá phá ra khỏi kênh.
     - Ba cái riêng: Ichimoku (trong mây = cho, ngoài mây = chặn theo
       hướng), Squeeze (đang nén = sắp phá biên = chặn), OBV (theo dòng
       tiền, chặn ngược chiều dòng tiền).

   CHÚ Ý hai chỗ mã KHÁC chú thích, đã kiểm tận nơi:
     - DMI: chú thích đầu tệp ghi "block BUY ngược xu hướng", nhưng mã
       `return false` cho CẢ HAI chiều khi |gap| >= ngưỡng.
     - Pivot buffer: nhãn trong DCA_Inputs.mqh ghi "(0.05 = 0.05%)",
       nhưng mã tính `range * pct / 100` — tức phần trăm ĐỘ RỘNG VÙNG
       PIVOT, không phải phần trăm giá.

2. 56 tham số của khung giờ 2..5 — bốn bản sao y hệt khung giờ 1 (đã
   viết tay từ trước), nên CHÉP LẠI từ khung 1 thay vì viết lại bốn
   lần. Chép xong thêm một câu cảnh báo về Nguồn Config, vì đó là cái
   bẫy im lặng của cả nhóm này (xem GHI_CHU_NGUON_CONFIG).
"""
import io
import json
import os
import sys

TEP = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dien_giai.json')


# =====================================================================
# 1. BỘ LỌC XII.2 .. XII.28
# =====================================================================

_TF = ('Khung thời gian lấy {ten}. `PERIOD_CURRENT` là theo khung của '
       'chart đang gắn EA.')
_PERIOD = ('Số nến tính {ten}. Nhỏ thì nhạy và hay đổi trạng thái, lớn thì '
           'chậm và ổn định hơn.')

BO_LOC = {
    # ---------- XII.2 STOCHASTIC ----------
    'InpEnableStochFilter': 'Bật thì bộ lọc Stochastic tham gia quyết định có cho mở lệnh hay không. Stochastic đo vị trí giá đóng cửa so với biên độ cao–thấp của N nến gần nhất.',
    'InpStochOverbought': 'Stochastic vượt ngưỡng này thì **không mở BUY** nữa — coi như đã quá mua.',
    'InpStochOversold': 'Stochastic xuống dưới ngưỡng này thì **không mở SELL** nữa — coi như đã quá bán.',
    'InpStochTimeframe': _TF.format(ten='Stochastic'),
    'InpStochKPeriod': 'Số nến tính đường %K (đường chính, nhạy). Nhỏ thì nhạy và hay đổi trạng thái, lớn thì mượt hơn.',
    'InpStochDPeriod': 'Số nến làm mượt đường %D (đường tín hiệu, chạy chậm sau %K).',
    'InpStochSlowing': 'Mức làm chậm thêm cho %K. Tăng lên thì đường ít răng cưa, ít tín hiệu nhiễu hơn.',

    # ---------- XII.3 CCI ----------
    'InpEnableCCIFilter': 'Bật thì bộ lọc CCI tham gia quyết định có cho mở lệnh hay không. CCI đo giá hiện tại lệch bao xa khỏi mức trung bình của nó.',
    'InpCCIOverbought': 'CCI vượt ngưỡng này thì **không mở BUY** nữa — giá đã chạy xa trên trung bình.',
    'InpCCIOversold': 'CCI xuống dưới ngưỡng này thì **không mở SELL** nữa — giá đã chạy xa dưới trung bình. Giá trị thường là số âm, ví dụ `-100`.',
    'InpCCITimeframe': _TF.format(ten='CCI'),
    'InpCCIPeriod': _PERIOD.format(ten='CCI'),

    # ---------- XII.4 WILLIAMS %R ----------
    'InpEnableWPRFilter': 'Bật thì bộ lọc Williams %R tham gia quyết định có cho mở lệnh hay không. Chỉ báo này đo giá đóng cửa nằm ở đâu trong biên độ cao–thấp gần nhất.',
    'InpWPROverbought': 'Williams %R vượt ngưỡng này thì **không mở BUY** nữa. Thang đo của chỉ báo này chạy từ `-100` tới `0`, nên ngưỡng quá mua là số âm gần 0, ví dụ `-20`.',
    'InpWPROversold': 'Williams %R xuống dưới ngưỡng này thì **không mở SELL** nữa. Ví dụ `-80`.',
    'InpWPRTimeframe': _TF.format(ten='Williams %R'),
    'InpWPRPeriod': _PERIOD.format(ten='Williams %R'),

    # ---------- XII.5 MFI ----------
    'InpEnableMFIFilter': 'Bật thì bộ lọc MFI tham gia quyết định có cho mở lệnh hay không. MFI giống RSI nhưng **có tính cả khối lượng**, nên phản ánh dòng tiền chứ không chỉ giá.',
    'InpMFIOverbought': 'MFI vượt ngưỡng này thì **không mở BUY** nữa — dòng tiền vào đã quá mạnh.',
    'InpMFIOversold': 'MFI xuống dưới ngưỡng này thì **không mở SELL** nữa — dòng tiền ra đã quá mạnh.',
    'InpMFITimeframe': _TF.format(ten='MFI'),
    'InpMFIPeriod': _PERIOD.format(ten='MFI'),

    # ---------- XII.6 DeMARKER ----------
    'InpEnableDeMarkerFilter': 'Bật thì bộ lọc DeMarker tham gia quyết định có cho mở lệnh hay không. DeMarker so đỉnh và đáy của nến này với nến trước để đo sức cạn của một chiều.',
    'InpDeMarkerOverbought': 'DeMarker vượt ngưỡng này thì **không mở BUY** nữa. Thang đo chạy từ `0` tới `1`, nên ngưỡng là số lẻ, ví dụ `0.7`.',
    'InpDeMarkerOversold': 'DeMarker xuống dưới ngưỡng này thì **không mở SELL** nữa. Ví dụ `0.3`.',
    'InpDeMarkerTimeframe': _TF.format(ten='DeMarker'),
    'InpDeMarkerPeriod': _PERIOD.format(ten='DeMarker'),

    # ---------- XII.7 ULTIMATE OSCILLATOR ----------
    'InpEnableUOFilter': 'Bật thì bộ lọc Ultimate Oscillator tham gia quyết định có cho mở lệnh hay không. Chỉ báo này gộp **ba chu kỳ dài ngắn khác nhau** vào một con số, nên ít bị đánh lừa bởi một nhịp giá ngắn.',
    'InpUOOverbought': 'Ultimate Oscillator vượt ngưỡng này thì **không mở BUY** nữa.',
    'InpUOOversold': 'Ultimate Oscillator xuống dưới ngưỡng này thì **không mở SELL** nữa.',
    'InpUOTimeframe': _TF.format(ten='Ultimate Oscillator'),
    'InpUOPeriod1': 'Chu kỳ **ngắn** — phần nhạy nhất, bắt nhịp giá gần đây.',
    'InpUOPeriod2': 'Chu kỳ **vừa**. Thường để gấp đôi chu kỳ ngắn.',
    'InpUOPeriod3': 'Chu kỳ **dài** — phần ổn định nhất. Thường để gấp bốn chu kỳ ngắn.',

    # ---------- XII.8 BOLLINGER %B ----------
    'InpEnableBBFilter': 'Bật thì bộ lọc Bollinger %B tham gia quyết định có cho mở lệnh hay không. %B cho biết giá đang nằm ở đâu **giữa hai dải Bollinger**: `0` là chạm dải dưới, `1` là chạm dải trên.',
    'InpBBOverbought': 'Giá chạm hoặc vượt dải trên (%B đạt ngưỡng này) thì **không mở BUY** nữa. Để `1.0` nghĩa là đúng lúc chạm dải trên.',
    'InpBBOversold': 'Giá chạm hoặc thủng dải dưới (%B xuống tới ngưỡng này) thì **không mở SELL** nữa. Để `0.0` nghĩa là đúng lúc chạm dải dưới.',
    'InpBBTimeframe': _TF.format(ten='Bollinger Bands'),
    'InpBBPeriod': 'Số nến tính đường trung bình giữa của Bollinger.',
    'InpBBDeviation': 'Hai dải cách đường giữa bao nhiêu **độ lệch chuẩn**. Lớn thì dải rộng, ít khi chạm; nhỏ thì dải hẹp, chạm liên tục.',

    # ---------- XII.9 RCI ----------
    'InpEnableRCIFilter': 'Bật thì bộ lọc RCI tham gia quyết định có cho mở lệnh hay không. RCI đo mức độ giá đi **đều theo một chiều** theo thứ hạng thời gian, thay vì đo biên độ.',
    'InpRCIOverbought': 'RCI vượt ngưỡng này thì **không mở BUY** nữa. Thang đo chạy từ `-100` tới `100`.',
    'InpRCIOversold': 'RCI xuống dưới ngưỡng này thì **không mở SELL** nữa. Thường để số âm đối xứng với ngưỡng quá mua.',
    'InpRCITimeframe': _TF.format(ten='RCI'),
    'InpRCIPeriod': _PERIOD.format(ten='RCI'),

    # ---------- XII.10 RVI ----------
    'InpEnableRVIFilter': 'Bật thì bộ lọc RVI tham gia quyết định có cho mở lệnh hay không. RVI so **thân nến** với **biên độ nến** để đo phe nào đang thực sự cầm nhịp.',
    'InpRVIOverbought': 'RVI vượt ngưỡng này thì **không mở BUY** nữa. Thang đo quanh `0`, nên ngưỡng là số lẻ nhỏ, ví dụ `0.3`.',
    'InpRVIOversold': 'RVI xuống dưới ngưỡng này thì **không mở SELL** nữa. Ví dụ `-0.3`.',
    'InpRVIFilterTimeframe': _TF.format(ten='RVI'),
    'InpRVIPeriod': _PERIOD.format(ten='RVI'),

    # ---------- XII.11 ADX ----------
    'InpEnableADXFilter': 'Bật thì bộ lọc ADX tham gia quyết định có cho mở lệnh hay không. ADX đo **sức mạnh xu hướng**, không đo chiều. Đây là bộ lọc sideway kinh điển cho DCA.',
    'InpADXMaxLevel': 'ADX đạt hoặc vượt ngưỡng này thì **chặn cả hai chiều** — thị trường đang có xu hướng mạnh, môi trường xấu nhất cho DCA. ADX thấp nghĩa là đi ngang, tức là được phép vào.',
    'InpADXTimeframe': _TF.format(ten='ADX'),
    'InpADXPeriod': _PERIOD.format(ten='ADX'),

    # ---------- XII.12 CHOPPINESS ----------
    'InpEnableChopFilter': 'Bật thì bộ lọc Choppiness tham gia quyết định có cho mở lệnh hay không. Chỉ số này đo thị trường đang **lình xình hay đang chạy**.',
    'InpChoppinessTrendingLevel': 'Choppiness **xuống dưới** ngưỡng này nghĩa là đang có xu hướng → **chặn cả hai chiều**. Lưu ý thang đo ngược với trực giác: chỉ số CAO là đi ngang (tốt cho DCA), chỉ số THẤP là đang chạy xu hướng.',
    'InpChopTimeframe': _TF.format(ten='Choppiness'),
    'InpChoppinessPeriod': _PERIOD.format(ten='Choppiness'),

    # ---------- XII.13 AROON ----------
    'InpEnableAroonFilter': 'Bật thì bộ lọc Aroon tham gia quyết định có cho mở lệnh hay không. Aroon đo **đã bao lâu rồi kể từ đỉnh/đáy gần nhất** để nhận ra xu hướng.',
    'InpAroonTrendingLevel': 'Aroon đạt hoặc vượt ngưỡng này nghĩa là xu hướng mạnh → **chặn cả hai chiều**.',
    'InpAroonTimeframe': _TF.format(ten='Aroon'),
    'InpAroonPeriod': _PERIOD.format(ten='Aroon'),

    # ---------- XII.14 DMI ----------
    'InpEnableDMIFilter': 'Bật thì bộ lọc DMI tham gia quyết định có cho mở lệnh hay không. DMI so sức mua (+DI) với sức bán (−DI).',
    'InpDMITrendingLevel': 'Chênh lệch giữa +DI và −DI đạt ngưỡng này thì **chặn cả hai chiều** — dù đang tăng mạnh hay giảm mạnh. Chênh lệch nhỏ nghĩa là hai phe cân bằng, thị trường đi ngang, DCA được vào.',
    'InpDMITimeframe': _TF.format(ten='DMI'),
    'InpDMIPeriod': _PERIOD.format(ten='DMI'),

    # ---------- XII.15 VIDYA ----------
    'InpEnableVIDYAFilter': 'Bật thì bộ lọc VIDYA tham gia quyết định có cho mở lệnh hay không. VIDYA là đường trung bình **tự đổi độ nhạy** theo biến động: thị trường chạy thì nó bám sát, lình xình thì nó đứng yên.',
    'InpVIDYAPeriod': 'Số nến tính đường VIDYA. Lớn thì đường mượt và chậm hơn.',
    'InpVIDYACMOPeriod': 'Số nến tính chỉ số động lượng (CMO) — chính nó quyết định VIDYA nhạy hay chậm ở từng thời điểm.',
    'InpVIDYASlopeThreshold': 'Độ dốc của đường VIDYA đạt ngưỡng này thì coi là đang có xu hướng → **chặn cả hai chiều**. Đơn vị là mức giá thay đổi trên mỗi nến.',
    'InpVIDYATimeframe': _TF.format(ten='VIDYA'),

    # ---------- XII.16 ELDER POWER ----------
    'InpEnableElderFilter': 'Bật thì bộ lọc Elder Power tham gia quyết định có cho mở lệnh hay không. Chỉ báo này đo phe mua và phe bán đẩy giá ra xa đường trung bình được bao nhiêu.',
    'InpElderThreshold': 'Sức đẩy đạt ngưỡng này thì **chặn cả hai chiều**. Đơn vị là giá.',
    'InpElderTimeframe': _TF.format(ten='Elder Power'),
    'InpElderPeriod': 'Số nến tính đường trung bình (EMA) làm mốc cho Bulls/Bears Power.',

    # ---------- XII.17 MACD HISTOGRAM ----------
    'InpEnableMACDFilter': 'Bật thì bộ lọc MACD Histogram tham gia quyết định có cho mở lệnh hay không. Phần histogram đo **đà** của xu hướng đang mạnh lên hay yếu đi.',
    'InpMACDFast': 'Số nến của đường trung bình **nhanh**.',
    'InpMACDSlow': 'Số nến của đường trung bình **chậm**. Phải lớn hơn đường nhanh.',
    'InpMACDSignal': 'Số nến làm mượt đường tín hiệu. Histogram chính là khoảng cách giữa MACD và đường này.',
    'InpMACDTrendingLevel': 'Histogram đạt ngưỡng này thì coi là xu hướng mạnh → **chặn cả hai chiều**. Đơn vị là giá gốc của mã, nên mỗi mã một mức khác nhau — vàng và EURUSD không dùng chung con số được.',
    'InpMACDTimeframe': _TF.format(ten='MACD'),

    # ---------- XII.18 PARABOLIC SAR ----------
    'InpEnableSARFilter': 'Bật thì bộ lọc Parabolic SAR tham gia quyết định có cho mở lệnh hay không. SAR là chuỗi chấm bám theo giá, càng xa giá thì xu hướng càng chắc.',
    'InpSARStep': 'Tốc độ SAR bám theo giá. Lớn thì bám sát và hay đảo chiều, nhỏ thì thong thả hơn.',
    'InpSARMax': 'Trần tốc độ bám. Giữ cho SAR không bám quá sát khi xu hướng kéo dài.',
    'InpSARTrendingLevel': 'Khoảng cách giữa SAR và giá đóng cửa đạt ngưỡng này thì coi là xu hướng mạnh → **chặn cả hai chiều**. Đơn vị là giá gốc của mã.',
    'InpSARTimeframe': _TF.format(ten='Parabolic SAR'),

    # ---------- XII.19 SUPERTREND ----------
    'InpEnableSupertrendFilter': 'Bật thì bộ lọc Supertrend tham gia quyết định có cho mở lệnh hay không. Supertrend là đường chặn tính từ ATR, đổi bên mỗi khi xu hướng đảo.',
    'InpSupertrendPeriod': 'Số nến tính ATR — chính nó quyết định đường Supertrend rộng hay hẹp.',
    'InpSupertrendMultiplier': 'Nhân ATR lên bấy nhiêu lần để đặt đường chặn. Lớn thì đường xa giá, ít đảo chiều; nhỏ thì bám sát, đảo liên tục.',
    'InpSupertrendThreshold': 'Khoảng cách giữa giá và đường Supertrend đạt ngưỡng này thì coi là xu hướng mạnh → **chặn cả hai chiều**. Đơn vị là giá.',
    'InpSupertrendTimeframe': _TF.format(ten='Supertrend'),

    # ---------- XII.20 HIGH LOW BREAKOUT/SIDEWAY ----------
    'InpEnablePivotFilter': 'Bật thì bộ lọc High Low tham gia quyết định có cho mở lệnh hay không. Nó dựng một vùng từ đỉnh và đáy gần nhất; giá còn trong vùng thì cho vào lệnh, **phá ra khỏi vùng thì chặn**.',
    'InpPivotLeftBars': 'Một đỉnh chỉ được công nhận khi bấy nhiêu nến **bên trái** nó đều thấp hơn (đáy thì ngược lại). Lớn thì đỉnh/đáy chắc chắn hơn nhưng nhận ra chậm hơn.',
    'InpPivotRightBars': 'Số nến **bên phải** phải xác nhận. Đây chính là độ trễ: để `5` thì phải đợi 5 nến sau mới biết đó là đỉnh.',
    'InpPivotBufferPercent': 'Phần chừa thêm ra ngoài vùng để chống **phá biên giả**. Tính theo **phần trăm độ rộng của chính vùng pivot**, không phải phần trăm giá — vùng rộng 100 giá, buffer `5` thì chừa thêm 5 giá mỗi bên.',
    'InpPivotTimeframe': _TF.format(ten='đỉnh đáy'),

    # ---------- XII.21 BB CHANNEL ----------
    'InpEnableBBChFilter': 'Bật thì bộ lọc BB Channel tham gia quyết định có cho mở lệnh hay không. Khác với Bollinger %B ở trên: cái này xét **phá kênh**, không xét quá mua quá bán.',
    'InpBBChPeriod': 'Số nến tính đường trung bình giữa của kênh.',
    'InpBBChDeviation': 'Hai dải cách đường giữa bao nhiêu độ lệch chuẩn.',
    'InpBBChTimeframe': _TF.format(ten='BB Channel'),
    'InpBBChBufferPercent': 'Phần chừa thêm ngoài kênh để chống phá biên giả, tính theo phần trăm độ rộng kênh.',

    # ---------- XII.22 KELTNER CHANNEL ----------
    'InpEnableKCFilter': 'Bật thì bộ lọc Keltner Channel tham gia quyết định có cho mở lệnh hay không. Kênh này dựng từ EMA cộng trừ ATR, nên bám theo **biến động thật** chứ không theo độ lệch chuẩn.',
    'InpKCEmaPeriod': 'Số nến tính đường EMA ở giữa kênh.',
    'InpKCATRPeriod': 'Số nến tính ATR — quyết định kênh rộng hay hẹp.',
    'InpKCMultiplier': 'Nhân ATR lên bấy nhiêu lần để đặt hai mép kênh.',
    'InpKCTimeframe': _TF.format(ten='Keltner Channel'),
    'InpKCBufferPercent': 'Phần chừa thêm ngoài kênh để chống phá biên giả, tính theo phần trăm độ rộng kênh.',

    # ---------- XII.23 DONCHIAN CHANNEL ----------
    'InpEnableDCFilter': 'Bật thì bộ lọc Donchian tham gia quyết định có cho mở lệnh hay không. Kênh Donchian đơn giản nhất: **đỉnh cao nhất và đáy thấp nhất** trong N nến. Giá phá ra là chặn cả hai chiều.',
    'InpDCPeriod': 'Lấy đỉnh cao nhất và đáy thấp nhất trong bấy nhiêu nến gần đây.',
    'InpDCTimeframe': _TF.format(ten='Donchian Channel'),
    'InpDCBufferPercent': 'Phần chừa thêm ngoài kênh để chống phá biên giả, tính theo phần trăm độ rộng kênh.',

    # ---------- XII.24 ICHIMOKU ----------
    'InpEnableIchiFilter': 'Bật thì bộ lọc Ichimoku tham gia quyết định có cho mở lệnh hay không. Cách dùng ở đây: giá **nằm trong mây** là đi ngang → cho vào lệnh; giá **ra ngoài mây** là có xu hướng → chặn theo chiều đó.',
    'InpIchiTenkan': 'Số nến của đường **nhanh** (Tenkan).',
    'InpIchiKijun': 'Số nến của đường **chậm** (Kijun).',
    'InpIchiSenkouB': 'Số nến của cạnh xa của mây (Senkou B). Đây là thành phần chậm nhất, quyết định mây dày hay mỏng.',
    'InpIchiCheckSqueeze': 'Bật thì khi **mây mỏng** — dấu hiệu sắp phá biên — EA chặn cả hai chiều. Tắt thì chỉ xét giá trong hay ngoài mây.',
    'InpIchiThicknessMode': 'Cách đo độ dày mây: theo **giá** (một con số cố định) hay theo **ATR** (co giãn theo biến động). Chọn ATR thì cùng một cấu hình dùng được cho nhiều mã.',
    'InpIchiCloudThreshold': 'Mây mỏng hơn mức này thì coi là đang nén. Đơn vị tuỳ ô trên: chọn Giá thì đây là mức giá, chọn ATR thì đây là hệ số — `0.5` nghĩa là bằng 50% ATR.',
    'InpIchiCloudATRPeriod': 'Số nến tính ATR, chỉ dùng khi chế độ đo mây đặt là ATR.',
    'InpIchiTimeframe': _TF.format(ten='Ichimoku'),

    # ---------- XII.25 ATR ----------
    'InpEnableATRFilter': 'Bật thì bộ lọc ATR tham gia quyết định có cho mở lệnh hay không. ATR đo **biên độ dao động trung bình** mỗi nến.\n\n⚠️ **Bật ô này thì BẮT BUỘC phải đặt ngưỡng khác 0** — xem ô dưới.',
    'InpATRMaxLevel': 'ATR đạt hoặc vượt ngưỡng này thì **chặn cả hai chiều** — thị trường đang động quá mạnh cho DCA. Đơn vị là **giá gốc của mã**, nên mỗi mã một mức khác nhau.\n\n⚠️ **Để `0` mà vẫn bật bộ lọc thì EA chặn SẠCH mọi lệnh**, vì ATR không bao giờ nhỏ hơn 0. Mà `0` lại đúng là giá trị mặc định — nhớ đổi nó trước khi bật.',
    'InpATRTimeframe': _TF.format(ten='ATR'),
    'InpATRPeriod': _PERIOD.format(ten='ATR'),

    # ---------- XII.26 ADR ----------
    'InpEnableADRFilter': 'Bật thì bộ lọc ADR tham gia quyết định có cho mở lệnh hay không. ADR là **biên độ trung bình một ngày**; bộ lọc này chặn khi giá hôm nay đã đi gần hết quãng thường ngày.',
    'InpADRPeriod': 'Lấy trung bình biên độ của bấy nhiêu ngày gần nhất để làm mốc.',
    'InpADRThreshold': 'Giá hôm nay đã đi được bấy nhiêu **phần trăm** so với biên độ trung bình ngày thì **chặn cả hai chiều**. Để `80` nghĩa là đi hết 80% quãng thường ngày là dừng — phần còn lại của ngày thường dễ đảo chiều.',

    # ---------- XII.27 SQUEEZE ----------
    'InpEnableSqueezeFilter': 'Bật thì bộ lọc Squeeze tham gia quyết định có cho mở lệnh hay không. Squeeze nhận ra lúc **Bollinger nằm gọn trong Keltner** — thị trường đang bị nén, thường là sắp phá biên mạnh → **chặn**.\n\nĐây là bộ lọc **phụ trợ**: nên dùng kèm một bộ lọc xác nhận đi ngang, đừng dùng một mình.',
    'InpSqueezeDataMode': 'Lấy số liệu BB và KC từ đâu: **Tự tính** bằng các thông số riêng ngay dưới đây, hay **dùng lại** cấu hình của hai bộ lọc BB Channel và Keltner ở trên. Dùng lại thì đỡ phải khai hai lần và luôn khớp nhau.',
    'InpSqueezeBBPeriod': 'Số nến tính Bollinger của riêng bộ lọc Squeeze. Chỉ dùng khi chọn chế độ Tự tính.',
    'InpSqueezeBBDeviation': 'Độ lệch chuẩn của Bollinger trong Squeeze. Chỉ dùng khi chọn chế độ Tự tính.',
    'InpSqueezeKCPeriod': 'Số nến tính EMA giữa của Keltner trong Squeeze. Chỉ dùng khi chọn chế độ Tự tính.',
    'InpSqueezeKCATRPeriod': 'Số nến tính ATR của Keltner trong Squeeze. Để mặc định thì khớp đúng với bộ lọc Keltner ở trên.',
    'InpSqueezeKCMultiplier': 'Nhân ATR lên bấy nhiêu lần để đặt mép Keltner trong Squeeze.',
    'InpSqueezeTimeframe': _TF.format(ten='Squeeze'),

    # ---------- XII.28 OBV ----------
    'InpEnableOBVFilter': 'Bật thì bộ lọc OBV tham gia quyết định có cho mở lệnh hay không. OBV cộng dồn khối lượng theo chiều nến để đo **dòng tiền đang vào hay đang ra**.\n\nKhác các bộ lọc khác ở chỗ nó chặn **theo chiều**: tiền đang vào thì cho BUY và chặn SELL; tiền đang ra thì cho SELL và chặn BUY.',
    'InpOBVTimeframe': _TF.format(ten='OBV'),
    'InpOBVPeriod': 'So OBV hiện tại với OBV của bấy nhiêu nến trước để biết dòng tiền đang tăng hay giảm.',
}


# =====================================================================
# 2. KHUNG GIỜ 2..5 — chép từ khung 1
# =====================================================================

# Cái bẫy im lặng của cả nhóm này, kiểm trong DCA_RangeConfig.mqh:
# GetEffectiveRange() cho khung 2..5 đọc InpConfigSourceN, mà MẶC ĐỊNH
# của cả bốn khung đều là CONFIG_SAME_AS_RANGE1. Tức là khách điền kín
# tham số cho khung 2 nhưng EA vẫn dùng của khung 1, không báo gì.
GHI_CHU_NGUON_CONFIG = (
    '\n\n⚠️ Ô này **chỉ có tác dụng khi `Nguồn Config` của khung giờ '
    'này đặt là "Tuỳ chỉnh riêng"**. Mặc định cả bốn khung 2–5 đều để '
    '"Dùng Config Range 1", nghĩa là điền xong vẫn chạy theo khung 1 và '
    'EA không báo gì cả.'
)

# 12 tham số dùng chung, đặt tên theo khuôn <goc><so><duoi>
KHUON_KHUNG_GIO = (
    ('InpTimeRange', ''), ('InpStepMode', ''), ('InpStartingStep', ''),
    ('InpPipStep', '_Str'), ('InpStepMultiplier', ''), ('InpTPMode', ''),
    ('InpTakeProfit', ''), ('InpTPPercent', ''), ('InpTPCustom', '_Str'),
    ('InpLotMode', ''), ('InpLayerLots', '_Str'), ('InpLayerMultiplier', ''),
)

# Hai tham số CHỈ khung 2..5 mới có
BAT_KHUNG = {
    2: 'InpUseSecondTimeRange',
    3: 'InpUseThirdTimeRange',
    4: 'InpUseFourthTimeRange',
    5: 'InpUseFifthTimeRange',
}


def dung_khung_gio(dg):
    """Sinh diễn giải cho khung giờ 2..5 từ khung giờ 1."""
    them = {}
    for so in (2, 3, 4, 5):
        them[BAT_KHUNG[so]] = (
            'Bật khung giờ %d. Tắt thì mọi cài đặt của khung này bị bỏ qua '
            'hoàn toàn.' % so)
        them['InpConfigSource%d' % so] = (
            'Khung giờ %d lấy bộ tham số từ đâu: **Tuỳ chỉnh riêng** (dùng '
            'các ô ngay dưới đây) hay **dùng lại** cấu hình của một khung '
            'khác.\n\nMặc định là *Dùng Config Range 1* — nên nếu bạn điền '
            'tham số riêng cho khung này mà quên đổi ô này, EA vẫn chạy '
            'theo khung 1 và **không báo gì cả**.\n\nCó thể trỏ dây chuyền '
            '(khung 3 dùng khung 2, khung 2 lại dùng khung 1) — EA tự lần '
            'theo tới gốc, và tự bỏ qua nếu ai đó trỏ vòng về chính mình.'
            % so)
        for goc, duoi in KHUON_KHUNG_GIO:
            khoa_1 = '%s1%s' % (goc, duoi)
            khoa_n = '%s%d%s' % (goc, so, duoi)
            if khoa_1 in dg:
                them[khoa_n] = dg[khoa_1] + GHI_CHU_NGUON_CONFIG
    return them


# =====================================================================
# CHẠY
# =====================================================================

def main():
    dg = json.load(io.open(TEP, encoding='utf-8'))
    truoc = len(dg)

    them = {}
    them.update(BO_LOC)
    them.update(dung_khung_gio(dg))

    # KHÔNG đè lên thứ đã viết tay — chạy lại tệp này phải an toàn.
    moi = {k: v for k, v in them.items() if k not in dg}
    dg.update(moi)

    # Sắp khoá cho tệp dễ đọc và diff gọn khi sửa về sau.
    ra = json.dumps(dict(sorted(dg.items())), ensure_ascii=False, indent=2)
    io.open(TEP, 'w', encoding='utf-8', newline='\n').write(ra + '\n')

    print('Truoc      : %d khoa' % truoc)
    print('Them moi   : %d khoa' % len(moi))
    print('Bo qua (da co): %d khoa' % (len(them) - len(moi)))
    print('Sau        : %d khoa' % len(dg))
    return 0


if __name__ == '__main__':
    sys.exit(main())
