//+------------------------------------------------------------------+ //| EnterpriseOrderPanel_Minimal.mq5 | //| Minimal MT5 panel: draggable window + minimize/close + buttons | //+------------------------------------------------------------------+ #property strict #property version "1.00" //==================================================================== // Inputs //==================================================================== input double InpUIScale = 1.00; input int InpStartX = 20; input int InpStartY = 30; input ulong InpMagicNumber=270927; input uint InpDeviationPoints=20; //==================================================================== // Geometry //==================================================================== #define BASE_W 270 #define HEADER_H 26 #define BODY_H 210 #define OUTER_PAD 6 #define BUTTON_H 24 //==================================================================== // Theme //==================================================================== color C_SHADOW = C'5,8,12'; color C_FRAME = C'42,52,66'; color C_BODY = C'12,19,28'; color C_HEADER = C'17,27,39'; color C_HEADER_TOP = C'24,38,53'; color C_TEXT = C'236,241,248'; color C_TEXT2 = C'196,208,224'; color C_MUTED = C'129,148,173'; color C_ACCENT = C'30,132,255'; color C_ACCENT_DARK = C'20,104,211'; color C_BTN = C'22,31,43'; color C_BTN_PRESS = C'34,47,63'; color C_CLOSE = C'178,47,58'; color C_CLOSE_PRESS = C'204,58,69'; //==================================================================== // State //==================================================================== double g_scale=1.0; int g_x=20; int g_y=30; bool g_minimized=false; enum ERiskUnit { RISK_DOLLAR=0, RISK_PERCENT, RISK_LOT }; ERiskUnit g_risk_unit=RISK_DOLLAR; double g_risk_value=0.0; string g_risk_text="0"; string g_dollar_text="0",g_percent_text="1"; string g_lot_text="0.01"; string N_LOT_BG="MIN_LOT_BG",N_LOT_TX="MIN_LOT_TX"; bool g_state_ready=false; bool g_restore_stop=false,g_restore_entry=false; double g_saved_stop=0.0,g_saved_entry=0.0; string N_STATE=""; string N_EXEC_LOCK=""; bool g_executing=false; bool g_prev_left=false; bool g_dragging=false; int g_drag_dx=0; int g_drag_dy=0; bool g_chart_scroll_before=true; bool g_stop_initialized=false; double g_stop_price=0.0; string g_stop_caption=""; int g_stop_label_x=-1,g_stop_label_y=-1; bool g_stop_label_visible=false; enum EEntryMode { ENTRY_MARKET=0, ENTRY_LIMIT }; EEntryMode g_entry_mode=ENTRY_MARKET; bool g_entry_initialized=false; double g_entry_price=0.0; string g_entry_caption=""; int g_entry_label_x=-1,g_entry_label_y=-1; bool g_entry_label_visible=false; bool g_tp_rr=true,g_tp_initialized=false; double g_tp_reward=3.0,g_tp_price=0.0,g_tp_last_set=0.0,g_calc_volume=0.0; string g_tp_caption=""; int g_tp_x=-1,g_tp_y=-1; bool g_tp_visible=false; string N_TP_LINE="MIN_TP_LINE",N_TP_PRICE="MIN_TP_PRICE"; string N_TP_EDIT="MIN_TP_EDIT",N_TP_TITLE="MIN_TP_TITLE"; string N_TP_RR_BG="MIN_TP_RR_BG",N_TP_RR_TX="MIN_TP_RR_TX"; string N_TP_VALUE_BG="MIN_TP_VALUE_BG",N_TP_VALUE_TX="MIN_TP_VALUE_TX"; int g_steps=1; bool g_lines_cleared=false; string N_CLEAR_BG="MIN_CLEAR_BG",N_CLEAR_TX="MIN_CLEAR_TX"; string N_STEPS_TITLE="MIN_STEPS_TITLE"; string N_DAY_TITLE="MIN_DAY_TITLE",N_DAY_VALUE="MIN_DAY_VALUE"; ulong g_day_refresh=0; string N_STEPS_BG[3]={"MIN_STEPS_1_BG","MIN_STEPS_2_BG","MIN_STEPS_3_BG"}; string N_STEPS_TX[3]={"MIN_STEPS_1_TX","MIN_STEPS_2_TX","MIN_STEPS_3_TX"}; string N_STAGE_LINE[2]={"MIN_ENTRY_2","MIN_ENTRY_3"}; string N_STAGE_LABEL[2]={"MIN_ENTRY_2_PRICE","MIN_ENTRY_3_PRICE"}; double g_stage_price[2]={0.0,0.0}; string g_stage_caption[2]={"",""}; int g_stage_x[2]={-1,-1},g_stage_y[2]={-1,-1}; bool g_stage_visible[2]={false,false}; //==================================================================== // Object names //==================================================================== string N_SHADOW ="MIN_SHADOW"; string N_FRAME ="MIN_FRAME"; string N_BODY ="MIN_BODY"; string N_HEADER ="MIN_HEADER"; string N_TOPLINE ="MIN_TOPLINE"; string N_TITLE ="MIN_TITLE"; string N_SUBTITLE ="MIN_SUBTITLE"; string N_MIN_BG ="MIN_MIN_BG"; string N_MIN_TX ="MIN_MIN_TX"; string N_CLOSE_BG ="MIN_CLOSE_BG"; string N_CLOSE_TX ="MIN_CLOSE_TX"; string N_LEFT_BG ="MIN_LEFT_BG"; string N_LEFT_TX ="MIN_LEFT_TX"; string N_RIGHT_BG ="MIN_RIGHT_BG"; string N_RIGHT_TX ="MIN_RIGHT_TX"; string N_RISK_EDIT="MIN_RISK_EDIT"; string N_DOLLAR_BG="MIN_DOLLAR_BG"; string N_DOLLAR_TX="MIN_DOLLAR_TX"; string N_PERCENT_BG="MIN_PERCENT_BG"; string N_PERCENT_TX="MIN_PERCENT_TX"; string N_STOP_LINE="MIN_STOP_LINE"; string N_STOP_PRICE="MIN_STOP_PRICE"; string N_ENTRY_LINE="MIN_ENTRY_LINE"; string N_ENTRY_PRICE="MIN_ENTRY_PRICE"; string N_MARKET_BG="MIN_MARKET_BG"; string N_MARKET_TX="MIN_MARKET_TX"; string N_LIMIT_BG="MIN_LIMIT_BG"; string N_LIMIT_TX="MIN_LIMIT_TX"; //==================================================================== // Press targets //==================================================================== enum EPressTarget { PRESS_NONE=0, PRESS_MIN, PRESS_CLOSE, PRESS_HEADER, PRESS_LEFT, PRESS_RIGHT }; EPressTarget g_pressed=PRESS_NONE; //==================================================================== // Helpers //==================================================================== int S(double v) { return (int)MathRound(v*g_scale); } int FS(double v) { int n=(int)MathRound((v+1.0)*g_scale); return n<7 ? 7 : n; } void Del(string name) { if(ObjectFind(0,name)>=0) ObjectDelete(0,name); } bool Rect(string name,int x,int y,int w,int h,color bg,color edge,long z=1) { if(!ObjectCreate(0,name,OBJ_RECTANGLE_LABEL,0,0,0)) return false; ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER); ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x); ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y); ObjectSetInteger(0,name,OBJPROP_XSIZE,w); ObjectSetInteger(0,name,OBJPROP_YSIZE,h); ObjectSetInteger(0,name,OBJPROP_BGCOLOR,bg); ObjectSetInteger(0,name,OBJPROP_COLOR,edge); ObjectSetInteger(0,name,OBJPROP_BORDER_TYPE,BORDER_FLAT); ObjectSetInteger(0,name,OBJPROP_WIDTH,1); ObjectSetInteger(0,name,OBJPROP_BACK,false); ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,name,OBJPROP_HIDDEN,true); ObjectSetInteger(0,name,OBJPROP_ZORDER,z); return true; } bool Label(string name,string text,int x,int y,int font_size,color clr, string font="Segoe UI",long z=20) { if(!ObjectCreate(0,name,OBJ_LABEL,0,0,0)) return false; ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER); ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x); ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y); ObjectSetInteger(0,name,OBJPROP_COLOR,clr); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size); ObjectSetString(0,name,OBJPROP_FONT,font); ObjectSetString(0,name,OBJPROP_TEXT,text); // Chart-level captions use z=6; panel labels stay in the foreground. // ZORDER controls mouse priority, while BACK controls the drawing layer. ObjectSetInteger(0,name,OBJPROP_BACK,z<10); ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,name,OBJPROP_HIDDEN,true); ObjectSetInteger(0,name,OBJPROP_ZORDER,z); return true; } void XYWH(string name,int x,int y,int w=-1,int h=-1) { if(ObjectFind(0,name)<0) return; ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x); ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y); if(w>=0) ObjectSetInteger(0,name,OBJPROP_XSIZE,w); if(h>=0) ObjectSetInteger(0,name,OBJPROP_YSIZE,h); } void XY(string name,int x,int y) { XYWH(name,x,y); } void CenterText(string name,int x,int y,int w,int h) { if(ObjectFind(0,name)<0) return; ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_CENTER); XY(name,x+w/2,y+h/2); } void Visible(string name,bool visible) { if(ObjectFind(0,name)<0) return; ObjectSetInteger( 0, name, OBJPROP_TIMEFRAMES, visible ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS ); } void RectStyle(string name,color bg,color edge) { if(ObjectFind(0,name)<0) return; ObjectSetInteger(0,name,OBJPROP_BGCOLOR,bg); ObjectSetInteger(0,name,OBJPROP_COLOR,edge); } bool Hit(int px,int py,int x,int y,int w,int h) { return (px>=x && px=y && py0.0) price=MathRound(price/step)*step; return NormalizeDouble(price,_Digits); } void UpdatePriceLabel(string line,string label,string prefix,bool below, double &line_price,string &last_caption, int &last_x,int &last_y,bool &last_visible) { bool exists=(ObjectFind(0,line)>=0); bool show=false; int x=0,y=0; if(exists) { line_price=ObjectGetDouble(0,line,OBJPROP_PRICE); datetime time=0; double price=0.0; int window=0,unused_x=0; if(ChartXYToTimePrice(0,0,0,window,time,price) && ChartTimePriceToXY(0,0,time,line_price,unused_x,y)) { int height=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,0); x=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS,0)-S(12); show=(y>=0 && y0.0) return tick.bid; if(tick.last>0.0) return tick.last; return tick.ask; } void SaveState() { if(!g_state_ready) return; bool has_stop=(ObjectFind(0,N_STOP_LINE)>=0); bool has_entry=(ObjectFind(0,N_ENTRY_LINE)>=0); if(has_stop) { g_saved_stop=ObjectGetDouble(0,N_STOP_LINE,OBJPROP_PRICE); g_restore_stop=true; } if(has_entry) { g_saved_entry=ObjectGetDouble(0,N_ENTRY_LINE,OBJPROP_PRICE); g_restore_entry=true; } for(int i=0;i<2;i++) if(ObjectFind(0,N_STAGE_LINE[i])>=0) g_stage_price[i]=ObjectGetDouble(0,N_STAGE_LINE[i],OBJPROP_PRICE); string state=IntegerToString(g_x)+"|"+IntegerToString(g_y)+"|"+ IntegerToString((int)g_minimized)+"|"+IntegerToString((int)g_risk_unit)+"|"+ IntegerToString((int)g_entry_mode)+"|"+g_dollar_text+"|"+g_percent_text+"|"+ IntegerToString((int)g_restore_stop)+"|"+DoubleToString(g_saved_stop,16)+"|"+ IntegerToString((int)g_restore_entry)+"|"+DoubleToString(g_saved_entry,16)+"|"+ IntegerToString((int)g_tp_rr)+"|"+DoubleToString(g_tp_reward,8)+"|"+DoubleToString(g_tp_price,16)+"|"+ IntegerToString(g_steps)+"|"+DoubleToString(g_stage_price[0],16)+"|"+DoubleToString(g_stage_price[1],16)+"|"+ IntegerToString((int)g_lines_cleared)+"|"+g_lot_text; if(ObjectFind(0,N_STATE)<0) { if(!ObjectCreate(0,N_STATE,OBJ_LABEL,0,0,0)) return; ObjectSetInteger(0,N_STATE,OBJPROP_TIMEFRAMES,OBJ_NO_PERIODS); ObjectSetInteger(0,N_STATE,OBJPROP_HIDDEN,true); ObjectSetInteger(0,N_STATE,OBJPROP_SELECTABLE,false); } if(ObjectGetString(0,N_STATE,OBJPROP_TEXT)!=state) ObjectSetString(0,N_STATE,OBJPROP_TEXT,state); } void LoadState() { // This invisible chart object is deliberately excluded from DeleteAll(). // It survives timeframe/input reinitialization and belongs to this chart only. if(ObjectFind(0,N_STATE)<0) return; string fields[]; int count=StringSplit(ObjectGetString(0,N_STATE,OBJPROP_TEXT),'|',fields); if(count!=11 && count!=14 && count!=17 && count!=18 && count!=19) return; g_x=(int)StringToInteger(fields[0]); g_y=(int)StringToInteger(fields[1]); g_minimized=(StringToInteger(fields[2])!=0); g_risk_unit=StringToInteger(fields[3])==1 ? RISK_PERCENT : RISK_DOLLAR; if(count>=19 && StringToInteger(fields[3])==2) g_risk_unit=RISK_LOT; g_entry_mode=StringToInteger(fields[4])==1 ? ENTRY_LIMIT : ENTRY_MARKET; g_dollar_text=fields[5]; g_percent_text=fields[6]; g_restore_stop=(StringToInteger(fields[7])!=0); g_saved_stop=StringToDouble(fields[8]); g_restore_entry=(StringToInteger(fields[9])!=0); g_saved_entry=StringToDouble(fields[10]); if(count>=14) { g_tp_rr=(StringToInteger(fields[11])!=0); g_tp_reward=StringToDouble(fields[12]); g_tp_price=StringToDouble(fields[13]); if(g_tp_reward<=0.0) g_tp_reward=3.0; } if(count>=17) { g_steps=(int)MathMax(1,MathMin(3,StringToInteger(fields[14]))); g_stage_price[0]=StringToDouble(fields[15]); g_stage_price[1]=StringToDouble(fields[16]); } if(count>=18) g_lines_cleared=(StringToInteger(fields[17])!=0); if(count>=19) g_lot_text=fields[18]; } void ClearLines() { CommitRiskValue(); CommitTP(); SaveState(); g_lines_cleared=true; Del(N_STOP_LINE); Del(N_STOP_PRICE); Del(N_ENTRY_LINE); Del(N_ENTRY_PRICE); Del(N_TP_LINE); Del(N_TP_PRICE); for(int i=0;i<2;i++) { Del(N_STAGE_LINE[i]); Del(N_STAGE_LABEL[i]); g_stage_caption[i]=""; g_stage_x[i]=-1; g_stage_y[i]=-1; g_stage_visible[i]=false; } g_stop_initialized=false; g_entry_initialized=false; g_tp_initialized=false; g_stop_caption=""; g_entry_caption=""; g_tp_caption=""; g_stop_label_x=-1; g_stop_label_y=-1; g_entry_label_x=-1; g_entry_label_y=-1; g_tp_x=-1; g_tp_y=-1; g_stop_label_visible=false; g_entry_label_visible=false; g_tp_visible=false; SaveState(); ChartRedraw(); } void SetDefaults() { g_x=InpStartX; g_y=InpStartY; g_minimized=false; g_risk_unit=RISK_DOLLAR; g_dollar_text=DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE)*0.01,2); g_percent_text="1"; g_lot_text=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN),8); g_risk_text=g_dollar_text; g_risk_value=StringToDouble(g_risk_text); g_entry_mode=ENTRY_MARKET; g_steps=1; g_restore_stop=false; g_restore_entry=false; g_saved_stop=0.0; g_saved_entry=0.0; g_stop_price=0.0; g_entry_price=0.0; for(int i=0;i<2;i++) g_stage_price[i]=0.0; g_tp_rr=true; g_tp_reward=3.0; g_tp_price=0.0; g_tp_last_set=0.0; g_calc_volume=0.0; g_lines_cleared=false; } void ResetAll() { // Keep the panel and its inputs; only rebuild the chart levels. ClearLines(); Del(N_EXEC_LOCK); g_restore_stop=false; g_restore_entry=false; g_saved_stop=0.0; g_saved_entry=0.0; g_stop_price=0.0; g_entry_price=0.0; for(int i=0;i<2;i++) g_stage_price[i]=0.0; g_tp_price=0.0; g_tp_last_set=0.0; g_lines_cleared=false; ObjectSetString(0,N_RIGHT_TX,OBJPROP_TEXT,"EXECUTE"); ObjectSetString(0,N_RIGHT_BG,OBJPROP_TOOLTIP,"Execute the current trading plan"); CreateStopLine(); RefreshEntry(); TPControls(); SaveState(); ChartRedraw(); } void SetRiskUnit(ERiskUnit unit) { CommitRiskValue(); g_risk_unit=unit; g_risk_text=unit==RISK_DOLLAR ? g_dollar_text : (unit==RISK_PERCENT ? g_percent_text : g_lot_text); g_risk_value=StringToDouble(g_risk_text); ObjectSetString(0,N_RISK_EDIT,OBJPROP_TEXT,g_risk_text); UpdateRiskUnit(); SaveState(); } string EntryDirection(color &line_color) { line_color=C_MUTED; bool market=(g_entry_mode==ENTRY_MARKET); string neutral=market ? "MARKET -" : "PENDING -"; if(ObjectFind(0,N_ENTRY_LINE)<0 || ObjectFind(0,N_STOP_LINE)<0) return neutral; double current=CurrentChartPrice(); if(current<=0.0) return neutral; double entry=market ? current : ObjectGetDouble(0,N_ENTRY_LINE,OBJPROP_PRICE); double stop=ObjectGetDouble(0,N_STOP_LINE,OBJPROP_PRICE); // Stop versus entry determines side; entry versus market determines order type. double distance=entry-stop; if(MathAbs(distance)<_Point*0.5) return neutral; bool buy=(distance>0.0); line_color=buy ? C'46,196,145' : C'232,103,113'; if(market) return buy ? "BUY MARKET" : "SELL MARKET"; double offset=entry-current; if(MathAbs(offset)<_Point*0.5) return buy ? "BUY PENDING" : "SELL PENDING"; if(buy) return offset>0.0 ? "BUY STOP" : "BUY LIMIT"; return offset<0.0 ? "SELL STOP" : "SELL LIMIT"; } void SetTextIfChanged(string name,string value) { if(ObjectGetString(0,name,OBJPROP_TEXT)!=value) ObjectSetString(0,name,OBJPROP_TEXT,value); } string PositionVolumeText(double &estimated_loss,string line="") { if(line=="") line=N_ENTRY_LINE; estimated_loss=-1.0; g_calc_volume=0.0; if(ObjectFind(0,line)<0 || ObjectFind(0,N_STOP_LINE)<0) return "Lots: --"; double entry=ObjectGetDouble(0,line,OBJPROP_PRICE); double stop=ObjectGetDouble(0,N_STOP_LINE,OBJPROP_PRICE); if(MathAbs(entry-stop)<_Point*0.5) return "Lots: --"; bool buy=(entry>stop); if(line!=N_ENTRY_LINE && buy!=(ObjectGetDouble(0,N_ENTRY_LINE,OBJPROP_PRICE)>stop)) return "Lots: -- (SL side)"; if(g_entry_mode==ENTRY_MARKET && line==N_ENTRY_LINE) { MqlTick tick; if(!SymbolInfoTick(_Symbol,tick)) return "Lots: --"; entry=buy ? tick.ask : tick.bid; } if(entry<=0.0 || stop<=0.0) return "Lots: --"; // OrderCalcProfit returns money in account currency, including conversion. double risk=g_risk_unit==RISK_PERCENT ? AccountInfoDouble(ACCOUNT_BALANCE)*g_risk_value/100.0 : g_risk_value; double minimum=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); double maximum=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); double step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); if(!MathIsValidNumber(risk) || risk<0.0 || minimum<=0.0 || maximum1e-10) digits++; double loss=0.0; if(!OrderCalcProfit(buy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL, _Symbol,minimum,entry,stop,loss) || !MathIsValidNumber(loss) || loss>=0.0) return "Lots: --"; double raw=g_risk_unit==RISK_LOT ? MathMax(minimum,g_risk_value) : risk/(-loss)*minimum; if(!MathIsValidNumber(raw)) return "Lots: --"; // Round down so the displayed volume does not exceed the requested risk. double volume=NormalizeDouble(MathFloor(MathMin(raw,maximum)/step)*step,digits); if(volume0.0) { loss*=tick.bid; converted=true; break; } if(inverse && tick.ask>0.0) { loss/=tick.ask; converted=true; break; } } if(!converted) return title+" USD: --"; } return title+": $"+DoubleToString(loss,2); } string StopLossText() { double loss=0.0; for(int i=0;i1 ? "Total loss" : "Loss"); } void StepControls() { for(int i=0;i<3;i++) { bool active=(g_steps==i+1); RectStyle(N_STEPS_BG[i],active ? C_ACCENT : C_BTN,active ? C_ACCENT : C_FRAME); ObjectSetInteger(0,N_STEPS_TX[i],OBJPROP_COLOR,active ? C_TEXT : C_MUTED); } } void RefreshStages() { if(ObjectFind(0,N_ENTRY_LINE)<0 || ObjectFind(0,N_STOP_LINE)<0) return; double first=ObjectGetDouble(0,N_ENTRY_LINE,OBJPROP_PRICE); double stop=ObjectGetDouble(0,N_STOP_LINE,OBJPROP_PRICE); double current=CurrentChartPrice(); for(int i=0;i<2;i++) { if(i+2>g_steps) { if(ObjectFind(0,N_STAGE_LINE[i])>=0) g_stage_price[i]=ObjectGetDouble(0,N_STAGE_LINE[i],OBJPROP_PRICE); Del(N_STAGE_LINE[i]); Del(N_STAGE_LABEL[i]); g_stage_caption[i]=""; g_stage_x[i]=-1; g_stage_y[i]=-1; g_stage_visible[i]=false; continue; } if(ObjectFind(0,N_STAGE_LINE[i])<0) { if(g_stage_price[i]<=0.0) g_stage_price[i]=StopPrice(first+(stop-first)*(i+1)/3.0); if(!ObjectCreate(0,N_STAGE_LINE[i],OBJ_HLINE,0,0,g_stage_price[i])) continue; ObjectSetInteger(0,N_STAGE_LINE[i],OBJPROP_BACK,true); ObjectSetInteger(0,N_STAGE_LINE[i],OBJPROP_STYLE,STYLE_DASHDOT); ObjectSetInteger(0,N_STAGE_LINE[i],OBJPROP_SELECTABLE,true); ObjectSetInteger(0,N_STAGE_LINE[i],OBJPROP_SELECTED,true); ObjectSetInteger(0,N_STAGE_LINE[i],OBJPROP_HIDDEN,true); ObjectSetInteger(0,N_STAGE_LINE[i],OBJPROP_ZORDER,5); if(!Label(N_STAGE_LABEL[i],"",0,0,FS(8),C_ACCENT,"Segoe UI Semibold",6)) { Del(N_STAGE_LINE[i]); continue; } ObjectSetInteger(0,N_STAGE_LABEL[i],OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER); Visible(N_STAGE_LABEL[i],false); } double entry=ObjectGetDouble(0,N_STAGE_LINE[i],OBJPROP_PRICE); bool buy=(first>stop); string direction=buy ? (entry>current ? "BUY STOP" : "BUY LIMIT") : (entry=0) { double moved=ObjectGetDouble(0,N_TP_LINE,OBJPROP_PRICE); if(MathAbs(moved-g_tp_last_set)>_Point*0.1) { g_tp_rr=false; g_tp_price=moved; g_tp_last_set=moved; TPControls(); } } double entry=ObjectGetDouble(0,N_ENTRY_LINE,OBJPROP_PRICE); double stop=ObjectGetDouble(0,N_STOP_LINE,OBJPROP_PRICE); bool buy=entry>stop; if(g_entry_mode==ENTRY_MARKET) { MqlTick tick; if(!SymbolInfoTick(_Symbol,tick)) return; entry=buy ? tick.ask : tick.bid; } if(entry<=0.0 || MathAbs(entry-stop)<_Point*0.5) return; bool seed_price=(!g_tp_rr && g_tp_price<=0.0); double target=(g_tp_rr || seed_price) ? StopPrice(entry+(entry-stop)*g_tp_reward) : g_tp_price; if(!MathIsValidNumber(target) || target<=0.0) return; if(!g_tp_initialized) { if(!ObjectCreate(0,N_TP_LINE,OBJ_HLINE,0,0,target)) return; ObjectSetInteger(0,N_TP_LINE,OBJPROP_BACK,true); ObjectSetInteger(0,N_TP_LINE,OBJPROP_COLOR,C'46,196,145'); ObjectSetInteger(0,N_TP_LINE,OBJPROP_STYLE,STYLE_DASH); ObjectSetInteger(0,N_TP_LINE,OBJPROP_SELECTABLE,true); ObjectSetInteger(0,N_TP_LINE,OBJPROP_SELECTED,true); ObjectSetInteger(0,N_TP_LINE,OBJPROP_HIDDEN,true); ObjectSetInteger(0,N_TP_LINE,OBJPROP_ZORDER,5); if(!Label(N_TP_PRICE,"",0,0,FS(8),C'46,196,145',"Segoe UI Semibold",6)) { Del(N_TP_LINE); return; } ObjectSetInteger(0,N_TP_PRICE,OBJPROP_ANCHOR,ANCHOR_RIGHT_LOWER); Visible(N_TP_PRICE,false); g_tp_initialized=true; } if(ObjectGetDouble(0,N_TP_LINE,OBJPROP_PRICE)!=target) ObjectSetDouble(0,N_TP_LINE,OBJPROP_PRICE,target); g_tp_last_set=target; g_tp_price=target; if(seed_price) TPControls(); double profit=0.0; bool valid=true; for(int i=0;i0.0 && !OrderCalcProfit(buy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL, _Symbol,g_calc_volume,open,target,part)) { valid=false; break; } profit+=part; } string money="Profit: --"; if(valid) money=MoneyUSD(profit,g_steps>1 ? "Total profit" : "Profit"); double rr=(target-entry)/(entry-stop); string prefix="TP | "+money+" | R:R "+DoubleToString(rr,2)+" |"; UpdatePriceLabel(N_TP_LINE,N_TP_PRICE,prefix,false,g_tp_price,g_tp_caption,g_tp_x,g_tp_y,g_tp_visible); } void CommitTP() { string value=ObjectGetString(0,N_TP_EDIT,OBJPROP_TEXT); StringTrimLeft(value); StringTrimRight(value); bool digit=false,decimal=false,valid=true; for(int i=0;i='0' && c<='9') digit=true; else if(c=='.' && !decimal) decimal=true; else { valid=false; break; } } double number=StringToDouble(value); if(valid && digit && MathIsValidNumber(number) && number>0.0) { if(g_tp_rr) g_tp_reward=number; else g_tp_price=StopPrice(number); } TPControls(); RefreshTP(); SaveState(); ChartRedraw(); } void UpdateEntryLabel() { if(g_entry_initialized) { color line_color; double estimated_loss; string direction=EntryDirection(line_color); if((color)ObjectGetInteger(0,N_ENTRY_LINE,OBJPROP_COLOR)!=line_color) { ObjectSetInteger(0,N_ENTRY_LINE,OBJPROP_COLOR,line_color); ObjectSetInteger(0,N_ENTRY_PRICE,OBJPROP_COLOR,line_color); } UpdatePriceLabel(N_ENTRY_LINE,N_ENTRY_PRICE, (g_steps>1 ? "#1 " : "")+direction+" | "+PositionVolumeText(estimated_loss)+" |",true, g_entry_price,g_entry_caption,g_entry_label_x, g_entry_label_y,g_entry_label_visible); RefreshStages(); UpdateStopLabel(); RefreshTP(); } } void RefreshEntry() { if(g_lines_cleared) return; bool market=(g_entry_mode==ENTRY_MARKET); double current=CurrentChartPrice(); if(!g_entry_initialized) { if(current<=0.0 && (market || !g_restore_entry)) return; double price=current; if(!market) { if(g_restore_entry) price=g_saved_entry; else { if(ObjectFind(0,N_STOP_LINE)<0) return; price=StopPrice((current+ObjectGetDouble(0,N_STOP_LINE,OBJPROP_PRICE))*0.5); } } if(!ObjectCreate(0,N_ENTRY_LINE,OBJ_HLINE,0,0,price)) return; ObjectSetInteger(0,N_ENTRY_LINE,OBJPROP_COLOR,C_ACCENT); ObjectSetInteger(0,N_ENTRY_LINE,OBJPROP_STYLE,STYLE_SOLID); ObjectSetInteger(0,N_ENTRY_LINE,OBJPROP_WIDTH,1); ObjectSetInteger(0,N_ENTRY_LINE,OBJPROP_BACK,true); ObjectSetInteger(0,N_ENTRY_LINE,OBJPROP_SELECTABLE,!market); ObjectSetInteger(0,N_ENTRY_LINE,OBJPROP_SELECTED,!market); ObjectSetInteger(0,N_ENTRY_LINE,OBJPROP_HIDDEN,true); ObjectSetInteger(0,N_ENTRY_LINE,OBJPROP_ZORDER,5); ObjectSetString(0,N_ENTRY_LINE,OBJPROP_TOOLTIP, market ? "Market entry | follows current price" : "Pending entry | Limit / Stop | drag to move"); if(!Label(N_ENTRY_PRICE,"",0,0,FS(8),C_ACCENT,"Segoe UI Semibold",6)) { Del(N_ENTRY_LINE); return; } ObjectSetInteger(0,N_ENTRY_PRICE,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER); Visible(N_ENTRY_PRICE,false); g_entry_initialized=true; } // Limit placement happens only on mode entry; ticks never move a manual limit. if(market && current>0.0 && ObjectFind(0,N_ENTRY_LINE)>=0 && ObjectGetDouble(0,N_ENTRY_LINE,OBJPROP_PRICE)!=current) ObjectSetDouble(0,N_ENTRY_LINE,OBJPROP_PRICE,current); UpdateEntryLabel(); } void UpdateEntryModeStyle() { bool market=(g_entry_mode==ENTRY_MARKET); RectStyle(N_MARKET_BG,market ? C_ACCENT : C_BTN,market ? C_ACCENT : C_FRAME); RectStyle(N_LIMIT_BG,market ? C_BTN : C_ACCENT,market ? C_FRAME : C_ACCENT); ObjectSetInteger(0,N_MARKET_TX,OBJPROP_COLOR,market ? C_TEXT : C_MUTED); ObjectSetInteger(0,N_LIMIT_TX,OBJPROP_COLOR,market ? C_MUTED : C_TEXT); SetTextIfChanged(N_MARKET_TX,"Market"); SetTextIfChanged(N_LIMIT_TX,"Limit"); } void SetEntryMode(EEntryMode mode) { if(mode==g_entry_mode) return; g_entry_mode=mode; g_restore_entry=false; Del(N_ENTRY_LINE); Del(N_ENTRY_PRICE); g_entry_initialized=false; g_entry_caption=""; g_entry_label_x=-1; g_entry_label_y=-1; g_entry_label_visible=false; UpdateEntryModeStyle(); RefreshEntry(); ChartRedraw(); } bool CreateStopLine() { if(g_lines_cleared) return false; double price=g_saved_stop; if(!g_restore_stop) { double low=ChartGetDouble(0,CHART_PRICE_MIN,0); double high=ChartGetDouble(0,CHART_PRICE_MAX,0); MqlTick tick; if(high<=low || !SymbolInfoTick(_Symbol,tick)) return false; double current=tick.bid; if(current<=0.0) current=tick.last; if(current<=0.0) current=tick.ask; if(current<=0.0) return false; // In the bottom 20% of the visible range, place the stop above price. double edge=(current-low<=(high-low)*0.20) ? high : low; price=StopPrice((current+edge)*0.5); } if(!ObjectCreate(0,N_STOP_LINE,OBJ_HLINE,0,0,price)) return false; ObjectSetInteger(0,N_STOP_LINE,OBJPROP_COLOR,C'232,103,113'); ObjectSetInteger(0,N_STOP_LINE,OBJPROP_STYLE,STYLE_DASH); ObjectSetInteger(0,N_STOP_LINE,OBJPROP_WIDTH,1); ObjectSetInteger(0,N_STOP_LINE,OBJPROP_BACK,true); ObjectSetInteger(0,N_STOP_LINE,OBJPROP_SELECTABLE,true); ObjectSetInteger(0,N_STOP_LINE,OBJPROP_SELECTED,true); ObjectSetInteger(0,N_STOP_LINE,OBJPROP_HIDDEN,true); ObjectSetInteger(0,N_STOP_LINE,OBJPROP_ZORDER,5); ObjectSetString(0,N_STOP_LINE,OBJPROP_TOOLTIP,"Stop loss | drag to move"); if(!Label(N_STOP_PRICE,"",0,0,FS(8),C'232,103,113',"Segoe UI Semibold",6)) { Del(N_STOP_LINE); return false; } ObjectSetInteger(0,N_STOP_PRICE,OBJPROP_ANCHOR,ANCHOR_RIGHT_LOWER); Visible(N_STOP_PRICE,false); g_stop_initialized=true; UpdateStopLabel(); ChartRedraw(); return true; } //==================================================================== // Size / layout //==================================================================== void UpdateRiskUnit() { bool dollar=(g_risk_unit==RISK_DOLLAR); RectStyle(N_DOLLAR_BG,dollar ? C_ACCENT : C_BTN,dollar ? C_ACCENT : C_FRAME); bool percent=(g_risk_unit==RISK_PERCENT); bool lot=(g_risk_unit==RISK_LOT); RectStyle(N_PERCENT_BG,percent ? C_ACCENT : C_BTN,percent ? C_ACCENT : C_FRAME); RectStyle(N_LOT_BG,lot ? C_ACCENT : C_BTN,lot ? C_ACCENT : C_FRAME); ObjectSetInteger(0,N_DOLLAR_TX,OBJPROP_COLOR,dollar ? C_TEXT : C_MUTED); ObjectSetInteger(0,N_PERCENT_TX,OBJPROP_COLOR,percent ? C_TEXT : C_MUTED); ObjectSetInteger(0,N_LOT_TX,OBJPROP_COLOR,lot ? C_TEXT : C_MUTED); ChartRedraw(); } void CommitRiskValue() { string value=ObjectGetString(0,N_RISK_EDIT,OBJPROP_TEXT); StringTrimLeft(value); StringTrimRight(value); bool digit=false,decimal=false,valid=true; for(int i=0;i='0' && c<='9') digit=true; else if(c=='.' && !decimal) decimal=true; else { valid=false; break; } } double number=StringToDouble(value); if(valid && digit && MathIsValidNumber(number) && number>=0.0) { // Preserve the user's numeric text; broker volume limits are applied by PositionVolumeText. g_risk_value=number; g_risk_text=value; if(g_risk_unit==RISK_DOLLAR) g_dollar_text=value; else if(g_risk_unit==RISK_PERCENT) g_percent_text=value; else g_lot_text=value; } ObjectSetString(0,N_RISK_EDIT,OBJPROP_TEXT,g_risk_text); SaveState(); ChartRedraw(); } int PanelHeight() { if(g_minimized) return S(HEADER_H); return S(HEADER_H + BODY_H); } void Layout(bool redraw=true) { int w=S(BASE_W); int h=PanelHeight(); XYWH(N_SHADOW,g_x+S(3),g_y+S(4),w,h); XYWH(N_FRAME,g_x,g_y,w,h); XYWH(N_BODY,g_x+S(1),g_y+S(1),w-S(2),h-S(2)); XYWH(N_HEADER,g_x+S(1),g_y+S(1),w-S(2),S(HEADER_H)-S(1)); XYWH(N_TOPLINE,g_x+S(1),g_y+S(1),w-S(2),S(2)); XY(N_TITLE,g_x+S(6),g_y+S(5)); XY(N_SUBTITLE,g_x+S(176),g_y+S(6)); XYWH(N_MIN_BG,g_x+w-S(46),g_y+S(3),S(20),S(20)); CenterText(N_MIN_TX,g_x+w-S(46),g_y+S(3),S(20),S(20)); XYWH(N_CLOSE_BG,g_x+w-S(24),g_y+S(3),S(20),S(20)); CenterText(N_CLOSE_TX,g_x+w-S(24),g_y+S(3),S(20),S(20)); ObjectSetString(0,N_MIN_TX,OBJPROP_TEXT,g_minimized ? "+" : "–"); bool show=!g_minimized; Visible(N_LEFT_BG,show); Visible(N_CLEAR_BG,show); Visible(N_CLEAR_TX,show); Visible(N_LEFT_TX,show); Visible(N_RIGHT_BG,show); Visible(N_RIGHT_TX,show); Visible(N_RISK_EDIT,show); Visible(N_DOLLAR_BG,show); Visible(N_DOLLAR_TX,show); Visible(N_PERCENT_BG,show); Visible(N_PERCENT_TX,show); Visible(N_LOT_BG,show); Visible(N_LOT_TX,show); Visible(N_MARKET_BG,show); Visible(N_MARKET_TX,show); Visible(N_LIMIT_BG,show); Visible(N_LIMIT_TX,show); Visible(N_TP_TITLE,show); Visible(N_TP_EDIT,show); Visible(N_TP_RR_BG,show); Visible(N_TP_RR_TX,show); Visible(N_TP_VALUE_BG,show); Visible(N_TP_VALUE_TX,show); Visible(N_STEPS_TITLE,show); Visible(N_DAY_TITLE,show); Visible(N_DAY_VALUE,show); for(int i=0;i<3;i++) { Visible(N_STEPS_BG[i],show); Visible(N_STEPS_TX[i],show); } if(show) { int left =g_x+S(OUTER_PAD); int right=g_x+w-S(OUTER_PAD); int y=g_y+S(HEADER_H+BODY_H-OUTER_PAD-BUTTON_H); int risk_y=g_y+S(HEADER_H+8); XYWH(N_RISK_EDIT,left,risk_y,S(126),S(28)); XYWH(N_DOLLAR_BG,left+S(134),risk_y,S(36),S(28)); CenterText(N_DOLLAR_TX,left+S(134),risk_y,S(36),S(28)); XYWH(N_PERCENT_BG,left+S(174),risk_y,S(36),S(28)); CenterText(N_PERCENT_TX,left+S(174),risk_y,S(36),S(28)); XYWH(N_LOT_BG,left+S(214),risk_y,S(44),S(28)); CenterText(N_LOT_TX,left+S(214),risk_y,S(44),S(28)); int mode_y=risk_y+S(34); XYWH(N_MARKET_BG,left,mode_y,S(127),S(26)); CenterText(N_MARKET_TX,left,mode_y,S(127),S(26)); XYWH(N_LIMIT_BG,left+S(131),mode_y,S(127),S(26)); CenterText(N_LIMIT_TX,left+S(131),mode_y,S(127),S(26)); int tp_y=mode_y+S(34); XY(N_TP_TITLE,left,tp_y+S(6)); XYWH(N_TP_EDIT,left+S(24),tp_y,S(120),S(28)); XYWH(N_TP_RR_BG,left+S(150),tp_y,S(48),S(28)); CenterText(N_TP_RR_TX,left+S(150),tp_y,S(48),S(28)); XYWH(N_TP_VALUE_BG,left+S(202),tp_y,S(56),S(28)); CenterText(N_TP_VALUE_TX,left+S(202),tp_y,S(56),S(28)); int steps_y=tp_y+S(34); XY(N_DAY_TITLE,left,steps_y+S(34)); ObjectSetInteger(0,N_DAY_VALUE,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER); XY(N_DAY_VALUE,right,steps_y+S(34)); XY(N_STEPS_TITLE,left,steps_y+S(5)); for(int i=0;i<3;i++) { XYWH(N_STEPS_BG[i],left+S(102+i*52),steps_y,S(50),S(26)); CenterText(N_STEPS_TX[i],left+S(102+i*52),steps_y,S(50),S(26)); } XYWH(N_LEFT_BG,left,y,S(72),S(BUTTON_H)); CenterText(N_LEFT_TX,left,y,S(72),S(BUTTON_H)); XYWH(N_CLEAR_BG,left+S(78),y,S(70),S(BUTTON_H)); CenterText(N_CLEAR_TX,left+S(78),y,S(70),S(BUTTON_H)); XYWH(N_RIGHT_BG,right-S(104),y,S(104),S(BUTTON_H)); CenterText(N_RIGHT_TX,right-S(104),y,S(104),S(BUTTON_H)); } if(redraw) ChartRedraw(); } //==================================================================== // Build / delete //==================================================================== void DeleteAll() { string names[]= { N_SHADOW,N_FRAME,N_BODY,N_HEADER,N_TOPLINE, N_TITLE,N_SUBTITLE, N_MIN_BG,N_MIN_TX,N_CLOSE_BG,N_CLOSE_TX, N_LEFT_BG,N_LEFT_TX,N_RIGHT_BG,N_RIGHT_TX,N_CLEAR_BG,N_CLEAR_TX, N_RISK_EDIT,N_DOLLAR_BG,N_DOLLAR_TX,N_PERCENT_BG,N_PERCENT_TX,N_LOT_BG,N_LOT_TX, N_STOP_LINE,N_STOP_PRICE,N_ENTRY_LINE,N_ENTRY_PRICE, N_MARKET_BG,N_MARKET_TX,N_LIMIT_BG,N_LIMIT_TX, N_TP_LINE,N_TP_PRICE,N_TP_TITLE,N_TP_EDIT,N_TP_RR_BG,N_TP_RR_TX,N_TP_VALUE_BG,N_TP_VALUE_TX }; for(int i=0;i(int)cw) g_x=(int)MathMax(0,cw-w); if(g_y+visible_h>(int)ch) g_y=(int)MathMax(0,ch-visible_h); } void BeginDrag(int mx,int my) { g_dragging=true; g_drag_dx=mx-g_x; g_drag_dy=my-g_y; long scroll=1; ChartGetInteger(0,CHART_MOUSE_SCROLL,0,scroll); g_chart_scroll_before=(scroll!=0); ChartSetInteger(0,CHART_MOUSE_SCROLL,false); } void EndDrag() { if(!g_dragging) return; g_dragging=false; KeepInside(); Layout(); ChartSetInteger(0,CHART_MOUSE_SCROLL,g_chart_scroll_before); } color ExecuteRiskColor(double percent) { if(percent<=1.0) return C'164,224,180'; if(percent<3.0) return C'24,142,72'; double t=MathMin(1.0,(percent-3.0)/7.0); int red=(int)MathRound(24+(230-24)*t); int green=(int)MathRound(142*(1.0-t)); int blue=(int)MathRound(72*(1.0-t)); return (color)(red | (green<<8) | (blue<<16)); } void UpdateExecuteRisk() { if(ObjectFind(0,N_RIGHT_BG)<0) return; double balance=AccountInfoDouble(ACCOUNT_BALANCE); double total=0.0; bool valid=(!g_lines_cleared && balance>0.0); for(int i=0;i>8)&255)*4/5)<<8) | ((((rgb>>16)&255)*4/5)<<16)); } string tip=valid ? "Total risk: "+DoubleToString(percent,2)+"% of balance | "+ DoubleToString(total,2)+" "+AccountInfoString(ACCOUNT_CURRENCY)+ " | "+IntegerToString(g_steps)+" entries" : "Risk unavailable: check entry, stop and balance"; if(valid && percent>=10.0) tip+=" | HIGH RISK"; bool changed=((color)ObjectGetInteger(0,N_RIGHT_BG,OBJPROP_BGCOLOR)!=bg || (color)ObjectGetInteger(0,N_RIGHT_TX,OBJPROP_COLOR)!=fg || ObjectGetString(0,N_RIGHT_BG,OBJPROP_TOOLTIP)!=tip); if(!changed) return; RectStyle(N_RIGHT_BG,bg,bg); ObjectSetInteger(0,N_RIGHT_TX,OBJPROP_COLOR,fg); ObjectSetString(0,N_RIGHT_BG,OBJPROP_TOOLTIP,tip); ObjectSetString(0,N_RIGHT_TX,OBJPROP_TOOLTIP,tip); ChartRedraw(); } void ResetPressed() { RectStyle(N_MIN_BG,C_BTN,C_BTN); RectStyle(N_CLOSE_BG,C_CLOSE,C_CLOSE); RectStyle(N_LEFT_BG,C_BTN,C_FRAME); UpdateExecuteRisk(); ChartRedraw(); } //==================================================================== // Lifecycle //==================================================================== void ExecutionError(string message) { Print("RiskManagement: ",message); Alert("RiskManagement: ",message); ObjectSetString(0,N_RIGHT_BG,OBJPROP_TOOLTIP,message); } bool PrepareRequest(int stage,MqlTradeRequest &request,string &error) { ZeroMemory(request); string line=stage==0 ? N_ENTRY_LINE : N_STAGE_LINE[stage-1]; if(ObjectFind(0,line)<0 || ObjectFind(0,N_STOP_LINE)<0 || ObjectFind(0,N_TP_LINE)<0) { error="Missing entry, SL or TP line."; return false; } MqlTick tick; if(!SymbolInfoTick(_Symbol,tick) || tick.ask<=0.0 || tick.bid<=0.0) { error="No valid quote."; return false; } double first=ObjectGetDouble(0,N_ENTRY_LINE,OBJPROP_PRICE); double sl=StopPrice(ObjectGetDouble(0,N_STOP_LINE,OBJPROP_PRICE)); double tp=StopPrice(ObjectGetDouble(0,N_TP_LINE,OBJPROP_PRICE)); bool buy=first>sl; bool market=(stage==0 && g_entry_mode==ENTRY_MARKET); double price=market ? (buy ? tick.ask : tick.bid) : StopPrice(ObjectGetDouble(0,line,OBJPROP_PRICE)); double loss; PositionVolumeText(loss,line); if(loss<0.0 || g_calc_volume<=0.0) { error="Invalid risk/SL or volume below broker minimum."; return false; } if(price<=0.0 || sl<=0.0 || tp<=0.0 || (buy && (sl>=price || tp<=price)) || (!buy && (sl<=price || tp>=price))) { error="SL and TP must be on the correct sides of every entry."; return false; } double gap=SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point; double reference=market ? (buy ? tick.bid : tick.ask) : price; if((buy && (reference-slquote ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP); } request.symbol=_Symbol; request.magic=InpMagicNumber; request.volume=g_calc_volume; request.price=price; request.sl=sl; request.tp=tp; request.deviation=InpDeviationPoints; request.type_time=ORDER_TIME_GTC; request.type_filling=ORDER_FILLING_RETURN; if(market) { long flags=SymbolInfoInteger(_Symbol,SYMBOL_FILLING_MODE); if((flags&SYMBOL_FILLING_FOK)!=0) request.type_filling=ORDER_FILLING_FOK; else if((flags&SYMBOL_FILLING_IOC)!=0) request.type_filling=ORDER_FILLING_IOC; else if(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_MARKET) { error="No supported market filling policy."; return false; } } request.comment="RiskManagement #"+IntegerToString(stage+1); return true; } void ExecutePlan() { if(g_executing) return; if(g_lines_cleared) { ExecutionError("Use Reset to create a trading plan first."); return; } if(!TerminalInfoInteger(TERMINAL_CONNECTED) || !TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || !MQLInfoInteger(MQL_TRADE_ALLOWED) || !AccountInfoInteger(ACCOUNT_TRADE_ALLOWED) || !AccountInfoInteger(ACCOUNT_TRADE_EXPERT)) { ExecutionError("Trading is unavailable. Check connection and Algo Trading permissions."); return; } // Netting adds to or offsets an existing position, including unrelated trades. if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING && PositionSelect(_Symbol)) { ExecutionError("An existing netting position on this symbol must be managed before starting a new plan."); return; } CommitRiskValue(); CommitTP(); RefreshEntry(); MqlTradeRequest requests[3]; string error; double total_margin=0.0; for(int i=0;iAccountInfoDouble(ACCOUNT_MARGIN_FREE)) { ExecutionError("Insufficient free margin for all entries."); return; } // Guard only the active submission; later clicks may execute the same plan again. g_executing=true; int accepted=0; string details=""; for(int i=0;i1.50) g_scale=1.50; // Chart-local storage is also symbol-specific; never reuse another symbol's prices. // Legacy state has no symbol identity and therefore cannot be safely migrated. N_STATE="RM_STATE_V2_"+_Symbol; N_EXEC_LOCK="RM_EXEC_"+IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN))+"_"+_Symbol; // Remove the persistent submission guard left by earlier versions. Del(N_EXEC_LOCK); SetDefaults(); LoadState(); g_risk_text=g_risk_unit==RISK_DOLLAR ? g_dollar_text : (g_risk_unit==RISK_PERCENT ? g_percent_text : g_lot_text); g_risk_value=StringToDouble(g_risk_text); g_prev_left=false; g_dragging=false; g_pressed=PRESS_NONE; ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true); if(!Build()) return INIT_FAILED; // Retry from the timer if chart geometry or quotes are not ready yet. CreateStopLine(); RefreshEntry(); if(!EventSetMillisecondTimer(50)) return INIT_FAILED; g_state_ready=true; SaveState(); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { EventKillTimer(); if(g_state_ready && ObjectFind(0,N_RISK_EDIT)>=0) CommitRiskValue(); if(g_state_ready && ObjectFind(0,N_TP_EDIT)>=0) CommitTP(); SaveState(); g_state_ready=false; if(g_dragging) ChartSetInteger(0,CHART_MOUSE_SCROLL,g_chart_scroll_before); DeleteAll(); } void OnTick() { RefreshEntry(); } void UpdateDailyPL() { ulong now_ms=GetTickCount64(); if(g_day_refresh!=0 && now_ms-g_day_refresh<5000) return; g_day_refresh=now_ms; datetime now=TimeTradeServer(); if(now<=0) now=TimeCurrent(); MqlDateTime day; if(now<=0 || !TimeToStruct(now,day)) return; day.hour=0; day.min=0; day.sec=0; datetime start=StructToTime(day); if(!HistorySelect(start,now)) { SetTextIfChanged(N_DAY_VALUE,"-- | --%"); ObjectSetInteger(0,N_DAY_VALUE,OBJPROP_COLOR,C_MUTED); return; } double net=0.0,balance_change=0.0; for(int i=0;i0.0 ? (net>0.0 ? "+" : "")+DoubleToString(net/opening*100.0,2)+"%" : "--%"; string money=MoneyUSD(net,""); if(StringFind(money,": $")==0) money=StringSubstr(money,2); else money="USD --"; if(net>0.0 && StringFind(money,"$")==0) money="+"+money; string text=money+" | "+percent; SetTextIfChanged(N_DAY_VALUE,text); ObjectSetInteger(0,N_DAY_VALUE,OBJPROP_COLOR,net>0.005 ? C'46,196,145' : (net< -0.005 ? C'232,103,113' : C_MUTED)); ObjectSetString(0,N_DAY_VALUE,OBJPROP_TOOLTIP, "All account trades | Broker day "+TimeToString(start,TIME_DATE)+ " | Realized P/L + commissions, swap and fees booked today (including entry fees). Floating P/L excluded."+ " | Opening balance: "+DoubleToString(opening,2)+" "+AccountInfoString(ACCOUNT_CURRENCY)); ChartRedraw(); } void OnTradeTransaction(const MqlTradeTransaction &transaction,const MqlTradeRequest &request,const MqlTradeResult &result) { g_day_refresh=0; } void OnTimer() { UpdateDailyPL(); if(!g_stop_initialized) CreateStopLine(); else UpdateStopLabel(); RefreshEntry(); SaveState(); UpdateExecuteRisk(); } //==================================================================== // Events //==================================================================== void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id==CHARTEVENT_OBJECT_CLICK && !g_minimized && (sparam==N_CLEAR_BG || sparam==N_CLEAR_TX)) { ClearLines(); return; } for(int i=0;i<2;i++) if((id==CHARTEVENT_OBJECT_DRAG || id==CHARTEVENT_OBJECT_CHANGE) && sparam==N_STAGE_LINE[i]) { ObjectSetDouble(0,N_STAGE_LINE[i],OBJPROP_PRICE,StopPrice(ObjectGetDouble(0,N_STAGE_LINE[i],OBJPROP_PRICE))); UpdateEntryLabel(); SaveState(); ChartRedraw(); return; } if(id==CHARTEVENT_OBJECT_CLICK && !g_minimized) for(int i=0;i<3;i++) if(sparam==N_STEPS_BG[i] || sparam==N_STEPS_TX[i]) { CommitRiskValue(); g_steps=i+1; StepControls(); UpdateEntryLabel(); SaveState(); ChartRedraw(); return; } if(id==CHARTEVENT_OBJECT_ENDEDIT && sparam==N_TP_EDIT) { CommitTP(); return; } if((id==CHARTEVENT_OBJECT_DRAG || id==CHARTEVENT_OBJECT_CHANGE) && sparam==N_TP_LINE) { g_tp_rr=false; g_tp_price=StopPrice(ObjectGetDouble(0,N_TP_LINE,OBJPROP_PRICE)); ObjectSetDouble(0,N_TP_LINE,OBJPROP_PRICE,g_tp_price); g_tp_last_set=g_tp_price; TPControls(); RefreshTP(); SaveState(); ChartRedraw(); return; } if(id==CHARTEVENT_CHART_CHANGE) { KeepInside(); Layout(); UpdateStopLabel(); UpdateEntryLabel(); return; } if((id==CHARTEVENT_OBJECT_DRAG || id==CHARTEVENT_OBJECT_CHANGE) && sparam==N_ENTRY_LINE) { if(g_entry_mode==ENTRY_LIMIT) ObjectSetDouble(0,N_ENTRY_LINE,OBJPROP_PRICE, StopPrice(ObjectGetDouble(0,N_ENTRY_LINE,OBJPROP_PRICE))); RefreshEntry(); ChartRedraw(); return; } if((id==CHARTEVENT_OBJECT_DRAG || id==CHARTEVENT_OBJECT_CHANGE) && sparam==N_STOP_LINE) { double price=StopPrice(ObjectGetDouble(0,N_STOP_LINE,OBJPROP_PRICE)); ObjectSetDouble(0,N_STOP_LINE,OBJPROP_PRICE,price); UpdateStopLabel(); UpdateEntryLabel(); ChartRedraw(); return; } if(id==CHARTEVENT_OBJECT_ENDEDIT && sparam==N_RISK_EDIT) { CommitRiskValue(); return; } if(id==CHARTEVENT_OBJECT_CLICK && !g_minimized) { if(sparam==N_DOLLAR_BG || sparam==N_DOLLAR_TX) { SetRiskUnit(RISK_DOLLAR); } else if(sparam==N_PERCENT_BG || sparam==N_PERCENT_TX) { SetRiskUnit(RISK_PERCENT); } else if(sparam==N_LOT_BG || sparam==N_LOT_TX) SetRiskUnit(RISK_LOT); else if(sparam==N_MARKET_BG || sparam==N_MARKET_TX) SetEntryMode(ENTRY_MARKET); else if(sparam==N_LIMIT_BG || sparam==N_LIMIT_TX) SetEntryMode(ENTRY_LIMIT); else if(sparam==N_TP_RR_BG || sparam==N_TP_RR_TX || sparam==N_TP_VALUE_BG || sparam==N_TP_VALUE_TX) { CommitTP(); g_tp_rr=(sparam==N_TP_RR_BG || sparam==N_TP_RR_TX); TPControls(); RefreshTP(); SaveState(); ChartRedraw(); } return; } if(id!=CHARTEVENT_MOUSE_MOVE) return; int mx=(int)lparam; int my=(int)dparam; // Read the actual line position during native dragging, not only on release. UpdateStopLabel(); UpdateEntryLabel(); uint state=(uint)StringToInteger(sparam); bool left=((state&1)==1); bool down=(left && !g_prev_left); bool up=(!left && g_prev_left); if(down) { g_pressed=PRESS_NONE; if(OverMin(mx,my)) { g_pressed=PRESS_MIN; RectStyle(N_MIN_BG,C_BTN_PRESS,C_BTN_PRESS); ChartRedraw(); } else if(OverClose(mx,my)) { g_pressed=PRESS_CLOSE; RectStyle(N_CLOSE_BG,C_CLOSE_PRESS,C_CLOSE_PRESS); ChartRedraw(); } else if(OverLeft(mx,my)) { g_pressed=PRESS_LEFT; RectStyle(N_LEFT_BG,C_BTN_PRESS,C_FRAME); ChartRedraw(); } else if(OverRight(mx,my)) { g_pressed=PRESS_RIGHT; UpdateExecuteRisk(); ChartRedraw(); } else if(OverHeader(mx,my)) { g_pressed=PRESS_HEADER; BeginDrag(mx,my); } } if(left && g_dragging && g_pressed==PRESS_HEADER) { g_x=mx-g_drag_dx; g_y=my-g_drag_dy; KeepInside(); Layout(false); ChartRedraw(); } if(up) { EPressTarget pressed=g_pressed; if(g_dragging) EndDrag(); g_pressed=PRESS_NONE; ResetPressed(); if(pressed==PRESS_MIN && OverMin(mx,my)) { g_minimized=!g_minimized; KeepInside(); Layout(); } else if(pressed==PRESS_CLOSE && OverClose(mx,my)) { g_prev_left=left; ExpertRemove(); return; } else if(pressed==PRESS_LEFT && OverLeft(mx,my)) { ResetAll(); } else if(pressed==PRESS_RIGHT && OverRight(mx,my)) { ExecutePlan(); } } g_prev_left=left; } //+------------------------------------------------------------------+