EA_SMC_Mql5/Include/CTradeManager.mqh
2025-07-03 23:37:57 +07:00

675 lines
No EOL
63 KiB
MQL5

//+------------------------------------------------------------------+
//| CTradeManager.mqh |
//| v22.0 - Final Corrected Version |
//+------------------------------------------------------------------+
#ifndef CTRADEMANAGER_MQH
#define CTRADEMANAGER_MQH
#include <Trade/Trade.mqh>
#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