Chiến lược & backtestStrategy & backtests

Chuỗi multiplier DCA — Persistent vs LegacyDCA multiplier chain — Persistent vs Legacy

Hiểu hai cách EA tính số mũ lot cho lệnh DCA sau khi một phần round bị Tia hoặc partial close đóng, và khi nào nên bật InpDcaChainPersistent. Understand the two ways the EA computes the lot exponent for DCA adds after part of a round is closed by Tia or a partial close, and when to turn on InpDcaChainPersistent.

  • Cập nhậtUpdated 18/09/2026
  • NguồnSource docs/CHAIN_PERSISTENT.md
  • Bản gốcOriginal v1.35 · 24/05/2026
  • Input InpDcaChainPersistent (bool, default false)
Bối cảnh phiên bảnVersion context

Tính năng ra đời ở v1.35 và vẫn còn nguyên trong EA production 1.42 b7 (cùng default false). Config ổn định v1.42.b7-pass58.set để InpDcaChainPersistent=false (legacy) và tắt DCANeg (InpDCANegEnable=false), nên thực tế chỉ nhánh DCAPos dùng công thức legacy bên dưới. Số dòng code trong trang này thuộc bản v1.35 và đã lệch. Tham số: Chain persistent · Config ổn định.

The feature arrived in v1.35 and is unchanged in the production EA 1.42 b7 (same default false). The stable config v1.42.b7-pass58.set sets InpDcaChainPersistent=false (legacy) and disables DCANeg (InpDCANegEnable=false), so in practice only the DCAPos branch uses the legacy formula below. Code line numbers on this page belong to v1.35 and are stale. Parameters: Chain persistent · Stable config.

1. Bối cảnh — vấn đề người dùng phát hiện1. Context — the issue the user spotted

Ảnh chụp backtest:

  • InpDCAPosStartLot = 0.10
  • InpDCAPosMultiplier = 1.5
  • Init R68: Buy 0.10 lot
  • DCAPos R68 B#1 (add đầu tiên): 0.10 lot

Người dùng kỳ vọng: 0.10 × 1.5 = 0.15 lot cho B#1.

Nguyên nhân gốc: lệnh Init Buy của R68 đã bị Tia đóng theo cụm trước khi B#1 khớp → nBuy = 0 tại thời điểm khớp → số mũ = 0 → lot reset về StartLot.

Backtest screenshot:

  • InpDCAPosStartLot = 0.10
  • InpDCAPosMultiplier = 1.5
  • Init R68: Buy 0.10 lot
  • DCAPos R68 B#1 (first add): 0.10 lot

The user expected 0.10 × 1.5 = 0.15 lot for B#1.

Root cause: R68's Init Buy had already been closed by a Tia cluster close before B#1 fired → nBuy = 0 at fire time → exponent = 0 → the lot reset to StartLot.

2. Hai chế độ — Legacy và Persistent2. Two modes — Legacy and Persistent

Legacy (InpDcaChainPersistent = false, mặc định)Legacy (InpDcaChainPersistent = false, default)

Công thức lot:Lot formula:

lot = InpDCAPosStartLot × InpDCAPosMultiplier^nBuy

Trong đó nBuy = số lệnh Buy ĐANG MỞ trong round (đếm tại thời điểm add khớp).

Hành vi: khi Tia/partial close làm nBuy giảm → số mũ giảm → lot của add kế tiếp RESET về mức nhỏ hơn.

Here nBuy = the number of Buy positions CURRENTLY OPEN in the round (counted when the add fires).

Behavior: when Tia or a partial close lowers nBuy → the exponent drops → the next add's lot RESETS to a smaller size.

Ưu điểmPros

  • Thân thiện với phục hồi: rủi ro giảm sau khi dọn cụm
  • Tự bảo vệ khỏi chuỗi DCA "chạy mất kiểm soát" khi lệnh bị đóng
  • Recovery-friendly: risk drops after a cluster is cleared
  • Self-protects against a runaway DCA chain as positions close

