//+------------------------------------------------------------------+ //| CChartDrawer.mqh | //| v5.0 - Dynamic Zone Drawing Logic | //+------------------------------------------------------------------+ #ifndef CCHARTDRAWER_MQH #define CCHARTDRAWER_MQH #include #include "CEnumSharing.mqh" #include "SMC_DataStructures.mqh" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CChartDrawer { private: long m_chart_id; string m_prefix; public: CChartDrawer(long chart_id, long magic_number) { m_chart_id = chart_id; m_prefix = StringFormat("EASMC_%d_", magic_number); } ~CChartDrawer() {} // --- ฟังก์ชันนี้ไม่ได้ใช้แล้ว แต่เก็บไว้เผื่อกรณีฉุกเฉิน --- void ClearAllDrawings() { ObjectsDeleteAll(m_chart_id, 0, -1, m_prefix); } void SynchronizeDrawings(CArrayObj* all_zones, CArrayObj* all_swings) { CArrayString* valid_object_names = new CArrayString(); if(all_zones != NULL) { for(int i=0; i=0) { valid_object_names.Add(m_prefix + "Link_" + (string)swing.time); } if(swing.status == STATUS_MAJOR && swing.bos_confirmation_time > 0) { valid_object_names.Add(m_prefix + "BOS_" + (string)swing.time); } } } } for(int i = ObjectsTotal(m_chart_id) - 1; i >= 0; i--) { string name = ObjectName(m_chart_id, i); if(StringFind(name, m_prefix) == 0) { if(valid_object_names.Search(name) < 0) { ObjectDelete(m_chart_id, name); } } } delete valid_object_names; } void DrawZone(CSignalZone* zone, CDataContext &context) { if(zone == NULL) return; string obj_name = zone.object_name; // 1. กำหนดสีและสไตล์ตามสถานะของโซน color box_color = clrWhite; ENUM_LINE_STYLE box_style = (zone.zone_type == ZONE_FVG) ? STYLE_DOT : STYLE_SOLID; bool fill_box = (zone.zone_type != ZONE_FVG); switch(zone.state) { case STATE_FRESH: box_color = (zone.direction == DIRECTION_BULLISH ? clrDarkGreen : clrMaroon); if(zone.zone_type==ZONE_FVG) box_color=clrGold; break; if(zone.zone_type == ZONE_FLIP) box_color = clrAqua; else if(zone.zone_type == ZONE_BREAKOUT_RETEST) box_color = clrDodgerBlue; case STATE_ACTIVE: box_color = (zone.direction == DIRECTION_BULLISH ? clrGreen : clrTomato); break; case STATE_MITIGATED: box_color = (zone.direction == DIRECTION_BULLISH ? clrForestGreen : clrIndianRed); break; //case STATE_CONFIRMED: box_color = (zone.direction == DIRECTION_BULLISH ? clrLime : clrRed); break; case STATE_INVALIDATED: box_color = clrDimGray; fill_box = false; break; } // 2. หาจุดสิ้นสุดของการวาดโซน datetime end_time; int start_idx = iBarShift(_Symbol, PERIOD_CURRENT, zone.time); if(start_idx < 0 || start_idx >= context.bars_to_load) { // ObjectDelete(m_chart_id, zone.object_name); // อาจจะลบ object เก่าทิ้งถ้ามี return; } // หาแท่งเทียนที่ "ทำลาย" หรือ "บัง" โซน int end_idx = -1; for(int i = start_idx - 1; i >= 0; i--) { // สำหรับโซน Demand, หาแท่งที่ Low ต่ำกว่าขอบล่างของโซน if(zone.direction == DIRECTION_BULLISH && context.rates[i].low < zone.bottom) { end_idx = i; break; } // สำหรับโซน Supply, หาแท่งที่ High สูงกว่าขอบบนของโซน if(zone.direction == DIRECTION_BEARISH && context.rates[i].high > zone.top) { end_idx = i; break; } } // 3. กำหนดเวลาสิ้นสุด if(end_idx != -1) { // ถ้าเจอแท่งที่ทำลายโซน ให้จบการวาดที่แท่งนั้น end_time = context.rates[end_idx].time; } else { // ถ้าไม่เจอ ให้ลากยาวไปจนถึงอนาคตเล็กน้อย (ขอบขวาสุดของจอ) end_time = context.rates[0].time + PeriodSeconds() * 10; } // ถ้ายังไม่มี Object นี้ ให้สร้างใหม่ if(ObjectFind(m_chart_id, obj_name) < 0) { ObjectCreate(m_chart_id, obj_name, OBJ_RECTANGLE, 0, zone.time, zone.top, end_time, zone.bottom); } // ถ้ามีอยู่แล้ว ให้อัปเดตเฉพาะสิ่งที่อาจเปลี่ยนแปลง else { ObjectSetInteger(m_chart_id, obj_name, OBJPROP_TIME, 1, end_time); } // อัปเดตคุณสมบัติอื่นๆ ทุกครั้ง เผื่อมีการเปลี่ยนแปลง ObjectSetInteger(m_chart_id, obj_name, OBJPROP_COLOR, box_color); ObjectSetInteger(m_chart_id, obj_name, OBJPROP_STYLE, box_style); ObjectSetInteger(m_chart_id, obj_name, OBJPROP_FILL, fill_box); ObjectSetInteger(m_chart_id, obj_name, OBJPROP_BACK, true); // วาด/อัปเดต Text Label string text_name = obj_name + "_Text"; string text = EnumToString(zone.zone_type) + " " + EnumToString(zone.state); if(zone.mitigation_count > 0) text += " T:" + (string)zone.mitigation_count; if(ObjectFind(m_chart_id, text_name) < 0) { ObjectCreate(m_chart_id, text_name, OBJ_TEXT, 0, zone.time, zone.top); } ObjectSetString(m_chart_id, text_name, OBJPROP_TEXT, text); ObjectSetInteger(m_chart_id, text_name, OBJPROP_COLOR, box_color); ObjectSetInteger(m_chart_id, text_name, OBJPROP_FONTSIZE, 8); ObjectSetInteger(m_chart_id, text_name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); } void DrawSwing(SwingPoint* swing, CArrayObj* all_swings, CDataContext &context) { if(swing == NULL || swing.status == STATUS_NOISE || swing.status == STATUS_UNCONFIRMED) return; string obj_name = m_prefix + "Swing_" + (string)swing.time; color symbol_color = clrWhite; int symbol_code = 0; int symbol_size = 1; switch(swing.status) { case STATUS_MAJOR: symbol_color = (swing.type == SWING_HIGH) ? clrRed : clrLimeGreen; symbol_size = 3; // FIXED: Use character codes for Wingdings font symbol_code = (swing.type == SWING_HIGH) ? 159 : 159; break; case STATUS_INDUCEMENT: symbol_color = clrGold; symbol_size = 2; symbol_code = 159; // '$' sign break; case STATUS_MINOR: symbol_color = clrDimGray; symbol_size = 1; symbol_code = 159; // Solid Circle break; default: return; } /*double atr_val = (swing.bar_index < context.bars_to_load) ? context.atr_buffer[swing.bar_index] : 0; double price_offset = atr_val * 0.2;*/ double draw_price = (swing.type == SWING_HIGH) ? swing.price + 12 : swing.price ; // วาด/อัปเดต สัญลักษณ์หลัก if(ObjectFind(m_chart_id, obj_name) < 0) { ObjectCreate(m_chart_id, obj_name, OBJ_ARROW, 0, swing.time, draw_price); } else { ObjectSetDouble(m_chart_id, obj_name, OBJPROP_PRICE, 0, draw_price); } ObjectSetInteger(m_chart_id, obj_name, OBJPROP_ARROWCODE, symbol_code); ObjectSetInteger(m_chart_id, obj_name, OBJPROP_COLOR, symbol_color); ObjectSetInteger(m_chart_id, obj_name, OBJPROP_WIDTH, symbol_size); ObjectSetInteger(m_chart_id, obj_name, OBJPROP_BACK, true); // วาด/อัปเดต เส้นเชื่อม Inducement . Major /*string link_name = m_prefix + "Link_" + (string)swing.time; if(swing.status == STATUS_INDUCEMENT && swing.linked_major_index >= 0 && swing.linked_major_index < all_swings.Total()) { SwingPoint *major_swing = all_swings.At(swing.linked_major_index); if(major_swing != NULL) { if(ObjectFind(m_chart_id, link_name) < 0) { ObjectCreate(m_chart_id, link_name, OBJ_TREND, 0, swing.time, swing.price, major_swing.time, major_swing.price); } ObjectSetInteger(m_chart_id, link_name, OBJPROP_COLOR, clrGoldenrod); ObjectSetInteger(m_chart_id, link_name, OBJPROP_STYLE, STYLE_DOT); } }*/ // วาด/อัปเดต ป้าย "BOS" if(swing.status == STATUS_MAJOR && swing.bos_confirmation_time > 0) { string bos_name = m_prefix + "BOS_" + (string)swing.time; datetime bos_time = swing.bos_confirmation_time; int bos_idx = iBarShift(_Symbol, PERIOD_CURRENT, bos_time); if(bos_idx >= 0 && bos_idx < context.bars_to_load) { double bos_atr_val = context.atr_buffer[bos_idx]; double bos_price_offset = bos_atr_val * 0.5; double bos_price = (swing.type == SWING_LOW) ? context.rates[bos_idx].high + bos_price_offset : context.rates[bos_idx].low - bos_price_offset; ObjectCreate(m_chart_id, bos_name, OBJ_TEXT, 0, bos_time, bos_price); ObjectSetString(m_chart_id, bos_name, OBJPROP_TEXT, "BOS"); ObjectSetInteger(m_chart_id, bos_name, OBJPROP_COLOR, clrAqua); ObjectSetInteger(m_chart_id, bos_name, OBJPROP_FONTSIZE, 8); ObjectSetInteger(m_chart_id, bos_name, OBJPROP_BACK, true); if(swing.type == SWING_LOW) ObjectSetInteger(m_chart_id, bos_name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); else ObjectSetInteger(m_chart_id, bos_name, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); } } } }; #endif //+------------------------------------------------------------------+