//+------------------------------------------------------------------+ //| CTradeManager.mqh | //| v22.0 - Final Corrected Version | //+------------------------------------------------------------------+ #ifndef CTRADEMANAGER_MQH #define CTRADEMANAGER_MQH #include #include "CRiskManager.mqh" #include "SMC_DataStructures.mqh" class CTradeManager { private: CTrade* m_trade; CArrayObj* m_open_trades; CRiskManager* m_risk_manager_ptr; long m_magic; string m_comment; ulong m_slippage; int m_sl_buffer_points; int m_min_sl_points; int m_max_sl_points; ENUM_EXIT_MODE m_exit_mode; double m_rr_ratio; int m_tp_points; double m_tier1_rr, m_tier2_rr, m_tier3_rr; int m_tier1_points, m_tier2_points, m_tier3_points; double m_psar_step; double m_psar_max; int m_psar_handle; bool m_use_partial_tp; double m_partial_tp_rr; double m_partial_close_percent; // --- Private helpers --- datetime GetEntrySwingTimeFromComment(string comment) { string marker = "|S:"; int pos = StringFind(comment, marker); if(pos < 0) return 0; string time_str = StringSubstr(comment, pos + StringLen(marker)); return (datetime)StringToInteger(time_str); } void SynchronizePositions() { // วนลูปแฟ้มข้อมูลของเรา เพื่อเช็คว่าออเดอร์จริงๆ ยังอยู่บน Server หรือไม่ for(int i = m_open_trades.Total() - 1; i >= 0; i--) { CManagedTrade* trade_info = m_open_trades.At(i); if(trade_info == NULL) continue; // ถ้า PositionSelectByTicket คืนค่า false แปลว่าออเดอร์นั้นถูกปิดไปแล้ว if(!PositionSelectByTicket(trade_info.ticket)) { PrintFormat("Position #%d no longer exists. Removing from internal list.", trade_info.ticket); m_open_trades.Delete(i); // ลบแฟ้มข้อมูลทิ้ง } } } SwingPoint* FindSwingByTime(datetime time, CArrayObj* all_swings) { if(time == 0 || all_swings == NULL) return NULL; for(int i = 0; i < all_swings.Total(); i++) { SwingPoint* s = all_swings.At(i); if(s != NULL && s.time == time) return s; } return NULL; } // 1. ปิดเมื่อเจอ Major Swing ฝั่งตรงข้าม bool CheckExit_OppositeMajor(ENUM_POSITION_TYPE position_type, datetime position_open_time, CArrayObj* all_swings) { if(all_swings == NULL) return false; SwingPoint* last_major_swing = NULL; for(int i = all_swings.Total() - 1; i >= 0; i--) { SwingPoint* s = all_swings.At(i); if(s != NULL && s.status == STATUS_MAJOR) { last_major_swing = s; break; } } // *** Condition Check: สวิงที่เจอต้องเกิด "หลัง" ออเดอร์เปิดแล้วเท่านั้น *** if(last_major_swing == NULL || last_major_swing.time <= position_open_time) return false; if(position_type == POSITION_TYPE_BUY && last_major_swing.type == SWING_HIGH) return true; if(position_type == POSITION_TYPE_SELL && last_major_swing.type == SWING_LOW) return true; return false; } // 2. ปิดเมื่อเจอ Minor Swing ซ้ำ 2 ครั้ง bool CheckExit_DoubleMinor(datetime position_open_time, CArrayObj* all_swings) { if(all_swings == NULL || all_swings.Total() < 2) return false; SwingPoint* last_swing = NULL; SwingPoint* second_last_swing = NULL; int found_count = 0; for(int i = all_swings.Total() - 1; i >= 0; i--) { SwingPoint* s = all_swings.At(i); if(s != NULL && s.status != STATUS_NOISE) { if(found_count == 0) last_swing = s; if(found_count == 1) second_last_swing = s; found_count++; if(found_count >= 2) break; } } // *** Condition Check: สวิงล่าสุดที่เจอ ต้องเกิด "หลัง" ออเดอร์เปิดแล้วเท่านั้น *** if(last_swing == NULL || second_last_swing == NULL || last_swing.time <= position_open_time) return false; if(last_swing.status == STATUS_MINOR && second_last_swing.status == STATUS_MINOR && last_swing.type == second_last_swing.type) { return true; } return false; } // 3. ปิดเมื่อเจอ Minor Swing ที่ขัดแย้งกับโครงสร้างเดิม bool CheckExit_ContradictoryMinor(ENUM_POSITION_TYPE position_type, SwingPoint* entry_structure_ref, datetime position_open_time, CArrayObj* all_swings) { if(entry_structure_ref == NULL || all_swings == NULL) return false; SwingPoint* latest_same_type_minor = NULL; for(int i = all_swings.Total() - 1; i >= 0; i--) { SwingPoint* s = all_swings.At(i); if(s != NULL && s.status == STATUS_MINOR && s.type == entry_structure_ref.type) { latest_same_type_minor = s; break; } } // *** Condition Check: Minor Swing ที่เจอต้องเกิด "หลัง" ออเดอร์เปิดแล้วเท่านั้น *** if(latest_same_type_minor == NULL || latest_same_type_minor.time <= position_open_time) return false; if(position_type == POSITION_TYPE_BUY && latest_same_type_minor.price < entry_structure_ref.price) return true; if(position_type == POSITION_TYPE_SELL && latest_same_type_minor.price > entry_structure_ref.price) return true; return false; } void Manage_PartialTP_and_Breakeven(CManagedTrade* trade_info) { if(trade_info == NULL) return; if(!PositionSelectByTicket(trade_info.ticket)) return; //PrintFormat("--- DEBUG: Checking Partial TP for ticket #%d ---", trade_info.ticket); // --- 1. ดึงข้อมูลสำคัญ --- double open_price = PositionGetDouble(POSITION_PRICE_OPEN); double initial_sl = trade_info.target_sl_price; ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); if(initial_sl == 0.0) { Print(" - FAILED: Initial SL is 0.0. Cannot calculate risk."); return; } // --- 2. คำนวณ Risk และ Reward ที่ต้องการ --- double initial_risk_dist = MathAbs(open_price - initial_sl); if(initial_risk_dist == 0) return; double required_profit_dist = initial_risk_dist * m_partial_tp_rr; // --- 3. คำนวณกำไรปัจจุบัน --- double current_price = (type == POSITION_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol, SYMBOL_ASK); double current_profit_dist = (type == POSITION_TYPE_BUY) ? (current_price - open_price) : (open_price - current_price); // --- 4. พิมพ์รายงานการตรวจสอบทั้งหมด --- //PrintFormat(" - Trade Type: %s, Open Price: %.5f, Initial SL: %.5f", EnumToString(type), open_price, initial_sl); //PrintFormat(" - Initial Risk Distance (Price): %.5f", initial_risk_dist); //PrintFormat(" - Partial TP Target: %.2f R", m_partial_tp_rr); //PrintFormat(" - Required Profit Distance (Price): %.5f", required_profit_dist); //PrintFormat(" - Current Price: %.5f", current_price); //PrintFormat(" - Current Profit Distance (Price): %.5f", current_profit_dist); //PrintFormat(" - CONDITION MET?: %s", (string)(current_profit_dist >= required_profit_dist)); // --- 5. ตรวจสอบเงื่อนไข --- if(current_profit_dist >= required_profit_dist) { Print(" >>> CONDITION MET! Attempting to close partial position..."); double initial_volume = PositionGetDouble(POSITION_VOLUME); double volume_to_close = NormalizeDouble(initial_volume * (m_partial_close_percent / 100.0), 2); if(volume_to_close < SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)) volume_to_close = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); if(volume_to_close >= initial_volume) volume_to_close = initial_volume; if(m_trade.PositionClose(trade_info.ticket, volume_to_close)) { PrintFormat(" >>> SUCCESS: Partial TP taken for ticket #%d.", trade_info.ticket); trade_info.is_partial_tp_taken = true; if(PositionSelectByTicket(trade_info.ticket)) { m_trade.PositionModify(trade_info.ticket, open_price, PositionGetDouble(POSITION_TP)); PrintFormat(" >>> SUCCESS: SL for ticket #%d moved to Break-Even.", trade_info.ticket); } } else { Print(" >>> FAILED to close partial position. Error: ", m_trade.ResultRetcodeDescription()); } } } void ManageTrailingStop_PSAR(CManagedTrade* trade_info) { if(m_psar_handle == INVALID_HANDLE || trade_info == NULL || !PositionSelectByTicket(trade_info.ticket)) { return; } // 1. ดึงข้อมูลที่จำเป็น ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double tp_price = PositionGetDouble(POSITION_TP); double current_sl = PositionGetDouble(POSITION_SL); double psar_buffer[1]; if(CopyBuffer(m_psar_handle, 0, 1, 1, psar_buffer) < 1) return; double new_sl_candidate = psar_buffer[0]; // SL ใหม่ที่ได้จาก PSAR // +++ NEW: Make PSAR Stop-Level Aware +++ // 2. ดึงกฎ Stop Level และราคาตลาดปัจจุบัน int stops_level_points = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); double stops_level_offset = stops_level_points * _Point; double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); // 3. ปรับแก้ราคา SL ที่จะส่ง ให้ถูกต้องตามกฎโบรกเกอร์ if(type == POSITION_TYPE_BUY) { // SL ของ Buy ต้องต่ำกว่าราคา Bid if(bid - new_sl_candidate < stops_level_offset) { new_sl_candidate = bid - stops_level_offset; } } else { // SELL // SL ของ Sell ต้องสูงกว่าราคา Ask if(new_sl_candidate - ask < stops_level_offset) { new_sl_candidate = ask + stops_level_offset; } } // ทำความสะอาดราคา (Normalize) int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); double final_new_sl = NormalizeDouble(new_sl_candidate, digits); // --- End of new logic --- // 4. ตรวจสอบและส่งคำสั่ง Modify ด้วยราคาที่ปรับแก้แล้ว // Logic การเปิดใช้งาน Trailing Stop ยังคงเดิม if(tp_price == 0.0) // Trail ทำงานอยู่แล้ว { if((type == POSITION_TYPE_BUY && final_new_sl > current_sl) || (type == POSITION_TYPE_SELL && final_new_sl < current_sl)) { m_trade.PositionModify(trade_info.ticket, final_new_sl, 0.0); } } else // ยังไม่เคย Trail, รอราคาถึง TP เพื่อเปิดใช้งาน { double current_price = (type == POSITION_TYPE_BUY) ? bid : ask; bool is_tp_reached = (type == POSITION_TYPE_BUY && current_price >= tp_price) || (type == POSITION_TYPE_SELL && current_price <= tp_price); if(is_tp_reached) { if((type == POSITION_TYPE_BUY && final_new_sl > current_sl) || (type == POSITION_TYPE_SELL && (final_new_sl < current_sl || current_sl == 0.0))) { if(m_trade.PositionModify(trade_info.ticket, final_new_sl, 0.0)) { PrintFormat("PSAR Trailing Stop for ticket #%d activated. New SL is %.5f. TP removed.", trade_info.ticket, final_new_sl); } } } } } /*void ManageTrailingStop_Fixed(CManagedTrade* trade_info) { if(!PositionSelectByTicket(trade_info.ticket)) return; PrintFormat("DEBUG: Checking Fixed Trailing Stop for ticket #%d...", trade_info.ticket); ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double open_price = PositionGetDouble(POSITION_PRICE_OPEN); double current_sl = PositionGetDouble(POSITION_SL); double current_price = (type == POSITION_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol, SYMBOL_ASK); // 1. คำนวณกำไรเป็นจุด double profit_points = 0; if(type == POSITION_TYPE_BUY) profit_points = (current_price - open_price) / _Point; else profit_points = (open_price - current_price) / _Point; // 2. ตรวจสอบว่ากำไรถึงจุดที่ต้องเริ่ม Trailing หรือยัง if(profit_points >= m_trail_start_points) { // 3. คำนวณ SL ใหม่จากราคาปัจจุบัน double new_sl = (type == POSITION_TYPE_BUY) ? current_price - (m_trail_stop_points * _Point) : current_price + (m_trail_stop_points * _Point); // 4. ตรวจสอบว่า SL ใหม่ดีกว่าเดิมหรือไม่ แล้วจึงเลื่อน if((type == POSITION_TYPE_BUY && new_sl > current_sl) || (type == POSITION_TYPE_SELL && new_sl < current_sl)) { PrintFormat("DEBUG: Fixed Trail condition met. Modifying SL for ticket #%d.", trade_info.ticket); PrintFormat("Trailing Stop Triggered for #%d. Profit: %.0f points. Moving SL to %.5f", trade_info.ticket, profit_points, new_sl); m_trade.PositionModify(trade_info.ticket, new_sl, PositionGetDouble(POSITION_TP)); } } }*/ void ManageTrailingStop_Fixed(CManagedTrade* trade_info) { if(!PositionSelectByTicket(trade_info.ticket)) return; ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); double open_price = PositionGetDouble(POSITION_PRICE_OPEN); double current_sl = PositionGetDouble(POSITION_SL); double initial_sl = trade_info.target_sl_price; double current_price = (type == POSITION_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol, SYMBOL_ASK); // --- Dynamic Trailing Logic --- // 1. คำนวณกำไรปัจจุบันในรูปแบบ R (Risk multiple) double initial_risk_dist = MathAbs(open_price - initial_sl); if(initial_risk_dist == 0) return; double current_profit_dist = (type == POSITION_TYPE_BUY) ? (current_price - open_price) : (open_price - current_price); double current_r_multiple = current_profit_dist / initial_risk_dist; // 2. เลือกระยะ Trailing Stop ที่จะใช้ตามขั้นบันได int dynamic_trail_points = 0; if(m_tier3_rr > 0 && current_r_multiple >= m_tier3_rr) { dynamic_trail_points = m_tier3_points; } else if (m_tier2_rr > 0 && current_r_multiple >= m_tier2_rr) { dynamic_trail_points = m_tier2_points; } else if (m_tier1_rr > 0 && current_r_multiple >= m_tier1_rr) { dynamic_trail_points = m_tier1_points; } // 3. ถ้าเข้าเงื่อนไข (มีกำไรถึงขั้นบันไดขั้นใดขั้นหนึ่ง) ให้เริ่ม Trailing if(dynamic_trail_points > 0) { // 4. คำนวณ SL ใหม่จากราคาปัจจุบันและระยะตามแบบไดนามิก double new_sl = (type == POSITION_TYPE_BUY) ? current_price - (dynamic_trail_points * _Point) : current_price + (dynamic_trail_points * _Point); // 5. ตรวจสอบว่า SL ใหม่ดีกว่าเดิมหรือไม่ แล้วจึงเลื่อน if((type == POSITION_TYPE_BUY && new_sl > current_sl) || (type == POSITION_TYPE_SELL && (new_sl < current_sl || current_sl == 0.0))) { // ยกเลิก TP เดิมเมื่อเริ่ม Trail ครั้งแรก double tp_price = PositionGetDouble(POSITION_TP) == 0.0 ? 0.0 : 0.0; m_trade.PositionModify(trade_info.ticket, new_sl, tp_price); } } } public: CTradeManager(CRiskManager* risk_manager, long magic, string comment, ulong slippage, int sl_buffer_points, int min_sl_points, int max_sl_points, ENUM_EXIT_MODE exit_mode, double rr_ratio, int tp_points, double tier1_rr, int tier1_pts, double tier2_rr, int tier2_pts, double tier3_rr, int tier3_pts, double psar_step, double psar_max, bool use_partial, double partial_rr, double partial_pct) { m_risk_manager_ptr = risk_manager; m_magic = magic; m_comment = comment; m_slippage = slippage; m_sl_buffer_points = sl_buffer_points; m_min_sl_points = min_sl_points; m_max_sl_points = max_sl_points; m_exit_mode = exit_mode; m_rr_ratio = rr_ratio; m_tp_points = tp_points; m_tier1_rr = tier1_rr; m_tier1_points = tier1_pts; m_tier2_rr = tier2_rr; m_tier2_points = tier2_pts; m_tier3_rr = tier3_rr; m_tier3_points = tier3_pts; m_psar_step = psar_step; m_psar_max = psar_max; m_use_partial_tp = use_partial; m_partial_tp_rr = partial_rr; m_partial_close_percent = partial_pct; m_trade = new CTrade(); m_open_trades = new CArrayObj(); m_open_trades.FreeMode(true); m_trade.SetExpertMagicNumber(m_magic); m_trade.SetDeviationInPoints((ulong)m_slippage); m_trade.SetTypeFillingBySymbol(_Symbol); m_psar_handle = (m_exit_mode == EXIT_MODE_PSAR_TRAIL) ? iSAR(_Symbol, PERIOD_CURRENT, m_psar_step, m_psar_max) : INVALID_HANDLE; } ~CTradeManager() { if(m_trade != NULL) delete m_trade; if(m_open_trades != NULL) delete m_open_trades; if(m_psar_handle != INVALID_HANDLE) IndicatorRelease(m_psar_handle); } void ManageUnsetSLTP() { if(m_exit_mode == EXIT_MODE_STRUCTURAL) return; // 1. ซิงค์ข้อมูลก่อนเสมอ เพื่อลบ "แฟ้มข้อมูล" ของออเดอร์ที่ถูกปิดไปแล้ว SynchronizePositions(); // 2. วนลูปดู "แฟ้มข้อมูล" ออเดอร์ทั้งหมดที่ EA จัดการอยู่ for(int i = 0; i < m_open_trades.Total(); i++) { CManagedTrade* trade_info = m_open_trades.At(i); // 3. มองหาเฉพาะออเดอร์ที่ยังไม่ได้ตั้ง SL/TP (is_sl_tp_set == false) if(trade_info != NULL && !trade_info.is_sl_tp_set) { // เลือก Position ใน Terminal เพื่อดึงข้อมูลสด if(!PositionSelectByTicket(trade_info.ticket)) continue; // --- 4. ตรวจสอบและปรับแก้ราคาให้สอดคล้องกับกฎ StopLevel ของโบรกเกอร์ --- int stops_level_points = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); double stops_level_offset = stops_level_points * _Point; double final_sl = trade_info.target_sl_price; double final_tp = trade_info.target_tp_price; double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); if(type == POSITION_TYPE_BUY) { // SL ของ Buy ต้องอยู่ต่ำกว่าราคา Bid ปัจจุบันอย่างน้อยเท่ากับ StopLevel if(final_sl != 0.0 && bid - final_sl < stops_level_offset) { final_sl = bid - stops_level_offset; } // TP ของ Buy ต้องอยู่สูงกว่าราคา Ask ปัจจุบันอย่างน้อยเท่ากับ StopLevel if(final_tp != 0.0 && final_tp - ask < stops_level_offset) { final_tp = ask + stops_level_offset; } } else { // SELL // SL ของ Sell ต้องอยู่สูงกว่าราคา Ask ปัจจุบันอย่างน้อยเท่ากับ StopLevel if(final_sl != 0.0 && final_sl - ask < stops_level_offset) { final_sl = ask + stops_level_offset; } // TP ของ Sell ต้องอยู่ต่ำกว่าราคา Bid ปัจจุบันอย่างน้อยเท่ากับ StopLevel if(final_tp != 0.0 && bid - final_tp < stops_level_offset) { final_tp = bid - stops_level_offset; } } // 5. "ทำความสะอาด" ราคา (Normalize) ให้มีทศนิยมถูกต้อง int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); final_sl = NormalizeDouble(final_sl, digits); final_tp = NormalizeDouble(final_tp, digits); // 6. ส่งคำสั่งแก้ไขเพื่อตั้ง SL/TP if(!m_exit_mode == EXIT_MODE_PSAR_TRAIL || !m_exit_mode == EXIT_MODE_TRAILING_STOP){ if(m_trade.PositionModify(trade_info.ticket, final_sl, final_tp)) { // 7. ถ้าสำเร็จ ให้อัปเดต "แฟ้มข้อมูล" ว่าออเดอร์นี้ถูกตั้ง SL/TP แล้ว trade_info.is_sl_tp_set = true; PrintFormat("Successfully set initial SL/TP for ticket #%d", trade_info.ticket); } else { PrintFormat("Failed to set SL/TP for ticket #%d. Error: %s", trade_info.ticket, m_trade.ResultRetcodeDescription()); } }else{ if(m_trade.PositionModify(trade_info.ticket, final_sl, final_tp)) { // 7. ถ้าสำเร็จ ให้อัปเดต "แฟ้มข้อมูล" ว่าออเดอร์นี้ถูกตั้ง SL/TP แล้ว trade_info.is_sl_tp_set = true; PrintFormat("Successfully set initial SL/TP for ticket #%d", trade_info.ticket); } else { PrintFormat("Failed to set SL/TP for ticket #%d. Error: %s", trade_info.ticket, m_trade.ResultRetcodeDescription()); } } } } } void ManageActiveTrailing() { for(int i = 0; i < m_open_trades.Total(); i++) { CManagedTrade* trade_info = m_open_trades.At(i); // ตรวจสอบว่าออเดอร์พร้อมจัดการหรือยัง (มี SL/TP แล้ว) if(trade_info == NULL || !trade_info.is_sl_tp_set) { continue; } // --- FIXED LOGIC: Check both tasks independently (no "else") --- // Task 1: ตรวจสอบและจัดการ Partial TP & Breakeven if (m_use_partial_tp && !trade_info.is_partial_tp_taken) { Manage_PartialTP_and_Breakeven(trade_info); } // Task 2: ตรวจสอบและจัดการ Trailing Stop // ส่วนนี้จะทำงานโดยไม่ขึ้นอยู่กับว่า Partial TP จะทำสำเร็จแล้วหรือไม่ if (m_exit_mode == EXIT_MODE_TRAILING_STOP) { ManageTrailingStop_Fixed(trade_info); } else if (m_exit_mode == EXIT_MODE_PSAR_TRAIL) { ManageTrailingStop_PSAR(trade_info); } } } // --- NEW: Function 3 (Bar-based Task) --- void ManageStructuralExits(CArrayObj* all_swings) { // ทำงานเฉพาะเมื่ออยู่ในโหมด Structural Exit เท่านั้น if(m_exit_mode != EXIT_MODE_STRUCTURAL) { return; } SynchronizePositions(); for(int i = m_open_trades.Total() - 1; i >= 0; i--) { CManagedTrade* trade_info = m_open_trades.At(i); // เช็คเฉพาะออเดอร์ที่เป็น Structural Trade (ไม่มี SL/TP เริ่มต้น) if(trade_info == NULL || trade_info.target_sl_price != 0.0) continue; if(!PositionSelectByTicket(trade_info.ticket)) continue; ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); datetime position_open_time = (datetime)PositionGetInteger(POSITION_TIME); // หา "สวิงอ้างอิง" จาก trigger_level_time ที่บันทึกไว้ SwingPoint* entry_ref_swing = FindSwingByTime(trade_info.trigger_level_time, all_swings); bool close_signal = false; string exit_reason = ""; // +++ CORRECTED FUNCTION CALLS +++ // ส่งพารามิเตอร์ให้ถูกต้องตามที่แต่ละฟังก์ชันต้องการ if(CheckExit_OppositeMajor(position_type, position_open_time, all_swings)) { close_signal = true; exit_reason = "Opposite Major Swing"; } else if(CheckExit_DoubleMinor(position_open_time, all_swings)) { close_signal = true; exit_reason = "Double Minor Swings"; } else if(entry_ref_swing != NULL && CheckExit_ContradictoryMinor(position_type, entry_ref_swing, position_open_time, all_swings)) { close_signal = true; exit_reason = "Contradictory Minor Swing"; } if(close_signal) { PrintFormat("STRUCTURAL EXIT on ticket #%d. Reason: %s.", trade_info.ticket, exit_reason); m_trade.PositionClose(trade_info.ticket); } } } // This function runs once per new bar to check for structural exit conditions. void ManagePositionsOnNewBar(CArrayObj* all_swings) { // ทำงานเฉพาะเมื่ออยู่ในโหมด Structural Exit เท่านั้น if(m_exit_mode != EXIT_MODE_STRUCTURAL) { return; } SynchronizePositions(); for(int i = m_open_trades.Total() - 1; i >= 0; i--) { CManagedTrade* trade_info = m_open_trades.At(i); // เช็คเฉพาะออเดอร์ที่เป็น Structural Trade if(trade_info == NULL || trade_info.target_sl_price != 0.0) continue; if(!PositionSelectByTicket(trade_info.ticket)) continue; // --- ดึงข้อมูลที่จำเป็นทั้งหมดมาก่อน --- ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); datetime position_open_time = (datetime)PositionGetInteger(POSITION_TIME); SwingPoint* entry_ref_swing = FindSwingByTime(trade_info.trigger_level_time, all_swings); bool close_signal = false; string exit_reason = ""; // --- CORRECTED FUNCTION CALLS --- // ส่งพารามิเตอร์ให้ถูกต้องตามที่แต่ละฟังก์ชันต้องการ if(CheckExit_OppositeMajor(position_type, position_open_time, all_swings)) { close_signal = true; exit_reason = "Opposite Major Swing formed"; } else if(CheckExit_DoubleMinor(position_open_time, all_swings)) { close_signal = true; exit_reason = "Double Minor Swings formed"; } // เงื่อนไขนี้ต้องการ entry_ref_swing จึงต้องเช็คก่อนว่าไม่เป็น NULL else if(entry_ref_swing != NULL && CheckExit_ContradictoryMinor(position_type, entry_ref_swing, position_open_time, all_swings)) { close_signal = true; exit_reason = "Contradictory Minor Swing formed"; } // ถ้ามีสัญญาณให้ออก ให้สั่งปิดออเดอร์ if(close_signal) { PrintFormat("STRUCTURAL EXIT on ticket #%d. Reason: %s. Closing position.", trade_info.ticket, exit_reason); m_trade.PositionClose(trade_info.ticket); } } } bool OpenTradeFromSignal(CSignalZone* signal_zone) { if(signal_zone == NULL || m_risk_manager_ptr == NULL || m_trade == NULL) return false; // --- 1. คำนวณพารามิเตอร์พื้นฐาน --- ENUM_ORDER_TYPE type = (signal_zone.direction == DIRECTION_BULLISH) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL; double entry_price = (type == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); // --- 2. กำหนดค่า SL และ TP ตาม Exit Mode ที่เลือก --- double sl_price = 0.0; double tp_price = 0.0; double lots = 0.0; bool is_structural_trade = (m_exit_mode == EXIT_MODE_STRUCTURAL); if(is_structural_trade) { // --- สำหรับโหมด Structural Exit: ไม่มีการตั้ง SL/TP เริ่มต้น --- Print("Structural Exit Mode active. Opening position with NO initial SL/TP."); // เรายังคงต้องคำนวณ SL เสมือนเพื่อหา Lot Size double virtual_sl_price = (type == ORDER_TYPE_BUY) ? signal_zone.bottom - (m_sl_buffer_points * _Point) : signal_zone.top + (m_sl_buffer_points * _Point); double sl_distance_in_points = MathAbs(entry_price - virtual_sl_price) / _Point; lots = m_risk_manager_ptr.GetLotSize(sl_distance_in_points); sl_price = 0.0; // SL จริงเป็น 0 tp_price = 0.0; // TP จริงเป็น 0 } else { // --- สำหรับโหมดอื่นๆ: คำนวณ SL/TP ตามปกติ --- sl_price = (type == ORDER_TYPE_BUY) ? signal_zone.bottom - (m_sl_buffer_points * _Point) : signal_zone.top + (m_sl_buffer_points * _Point); double sl_distance_in_points = MathAbs(entry_price - sl_price) / _Point; if(m_min_sl_points > 0 && sl_distance_in_points < m_min_sl_points) { sl_distance_in_points = m_min_sl_points; sl_price = (type == ORDER_TYPE_BUY) ? entry_price - (sl_distance_in_points * _Point) : entry_price + (sl_distance_in_points * _Point); } if(m_max_sl_points > 0 && sl_distance_in_points > m_max_sl_points) { return false; } lots = m_risk_manager_ptr.GetLotSize(sl_distance_in_points); double sl_distance_in_price_final = MathAbs(entry_price - sl_price); if (m_exit_mode == EXIT_MODE_FIXED_TP) { tp_price = (type == ORDER_TYPE_BUY) ? entry_price + m_tp_points * _Point : entry_price - m_tp_points * _Point; } else { // RR_RATIO, TRAILING_STOP, PSAR_TRAIL use R:R if(m_rr_ratio > 0) { tp_price = (type == ORDER_TYPE_BUY) ? entry_price + sl_distance_in_price_final * m_rr_ratio : entry_price - sl_distance_in_price_final * m_rr_ratio; } } } // --- ส่วนที่เหลือเหมือนเดิม --- if(lots <= 0) return false; int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); double final_sl_price = NormalizeDouble(sl_price, digits); double final_tp_price = NormalizeDouble(tp_price, digits); // เปิดออเดอร์แบบไม่มี SL/TP เพื่อรอการจัดการจาก ManageUnsetSLTP if(!m_trade.PositionOpen(_Symbol, type, lots, entry_price, 0.0, 0.0, m_comment)) { return false; } // สร้างแฟ้มข้อมูลภายใน ulong new_ticket = m_trade.ResultOrder(); if(new_ticket > 0) { CManagedTrade* new_trade_info = new CManagedTrade(new_ticket, final_sl_price, final_tp_price, signal_zone.trigger_level_time, is_structural_trade); m_open_trades.Add(new_trade_info); } else { return false; } return true; } void CloseAllPositions(string reason = "") { PrintFormat("Closing all positions. Reason: %s", reason == "" ? "Request" : reason); for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionSelectByTicket(ticket) && PositionGetInteger(POSITION_MAGIC) == m_magic) { m_trade.PositionClose(ticket); } } } }; #endif