//+------------------------------------------------------------------+ //| MaePla_L2.mq5 | //| ระบบแม่ปลาปากกาเขียว — BACKTEST ระดับ 0 (baseline เปลือย) | //| | //| ตอบคำถามเดียว: "ตัวสัญญาณ (PA 5 รูปแบบ + ไส้หลังซิก) มีเปรียบ | //| เชิงสถิติจริงไหม เมื่ออยู่เดี่ยวๆ ไม่มีตัวกรองใดๆ" | //| | //| ⚠️ EA ตัวนี้ "ไม่เทรด" — เป็น census/measurement EA | //| เหตุผล: ระดับ 0 ต้องวัดจาก "เส้นทางราคา" ไม่ใช่ผลไม้ | //| (EA-SPEC §4.1 — RR 1:5 ต้องใช้ ~2,000 ไม้ถึงพิสูจน์ได้) | //| การเทรดจริงจะทำให้เสียสัญญาณที่ซ้อนกัน (1 position ต่อครั้ง) | //| → บันทึกทุกสัญญาณลง CSV แล้ววิเคราะห์ offline แทน | //| | //| กฎเหล็ก §0.2 : ห้ามอ่านแท่งที่ยังไม่ปิด | //| - ทุกเงื่อนไข PA/ไส้หลังซิก อ่าน index >= 1 เท่านั้น (มี assert) | //| - index 0 ใช้ได้แค่ iTime(0) (ตรวจแท่งใหม่) และ iOpen(0) ณ | //| วินาทีที่แท่งเปิด = ราคาปัจจุบันจริง (whitelist #2/#3) | //| - MFE/MAE ประเมินแบบ deferred: รอ 20 แท่งผ่านไปแล้วค่อยย้อนอ่าน | //| M1 ที่เป็นอดีตทั้งหมด → เป็นไปไม่ได้ที่จะ look-ahead | //+------------------------------------------------------------------+ #property copyright "Hon / FIN[psi]" #property version "1.00" #property description "MaePla Level-2 robustness: L1 logic on SYNTHETIC H4 bars built from H1 with a start-hour offset (0/1/2/3). Does NOT trade." //=== หน่วยจุด (EA-SPEC §0.1) — ห้ามใช้ _Point ตรงๆ ==================== #define PTV 0.01 //=== เอาต์พุต ========================================================= input group "Output" input string InpCsvName = "maepla_l2.csv"; // ชื่อไฟล์ CSV (เขียนลง Terminal/Common/Files) input int InpH4OffsetHours = 0; // เลื่อนจุดเริ่มแท่ง H4 สังเคราะห์ 0/1/2/3 ชม. (0 = 00/04/08/12/16/20) input bool InpRecordControl = true; // บันทึก "ทุกแท่ง" เป็น baseline สุ่ม (ต้องมี — เส้นที่ต้องชนะ) input bool InpRecordRejected = true; // บันทึก PA ที่ตกด่านไส้หลังซิกด้วย (เป็นข้อมูล funnel) input int InpEvalBars = 20; // วัด MFE/MAE กี่แท่ง H4 หลังเข้า //=== ทิศทาง (ระดับ 0 ไม่มี interaction ระหว่างฝั่ง — แยกได้ใน analysis) = input group "Direction" input int InpDirFilter = 0; // 0=ทั้งสองฝั่ง 1=BUY เท่านั้น 2=SELL เท่านั้น //=== PA — ค่า [SPEC-เดา] ทั้งหมดล็อกที่ default ตลอดระดับ 0 (EA-SPEC §5 ข้อ 10) input group "PA — PAT1 Pin [SPEC-DAO G1]" input double InpPinWickBodyRatio = 2.0; // ไส้ >= 2.0 เท่าของเนื้อ input double InpPinWickRangePct = 0.60; // ไส้ >= 60% ของ range input double InpPinOppWickPct = 0.15; // ไส้ตรงข้าม <= 15% ของ range input group "PA — PAT2 / PAT2 พิเศษ" input double InpEngulf50Min = 0.50; // [ปลา3 p.5] ล็อก input bool InpPat2FullEngulf = false; // [SPEC-DAO] input group "PA — PAT3 [SPEC-DAO G2/G3/G3b]" input double InpDojiBodyRangePct = 0.10; // Doji: เนื้อ <= 10% ของ range input double InpDojiVsFirstPct = 0.30; // เนื้อกลาง <= 30% ของแท่งแรก input double InpSmallVsFirstPct = 0.50; // แบบ2: เนื้อกลาง <= 50% ของแท่งแรก input bool InpApply50ToPat31_2 = true; // [SPEC-DAO G14] input int InpPat33Mode = 0; // 0=CLOSE_LEVEL 1=BODY_SUM [G3b] input double InpPat33MinPen = 0.50; // [ปลา3 p.6] ล็อก input bool InpPat33NoNewExtrm = true; // [SPEC-DAO] input bool InpPat33Harami = false; // [SPEC-DAO] input group "PA — กันแท่งจิ๋ว [SPEC-DAO]" input int InpMinBarRangePts = 80; // range ขั้นต่ำ (จุด) input int InpMinBodyPts = 30; // body ขั้นต่ำ (จุด) //=== ไส้หลังซิก (§1.3.2) ============================================== input group "PostWick [Mo1 p.16] [Pla3 p.9]" input int InpPostWickCloseRule = 0; // 0=OPPOSITE(โมจา) 1=SAME(ลูกแก้ว) 2=ANY §4.1b input double InpPostWickBodyMax = 0.50; // [โม1 p.16] ล็อก input double InpPostWickWickMult = 1.00; // [โม1 p.16] ล็อก //=== SL / TP (§1.6) =================================================== input group "SL / TP — ท่า A2: SL = ไส้ของ Sig [ปลา3 p.16]" input int InpTP1Points = 1500; // รอบวิ่ง H4 นับจากไส้หลังซิก [ปลา3 p.8] input int InpTP2Points = 3000; input int InpSLBufferPts = 0; // [SPEC-DAO] buffer เพิ่มจากไส้ (0 = ไส้เป๊ะตามโค้ช) //--- §4.1 ไส้หลังซิกถูกทำลายเมื่อไหร่ — ไม่ใช่พารามิเตอร์ตัวเลขตัวเดียว // เป็น comparator + buffer พร้อมกัน (GLOSSARY §4.1) // แม่ปลา : price > postwick (GT, buffer 0) // ลูกแก้ว : price >= postwick (GTE, buffer 0) — สัมผัสก็ตาย // โมจา : price > postwick + 300*0.01 (GT, buffer 300) input int InpWickBreakOp = 0; // 0=GT(แม่ปลา/โมจา) 1=GTE(ลูกแก้ว) input int InpWickBreakBufferPts = 0; // 0=แม่ปลา/ลูกแก้ว · 300=โมจา input int InpMojaBufferPts = 300; // [โม1 p.39] ล็อก //--- ⚠️ สองตัวล่างนี้ไม่ใช่ SL ของระบบ — เป็น "หัววัด" ของเกณฑ์ B ใน EA-SPEC §4.1 // ("P(MFE ถึง 1500 ก่อน MAE ถึง 300)") ซึ่งเป็นเรขาคณิตคงที่ไว้เทียบ baseline เท่านั้น // SL จริงของท่า A2 = ไส้ของ Sig (คอลัมน์ sl_price / sys_*) input int InpRaceSLPts = 300; // หัววัดเกณฑ์ B — ไม่ใช่ SL ระบบ input int InpRaceSL2Pts = 500; // หัววัดทางเลือก (ตลาดเมกา) //=== State ============================================================ datetime gLastBarTime = 0; // (คงไว้เพื่อความเข้ากันได้ — L2 ใช้ gLastH1) int gFH = INVALID_HANDLE; // สถิติ funnel int gCntBars=0, gCntCandidate[2]={0,0}, gCntValid[2]={0,0}; int gCntStatus[5]={0,0,0,0,0}; // 0=VALID 1=W1 2=W2 3=W3 4=W5 int gCntPatWin[6]={0,0,0,0,0,0}; // ต่อ pattern ที่ชนะ priority (เฉพาะ VALID) int gCntRaceM1=0, gCntRaceH4=0, gCntDropped=0; //=== Pending (deferred MFE/MAE) ====================================== #define MAXPEND 512 struct PendItem { int kind; // 0=CTRL 1=SIG int dir; // +1 BUY -1 SELL datetime entryTime; double entry; double postwick; double slAnchor; double sysSL; double sysTP1; double sysTP2; int patWin; // 0..5 , -1 = CTRL int patMask; int paN; int status; // 0=VALID 1..4 = reject reason double atr; int barsElapsed; // --- L1 context (คำนวณจากแท่ง index>=1 เท่านั้น) --- double lo1,hi1,lo2,hi2,lo3,hi3; // low/high ของ span 1/2/3 แท่ง สิ้นสุดที่แท่ง 2 double bbot,btop,cl2; // บอดี้ + close ของแท่ง 2 (แท่งใหม่สุดของชุด PA) double anch8,anch4; // กรอบวัน: ราคาเปิดแท่ง H4 08:00 / 04:00 ปัดเข้าเส้น 5.00 int d1b,h4b,swmask; // เทรนด์ Day · โครงสร้าง swing H4 · checklist SW 4 บิต }; PendItem gPend[MAXPEND]; int gPendN = 0; //=== L1 context ของแท่งปัจจุบัน (คำนวณครั้งเดียวใน OnBarClose) ========= double gCtxLo1=0,gCtxHi1=0,gCtxLo2=0,gCtxHi2=0,gCtxLo3=0,gCtxHi3=0; double gCtxBBot=0,gCtxBTop=0,gCtxCl2=0,gCtxAnch8=0,gCtxAnch4=0; int gCtxD1b=0,gCtxH4b=0,gCtxSw=0; //===================================================================== // แท่ง H4 "สังเคราะห์" — ประกอบจากแท่ง H1 ของดาต้าชุดเดียวกัน // offset 0 → 00/04/08/12/16/20 (ต้องได้ OHLC เท่าแท่ง H4 ของเทอร์มินัลเป๊ะ) // offset 1 → 01/05/09/13/17/21 · 2 → 02/06/... · 3 → 03/07/... //===================================================================== #define SEC_H4 14400 datetime gBT[]; double gBO[],gBH[],gBL[],gBC[],gTR[]; int gN = 0; // จำนวนแท่งสังเคราะห์ที่ปิดแล้ว datetime gCurTime = 0; // เวลา label ของแท่ง index 0 (แท่งที่กำลังเปิด) bool gCurHas = false; double gCurO=0,gCurH=0,gCurL=0,gCurC=0; datetime gLastH1 = 0; int gOffSec = 0; int gFoldJump = 0; // นับกรณี H1 กระโดดข้ามถัง (gap) — ควรเป็น 0 หรือน้อยมาก datetime BucketStart(const datetime t) { long x = (long)t - (long)gOffSec; long k = x / SEC_H4; if(x < 0 && (x % SEC_H4) != 0) k--; return (datetime)(k*SEC_H4 + gOffSec); } void GrowIfNeeded() { if(ArraySize(gBT) > gN) return; int cap = (ArraySize(gBT) < 4096) ? 4096 : ArraySize(gBT)*2; ArrayResize(gBT,cap); ArrayResize(gBO,cap); ArrayResize(gBH,cap); ArrayResize(gBL,cap); ArrayResize(gBC,cap); ArrayResize(gTR,cap); } void PushCurrent() { if(!gCurHas) return; GrowIfNeeded(); gBT[gN]=gCurTime; gBO[gN]=gCurO; gBH[gN]=gCurH; gBL[gN]=gCurL; gBC[gN]=gCurC; if(gN==0) gTR[gN] = gCurH-gCurL; else { double pc = gBC[gN-1]; gTR[gN] = MathMax(gCurH,pc) - MathMin(gCurL,pc); } gN++; gCurHas=false; } void FoldH1(const datetime tb,const double o,const double h,const double l,const double c) { datetime bs = BucketStart(tb); if(!gCurHas){ gCurTime=bs; gCurO=o; gCurH=h; gCurL=l; gCurC=c; gCurHas=true; return; } if(bs != gCurTime) { gFoldJump++; PushCurrent(); gCurTime=bs; gCurO=o; gCurH=h; gCurL=l; gCurC=c; gCurHas=true; return; } if(h>gCurH) gCurH=h; if(l= 1 (กฎเหล็ก §0.2) //===================================================================== bool gViolation=false; void GuardIdx(const int i) { if(i < 1 && !gViolation) { gViolation=true; Print("!!! LOOK-AHEAD VIOLATION: read bar index ",i); } } double Op(const int i){ GuardIdx(i); return gBO[gN-i]; } double Hi(const int i){ GuardIdx(i); return gBH[gN-i]; } double Lo(const int i){ GuardIdx(i); return gBL[gN-i]; } double Cl(const int i){ GuardIdx(i); return gBC[gN-i]; } datetime Tm(const int i){ if(i<=0) return gCurTime; GuardIdx(i); return gBT[gN-i]; } // ATR(14) บนแท่งสังเคราะห์ = SMA ของ True Range 14 แท่ง (อัลกอริทึมเดียวกับ iATR ของ MT5) double AtrAt(const int i) { int j = gN-i; if(j < 14) return 0.0; double s=0; for(int k=j-13;k<=j;k++) s+=gTR[k]; return s/14.0; } double Bd (const int i){ return MathAbs(Cl(i)-Op(i)); } double Rg (const int i){ return Hi(i)-Lo(i); } double BTop(const int i){ return MathMax(Op(i),Cl(i)); } double BBot(const int i){ return MathMin(Op(i),Cl(i)); } double UW (const int i){ return Hi(i)-BTop(i); } double DW (const int i){ return BBot(i)-Lo(i); } bool Bull(const int i){ return Cl(i)>Op(i); } bool Bear(const int i){ return Cl(i)0 ? Bull(i) : Bear(i); } bool AgainstDir(const int i,const int dir){ return dir>0 ? Bear(i) : Bull(i); } double SideWick (const int i,const int dir){ return dir>0 ? DW(i) : UW(i); } double OppWick (const int i,const int dir){ return dir>0 ? UW(i) : DW(i); } double SideExtr (const int i,const int dir){ return dir>0 ? Lo(i) : Hi(i); } double P(const int pts){ return pts*PTV; } // กฎ 50% (§1.2.0) — วัดกับ "บอดี้" ของแท่งก่อนหน้า double Penetration(const int prevIdx,const double closePrice,const int dir) { double b = Bd(prevIdx); if(b < P(InpMinBodyPts)) return 0.0; return dir*(closePrice - Cl(prevIdx)) / b; } bool IsTradeableBar(const int i){ return Rg(i) >= P(InpMinBarRangePts); } bool IsDoji(const int i) { return (Bd(i) <= InpDojiBodyRangePct*Rg(i)) && (Rg(i) > P(InpMinBarRangePts)); } //===================================================================== // L1 CONTEXT — ตัวกรองระดับ 1 (คำนวณครั้งเดียวต่อแท่ง ใช้ร่วมทั้ง CTRL/SIG) // ⚠️ อ่าน index >= 1 เท่านั้น (ผ่าน Hi/Lo/Op/Cl ที่มี GuardIdx) //===================================================================== input group "L1 context" input int InpSwWin = 6; // ครึ่งหน้าต่างของ checklist SW (แท่ง H4) [SPEC-DAO] input int InpSwAltMin = 3; // จำนวนครั้งที่สลับสีขั้นต่ำใน 6 แท่ง [SPEC-DAO] input int InpSwingFr = 2; // fractal ซ้าย/ขวา สำหรับ swing H4 [SPEC-DAO] input int InpSwingLook = 150; // ย้อนหลังหา swing กี่แท่ง H4 [SPEC-DAO] // เทรนด์ Day = สีแท่ง D1 ที่ปิดแล้ว [ปลา3 p.52] int D1Bias() { double o=iOpen(_Symbol,PERIOD_D1,1), c=iClose(_Symbol,PERIOD_D1,1); if(o<=0 || c<=0) return 0; if(c>o) return +1; if(c=Lo(i-k) || Lo(i)>=Lo(i+k)) isL=false; } if(isH && shn<2){ sh[shn]=Hi(i); shn++; } if(isL && sln<2){ sl[sln]=Lo(i); sln++; } } if(shn<2 || sln<2) return 0; if(sh[0]>sh[1] && sl[0]>sl[1]) return +1; if(sh[0]aHi) aHi=Hi(i); if(Lo(i)bHi) bHi=Hi(i); if(Lo(i)=bLo) m|=2; if(alt>=InpSwAltMin) m|=4; if((aHi-aLo)<=(bHi-bLo)) m|=8; return m; } // กรอบวัน 1,000 จุด: ราคาเปิดแท่ง H4 เวลา hh ล่าสุด ปัดเข้าเส้นสถิติ (ทวีคูณ 5.00) [ปลา3 p.27] // ⚠️ L2 ขยายนิยามเป็น "แท่งสังเคราะห์ที่ครอบเวลา hh:00" // ที่ offset 0 ให้ผลเท่ากับเงื่อนไข dt.hour==hh เป๊ะ (แท่งเปิด 00/04/08/... ) // ถ้าไม่ขยาย offset 1-3 จะไม่มีแท่งไหน hour==8 เลย → F5 ตายเงียบ (เทียบไม่ได้) double DayAnchor(const int hh) { MqlDateTime dt; int target = hh*3600; for(int i=1;i<=8;i++) { datetime t=Tm(i); if(t==0) break; TimeToStruct(t,dt); int sod = dt.hour*3600 + dt.min*60 + dt.sec; if(sod<=target && target InpPinOppWickPct *Rg(i)) return false; return true; } bool IsPAT2(const int i,const int dir) // กลืนกิน [ปลา3 p.5] { if(!AgainstDir(i+1,dir)) return false; if(!WithDir (i ,dir)) return false; if(Bd(i+1) < P(InpMinBodyPts)) return false; // ห้ามกลืน doji if(Penetration(i+1,Cl(i),dir) < 1.0) return false; if(InpPat2FullEngulf && dir*(Op(i)-Cl(i+1)) > 0) return false; return true; } bool IsPAT2S(const int i,const int dir) // PAT2 พิเศษ [ปลา3 p.5] { if(!AgainstDir(i+1,dir)) return false; if(!WithDir (i ,dir)) return false; double pen = Penetration(i+1,Cl(i),dir); return (pen > InpEngulf50Min && pen < 1.0); } bool IsPAT3_1(const int i,const int dir) // แท่งกลาง = Doji [ปลา3 p.3] { if(!AgainstDir(i+2,dir)) return false; if(!WithDir (i ,dir)) return false; if(!IsDoji(i+1)) return false; if(Bd(i+1) > InpDojiVsFirstPct*Bd(i+2)) return false; if(InpApply50ToPat31_2 && Penetration(i+2,Cl(i),dir) <= 0.5) return false; return true; } bool IsPAT3_2(const int i,const int dir) // แท่งกลางเล็ก ไม่ใช่ Doji { if(!AgainstDir(i+2,dir)) return false; if(!WithDir (i ,dir)) return false; if(IsDoji(i+1)) return false; // ตัวแยกแบบ1/แบบ2 if(Bd(i+1) > InpSmallVsFirstPct*Bd(i+2)) return false; if(InpApply50ToPat31_2 && Penetration(i+2,Cl(i),dir) <= 0.5) return false; return true; } bool IsPAT3_3(const int i,const int dir) // Three Inside [ปลา3 p.6] { if(!AgainstDir(i+2,dir)) return false; if(Bd(i+2) < P(InpMinBodyPts)) return false; // "แท่งแรกใหญ่" bool ok=false; if(InpPat33Mode==0) ok = (Penetration(i+2,Cl(i),dir) > InpPat33MinPen); else { double w = (WithDir(i+1,dir)?Bd(i+1):0.0) + (WithDir(i,dir)?Bd(i):0.0); double b = Bd(i+2); ok = (b>0 && w/b > InpPat33MinPen); } if(!ok) return false; if(InpPat33NoNewExtrm) { if(dir>0 && (Lo(i+1)Hi(i+2) || Hi(i)>Hi(i+2))) return false; } if(InpPat33Harami) if(!(BTop(i+1)<=BTop(i+2) && BBot(i+1)>=BBot(i+2))) return false; return true; } // ลำดับการตรวจ §1.2.5 → bitmask // bit0 PAT1 | bit1 PAT2 | bit2 PAT2S | bit3 PAT3_1 | bit4 PAT3_2 | bit5 PAT3_3 int DetectAllPA(const int i,const int dir) { int m = 0; if(IsPAT1(i,dir)) m |= 1; if(IsPAT2(i,dir)) m |= 2; else if(IsPAT2S(i,dir)) m |= 4; else if(IsPAT3_3(i,dir)) m |= 32; // "ไม่ถึง 50% → ไม่นับ PAT2 → ใช้เป็น PAT3 แบบ3" if(IsPAT3_1(i,dir)) m |= 8; if(IsPAT3_2(i,dir)) m |= 16; return m; } // Dedupe §1.2.7 — ทุก PA ที่ anchor เดียวกัน + dir เดียวกัน = ไส้หลังซิกแท่งเดียวกัน // → เหลือตัวเดียว ตามลำดับ PAT3_3 > PAT2 > PAT2S > PAT3_1 > PAT3_2 > PAT1 int PatWinner(const int mask) { if(mask & 32) return 5; // PAT3_3 if(mask & 2 ) return 1; // PAT2 if(mask & 4 ) return 2; // PAT2S if(mask & 8 ) return 3; // PAT3_1 if(mask & 16) return 4; // PAT3_2 if(mask & 1 ) return 0; // PAT1 return -1; } int PatBars(const int w) { if(w==0) return 1; if(w==1 || w==2) return 2; return 3; } string PatName(const int w) { switch(w) { case 0: return "PAT1"; case 1: return "PAT2"; case 2: return "PAT2S"; case 3: return "PAT3_1"; case 4: return "PAT3_2"; case 5: return "PAT3_3"; } return "CTRL"; } //=== ค่าของ "ชุด PA" (§1.1) ========================================== double PA_SideExtreme(const int i,const int n,const int dir) { double v = SideExtr(i,dir); for(int k=i+1;k0){ if(xv) v=x; } } return v; } double PA_BodyTop(const int i,const int n) { double v=BTop(i); for(int k=i+1;kv) v=BTop(k); return v; } double PA_BodyBot(const int i,const int n) { double v=BBot(i); for(int k=i+1;k InpPostWickBodyMax*Bd(i)) return 2; // W3 ไส้เทียนไม่เกินไส้เทียนแท่งก่อนหน้า if(SideWick(p,dir) > SideWick(i,dir)*InpPostWickWickMult) return 3; // W4 ไส้หลังซิกเกินครึ่งเนื้อชุด Sig → ใช้ไส้ยาวสุดของชุด Sig วาง SL แทน double bt = PA_BodyTop(i,n), bb = PA_BodyBot(i,n); double span = bt-bb; double retrace = (dir>0) ? (bt - Lo(p)) : (Hi(p) - bb); if(span>0 && retrace > 0.5*span) slAnchor = PA_SideExtreme(i,n,dir); // W5 ห้ามทำลายชุด PA double paX = PA_SideExtreme(i,n,dir); if(dir>0 && Lo(p) < paX) return 4; if(dir<0 && Hi(p) > paX) return 4; return 0; } //===================================================================== // OnInit / OnDeinit //===================================================================== int OnInit() { // §0.1 assert หน่วยจุด double pt=_Point; if(pt<=0 || (MathAbs(PTV/pt-1.0)>0.01 && MathAbs(PTV/pt-10.0)>0.01)) { PrintFormat("INIT_FAILED: _Point=%.5f ไม่เข้ากับ PT=0.01 (ทศนิยม 2 หรือ 3 เท่านั้น)",pt); return INIT_FAILED; } if(Period()!=PERIOD_H1) { Print("INIT_FAILED: MaePla_L2 ต้องรันบนกราฟ H1 (มันสร้างแท่ง H4 สังเคราะห์เอง)"); return INIT_FAILED; } if(InpH4OffsetHours<0 || InpH4OffsetHours>3) { Print("INIT_FAILED: InpH4OffsetHours ต้องอยู่ใน 0..3"); return INIT_FAILED; } gOffSec = InpH4OffsetHours*3600; GrowIfNeeded(); gFH = FileOpen(InpCsvName, FILE_WRITE|FILE_TXT|FILE_ANSI|FILE_COMMON); if(gFH==INVALID_HANDLE) { PrintFormat("INIT_FAILED: เปิดไฟล์ %s ไม่ได้ err=%d",InpCsvName,GetLastError()); return INIT_FAILED; } FileWriteString(gFH, "kind,datetime,year,direction,pattern_type,pattern_mask,pa_bars,status," "entry_price,postwick_price,sl_price,tp1_price,atr14," "mfe_points_20bars,mae_points_20bars,bars_to_max,bars_to_mae," "min_to_tp1,min_to_sl300,min_to_sl500,min_to_tp2," "min_to_systp1,min_to_sl_pla,min_to_sl_kaew,min_to_sl_moja,min_to_syssl," "hit_tp1,hit_sl,tp1_before_sl,sys_tp1_before_sl," "sys_tp1_before_sl_pla,sys_tp1_before_sl_kaew,sys_tp1_before_sl_moja," "fwd6_points,fwd20_points,race_src," "lo1,hi1,lo2,hi2,lo3,hi3,bbot,btop,cl2,anch8,anch4,d1b,h4b,swmask\r\n"); PrintFormat("MaePla_L2 census started. H4offset=%dh TP1=%d SL(race)=%d evalBars=%d dirFilter=%d csv=%s", InpH4OffsetHours,InpTP1Points,InpRaceSLPts,InpEvalBars,InpDirFilter,InpCsvName); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { if(gFH!=INVALID_HANDLE){ FileFlush(gFH); FileClose(gFH); gFH=INVALID_HANDLE; } gCntDropped += gPendN; PrintFormat("=== L2 CENSUS SUMMARY (H4 offset = %d h) ===", InpH4OffsetHours); PrintFormat("synthetic H4 bars : %d (fold-jump events: %d)", gN, gFoldJump); PrintFormat("bars processed : %d", gCntBars); PrintFormat("PA candidates BUY : %d | SELL : %d", gCntCandidate[0], gCntCandidate[1]); PrintFormat("VALID sigs BUY : %d | SELL : %d", gCntValid[0], gCntValid[1]); PrintFormat("status VALID/W1/W2/W3/W5 : %d / %d / %d / %d / %d", gCntStatus[0],gCntStatus[1],gCntStatus[2],gCntStatus[3],gCntStatus[4]); PrintFormat("valid by pattern PAT1/PAT2/PAT2S/PAT3_1/PAT3_2/PAT3_3 : %d / %d / %d / %d / %d / %d", gCntPatWin[0],gCntPatWin[1],gCntPatWin[2],gCntPatWin[3],gCntPatWin[4],gCntPatWin[5]); PrintFormat("race resolved by M1 : %d | fallback H4 : %d", gCntRaceM1, gCntRaceH4); PrintFormat("dropped (window incomplete at end) : %d", gCntDropped); if(gViolation) Print("!!! LOOK-AHEAD VIOLATION DETECTED — ผลรอบนี้ใช้ไม่ได้"); } //===================================================================== // OnTick //===================================================================== void OnTick() { datetime t1 = iTime(_Symbol,PERIOD_H1,0); // whitelist #1 (แท่ง H1 ที่เพิ่งเปิด) if(t1==0 || t1==gLastH1) return; gLastH1 = t1; // 1) พับแท่ง H1 ที่ "ปิดแล้ว" (index 1) เข้าถังของแท่งสังเคราะห์ datetime tb = iTime(_Symbol,PERIOD_H1,1); if(tb>0) FoldH1(tb, iOpen (_Symbol,PERIOD_H1,1), iHigh (_Symbol,PERIOD_H1,1), iLow (_Symbol,PERIOD_H1,1), iClose(_Symbol,PERIOD_H1,1)); // 2) แท่ง H1 ใหม่นี้เปิดถังใหม่ไหม → ถ้าใช่ แท่งสังเคราะห์เพิ่งปิด datetime bs1 = BucketStart(t1); if(gCurHas && bs1 != gCurTime) { PushCurrent(); gCurTime = bs1; // index 0 = แท่งสังเคราะห์ที่เพิ่งเปิด ณ วินาทีนี้ OnBarClose(); } } void OnBarClose() { if(gN < InpEvalBars+InpSwingLook+40) return; gCntBars++; // --- A) เดินเวลาให้ pending ทุกตัว for(int k=0;k0) continue; // baseline สุ่ม: เข้าที่ทุกแท่ง ด้วย SL/TP เดียวกัน if(InpRecordControl) AddPending(0,dir,entryTime,entry,0,0,0,0,0,-1,0,0,0,atr); // ---- สัญญาณจริง: ชุด PA จบที่แท่ง 2, ไส้หลังซิก = แท่ง 1 ---- int mask = DetectAllPA(2,dir); if(mask==0) continue; gCntCandidate[s]++; int win = PatWinner(mask); int n = PatBars(win); double postWick=0, slAnchor=0; int st = ValidatePostWick(2,n,dir,postWick,slAnchor); gCntStatus[(st==0?0:st)]++; if(st!=0 && !InpRecordRejected) continue; if(st==0){ gCntValid[s]++; gCntPatWin[win]++; } double sysSL = slAnchor - dir*P(InpSLBufferPts); double sysTP1 = postWick + dir*P(InpTP1Points); // [ปลา3 p.8] วัดจากไส้หลังซิกเสมอ double sysTP2 = postWick + dir*P(InpTP2Points); AddPending(1,dir,entryTime,entry,postWick,slAnchor,sysSL,sysTP1,sysTP2, win,mask,n,st,atr); } } void AddPending(const int kind,const int dir,const datetime et,const double entry, const double pw,const double sla,const double ssl,const double stp1,const double stp2, const int win,const int mask,const int n,const int st,const double atr) { if(gPendN>=MAXPEND){ Print("WARN: pending overflow"); return; } // --- L1 context (ค่าเดียวกันทุกแถวที่เกิดบนแท่งนี้ → CTRL กับ SIG เทียบกันตรงๆ ได้) gPend[gPendN].lo1=gCtxLo1; gPend[gPendN].hi1=gCtxHi1; gPend[gPendN].lo2=gCtxLo2; gPend[gPendN].hi2=gCtxHi2; gPend[gPendN].lo3=gCtxLo3; gPend[gPendN].hi3=gCtxHi3; gPend[gPendN].bbot=gCtxBBot; gPend[gPendN].btop=gCtxBTop; gPend[gPendN].cl2=gCtxCl2; gPend[gPendN].anch8=gCtxAnch8; gPend[gPendN].anch4=gCtxAnch4; gPend[gPendN].d1b=gCtxD1b; gPend[gPendN].h4b=gCtxH4b; gPend[gPendN].swmask=gCtxSw; gPend[gPendN].kind=kind; gPend[gPendN].dir=dir; gPend[gPendN].entryTime=et; gPend[gPendN].entry=entry; gPend[gPendN].postwick=pw; gPend[gPendN].slAnchor=sla; gPend[gPendN].sysSL=ssl; gPend[gPendN].sysTP1=stp1; gPend[gPendN].sysTP2=stp2; gPend[gPendN].patWin=win; gPend[gPendN].patMask=mask; gPend[gPendN].paN=n; gPend[gPendN].status=st; gPend[gPendN].atr=atr; gPend[gPendN].barsElapsed=0; gPendN++; } //===================================================================== // ประเมิน MFE/MAE + การแข่ง TP/SL จาก M1 ที่เป็นอดีตทั้งหมด //===================================================================== void FlushMature() { // นับว่ามีตัวครบกำหนดไหม int nMature=0; for(int k=0;k=InpEvalBars) nMature++; if(nMature==0) return; const int E = InpEvalBars; // แท่ง entry อยู่ที่ index E ตอนนี้ // เวลาเปิดของ 20 แท่งในหน้าต่าง (เก่า→ใหม่) = index E .. 1 datetime h4t[]; ArrayResize(h4t,E); for(int b=0;b E*30); // ต้องได้ M1 มากพอถึงจะเชื่อ // fwd6 / fwd20 (นับแท่ง entry เป็นแท่งที่ 1) double c6 = Cl(E-5); double c20 = Cl(1); for(int k=0;k=h4t[slot+1]) slot++; double fav = (dir>0) ? (m1[j].high-en) : (en-m1[j].low); double adv = (dir>0) ? (en-m1[j].low) : (m1[j].high-en); if(fav>mfe){ mfe=fav; barMfe=slot+1; } if(adv>mae){ mae=adv; barMae=slot+1; } int mins = (int)((t - it.entryTime)/60); // SL_FIRST: ถ้าชนทั้งคู่ในนาทีเดียวกัน SL ชนะ (ค่า minute เท่ากัน → ไม่นับว่า TP มาก่อน) if(mTp1 <0 && fav>=dTp1) mTp1 =mins; if(mTp2 <0 && fav>=dTp2) mTp2 =mins; if(mSl300 <0 && adv>=dSl3) mSl300 =mins; if(mSl500 <0 && adv>=dSl5) mSl500 =mins; if(mSysTp1<0 && fav>=dSysTp1) mSysTp1=mins; if(mSlPla <0 && adv> dSlPla) mSlPla =mins; // แม่ปลา GT if(mSlKaew<0 && adv>=dSlKaew) mSlKaew=mins; // ลูกแก้ว GTE (สัมผัสก็ตาย) if(mSlMoja<0 && adv> dSlMoja) mSlMoja=mins; // โมจา GT + 300 if(mSysSL <0 && (InpWickBreakOp==1 ? (adv>=dSlCfg) : (adv>dSlCfg))) mSysSL=mins; } } else { gCntRaceH4++; for(int b=0;b0) ? (hh-en) : (en-ll); double adv = (dir>0) ? (en-ll) : (hh-en); if(fav>mfe){ mfe=fav; barMfe=b+1; } if(adv>mae){ mae=adv; barMae=b+1; } int mins = b*240; if(mTp1 <0 && fav>=dTp1) mTp1 =mins; if(mTp2 <0 && fav>=dTp2) mTp2 =mins; if(mSl300 <0 && adv>=dSl3) mSl300 =mins; if(mSl500 <0 && adv>=dSl5) mSl500 =mins; if(mSysTp1<0 && fav>=dSysTp1) mSysTp1=mins; if(mSlPla <0 && adv> dSlPla) mSlPla =mins; if(mSlKaew<0 && adv>=dSlKaew) mSlKaew=mins; if(mSlMoja<0 && adv> dSlMoja) mSlMoja=mins; if(mSysSL <0 && (InpWickBreakOp==1 ? (adv>=dSlCfg) : (adv>dSlCfg))) mSysSL=mins; } } if(mfe<0) mfe=0; if(mae<0) mae=0; bool hitTp1 = (mTp1>=0); bool hitSl = (mSl300>=0); // TP1 ก่อน SL: ต้องมาถึงก่อนจริงๆ (เท่ากัน = SL ชนะ, IntrabarOrder=SL_FIRST §2 G20) bool tp1First = hitTp1 && (!hitSl || mTp1 < mSl300); bool sysPla = (mSysTp1>=0) && (mSlPla <0 || mSysTp1 < mSlPla ); bool sysKaew = (mSysTp1>=0) && (mSlKaew<0 || mSysTp1 < mSlKaew); bool sysMoja = (mSysTp1>=0) && (mSlMoja<0 || mSysTp1 < mSlMoja); bool sysTp1First = (mSysTp1>=0) && (mSysSL<0 || mSysTp1 < mSysSL); double fwd6 = dir*(c6 -en)/PTV; double fwd20 = dir*(c20-en)/PTV; MqlDateTime dt; TimeToStruct(it.entryTime,dt); string row = StringFormat( "%s,%s,%d,%s,%s,%d,%d,%d," "%.2f,%.2f,%.2f,%.2f,%.2f," "%.0f,%.0f,%d,%d," "%d,%d,%d,%d," "%d,%d,%d,%d,%d," "%d,%d,%d,%d," "%d,%d,%d," "%.0f,%.0f,%s," "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%d,%d,%d", (it.kind==0?"CTRL":"SIG"), TimeToString(it.entryTime,TIME_DATE|TIME_MINUTES), dt.year, (dir>0?"BUY":"SELL"), (it.kind==0?"CTRL":PatName(it.patWin)), it.patMask, it.paN, it.status, it.entry, it.postwick, it.sysSL, it.sysTP1, it.atr, mfe/PTV, mae/PTV, barMfe, barMae, mTp1, mSl300, mSl500, mTp2, mSysTp1, mSlPla, mSlKaew, mSlMoja, mSysSL, (hitTp1?1:0), (hitSl?1:0), (tp1First?1:0), (sysTp1First?1:0), (sysPla?1:0), (sysKaew?1:0), (sysMoja?1:0), fwd6, fwd20, (useM1?"M1":"H4"), it.lo1,it.hi1,it.lo2,it.hi2,it.lo3,it.hi3, it.bbot,it.btop,it.cl2,it.anch8,it.anch4, it.d1b,it.h4b,it.swmask); FileWriteString(gFH,row+"\r\n"); if((gCntRaceM1+gCntRaceH4)%500==0) FileFlush(gFH); } //+------------------------------------------------------------------+