Nhược điểmCons

  • Nhãn comment "B#1" không nhất quán với lot
  • Khi Init bị đóng, chuỗi bị "đứt" — add tiếp theo quay về lot tối thiểu
  • Lot không đi theo tiến trình DCA mà người dùng mong đợi
  • The "B#1" comment label does not consistently match the lot
  • When Init closes the chain "breaks" — the next add is back to the minimum lot
  • Lots do not follow the DCA progression the user expects

Persistent (InpDcaChainPersistent = true, v1.35+)Persistent (InpDcaChainPersistent = true, v1.35+)

Công thức lot:Lot formula:

nextBuyNum = max(scanMaxBuyNum, RoundChainGetMaxBuy(roundId)) + 1
lot = InpDCAPosStartLot × InpDCAPosMultiplier^nextBuyNum

Trong đó bộ đếm RoundChainEntry.maxBuyNum được lưu BỀN (persistent) theo từng round trong mảng toàn cục g_roundChain[]. Bộ đếm chỉ RESET khi cả round chết (không còn lệnh nào của round).

Hành vi: chuỗi tiếp tục qua mọi lần partial/Tia close. Lot khớp chính xác với số thứ tự B#N.

Here the counter RoundChainEntry.maxBuyNum is stored PERSISTENTLY per round in the global array g_roundChain[]. The counter RESETS only when the entire round dies (no positions of that round remain).

Behavior: the chain continues through every partial/Tia close. The lot matches the B#N numbering exactly.

Ưu điểmPros

  • Lot và nhãn luôn nhất quán: B#1 = 0.15, B#2 = 0.225, B#3 = 0.3375...
  • Tiến trình chuỗi DCA đúng ý đồ người dùng
  • Khớp logic Martingale nghiêm ngặt
  • Lot and label always agree: B#1 = 0.15, B#2 = 0.225, B#3 = 0.3375...
  • The DCA chain progresses as the user intended
  • Matches strict Martingale logic

Nhược điểmCons

  • Rủi ro cao hơn khi DD kéo dài: lot không reset xuống nhỏ
  • Có thể leo nhanh tới cap InpDCAPosMaxLot nếu chuỗi dài
  • Kịch bản tài khoản bị kẹt: Tia đóng lệnh thắng + chuỗi tiếp tục lớn dần → exposure tích luỹ
  • Higher risk in a long drawdown: lots do not reset to small sizes
  • Can climb quickly to the InpDCAPosMaxLot cap if the chain is long
  • Trapped-account scenario: Tia closes the winners + the chain keeps growing → exposure accumulates

3. So sánh song song — cùng một kịch bản3. Side-by-side comparison — same scenario

Thiết lập: Round R, InpDCAPosStartLot=0.10, InpDCAPosMultiplier=1.5Setup: Round R, InpDCAPosStartLot=0.10, InpDCAPosMultiplier=1.5

