//+------------------------------------------------------------------+ //| MaePla_Skeleton.mq5 | //| ระบบแม่ปลาปากกาเขียว — EA โครงเปล่า (ยังไม่เทรดจริง) | //| อ้างโครง/สไตล์จาก trade/ea/FTMO_Guardian.mq5 (อ่านเป็น template | //| เท่านั้น — คนละกองเรือ ห้ามแก้ไฟล์ต้นทาง) | //| Attach on: H4 chart (XAUUSD) | //+------------------------------------------------------------------+ #property copyright "Hon" #property version "0.10" #property description "MaePla skeleton: risk guard C15 (#4,#5,#11,#15) + stub PA/SIG detectors — ยังไม่ส่ง order จริง" #include CTrade trade; //=== Identity ========================================================= input group "Identity" input long InpMagic = 20260830; // เลขประจำตัว EA //=== Risk guard — C15 กฎ #4 / #5 / #11 / #15 (GLOSSARY.md §C15) ======= // #4 ขาดทุนตัดทันทีตามแผน ห้ามย้าย SL ห้าม "เผื่อมันกลับมา" // -> บังคับด้วยการไม่มีฟังก์ชันแก้ไข SL ที่ไหนในไฟล์นี้เลย (ห้ามเพิ่มทีหลัง) // #5 1 ไม้ = 1 Risk ที่กำหนดล่วงหน้า ห้ามเพิ่ม lot เพราะมั่นใจ ห้ามลดเพราะกลัว // -> RiskLots() คำนวณจาก InpRiskPercent เท่านั้น + กันไม่ให้เปิดไม้ซ้อนไม้เดิม // #11 แพ้ติดกัน 2 ไม้ = หยุดเทรดทันที (วันนั้น) // -> UpdateConsecLosses() นับจาก deal history วันนี้ -> gDayHalted // #15 ไม้ถัดไปต้องเล็กลงหรือไม่เข้าเลย ห้ามเพิ่มความเสี่ยงหลังขาดทุน // -> gRiskMultiplier คูณลดทุกไม้แพ้ติดกัน รีเซ็ตกลับ 1.0 เมื่อชนะ input group "Risk (C15 #4,5,11,15)" input double InpRiskPercent = 1.0; // ความเสี่ยงต่อไม้พื้นฐาน (% ของ equity) — กฎ #5 input double InpLossShrinkFactor = 0.5; // คูณ risk ไม้ถัดไปหลังแพ้ 1 ไม้ (0.5 = เหลือครึ่ง) — กฎ #15 //=== SL / TP (จุด) — ดู GLOSSARY.md §2.4, ตาราง TP ตาม TF ============= input group "SL / TP (points)" input int InpSL_Points = 300; // SL ตลาดเช้า-บ่าย (จุด) [โม1 p.16] input int InpSL_US_Points = 500; // SL ตลาดเมกา/US session (จุด) [โม1 p.16] input int InpTP1_Points = 1500; // รอบวิ่ง TP1 ของ H4 [โม1 p.20] input int InpTP2_Points = 3000; // รอบวิ่ง TP2 ของ H4 [โม1 p.20] input bool InpUseATR = false; // true = ใช้ ATR แทนจุดคงที่สำหรับระยะ TP1 input int InpATR_Period = 14; input double InpATR_Mult_TP1 = 3.0; //=== SIG — ค่าที่โค้ชขัดกัน ยังไม่ชี้ขาด (GLOSSARY.md §4.1, §4.3) ======= input group "SIG — ค่าที่โค้ชขัดกัน (รอพี่ฮอนชี้ขาด)" input int InpWickBreakTolerance = 0; // ไส้หลัง Sig ถูกทำลายเมื่อไหร่: 0=สัมผัส(แม่ปลา) / 1=เกิน(แก้ว) / 300=จุด(โมจา) input int InpHoldFrameBars = 1; // แท่งไส้หลัง Sig ต้องยืนกี่แท่งถึงคอนเฟิร์ม: 1 หรือ 3 input bool InpAllowAfterTP2 = false; // ครบ TP2 แล้วเทรดต่อได้ไหม: false=หยุด(แม่ปลา) / true=เข้าเพิ่มได้(โมจา) input bool InpUseHTFFilter = true; // เช็คเทรน TF ใหญ่กว่าก่อนเข้า ("เทรนชนะกรอบ") //=== State ============================================================= datetime gLastBarTime = 0; int gDay = -1; int gConsecLosses = 0; bool gDayHalted = false; double gRiskMultiplier = 1.0; // ลดลงหลังแพ้ (กฎ #15), รีเซ็ตเป็น 1.0 หลังชนะ int gAtr = INVALID_HANDLE; //+------------------------------------------------------------------+ int OnInit() { trade.SetExpertMagicNumber(InpMagic); trade.SetDeviationInPoints(20); trade.SetTypeFillingBySymbol(_Symbol); if(InpUseATR) { gAtr = iATR(_Symbol, PERIOD_CURRENT, InpATR_Period); if(gAtr == INVALID_HANDLE) return INIT_FAILED; } Print("MaePla Skeleton started. Magic=", InpMagic, " — DetectPA()/AtFrame() ยังเป็น stub (return false) EA นี้จะไม่เข้าไม้เลยจนกว่าจะเติม logic จริง"); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { if(gAtr != INVALID_HANDLE) IndicatorRelease(gAtr); } //+------------------------------------------------------------------+ //| OnTick — ทำงานเฉพาะตอนแท่งใหม่ปิดเท่านั้น (new-bar detection) | //+------------------------------------------------------------------+ void OnTick() { if(!IsNewBar()) return; OnBarClose(); } bool IsNewBar() { datetime t = iTime(_Symbol, PERIOD_CURRENT, 0); if(t == 0 || t == gLastBarTime) return false; gLastBarTime = t; return true; } //+------------------------------------------------------------------+ //| ทุกอย่างที่นี่ต้องอ่านแท่งที่ "ปิดแล้ว" เท่านั้น (index 1 ขึ้นไป) | //| ห้ามอ่าน index 0 เด็ดขาด — แท่งปัจจุบันยังไม่จบ = เห็นอนาคตในเทส | //+------------------------------------------------------------------+ void OnBarClose() { UpdateDay(); UpdateConsecLosses(); // กฎ #11 + #15 if(gDayHalted) return; // แพ้ติดกัน 2 ไม้แล้ว — จบวันนี้ // กฎ #4: ไม่มีการแก้ไข SL ของไม้ที่เปิดอยู่ที่ไหนในไฟล์นี้ทั้งสิ้น (ตั้งใจเว้นว่าง — ห้ามเพิ่ม) if(!AtFrame()) return; // TODO: ยัง stub — return false เสมอ if(!DetectPA()) return; // TODO: ยัง stub — return false เสมอ double postWick = FindPostWick(); // TODO: ยัง stub — return 0.0 เสมอ if(postWick <= 0) return; TryEnter(postWick); } //+------------------------------------------------------------------+ //| แท่งใหม่ของวัน (server day) — รีเซ็ตธงหยุดรายวัน | //+------------------------------------------------------------------+ void UpdateDay() { MqlDateTime dt; TimeToStruct(TimeTradeServer(), dt); int today = dt.year * 1000 + dt.day_of_year; if(today == gDay) return; gDay = today; gDayHalted = false; // วันใหม่ — ปลดล็อก (นับแพ้ติดกันใหม่จาก deal history ด้านล่าง) } //+------------------------------------------------------------------+ //| นับแพ้ติดกันของวันนี้จาก deal history จริง (magic+symbol เดียวกัน) | //| กฎ #11: แพ้ติดกัน 2 ไม้ -> gDayHalted = true | //| กฎ #15: ทุกไม้แพ้ คูณ gRiskMultiplier ลง, ไม้ชนะรีเซ็ตกลับ 1.0 | //+------------------------------------------------------------------+ void UpdateConsecLosses() { MqlDateTime dt; TimeToStruct(TimeTradeServer(), dt); dt.hour = 0; dt.min = 0; dt.sec = 0; datetime dayStart = StructToTime(dt); if(!HistorySelect(dayStart, TimeTradeServer() + 60)) return; int consec = 0; double mult = 1.0; int n = HistoryDealsTotal(); for(int i = 0; i < n; i++) { ulong tk = HistoryDealGetTicket(i); if(HistoryDealGetInteger(tk, DEAL_MAGIC) != InpMagic) continue; if(HistoryDealGetString(tk, DEAL_SYMBOL) != _Symbol) continue; if(HistoryDealGetInteger(tk, DEAL_ENTRY) != DEAL_ENTRY_OUT) continue; double profit = HistoryDealGetDouble(tk, DEAL_PROFIT) + HistoryDealGetDouble(tk, DEAL_SWAP) + HistoryDealGetDouble(tk, DEAL_COMMISSION); if(profit < 0) { consec++; mult *= InpLossShrinkFactor; // กฎ #15 } else { consec = 0; mult = 1.0; } } gConsecLosses = consec; gRiskMultiplier = mult; if(gConsecLosses >= 2) gDayHalted = true; // กฎ #11 } //+------------------------------------------------------------------+ //| PA 5 รูปแบบ (PAT1 Pin / PAT2 Engulf / PAT3x3 / Doji) | //| TODO: ยังไม่ implement — ตรวจจาก OHLC แท่งปิดแล้ว index 1 ขึ้นไป | //| ดู GLOSSARY.md ส่วน PA/SIG ก่อนเติม logic | //+------------------------------------------------------------------+ bool DetectPA() { return false; } //+------------------------------------------------------------------+ //| หา "ไส้หลัง Sig" (post-wick) ตาม GLOSSARY.md §2.1-2.2 | //| TODO: ยังไม่ implement — คืนราคา (double) เมื่อพร้อม, 0.0 = ยังไม่มี | //| ต้องใช้ InpWickBreakTolerance / InpHoldFrameBars ตอน implement จริง | //+------------------------------------------------------------------+ double FindPostWick() { return 0.0; } //+------------------------------------------------------------------+ //| เช็คว่าแท่งปิดล่าสุดอยู่ที่ "กรอบ" (S/R) หรือไม่ | //| TODO: ยังไม่ implement — สัมผัส 7-14 จุด หรือกรอบนัยยะลงท้าย 0/5 | //+------------------------------------------------------------------+ bool AtFrame() { return false; } //+------------------------------------------------------------------+ //| เข้าไม้ — ยังไม่ถูกเรียกจริงเพราะ AtFrame()/DetectPA() คืน false เสมอ | //| คงโครงไว้ให้เห็น flow: session-based SL -> risk lot -> ส่ง order | //+------------------------------------------------------------------+ void TryEnter(double postWick) { if(gDayHalted) return; if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_MAGIC) == InpMagic) return; // กฎ #5: มีไม้เปิดอยู่แล้ว ห้ามซ้อนไม้ int slPoints = InSession_US() ? InpSL_US_Points : InpSL_Points; // TODO: ทิศทาง (buy/sell) + ราคาเข้าจริง มาจาก DetectPA()/AtFrame()/postWick เมื่อ implement เสร็จ PrintFormat("TryEnter stub: postWick=%.2f slPoints=%d riskMult=%.2f — ยังไม่ส่ง order จริง", postWick, slPoints, gRiskMultiplier); } //+------------------------------------------------------------------+ //| lot size จาก risk % จริง (OrderCalcProfit รู้ contract size จริง) | //| กฎ #5 (1 ไม้=1 risk) + กฎ #15 (คูณ gRiskMultiplier หลังแพ้) | //+------------------------------------------------------------------+ double RiskLots(ENUM_ORDER_TYPE type, double entry, double sl) { double riskPct = InpRiskPercent * gRiskMultiplier; double riskMoney = AccountInfoDouble(ACCOUNT_EQUITY) * riskPct / 100.0; double lossPerLot = 0; if(!OrderCalcProfit(type, _Symbol, 1.0, entry, sl, lossPerLot)) return 0; lossPerLot = MathAbs(lossPerLot); if(lossPerLot <= 0) return 0; double lots = riskMoney / lossPerLot; double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); lots = MathFloor(lots / step) * step; double minL = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double maxL = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); if(lots < minL) return 0; // ห้ามปัดขึ้น — จะเกิน risk budget ที่ตั้งไว้ return MathMin(lots, maxL); } //+------------------------------------------------------------------+ //| ตลาดเมกา/US session แบบคร่าวๆ (server hour) | //| TODO: ยังไม่ยืนยันช่วงเวลาที่แน่นอนกับพี่ฮอน — ค่านี้เป็น placeholder | //+------------------------------------------------------------------+ bool InSession_US() { MqlDateTime dt; TimeToStruct(TimeTradeServer(), dt); return (dt.hour >= 13 && dt.hour < 21); } //+------------------------------------------------------------------+