2026-09-27 08:30:11 -07:00 | | | //+------------------------------------------------------------------+
|
| | | //| 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;
|
2026-09-27 09:49:10 -07:00 | | | input ulong InpMagicNumber=270927;
|
| | | input uint InpDeviationPoints=20;
|
2026-09-27 08:30:11 -07:00 | | |
|
| | | //====================================================================
|
| | | // Geometry
|
| | | //====================================================================
|
| | | #define BASE_W 270
|
| | | #define HEADER_H 26
|
2026-09-28 08:38:25 +03:00 | | | #define BODY_H 210
|
2026-09-27 08:30:11 -07:00 | | | #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;
|
| | |
|
2026-09-28 08:38:25 +03:00 | | | enum ERiskUnit { RISK_DOLLAR=0, RISK_PERCENT, RISK_LOT };
|
2026-09-27 08:30:11 -07:00 | | | 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";
|
2026-09-28 08:38:25 +03:00 | | | string g_lot_text="0.01";
|
| | | string N_LOT_BG="MIN_LOT_BG",N_LOT_TX="MIN_LOT_TX";
|
2026-09-27 08:30:11 -07:00 | | | 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;
|
2026-09-27 09:49:10 -07:00 | | | string N_STATE="";
|
| | | string N_EXEC_LOCK="";
|
| | | bool g_executing=false;
|
2026-09-27 08:30:11 -07:00 | | |
|
| | | 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";
|
2026-09-27 09:13:33 -07:00 | | | int g_steps=1;
|
2026-09-27 09:42:34 -07:00 | | | bool g_lines_cleared=false;
|
| | | string N_CLEAR_BG="MIN_CLEAR_BG",N_CLEAR_TX="MIN_CLEAR_TX";
|
2026-09-27 09:13:33 -07:00 | | | string N_STEPS_TITLE="MIN_STEPS_TITLE";
|
2026-09-28 08:38:25 +03:00 | | | string N_DAY_TITLE="MIN_DAY_TITLE",N_DAY_VALUE="MIN_DAY_VALUE";
|
| | | ulong g_day_refresh=0;
|
2026-09-27 09:13:33 -07:00 | | | 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};
|
2026-09-27 08:30:11 -07:00 | | |
|
| | | //====================================================================
|
| | | // 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);
|
2026-09-28 08:38:25 +03:00 | | | // 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);
|
2026-09-27 08:30:11 -07:00 | | | 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<x+w && py>=y && py<y+h);
|
| | | }
|
| | |
|
| | | bool HitObject(string name,int mx,int my)
|
| | | {
|
| | | if(ObjectFind(0,name)<0)
|
| | | return false;
|
| | |
|
| | | if(ObjectGetInteger(0,name,OBJPROP_TIMEFRAMES)==OBJ_NO_PERIODS)
|
| | | return false;
|
| | |
|
| | | return Hit(
|
| | | mx,
|
| | | my,
|
| | | (int)ObjectGetInteger(0,name,OBJPROP_XDISTANCE),
|
| | | (int)ObjectGetInteger(0,name,OBJPROP_YDISTANCE),
|
| | | (int)ObjectGetInteger(0,name,OBJPROP_XSIZE),
|
| | | (int)ObjectGetInteger(0,name,OBJPROP_YSIZE)
|
| | | );
|
| | | }
|
| | |
|
| | | //====================================================================
|
| | | // Stop line
|
| | | //====================================================================
|
| | | double StopPrice(double price)
|
| | | {
|
| | | double step=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);
|
| | | if(step>0.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 && y<height);
|
| | | y=below ? (int)MathMax(0,MathMin(height-S(18),y+S(3)))
|
| | | : (int)MathMax(S(18),y-S(3));
|
| | | }
|
| | | }
|
| | |
|
| | | bool changed=false;
|
| | | if(show!=last_visible)
|
| | | {
|
| | | Visible(label,show);
|
| | | last_visible=show;
|
| | | changed=true;
|
| | | }
|
| | | if(show)
|
| | | {
|
| | | string caption=prefix+" "+DoubleToString(line_price,_Digits);
|
| | | if(caption!=last_caption)
|
| | | {
|
| | | ObjectSetString(0,label,OBJPROP_TEXT,caption);
|
| | | last_caption=caption;
|
| | | changed=true;
|
| | | }
|
| | | if(x!=last_x || y!=last_y)
|
| | | {
|
| | | XY(label,x,y);
|
| | | last_x=x;
|
| | | last_y=y;
|
| | | changed=true;
|
| | | }
|
| | | }
|
| | | if(changed) ChartRedraw();
|
| | | }
|
| | |
|
| | | void UpdateStopLabel()
|
| | | {
|
| | | if(g_stop_initialized)
|
| | | UpdatePriceLabel(N_STOP_LINE,N_STOP_PRICE,"SL | "+StopLossText()+" |",false,g_stop_price,
|
| | | g_stop_caption,g_stop_label_x,g_stop_label_y,g_stop_label_visible);
|
| | | }
|
| | |
|
| | | double CurrentChartPrice()
|
| | | {
|
| | | MqlTick tick;
|
| | | if(!SymbolInfoTick(_Symbol,tick)) return 0.0;
|
| | | // Match the price source used to initialize the stop line.
|
| | | if(tick.bid>0.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; }
|
2026-09-27 09:13:33 -07:00 | | | 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);
|
2026-09-27 08:30:11 -07:00 | | | 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)+"|"+
|
2026-09-27 09:13:33 -07:00 | | | IntegerToString((int)g_tp_rr)+"|"+DoubleToString(g_tp_reward,8)+"|"+DoubleToString(g_tp_price,16)+"|"+
|
2026-09-27 09:42:34 -07:00 | | | IntegerToString(g_steps)+"|"+DoubleToString(g_stage_price[0],16)+"|"+DoubleToString(g_stage_price[1],16)+"|"+
|
2026-09-28 08:38:25 +03:00 | | | IntegerToString((int)g_lines_cleared)+"|"+g_lot_text;
|
2026-09-27 08:30:11 -07:00 | | | 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);
|
2026-09-28 08:38:25 +03:00 | | | if(count!=11 && count!=14 && count!=17 && count!=18 && count!=19) return;
|
2026-09-27 08:30:11 -07:00 | | | 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;
|
2026-09-28 08:38:25 +03:00 | | | if(count>=19 && StringToInteger(fields[3])==2) g_risk_unit=RISK_LOT;
|
2026-09-27 08:30:11 -07:00 | | | 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]);
|
2026-09-27 09:13:33 -07:00 | | | if(count>=14)
|
2026-09-27 08:30:11 -07:00 | | | {
|
| | | 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;
|
| | | }
|
2026-09-27 09:42:34 -07:00 | | | if(count>=17)
|
2026-09-27 09:13:33 -07:00 | | | {
|
| | | g_steps=(int)MathMax(1,MathMin(3,StringToInteger(fields[14])));
|
| | | g_stage_price[0]=StringToDouble(fields[15]);
|
| | | g_stage_price[1]=StringToDouble(fields[16]);
|
| | | }
|
2026-09-27 09:42:34 -07:00 | | | if(count>=18) g_lines_cleared=(StringToInteger(fields[17])!=0);
|
2026-09-28 08:38:25 +03:00 | | | if(count>=19) g_lot_text=fields[18];
|
2026-09-27 09:42:34 -07:00 | | | }
|
| | |
|
| | | 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();
|
| | | }
|
| | |
|
2026-09-27 09:49:10 -07:00 | | | void SetDefaults()
|
2026-09-27 09:42:34 -07:00 | | | {
|
2026-09-27 09:49:10 -07:00 | | | 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";
|
2026-09-28 08:38:25 +03:00 | | | g_lot_text=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN),8);
|
2026-09-27 09:49:10 -07:00 | | | 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;
|
2026-09-27 09:42:34 -07:00 | | | g_lines_cleared=false;
|
2026-09-27 09:49:10 -07:00 | | | }
|
| | |
|
| | | void ResetAll()
|
| | | {
|
2026-09-28 08:38:25 +03:00 | | | // Keep the panel and its inputs; only rebuild the chart levels.
|
| | | ClearLines();
|
2026-09-27 09:49:10 -07:00 | | | Del(N_EXEC_LOCK);
|
2026-09-28 08:38:25 +03:00 | | | 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");
|
2026-09-27 09:49:10 -07:00 | | | CreateStopLine();
|
2026-09-27 09:42:34 -07:00 | | | RefreshEntry();
|
2026-09-28 08:38:25 +03:00 | | | TPControls();
|
2026-09-27 09:42:34 -07:00 | | | SaveState();
|
| | | ChartRedraw();
|
2026-09-27 08:30:11 -07:00 | | | }
|
| | |
|
| | | void SetRiskUnit(ERiskUnit unit)
|
| | | {
|
| | | CommitRiskValue();
|
| | | g_risk_unit=unit;
|
2026-09-28 08:38:25 +03:00 | | | g_risk_text=unit==RISK_DOLLAR ? g_dollar_text : (unit==RISK_PERCENT ? g_percent_text : g_lot_text);
|
2026-09-27 08:30:11 -07:00 | | | 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);
|
| | | }
|
| | |
|
2026-09-27 09:13:33 -07:00 | | | string PositionVolumeText(double &estimated_loss,string line="")
|
2026-09-27 08:30:11 -07:00 | | | {
|
2026-09-27 09:13:33 -07:00 | | | if(line=="") line=N_ENTRY_LINE;
|
2026-09-27 08:30:11 -07:00 | | | estimated_loss=-1.0;
|
| | | g_calc_volume=0.0;
|
2026-09-27 09:13:33 -07:00 | | | if(ObjectFind(0,line)<0 || ObjectFind(0,N_STOP_LINE)<0) return "Lots: --";
|
| | | double entry=ObjectGetDouble(0,line,OBJPROP_PRICE);
|
2026-09-27 08:30:11 -07:00 | | | double stop=ObjectGetDouble(0,N_STOP_LINE,OBJPROP_PRICE);
|
| | | if(MathAbs(entry-stop)<_Point*0.5) return "Lots: --";
|
| | | bool buy=(entry>stop);
|
2026-09-27 09:13:33 -07:00 | | | 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)
|
2026-09-27 08:30:11 -07:00 | | | {
|
| | | 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 ||
|
| | | maximum<minimum || step<=0.0) return "Lots: --";
|
| | | int digits=0;
|
| | | while(digits<8 && MathAbs(step-NormalizeDouble(step,digits))>1e-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: --";
|
2026-09-28 08:38:25 +03:00 | | | double raw=g_risk_unit==RISK_LOT ? MathMax(minimum,g_risk_value) : risk/(-loss)*minimum;
|
2026-09-27 08:30:11 -07:00 | | | 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(volume<minimum)
|
| | | {
|
| | | estimated_loss=0.0;
|
| | | return "Lots: "+DoubleToString(0.0,digits)+" (< min)";
|
| | | }
|
| | | estimated_loss=(-loss)*volume/minimum;
|
| | | g_calc_volume=volume;
|
| | | return "Lots: "+DoubleToString(volume,digits);
|
| | | }
|
| | |
|
| | | string MoneyUSD(double loss,string title)
|
| | | {
|
| | | string currency=AccountInfoString(ACCOUNT_CURRENCY);
|
| | | if(currency!="USD" && loss!=0.0)
|
| | | {
|
| | | // Convert using an available direct/inverse quote, including broker suffixes.
|
| | | bool converted=false;
|
| | | for(int i=0;i<SymbolsTotal(false);i++)
|
| | | {
|
| | | string symbol=SymbolName(i,false);
|
| | | string base=SymbolInfoString(symbol,SYMBOL_CURRENCY_BASE);
|
| | | string quote=SymbolInfoString(symbol,SYMBOL_CURRENCY_PROFIT);
|
| | | bool direct=(base==currency && quote=="USD");
|
| | | bool inverse=(base=="USD" && quote==currency);
|
| | | if(!direct && !inverse) continue;
|
| | | if(!SymbolSelect(symbol,true)) continue;
|
| | | MqlTick tick;
|
| | | if(!SymbolInfoTick(symbol,tick)) continue;
|
| | | if(direct && tick.bid>0.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()
|
| | | {
|
2026-09-27 09:13:33 -07:00 | | | double loss=0.0;
|
| | | for(int i=0;i<g_steps;i++)
|
| | | {
|
| | | double part;
|
| | | PositionVolumeText(part,i==0 ? N_ENTRY_LINE : N_STAGE_LINE[i-1]);
|
| | | if(part<0.0) return "Total loss: --";
|
| | | loss+=part;
|
| | | }
|
| | | return MoneyUSD(loss,g_steps>1 ? "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;
|
2026-09-28 08:38:25 +03:00 | | | ObjectSetInteger(0,N_STAGE_LINE[i],OBJPROP_BACK,true);
|
2026-09-27 09:13:33 -07:00 | | | 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<current ? "SELL STOP" : "SELL LIMIT");
|
| | | if(MathAbs(entry-current)<_Point*0.5) direction=buy ? "BUY PENDING" : "SELL PENDING";
|
| | | double loss;
|
| | | string volume=PositionVolumeText(loss,N_STAGE_LINE[i]);
|
| | | color clr=loss<0.0 ? C_MUTED : (buy ? C'46,196,145' : C'232,103,113');
|
| | | ObjectSetInteger(0,N_STAGE_LINE[i],OBJPROP_COLOR,clr);
|
| | | ObjectSetInteger(0,N_STAGE_LABEL[i],OBJPROP_COLOR,clr);
|
| | | string caption="#"+IntegerToString(i+2)+" "+direction+" | "+volume+" |";
|
| | | UpdatePriceLabel(N_STAGE_LINE[i],N_STAGE_LABEL[i],caption,true,g_stage_price[i],
|
| | | g_stage_caption[i],g_stage_x[i],g_stage_y[i],g_stage_visible[i]);
|
| | | }
|
2026-09-27 08:30:11 -07:00 | | | }
|
| | |
|
| | | void TPControls(bool text=true)
|
| | | {
|
| | | RectStyle(N_TP_RR_BG,g_tp_rr ? C_ACCENT : C_BTN,g_tp_rr ? C_ACCENT : C_FRAME);
|
| | | RectStyle(N_TP_VALUE_BG,g_tp_rr ? C_BTN : C_ACCENT,g_tp_rr ? C_FRAME : C_ACCENT);
|
| | | ObjectSetInteger(0,N_TP_RR_TX,OBJPROP_COLOR,g_tp_rr ? C_TEXT : C_MUTED);
|
| | | ObjectSetInteger(0,N_TP_VALUE_TX,OBJPROP_COLOR,g_tp_rr ? C_MUTED : C_TEXT);
|
| | | if(text) ObjectSetString(0,N_TP_EDIT,OBJPROP_TEXT,
|
| | | g_tp_rr ? DoubleToString(g_tp_reward,2) : DoubleToString(g_tp_price,_Digits));
|
| | | }
|
| | |
|
| | | void RefreshTP()
|
| | | {
|
2026-09-27 09:42:34 -07:00 | | | if(g_lines_cleared) return;
|
2026-09-27 08:30:11 -07:00 | | | if(ObjectFind(0,N_ENTRY_LINE)<0 || ObjectFind(0,N_STOP_LINE)<0) return;
|
| | | // Native dragging changes the object price before OBJECT_DRAG is delivered.
|
| | | if(g_tp_initialized && ObjectFind(0,N_TP_LINE)>=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;
|
2026-09-28 08:38:25 +03:00 | | | 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;
|
2026-09-27 08:30:11 -07:00 | | | if(!MathIsValidNumber(target) || target<=0.0) return;
|
| | | if(!g_tp_initialized)
|
| | | {
|
| | | if(!ObjectCreate(0,N_TP_LINE,OBJ_HLINE,0,0,target)) return;
|
2026-09-28 08:38:25 +03:00 | | | ObjectSetInteger(0,N_TP_LINE,OBJPROP_BACK,true);
|
2026-09-27 08:30:11 -07:00 | | | 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;
|
2026-09-28 08:38:25 +03:00 | | | if(seed_price) TPControls();
|
2026-09-27 09:13:33 -07:00 | | | double profit=0.0;
|
| | | bool valid=true;
|
| | | for(int i=0;i<g_steps;i++)
|
| | | {
|
| | | string line=i==0 ? N_ENTRY_LINE : N_STAGE_LINE[i-1];
|
| | | double loss,part=0.0;
|
| | | PositionVolumeText(loss,line);
|
| | | if(loss<0.0) { valid=false; break; }
|
| | | double open=i==0 ? entry : ObjectGetDouble(0,line,OBJPROP_PRICE);
|
| | | if(g_calc_volume>0.0 && !OrderCalcProfit(buy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL,
|
| | | _Symbol,g_calc_volume,open,target,part))
|
| | | { valid=false; break; }
|
| | | profit+=part;
|
| | | }
|
2026-09-27 08:30:11 -07:00 | | | string money="Profit: --";
|
2026-09-27 09:13:33 -07:00 | | | if(valid) money=MoneyUSD(profit,g_steps>1 ? "Total profit" : "Profit");
|
2026-09-27 08:30:11 -07:00 | | | 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<StringLen(value);i++)
|
| | | {
|
| | | ushort c=StringGetCharacter(value,i);
|
| | | if(c>='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,
|
2026-09-27 09:13:33 -07:00 | | | (g_steps>1 ? "#1 " : "")+direction+" | "+PositionVolumeText(estimated_loss)+" |",true,
|
2026-09-27 08:30:11 -07:00 | | | g_entry_price,g_entry_caption,g_entry_label_x,
|
| | | g_entry_label_y,g_entry_label_visible);
|
2026-09-27 09:13:33 -07:00 | | | RefreshStages();
|
2026-09-27 08:30:11 -07:00 | | | UpdateStopLabel();
|
| | | RefreshTP();
|
| | | }
|
| | | }
|
| | |
|
| | | void RefreshEntry()
|
| | | {
|
2026-09-27 09:42:34 -07:00 | | | if(g_lines_cleared) return;
|
2026-09-27 08:30:11 -07:00 | | | 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);
|
2026-09-28 08:38:25 +03:00 | | | ObjectSetInteger(0,N_ENTRY_LINE,OBJPROP_BACK,true);
|
2026-09-27 08:30:11 -07:00 | | | 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()
|
| | | {
|
2026-09-27 09:42:34 -07:00 | | | if(g_lines_cleared) return false;
|
2026-09-27 08:30:11 -07:00 | | | 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);
|
2026-09-28 08:38:25 +03:00 | | | ObjectSetInteger(0,N_STOP_LINE,OBJPROP_BACK,true);
|
2026-09-27 08:30:11 -07:00 | | | 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);
|
2026-09-28 08:38:25 +03:00 | | | 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);
|
2026-09-27 08:30:11 -07:00 | | | ObjectSetInteger(0,N_DOLLAR_TX,OBJPROP_COLOR,dollar ? C_TEXT : C_MUTED);
|
2026-09-28 08:38:25 +03:00 | | | ObjectSetInteger(0,N_PERCENT_TX,OBJPROP_COLOR,percent ? C_TEXT : C_MUTED);
|
| | | ObjectSetInteger(0,N_LOT_TX,OBJPROP_COLOR,lot ? C_TEXT : C_MUTED);
|
2026-09-27 08:30:11 -07:00 | | | 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<StringLen(value);i++)
|
| | | {
|
| | | ushort c=StringGetCharacter(value,i);
|
| | | if(c>='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)
|
| | | {
|
2026-09-28 08:38:25 +03:00 | | | // Preserve the user's numeric text; broker volume limits are applied by PositionVolumeText.
|
2026-09-27 08:30:11 -07:00 | | | g_risk_value=number;
|
| | | g_risk_text=value;
|
| | | if(g_risk_unit==RISK_DOLLAR) g_dollar_text=value;
|
2026-09-28 08:38:25 +03:00 | | | else if(g_risk_unit==RISK_PERCENT) g_percent_text=value;
|
| | | else g_lot_text=value;
|
2026-09-27 08:30:11 -07:00 | | | }
|
| | | 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);
|
2026-09-27 09:42:34 -07:00 | | | Visible(N_CLEAR_BG,show); Visible(N_CLEAR_TX,show);
|
2026-09-27 08:30:11 -07:00 | | | 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);
|
2026-09-28 08:38:25 +03:00 | | | Visible(N_LOT_BG,show); Visible(N_LOT_TX,show);
|
2026-09-27 08:30:11 -07:00 | | | 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);
|
2026-09-27 09:13:33 -07:00 | | | Visible(N_STEPS_TITLE,show);
|
2026-09-28 08:38:25 +03:00 | | | Visible(N_DAY_TITLE,show); Visible(N_DAY_VALUE,show);
|
2026-09-27 09:13:33 -07:00 | | | for(int i=0;i<3;i++) { Visible(N_STEPS_BG[i],show); Visible(N_STEPS_TX[i],show); }
|
2026-09-27 08:30:11 -07:00 | | |
|
| | | 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);
|
2026-09-28 08:38:25 +03:00 | | | 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));
|
2026-09-27 08:30:11 -07:00 | | |
|
| | | 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));
|
2026-09-27 09:13:33 -07:00 | | | int steps_y=tp_y+S(34);
|
2026-09-28 08:38:25 +03:00 | | | 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));
|
2026-09-27 09:13:33 -07:00 | | | 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));
|
| | | }
|
2026-09-27 08:30:11 -07:00 | | |
|
| | | XYWH(N_LEFT_BG,left,y,S(72),S(BUTTON_H));
|
| | | CenterText(N_LEFT_TX,left,y,S(72),S(BUTTON_H));
|
2026-09-27 09:42:34 -07:00 | | | 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));
|
2026-09-27 08:30:11 -07:00 | | |
|
| | | 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,
|
2026-09-27 09:42:34 -07:00 | | | N_LEFT_BG,N_LEFT_TX,N_RIGHT_BG,N_RIGHT_TX,N_CLEAR_BG,N_CLEAR_TX,
|
2026-09-28 08:38:25 +03:00 | | | N_RISK_EDIT,N_DOLLAR_BG,N_DOLLAR_TX,N_PERCENT_BG,N_PERCENT_TX,N_LOT_BG,N_LOT_TX,
|
2026-09-27 08:30:11 -07:00 | | | 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<ArraySize(names);i++)
|
| | | Del(names[i]);
|
2026-09-27 09:13:33 -07:00 | | | Del(N_STEPS_TITLE);
|
2026-09-28 08:38:25 +03:00 | | | Del(N_DAY_TITLE); Del(N_DAY_VALUE);
|
2026-09-27 09:13:33 -07:00 | | | for(int i=0;i<3;i++) { Del(N_STEPS_BG[i]); Del(N_STEPS_TX[i]); }
|
| | | 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;
|
| | | }
|
2026-09-27 08:30:11 -07:00 | | |
|
| | | g_stop_initialized=false;
|
| | | g_stop_caption="";
|
| | | g_stop_label_x=-1;
|
| | | g_stop_label_y=-1;
|
| | | g_stop_label_visible=false;
|
| | | g_entry_initialized=false;
|
| | | g_entry_caption="";
|
| | | g_entry_label_x=-1;
|
| | | g_entry_label_y=-1;
|
| | | g_entry_label_visible=false;
|
| | |
|
| | | g_tp_initialized=false; g_tp_caption="";
|
| | | g_tp_x=-1; g_tp_y=-1; g_tp_visible=false;
|
| | |
|
| | | ChartRedraw();
|
| | | }
|
| | |
|
| | | bool Build()
|
| | | {
|
| | | DeleteAll();
|
| | |
|
| | | int w=S(BASE_W);
|
| | | int h=PanelHeight();
|
| | |
|
| | | if(!Rect(N_SHADOW,g_x+S(3),g_y+S(4),w,h,C_SHADOW,C_SHADOW,0)) return false;
|
| | | if(!Rect(N_FRAME,g_x,g_y,w,h,C_FRAME,C_FRAME,1)) return false;
|
| | | if(!Rect(N_BODY,g_x+S(1),g_y+S(1),w-S(2),h-S(2),C_BODY,C_BODY,2)) return false;
|
| | |
|
| | | if(!Rect(N_HEADER,g_x,g_y,w,S(HEADER_H),C_HEADER,C_HEADER,10)) return false;
|
| | | if(!Rect(N_TOPLINE,g_x,g_y,w,S(2),C_HEADER_TOP,C_HEADER_TOP,11)) return false;
|
| | |
|
| | | if(!Label(N_TITLE,"Risk Management",g_x,g_y,FS(8),C_TEXT,"Segoe UI Semibold",20)) return false;
|
| | | if(!Label(N_SUBTITLE,"| WINDOW",g_x,g_y,FS(5),C_MUTED,"Segoe UI",20)) return false;
|
| | |
|
| | | if(!Rect(N_MIN_BG,g_x,g_y,S(14),S(14),C_BTN,C_BTN,30)) return false;
|
| | | if(!Label(N_MIN_TX,"–",g_x,g_y,FS(8),C_TEXT2,"Segoe UI Semibold",31)) return false;
|
| | |
|
| | | if(!Rect(N_CLOSE_BG,g_x,g_y,S(14),S(14),C_CLOSE,C_CLOSE,30)) return false;
|
| | | if(!Label(N_CLOSE_TX,"×",g_x,g_y,FS(8),C_TEXT,"Segoe UI Semibold",31)) return false;
|
| | |
|
| | | if(!ObjectCreate(0,N_RISK_EDIT,OBJ_EDIT,0,0,0)) return false;
|
| | | ObjectSetInteger(0,N_RISK_EDIT,OBJPROP_CORNER,CORNER_LEFT_UPPER);
|
| | | ObjectSetInteger(0,N_RISK_EDIT,OBJPROP_BGCOLOR,C_BODY);
|
| | | ObjectSetInteger(0,N_RISK_EDIT,OBJPROP_BORDER_COLOR,C_FRAME);
|
| | | ObjectSetInteger(0,N_RISK_EDIT,OBJPROP_COLOR,C_TEXT);
|
| | | ObjectSetInteger(0,N_RISK_EDIT,OBJPROP_FONTSIZE,FS(9));
|
| | | ObjectSetInteger(0,N_RISK_EDIT,OBJPROP_ALIGN,ALIGN_CENTER);
|
| | | ObjectSetInteger(0,N_RISK_EDIT,OBJPROP_READONLY,false);
|
| | | ObjectSetInteger(0,N_RISK_EDIT,OBJPROP_SELECTABLE,false);
|
| | | ObjectSetInteger(0,N_RISK_EDIT,OBJPROP_HIDDEN,true);
|
| | | ObjectSetInteger(0,N_RISK_EDIT,OBJPROP_ZORDER,30);
|
| | | ObjectSetString(0,N_RISK_EDIT,OBJPROP_FONT,"Segoe UI");
|
| | | ObjectSetString(0,N_RISK_EDIT,OBJPROP_TEXT,g_risk_text);
|
| | | ObjectSetString(0,N_RISK_EDIT,OBJPROP_TOOLTIP,"Risk amount (e.g. 10 or 2.5)");
|
| | | if(!Rect(N_DOLLAR_BG,g_x,g_y,S(36),S(28),C_BTN,C_FRAME,30)) return false;
|
| | | if(!Label(N_DOLLAR_TX,"$",g_x,g_y,FS(10),C_TEXT,"Segoe UI Semibold",31)) return false;
|
| | | if(!Rect(N_PERCENT_BG,g_x,g_y,S(36),S(28),C_BTN,C_FRAME,30)) return false;
|
| | | if(!Label(N_PERCENT_TX,"%",g_x,g_y,FS(10),C_TEXT,"Segoe UI Semibold",31)) return false;
|
2026-09-28 08:38:25 +03:00 | | | if(!Rect(N_LOT_BG,g_x,g_y,S(44),S(28),C_BTN,C_FRAME,30)) return false;
|
| | | if(!Label(N_LOT_TX,"Lot",g_x,g_y,FS(8),C_TEXT,"Segoe UI Semibold",31)) return false;
|
2026-09-27 08:30:11 -07:00 | | | UpdateRiskUnit();
|
| | |
|
| | | if(!Rect(N_MARKET_BG,g_x,g_y,S(127),S(26),C_BTN,C_FRAME,30)) return false;
|
| | | if(!Label(N_MARKET_TX,"Market",g_x,g_y,FS(8),C_TEXT,"Segoe UI Semibold",31)) return false;
|
| | | if(!Rect(N_LIMIT_BG,g_x,g_y,S(127),S(26),C_BTN,C_FRAME,30)) return false;
|
| | | if(!Label(N_LIMIT_TX,"Limit",g_x,g_y,FS(8),C_TEXT,"Segoe UI Semibold",31)) return false;
|
| | | UpdateEntryModeStyle();
|
| | |
|
| | | // Bottom buttons retained from the original panel.
|
| | | if(!Label(N_TP_TITLE,"TP",g_x,g_y,FS(8),C_MUTED)) return false;
|
| | | if(!ObjectCreate(0,N_TP_EDIT,OBJ_EDIT,0,0,0)) return false;
|
| | | ObjectSetInteger(0,N_TP_EDIT,OBJPROP_CORNER,CORNER_LEFT_UPPER);
|
| | | ObjectSetInteger(0,N_TP_EDIT,OBJPROP_BGCOLOR,C_BODY);
|
| | | ObjectSetInteger(0,N_TP_EDIT,OBJPROP_BORDER_COLOR,C_FRAME);
|
| | | ObjectSetInteger(0,N_TP_EDIT,OBJPROP_COLOR,C_TEXT);
|
| | | ObjectSetInteger(0,N_TP_EDIT,OBJPROP_FONTSIZE,FS(9));
|
| | | ObjectSetInteger(0,N_TP_EDIT,OBJPROP_ALIGN,ALIGN_CENTER);
|
| | | ObjectSetInteger(0,N_TP_EDIT,OBJPROP_READONLY,false);
|
| | | ObjectSetInteger(0,N_TP_EDIT,OBJPROP_SELECTABLE,false);
|
| | | ObjectSetInteger(0,N_TP_EDIT,OBJPROP_HIDDEN,true);
|
| | | ObjectSetInteger(0,N_TP_EDIT,OBJPROP_ZORDER,30);
|
| | | ObjectSetString(0,N_TP_EDIT,OBJPROP_FONT,"Segoe UI");
|
| | | if(!Rect(N_TP_RR_BG,g_x,g_y,S(48),S(28),C_BTN,C_FRAME,30)) return false;
|
| | | if(!Label(N_TP_RR_TX,"R:R",g_x,g_y,FS(8),C_TEXT,"Segoe UI Semibold",31)) return false;
|
| | | if(!Rect(N_TP_VALUE_BG,g_x,g_y,S(56),S(28),C_BTN,C_FRAME,30)) return false;
|
| | | if(!Label(N_TP_VALUE_TX,"Price",g_x,g_y,FS(8),C_TEXT,"Segoe UI Semibold",31)) return false;
|
| | | TPControls();
|
2026-09-27 09:13:33 -07:00 | | |
|
| | | if(!Label(N_STEPS_TITLE,"Entries",g_x,g_y,FS(8),C_MUTED)) return false;
|
| | | for(int i=0;i<3;i++)
|
| | | {
|
| | | if(!Rect(N_STEPS_BG[i],g_x,g_y,S(50),S(26),C_BTN,C_FRAME,30)) return false;
|
| | | if(!Label(N_STEPS_TX[i],IntegerToString(i+1),g_x,g_y,FS(8),C_TEXT,"Segoe UI Semibold",31)) return false;
|
| | | }
|
| | | StepControls();
|
2026-09-28 08:38:25 +03:00 | | | if(!Label(N_DAY_TITLE,"Today P/L",g_x,g_y,FS(7),C_MUTED)) return false;
|
| | | if(!Label(N_DAY_VALUE,"-- | --%",g_x,g_y,FS(7),C_MUTED,"Segoe UI Semibold")) return false;
|
| | | g_day_refresh=0;
|
2026-09-27 08:30:11 -07:00 | | | if(!Rect(N_LEFT_BG,g_x,g_y,S(72),S(BUTTON_H),C_BTN,C_FRAME,20)) return false;
|
| | | if(!Label(N_LEFT_TX,"Reset",g_x,g_y,FS(7),C_TEXT2,"Segoe UI",21)) return false;
|
2026-09-27 09:42:34 -07:00 | | | if(!Rect(N_CLEAR_BG,g_x,g_y,S(70),S(BUTTON_H),C_BTN,C_FRAME,20)) return false;
|
| | | if(!Label(N_CLEAR_TX,"Clear",g_x,g_y,FS(7),C_TEXT2,"Segoe UI",21)) return false;
|
2026-09-27 08:30:11 -07:00 | | |
|
| | | if(!Rect(N_RIGHT_BG,g_x,g_y,S(104),S(BUTTON_H),C_ACCENT,C_ACCENT,20)) return false;
|
| | | if(!Label(N_RIGHT_TX,"EXECUTE",g_x,g_y,FS(7),C_TEXT,"Segoe UI Semibold",21)) return false;
|
| | |
|
| | | Layout();
|
| | | return true;
|
| | | }
|
| | |
|
| | | //====================================================================
|
| | | // Hit testing
|
| | | //====================================================================
|
| | | bool OverMin(int mx,int my)
|
| | | {
|
| | | return HitObject(N_MIN_BG,mx,my);
|
| | | }
|
| | |
|
| | | bool OverClose(int mx,int my)
|
| | | {
|
| | | return HitObject(N_CLOSE_BG,mx,my);
|
| | | }
|
| | |
|
| | | bool OverHeader(int mx,int my)
|
| | | {
|
| | | if(!Hit(mx,my,g_x,g_y,S(BASE_W),S(HEADER_H)))
|
| | | return false;
|
| | |
|
| | | if(OverMin(mx,my) || OverClose(mx,my))
|
| | | return false;
|
| | |
|
| | | return true;
|
| | | }
|
| | |
|
| | | bool OverLeft(int mx,int my)
|
| | | {
|
| | | return !g_minimized && HitObject(N_LEFT_BG,mx,my);
|
| | | }
|
| | |
|
| | | bool OverRight(int mx,int my)
|
| | | {
|
| | | return !g_minimized && HitObject(N_RIGHT_BG,mx,my);
|
| | | }
|
| | |
|
| | | //====================================================================
|
| | | // Dragging
|
| | | //====================================================================
|
| | | void KeepInside()
|
| | | {
|
| | | long cw=0,ch=0;
|
| | | ChartGetInteger(0,CHART_WIDTH_IN_PIXELS,0,cw);
|
| | | ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,0,ch);
|
| | |
|
| | | int w=S(BASE_W);
|
| | | int visible_h=PanelHeight();
|
| | |
|
| | | if(g_x<0) g_x=0;
|
| | | if(g_y<0) g_y=0;
|
| | |
|
| | | if(g_x+w>(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);
|
| | | }
|
| | |
|
2026-09-28 08:38:25 +03:00 | | | 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<g_steps && valid;i++)
|
| | | {
|
| | | double loss;
|
| | | PositionVolumeText(loss,i==0 ? N_ENTRY_LINE : N_STAGE_LINE[i-1]);
|
| | | if(loss<0.0 || !MathIsValidNumber(loss)) valid=false;
|
| | | else total+=loss;
|
| | | }
|
| | | double percent=valid ? total/balance*100.0 : 0.0;
|
| | | color bg=valid ? ExecuteRiskColor(percent) : C_BTN;
|
| | | color fg=valid && percent<=1.0 ? C'16,55,29' : C_TEXT;
|
| | | // Preserve the risk hue while the button is held down.
|
| | | if(g_pressed==PRESS_RIGHT)
|
| | | {
|
| | | uint rgb=(uint)bg;
|
| | | bg=(color)(((rgb&255)*4/5) | ((((rgb>>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();
|
| | | }
|
| | |
|
2026-09-27 08:30:11 -07:00 | | | 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);
|
2026-09-28 08:38:25 +03:00 | | | UpdateExecuteRisk();
|
2026-09-27 08:30:11 -07:00 | | | ChartRedraw();
|
| | | }
|
| | |
|
| | | //====================================================================
|
| | | // Lifecycle
|
| | | //====================================================================
|
2026-09-27 09:49:10 -07:00 | | | 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-sl<gap || tp-reference<gap)) ||
|
| | | (!buy && (sl-reference<gap || reference-tp<gap)))
|
| | | { error="SL/TP is too close for the broker's stop distance."; return false; }
|
| | | request.action=market ? TRADE_ACTION_DEAL : TRADE_ACTION_PENDING;
|
| | | request.type=buy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
|
| | | if(!market)
|
| | | {
|
| | | double quote=buy ? tick.ask : tick.bid;
|
| | | if(MathAbs(price-quote)<MathMax(gap,_Point*0.5))
|
| | | { error="Pending entry is too close to the current quote."; return false; }
|
| | | request.type=buy ? (price<quote ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP)
|
| | | : (price>quote ? 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;i<g_steps;i++)
|
| | | {
|
| | | if(!PrepareRequest(i,requests[i],error))
|
| | | { ExecutionError("Entry "+IntegerToString(i+1)+": "+error); return; }
|
| | | MqlTradeCheckResult check={};
|
| | | if(!OrderCheck(requests[i],check))
|
| | | { ExecutionError("Entry "+IntegerToString(i+1)+": "+check.comment+" ("+IntegerToString(check.retcode)+")"); return; }
|
| | | double margin=0.0;
|
| | | bool buy=requests[i].type==ORDER_TYPE_BUY || requests[i].type==ORDER_TYPE_BUY_LIMIT || requests[i].type==ORDER_TYPE_BUY_STOP;
|
| | | if(!OrderCalcMargin(buy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL,_Symbol,requests[i].volume,requests[i].price,margin))
|
| | | { ExecutionError("Cannot calculate margin for entry "+IntegerToString(i+1)); return; }
|
| | | total_margin+=margin;
|
| | | }
|
| | | if(total_margin>AccountInfoDouble(ACCOUNT_MARGIN_FREE))
|
| | | { ExecutionError("Insufficient free margin for all entries."); return; }
|
2026-09-28 08:38:25 +03:00 | | | // Guard only the active submission; later clicks may execute the same plan again.
|
2026-09-27 09:49:10 -07:00 | | | g_executing=true;
|
| | | int accepted=0;
|
| | | string details="";
|
| | | for(int i=0;i<g_steps;i++)
|
| | | {
|
| | | MqlTradeCheckResult check={};
|
| | | if(!OrderCheck(requests[i],check))
|
| | | { details="Entry "+IntegerToString(i+1)+" check failed: "+check.comment; break; }
|
| | | MqlTradeResult result={};
|
| | | bool sent=OrderSend(requests[i],result);
|
| | | PrintFormat("Entry %d: sent=%s retcode=%u order=%I64u deal=%I64u volume=%.8f %s",
|
| | | i+1,sent ? "true" : "false",result.retcode,result.order,result.deal,result.volume,result.comment);
|
| | | if(sent && (result.retcode==TRADE_RETCODE_DONE || result.retcode==TRADE_RETCODE_PLACED ||
|
| | | result.retcode==TRADE_RETCODE_DONE_PARTIAL))
|
| | | {
|
| | | accepted++;
|
| | | if(result.retcode==TRADE_RETCODE_DONE_PARTIAL)
|
| | | { details="Entry "+IntegerToString(i+1)+" partially filled; remaining entries were not sent."; break; }
|
| | | }
|
| | | else
|
| | | { details="Entry "+IntegerToString(i+1)+": "+result.comment+" ("+IntegerToString(result.retcode)+"). Check Trade/History before retrying."; break; }
|
| | | }
|
| | | g_executing=false;
|
| | | string summary=IntegerToString(accepted)+"/"+IntegerToString(g_steps)+" entries accepted. "+details;
|
| | | ObjectSetString(0,N_RIGHT_BG,OBJPROP_TOOLTIP,summary);
|
2026-09-28 08:38:25 +03:00 | | | if(accepted<g_steps || details!="") ExecutionError(summary);
|
| | | else
|
| | | {
|
| | | Print(summary);
|
| | | ClearLines();
|
| | | UpdateExecuteRisk();
|
| | | }
|
| | | ChartRedraw();
|
2026-09-27 09:49:10 -07:00 | | | }
|
| | |
|
2026-09-27 08:30:11 -07:00 | | | int OnInit()
|
| | | {
|
| | | g_scale=InpUIScale;
|
| | | if(g_scale<0.70) g_scale=0.70;
|
| | | if(g_scale>1.50) g_scale=1.50;
|
| | |
|
2026-09-27 09:49:10 -07:00 | | | // 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;
|
2026-09-28 08:38:25 +03:00 | | | // Remove the persistent submission guard left by earlier versions.
|
| | | Del(N_EXEC_LOCK);
|
2026-09-27 09:49:10 -07:00 | | | SetDefaults();
|
2026-09-27 08:30:11 -07:00 | | | LoadState();
|
2026-09-28 08:38:25 +03:00 | | | g_risk_text=g_risk_unit==RISK_DOLLAR ? g_dollar_text : (g_risk_unit==RISK_PERCENT ? g_percent_text : g_lot_text);
|
2026-09-27 08:30:11 -07:00 | | | 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();
|
| | | }
|
| | |
|
2026-09-28 08:38:25 +03:00 | | | 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;i<HistoryDealsTotal();i++)
|
| | | {
|
| | | ulong ticket=HistoryDealGetTicket(i);
|
| | | if(ticket==0) continue;
|
| | | ENUM_DEAL_TYPE type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket,DEAL_TYPE);
|
| | | double amount=HistoryDealGetDouble(ticket,DEAL_PROFIT)+HistoryDealGetDouble(ticket,DEAL_COMMISSION)+
|
| | | HistoryDealGetDouble(ticket,DEAL_SWAP)+HistoryDealGetDouble(ticket,DEAL_FEE);
|
| | | // Deposits/withdrawals affect the opening-balance reconstruction, not trading P/L.
|
| | | if(type!=DEAL_TYPE_CREDIT) balance_change+=amount;
|
| | | bool trade=(type==DEAL_TYPE_BUY || type==DEAL_TYPE_SELL);
|
| | | bool commission=(type==DEAL_TYPE_COMMISSION || type==DEAL_TYPE_COMMISSION_DAILY ||
|
| | | type==DEAL_TYPE_COMMISSION_MONTHLY || type==DEAL_TYPE_COMMISSION_AGENT_DAILY ||
|
| | | type==DEAL_TYPE_COMMISSION_AGENT_MONTHLY);
|
| | | if(trade || commission) net+=amount;
|
| | | }
|
| | | double opening=AccountInfoDouble(ACCOUNT_BALANCE)-balance_change;
|
| | | string percent=opening>0.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;
|
| | | }
|
| | |
|
2026-09-27 08:30:11 -07:00 | | | void OnTimer()
|
| | | {
|
2026-09-28 08:38:25 +03:00 | | | UpdateDailyPL();
|
2026-09-27 08:30:11 -07:00 | | | if(!g_stop_initialized) CreateStopLine();
|
| | | else UpdateStopLabel();
|
| | | RefreshEntry();
|
| | | SaveState();
|
2026-09-28 08:38:25 +03:00 | | | UpdateExecuteRisk();
|
2026-09-27 08:30:11 -07:00 | | | }
|
| | |
|
| | | //====================================================================
|
| | | // Events
|
| | | //====================================================================
|
| | | void OnChartEvent(const int id,
|
| | | const long &lparam,
|
| | | const double &dparam,
|
| | | const string &sparam)
|
| | | {
|
2026-09-27 09:42:34 -07:00 | | | if(id==CHARTEVENT_OBJECT_CLICK && !g_minimized && (sparam==N_CLEAR_BG || sparam==N_CLEAR_TX))
|
| | | { ClearLines(); return; }
|
2026-09-27 09:13:33 -07:00 | | | 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;
|
| | | }
|
2026-09-27 08:30:11 -07:00 | | | 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);
|
| | | }
|
2026-09-28 08:38:25 +03:00 | | | else if(sparam==N_LOT_BG || sparam==N_LOT_TX)
|
| | | SetRiskUnit(RISK_LOT);
|
2026-09-27 08:30:11 -07:00 | | | 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;
|
2026-09-28 08:38:25 +03:00 | | | UpdateExecuteRisk();
|
2026-09-27 08:30:11 -07:00 | | | 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))
|
| | | {
|
2026-09-27 09:49:10 -07:00 | | | ResetAll();
|
2026-09-27 08:30:11 -07:00 | | | }
|
| | | else if(pressed==PRESS_RIGHT && OverRight(mx,my))
|
| | | {
|
2026-09-27 09:49:10 -07:00 | | | ExecutePlan();
|
2026-09-27 08:30:11 -07:00 | | | }
|
| | | }
|
| | |
|
| | | g_prev_left=left;
|
| | | }
|
| | | //+------------------------------------------------------------------+
|