Sự kiệnEvent Legacy (false) Persistent (true)
Init Buy khớpInit Buy firesnBuy=1 (sau khi mở), lot 0.10nBuy=1 (after), lot 0.10maxBuyNum=0 (Init không có B#), lot 0.10maxBuyNum=0 (Init has no B#), lot 0.10
Giá lên → Buy add #1 khớpPrice up → Buy add #1 firesnBuy=1 (trước khi khớp), exp=1 → lot 0.15nBuy=1 (before fire), exp=1 → lot 0.15nextBuyNum=1, exp=1 → lot 0.15
Buy add #2 khớpBuy add #2 firesnBuy=2 → lot 0.225nextBuyNum=2, exp=2 → lot 0.225
Tia đóng Init BuyTia closes Init BuynBuy=2 (B#1+B#2 còn mở)nBuy=2 (B#1+B#2 alive)maxBuyNum=2 (lưu bền)maxBuyNum=2 persistent
Buy add #3 khớpBuy add #3 firesnBuy=2 → lot 0.3375nextBuyNum=3, exp=3 → lot 0.3375 giống nhausame
Tia đóng B#1, B#2Tia closes B#1, B#2 (đã đóng hết Buy)(all Buys closed)nBuy=0maxBuyNum=2 (vẫn lưu — round còn sống nhờ phía bên kia?)maxBuyNum=2 (still persistent — round alive due to the other side?)
Round vẫn sống (còn Sell)Round still alive (Sells exist)—maxBuyNum=2
Buy add #4 khớpBuy add #4 firesnBuy=0 → lot 0.10 RESETnextBuyNum=3, exp=3 → lot 0.3375 tiếp tụccontinues
Mọi lệnh của round đã đóng (round chết)All round members closed (round dies)—RoundChainCleanup xoá entryRoundChainCleanup removes the entry
Round mới R' initNew round R' initnBuy=1, lot 0.10maxBuyNum=0 (mới), lot 0.10maxBuyNum=0 fresh, lot 0.10

4. Chi tiết triển khai4. Implementation details

Cấu trúc dữ liệuData structure

struct RoundChainEntry {
   int roundId;
   int maxBuyNum;     // highest B#N ever assigned in round
   int maxSellNum;    // highest S#N ever assigned in round
};
RoundChainEntry g_roundChain[];
int             g_roundChainCount = 0;

Hàm hỗ trợHelper functions

FunctionMục đíchPurpose
RoundChainGetMaxBuy(roundId)Tra maxBuyNum đã lưu của round (trả 0 nếu chưa tồn tại)Look up the persistent maxBuyNum for the round (returns 0 if absent)
RoundChainGetMaxSell(roundId)Tương tự cho phía SellSame for the Sell side
RoundChainSetMaxBuy(roundId, n)Cập nhật bộ đếm (chỉ tăng, không bao giờ giảm)Update the counter (only increases, never decreases)
RoundChainSetMaxSell(roundId, n)Tương tự cho SellSame for Sell
RoundChainCleanup()Xoá entry của các round không còn lệnh nào (round chết)Remove entries of rounds with no positions left (round dies)

Logic dọn dẹpCleanup logic

RoundChainCleanup() được gắn vào chu kỳ dọn dẹp 30 giây cùng với TicketMapCleanup():RoundChainCleanup() is hooked into the 30-second cleanup cycle alongside TicketMapCleanup():

if (TimeCurrent() - lastMapClean >= 30) {
   TicketMapCleanup();
   RoundChainCleanup();  // v1.35
   ...
}

Mỗi entry trong g_roundChain[] được kiểm tra: còn lệnh nào cùng roundId đang mở không? Nếu không → xoá entry.Each entry in g_roundChain[] is checked: is any position with the same roundId still open? If not → the entry is removed.

4 nhánh đã sửaFour modified paths

PathVị trí (v1.35)Location (v1.35)
DCAPos Buy addGoldenRobber.mq5:3464
DCAPos Sell addGoldenRobber.mq5:3496
DCANeg Buy addGoldenRobber.mq5:3502
DCANeg Sell addGoldenRobber.mq5:3533

Mỗi nhánh có đoạn chuyển chế độ tương tự:Each path has a similar mode switch:

int nextBuyNum = (InpDcaChainPersistent
                  ? MathMax(maxBuyNum, RoundChainGetMaxBuy(roundId)) + 1
                  : maxBuyNum + 1);
int lotExp = (InpDcaChainPersistent ? nextBuyNum : nBuy);
double rawLot = MathMin(InpDCAPosStartLot * MathPow(InpDCAPosMultiplier, lotExp), InpDCAPosMaxLot);

MathMax(maxBuyNum, RoundChainGetMaxBuy(roundId)) là chốt an toàn dự phòng: nếu g_roundChain mất trạng thái (vd. EA khởi động lại), dùng maxBuyNum quét từ lệnh đang mở làm mức tối thiểu.MathMax(maxBuyNum, RoundChainGetMaxBuy(roundId)) is a fallback safety: if g_roundChain loses its state (e.g. an EA restart), the scan-based maxBuyNum is used as the minimum.

Riêng DCANegDCANeg specifics

Nhánh DCANeg dùng lvl = nBuy - 1 (logic gốc, số mũ bắt đầu từ 0 cho add đầu tiên). v1.35 giữ nguyên ngữ nghĩa này:The DCANeg path uses lvl = nBuy - 1 (original logic, the exponent starts at 0 for the first add). v1.35 preserves those semantics:

if (InpDcaChainPersistent) {
   lotExp = MathMax(0, nextBuyNum - 1);  // first add exp = 0
} else {
   int lvl = nBuy - 1; if (lvl < 0) lvl = 0;
   lotExp = lvl;
}

5. Khi nào dùng chế độ nào?5. Which mode when?

Dùng Legacy (false) khiUse Legacy (false) when

  • Cần tương thích ngược với config cũ (Pass 10, ScalpingTrending, A baseline)
  • Chiến lược ưu tiên phục hồi (sau Tia close, bắt đầu lại từ lot nhỏ)
  • Khẩu vị rủi ro thận trọng
  • E ngại tail risk khi chuỗi leo cao
  • Mặc định — không cần thay đổi gì
  • You need backward compatibility with old configs (Pass 10, ScalpingTrending, A baseline)
  • The strategy is recovery-friendly (after a Tia close, restart small)
  • Your risk profile is conservative
  • You worry about tail risk as the chain climbs
  • Default — nothing to change

Dùng Persistent (true) khiUse Persistent (true) when

  • Muốn tiến trình chuỗi DCA nghiêm ngặt (Martingale truyền thống)
  • Equity cao, chấp nhận rủi ro lớn hơn để tận dụng chuỗi
  • Chiến lược được thiết kế quanh ý "chuỗi bền" như spec gốc
  • Lot và nhãn B#N phải nhất quán cho việc phân tích
  • Đã backtest và xác nhận hành vi tốt hơn legacy
  • You want a strict DCA chain progression (classic Martingale)
  • Equity is high and you accept more risk to exploit the chain
  • The strategy is built around "chain persistence" as in the original spec
  • Lot and B#N label must agree for analysis
  • You have backtested and confirmed it behaves better than legacy

6. Cảnh báo khi bật Persistent6. Warnings when enabling Persistent

  • Phình rủi ro: chuỗi không reset → lot leo cao → exposure tích luỹ. Cap bằng InpDCAPosMaxLot / InpDCANegMaxLot là lưới an toàn SỐNG CÒN.
  • Kết hợp với Tia close: Tia đóng lệnh thắng không reset chuỗi → add kế tiếp có thể leo nhanh tới max lot → rủi ro tăng đột biến nếu DD đổi chiều ngay sau đó.
  • Round dài = lot cao: round sống lâu (qua nhiều lần partial close) → chuỗi leo cao → các add cuối ở max lot. Nếu round không đóng được → tail risk cực lớn.
  • Backtest xác nhận TRƯỚC khi chạy live: với InpDcaChainPersistent=true, động lực hành vi khác hẳn. CẦN chạy lại backtest cùng config và so sánh: tỷ lệ Profit/DD, Sharpe, sự kiện chuỗi thua dài nhất, Margin Level thấp nhất.
  • Risk inflation: the chain does not reset → lots climb → exposure accumulates. The InpDCAPosMaxLot / InpDCANegMaxLot caps are a CRITICAL safety net.
  • Combined with Tia close: Tia closing winners does not reset the chain → the next add can climb fast to max lot → risk spikes if the drawdown reverses right after.
  • Long round = high lot: a long-lived round (through many partial closes) → the chain climbs high → final adds sit at max lot. If the round never closes → extreme tail risk.
  • Backtest BEFORE going live: with InpDcaChainPersistent=true the dynamics are completely different. You MUST re-run the backtest with the same config and compare: Profit/DD ratio, Sharpe, max consecutive-loss event, minimum Margin Level.

7. Checklist chuyển đổi7. Migration checklist

Khi nạp config cũ vào EA v1.35Loading an old config into EA v1.35

  1. Input InpDcaChainPersistent mặc định false → hành vi giống hệt v1.34 và trước đó.
  2. File .set cũ không có InpDcaChainPersistent → EA dùng mặc định false → an toàn.
  3. Các round đang chạy: g_roundChain[] ban đầu rỗng → khi dựng lại qua chu kỳ cleanup, chỉ đếm các round đang có lệnh.
  1. InpDcaChainPersistent defaults to false → behavior identical to v1.34 and earlier.
  2. Old .set files lack InpDcaChainPersistent → the EA defaults to false → safe.
  3. Rounds already running: g_roundChain[] starts empty → when rebuilt via the cleanup cycle, only rounds with current positions are counted.

Khi muốn chuyển sang PersistentSwitching to Persistent mode

  1. Backtest trước: nạp cùng config + giai đoạn với InpDcaChainPersistent=true → so sánh chỉ số.
  2. Kiểm tra vốn: chuỗi leo cao hơn → vốn cần có thể tăng (xem Hướng dẫn vốn).
  3. Theo dõi sát: tail risk tăng → giảm InpDCAPosMaxLot / InpDCANegMaxLot tương ứng nếu cần.
  4. Forward test: chạy vốn nhỏ trước khi dồn toàn bộ.
  1. Backtest first: load the same config + period with InpDcaChainPersistent=true → compare metrics.
  2. Capital check: the chain climbs higher → required capital may rise (see Capital sizing).
  3. Monitor closely: tail risk rises → reduce InpDCAPosMaxLot / InpDCANegMaxLot proportionally if needed.
  4. Forward test: deploy small capital before committing fully.

8. Tra cứu nhanh8. Quick reference

Câu hỏiQuestionTrả lờiAnswer
Chế độ mặc định là gì?What is the default mode?InpDcaChainPersistent = false (legacy)
Logic CŨ tương ứng giá trị nào?Which value is the OLD logic?false
Logic MỚI v1.35 tương ứng giá trị nào?Which value is the NEW v1.35 logic?true
Tương thích ngược khi nạp config cũ?Backward compatible with old configs?CóYes Mặc định false → giống v1.34Default false → same as v1.34
Cần backtest lại khi bật true?Re-backtest when enabling true?CÓYES động lực hành vi khácthe dynamics differ
Dọn dẹp ở đâu?Where is cleanup?RoundChainCleanup() gắn vào chu kỳ dọn TicketMap 30sRoundChainCleanup() hooked into the 30s TicketMap cleanup
Chuỗi reset khi nào (chế độ true)?When does the chain reset (true mode)?Chỉ khi cả round chết (không còn lệnh)Only when the entire round dies (no positions)

9. Tham chiếu mã nguồn (v1.35)9. Source code references (v1.35)

Các số dòng dưới đây thuộc GoldenRobber.mq5 bản v1.35, không còn khớp với bản hiện tại.The line numbers below belong to GoldenRobber.mq5 v1.35 and no longer match current builds.

MụcItemVị tríLocation
Khai báo inputInput declarationGoldenRobber.mq5:210
Định nghĩa structStruct definitionGoldenRobber.mq5:431-441
Hàm hỗ trợHelper functionsGoldenRobber.mq5:2322-2380
DCAPos Buy đã sửaDCAPos Buy modifiedGoldenRobber.mq5:3464-3496
DCAPos Sell đã sửaDCAPos Sell modifiedGoldenRobber.mq5:3496-3525
DCANeg Buy đã sửaDCANeg Buy modifiedGoldenRobber.mq5:3502-3540
DCANeg Sell đã sửaDCANeg Sell modifiedGoldenRobber.mq5:3543-3580
Điểm gắn cleanupCleanup hookGoldenRobber.mq5:5816
Ghi chú lịch sửHistory noteGoldenRobber.mq5:7