forked from ntsinh90/EA_SMC_Mql5
675 lines
No EOL
63 KiB
MQL5
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 |