//+------------------------------------------------------------------+ //| MarketReplay.mq5 | //| v9.10 - Tick engine | Pending orders | Speed slider | Trades | //| Virtual microsecond clock (0.1 ms grid), tick-by-tick SL/TP, | //| fire-on-press UI, right-click Limit/Stop orders, floating table | //+------------------------------------------------------------------+ #property copyright "Grok Replay" #property version "9.10" #property strict #property description "Tick-level Market Replay with Limit/Stop orders, speed slider, calendar range picker and Trades window" //=== ENUM ========================================================== enum ENUM_TRADE_MODE { MODE_SINGLE = 0, // Single Trade | تک‌معامله MODE_MARTINGALE = 1 // Martingale Basket | سبد مارتینگل }; //=== INPUTS (Bilingual) ============================================ input group "══════ [1] ACCOUNT & TRADE MODE | حساب و حالت معامله ══════" input double InpBalance = 500.0; // Starting Balance | موجودی اولیه input ENUM_TRADE_MODE InpTradeMode = MODE_MARTINGALE; // Trade Mode | حالت معامله input double InpLot = 0.02; // Initial Lot (editable in panel) | لات اولیه (در پنل قابل تغییر است) input double InpLotMin = 0.01; // Minimum Lot | حداقل لات input double InpLotMax = 100.0; // Maximum Lot | حداکثر لات input double InpLotStep = 0.01; // Lot Step | گام تغییر لات input group "══════ [2] INSTRUMENT & COSTS | مشخصات نماد و هزینه‌ها ══════" input double InpPoint = 0.01; // Point Size (Gold = 0.01) | اندازه‌ی یک پوینت (طلا = 0.01) input double InpContractSize = 100.0; // Contract Size (Gold = 100) | اندازه‌ی قرارداد (طلا = 100) input int InpSpreadPts = 26; // Fixed Spread in points (0 = none) | اسپرد ثابت به پوینت (صفر = بدون اسپرد) input group "══════ [3] LEVERAGE & MARGIN | لوریج و مارجین ══════" input int InpLeverage = 500; // Leverage 1:X (editable in panel) | لوریج ۱:X (در پنل قابل تغییر است) input double InpStopOutPct = 50.0; // Stop-out Margin Level % | سطح استاپ‌اوت (درصد) input bool InpAutoReset = true; // Auto-reset Balance on Stop-out | ریست خودکار بالانس هنگام استاپ‌اوت input group "══════ [4] SINGLE TRADE MODE | حالت تک‌معامله ══════" input int InpSLpts = 150; // Stop Loss in points | حد ضرر (پوینت) input int InpTPpts = 300; // Take Profit in points | حد سود (پوینت) input bool InpTrailing = true; // Enable Trailing Stop | فعال‌سازی تریلینگ استاپ input int InpTrailStart = 170; // Trailing Start in points of profit | شروع تریلینگ (پوینت سود) input int InpTrailStep = 50; // Trailing Step in points | فاصله‌ی تریلینگ از قیمت (پوینت) input group "══════ [5] MARTINGALE BASKET MODE | حالت سبد مارتینگل ══════" input int InpMartTPpts = 300; // Basket Take Profit in points | حد سود سبد (پوینت از میانگین ورود) input int InpMartTrailStart = 170; // Basket Trailing Start in points | شروع تریلینگ سبد (پوینت) input int InpMartTrailStep = 50; // Basket Trailing Step in points | فاصله‌ی تریلینگ سبد (پوینت) input double InpMartMultiplier = 1.0; // Lot Multiplier after a Loss | ضریب افزایش لات پس از ضرر input int InpMartMaxLevels = 40; // Maximum Martingale Levels | بیشترین سطح مارتینگل input group "══════ [6] TICK ENGINE & SPEED | موتور تیک و سرعت ══════" input ENUM_TIMEFRAMES InpBaseTF = PERIOD_M1; // Base Data Timeframe (finest) | تایم‌فریم پایه‌ی داده (ریزترین) input int InpMinTicks = 8; // Minimum Ticks per Bar | کمترین تعداد تیک در هر کندل input int InpMaxTicks = 1200; // Maximum Ticks per Bar | بیشترین تعداد تیک در هر کندل input double InpPathNoise = 0.5; // Intrabar Path Noise 0..1 | نویز مسیر قیمت داخل کندل (۰ تا ۱) input int InpUiMs = 40; // Panel Refresh in ms | بازه‌ی به‌روزرسانی پنل (میلی‌ثانیه) input bool InpFireOnPress = true; // Fire Buttons on Mouse-Down | اجرای دکمه‌ها هنگام فشردن ماوس input group "══════ [7] CHART LINES | خطوط روی چارت ══════" input color InpColEntry = clrDodgerBlue; // Entry / Pending Line Colour | رنگ خط ورود و سفارش‌های Pending input color InpColSL = clrRed; // Stop Loss Line Colour | رنگ خط حد ضرر input color InpColTP = clrLimeGreen; // Take Profit Line Colour | رنگ خط حد سود input group "══════ [8] DATA & HISTORY | داده و تاریخچه ══════" input int InpHistoryBars = 30000; // Base Bars Shown before Start | تعداد کندل پایه پیش از نقطه‌ی شروع input int InpBarsToSave = 300000; // Max Bars to Save per Timeframe | حداکثر کندل ذخیره برای هر تایم‌فریم input int InpBarsToLoad = 150000; // Max Bars to Load per Timeframe | حداکثر کندل بارگذاری برای هر تایم‌فریم input bool InpResaveOnInit = true; // Re-export History on Every Start | ذخیره‌ی دوباره‌ی تاریخچه در هر اجرا //=== Types ========================================================= struct Pos { ulong ticket; int dir; // +1 BUY, -1 SELL double lots, open, sl, tp; datetime time; bool open_; int level; }; struct Ord // pending order { ulong ticket; int type; // 1 BuyLimit, 2 SellLimit, 3 BuyStop, 4 SellStop double price, lots; datetime time; bool live; }; struct HEv // trade-history mark queued for drawing { ulong ticket; int kind; // 0 open, 1 close int dir; datetime t, t0; double price, p0, profit; int part; // 0 = normal event, >0 = partial-close sequence number double lots; // closed volume (partial close marks) }; //=== Globals ======================================================= string baseSym, customSym, folderName; string P = "RP_"; long panelChart = 0; bool ready = false; ENUM_TIMEFRAMES usedTF = PERIOD_M1; ENUM_TIMEFRAMES currentTF = PERIOD_M5; bool playing = false; double speedMult = 1.0; double lotSize = 0.10; bool editing = false; int leverage = 500, slPts = 0, tpPts = 0; double resetBal = 10000.0; bool trailOn = true; int trStart = 170, trStep = 50; int dragKind = 0; ulong dragTicket = 0; double dragPrice = 0, dragPrice0 = 0; string dragName = ""; bool modOpen = false; ulong modTicket = 0; int modX = 0, modY = 0; const int MOD_W = 300, MOD_H = 150; bool volOpen = false; ulong volTicket = 0; int volX = 0, volY = 0; // Volume (partial close) popup HEv hev[]; string hDrawn[]; datetime curTime = 0; int partSeq = 0; int cur = 0, total = 0, endBar = 0; double bal = 0, eq = 0; MqlRates rates[]; // base series (replayed) MqlRates cser[]; // coarser TF series concatenated (history stitching) int cOff[8], cCnt[8]; ENUM_TIMEFRAMES cTF[8]; int cK = 0; int digitsN = 2; double spreadPx = 0; // --- tick engine double barDurUs = 60e6; double tkOff[]; double tkBid[]; double wkb[]; int tkN = 0, tkIdx = 0; double barClockUs = 0; ulong lastUs = 0; ulong lastUiUs = 0; double curBid = 0, curAsk = 0; MqlTick outTk[]; int outN = 0; ulong rngS = 88172645463325252; const double BUDGET_US = 8000.0; // --- UI flags bool uiForce = false, linesDirty = true, btnDirty = true, needCompact = false, ordCompact = false; bool mouseDown = false, rightDown = false; string lastMsg = ""; string pendLog = ""; string pressId = ""; ulong pressMs = 0; // --- registry of clickable panel buttons string rId[48]; int rX[48], rY[48], rW[48], rH[48]; int rN = 0; int panW = 480, panH = 400; // --- label cache (avoid redundant ObjectSet calls) string lcT[40]; color lcC[40]; const int K_BAL=0,K_EQ=1,K_FLO=2,K_POS=3,K_TD=4,K_YD=5,K_WK=6,K_MO=7,K_LOTS=8,K_MODE=9,K_INFO=10,K_INFO2=11, K_MSG=12,K_SELL=13,K_BUY=14,K_SPEED=15,K_SPR=16,K_MG=17,K_FM=18,K_ML=19,K_LI=20; // --- speed slider const double SPD_MIN=0.1, SPD_MAX=1000.0; const int SL_TH=14, SL_H=24; int slX=0, slY=0, slW=262; bool sliderDrag = false; // --- calendar popup const int CAL_HDR=32, CAL_WK=22, CAL_CW=36, CAL_CH=28; bool calOpen=false; int calTarget=0, calYear=2026, calMon=1; int calX=0, calY=0, calW=7*36+16, calH=32+22+6*28+36; int dateRowY=0, fromFieldX=0, toFieldX=0; datetime calDate[42]; bool calOn[42]; datetime selFrom=0, selTo=0, dataFirst=0, dataLast=0; bool selDirty=false; string MonName[12]={"January","February","March","April","May","June","July","August","September","October","November","December"}; // --- right-click context menu bool ctxOpen=false; double ctxPrice=0; int ctxX=0, ctxY=0, ctxN=0; string ctxId[4]; int ctxRY[4]; const int CTX_W=250, CTX_ROW=26; // --- Trades window bool trOpen=false; int trX=60, trY=120, trW=700, trH=250, trTop=0; bool trDrag=false, trSize=false, trSizeDirty=false; int trDX=0, trDY=0; ulong trSizeUs=0; const int TR_TITLE=24, TR_HEAD=28, TR_FOOT=36, TR_ROWH=22, TR_MAXROWS=40; string twN[600]; int twOX[600], twOY[600]; int twCnt=0; string twC[500]; color twCC[500]; int twKind[800], twIdx[800], twRows=0, twVis=0; // --- stats double dayProfit = 0, ydayProfit = 0, weekProfit = 0, monthProfit = 0; double dayLots = 0, weekLots = 0, monthLots = 0; datetime lastDay = 0, lastWeek = 0, lastMonth = 0; double closedToday = 0; // --- positions & orders Pos pos[]; Ord ords[]; int openCnt = 0, ordLive = 0; ulong nextT = 1; // --- martingale int martLevel = 0; int martDir = 0; double basketAvg = 0, basketLots = 0, basketSL = 0, basketTP = 0; int bDir = 0; bool basketTrailActive = false; //+------------------------------------------------------------------+ //| Helpers | //+------------------------------------------------------------------+ string CleanBase(string sym) { while(StringFind(sym,"REPLAY_")==0) sym = StringSubstr(sym,7); StringReplace(sym,"+",""); StringReplace(sym,".",""); int len=StringLen(sym); if(len>0){ string last=StringSubstr(sym,len-1,1); if(last=="m"||last=="c"||last=="p") sym=StringSubstr(sym,0,len-1); } if(StringLen(sym)<3) sym="XAUUSD"; return sym; } string TFName(ENUM_TIMEFRAMES tf) { if(tf==PERIOD_M1) return "M1"; if(tf==PERIOD_M5) return "M5"; if(tf==PERIOD_M15) return "M15"; if(tf==PERIOD_M30) return "M30"; if(tf==PERIOD_H1) return "H1"; if(tf==PERIOD_H4) return "H4"; if(tf==PERIOD_D1) return "D1"; if(tf==PERIOD_W1) return "W1"; return "M5"; } string StatePath(){ return folderName+"\\state.bin"; } double Rnd() { rngS ^= rngS >> 12; rngS ^= rngS << 25; rngS ^= rngS >> 27; ulong r = rngS * 2685821657736338717; return (double)(r >> 11) * (1.0/9007199254740992.0); } void SeedRng(ulong s) { rngS = s * 6364136223846793005 + 1442695040888963407; if(rngS==0) rngS = 88172645463325252; for(int i=0;i<4;i++) Rnd(); } string Px(const double v){ return DoubleToString(v,digitsN); } double CalcProfit(const int dir,const double open,const double lots,const double price) { double m = (dir>0) ? (price-open) : (open-price); return NormalizeDouble(m * lots * InpContractSize, 2); } // MT5-style money text: 10 000.00 string Money(const double v) { string s=DoubleToString(MathAbs(v),2); int dot=StringFind(s,"."); string ip=StringSubstr(s,0,dot), fp=StringSubstr(s,dot); string o=""; int L=StringLen(ip); for(int i=0;i0 && (L-i)%3==0) o+=" "; o+=StringSubstr(ip,i,1); } return (v<0?"-":"")+o+fp; } string MoneyS(const double v){ return (v>0?"+":"")+Money(v); } color PnlCol(const double v){ return (v>0.004)?C'90,225,130':((v<-0.004)?C'255,100,100':clrWhite); } //+------------------------------------------------------------------+ //| Init / Deinit | //+------------------------------------------------------------------+ int OnInit() { panelChart = ChartID(); baseSym = CleanBase(_Symbol); customSym = "REPLAY_"+baseSym; folderName = "Custom_"+baseSym; digitsN = (int)MathRound(-MathLog10(MathMax(InpPoint,1e-8))); if(digitsN<0) digitsN=0; if(digitsN>8) digitsN=8; spreadPx = InpSpreadPts*InpPoint; lotSize = MathMax(InpLotMin,MathMin(InpLotMax,InpLot)); leverage = MathMax(1,InpLeverage); resetBal = InpBalance; slPts = InpSLpts; tpPts = (InpTradeMode==MODE_SINGLE)?InpTPpts:InpMartTPpts; trailOn = (InpTradeMode==MODE_SINGLE)?InpTrailing:(InpMartTrailStart>0); trStart = (InpTradeMode==MODE_SINGLE)?InpTrailStart:InpMartTrailStart; trStep = (InpTradeMode==MODE_SINGLE)?InpTrailStep:InpMartTrailStep; ready=false; Print("=== MarketReplay v9.10 | Mode: ",(InpTradeMode==MODE_SINGLE?"Single":"Martingale")," ==="); FolderCreate(folderName,0); // --- Stage 1: broker symbol -> export all TFs, create custom symbol, switch chart if(_Symbol!=customSym) { SaveSource(); if(!SetupCustom()){ Alert("Symbol error | خطا سمبل"); return INIT_FAILED; } if(!ChartSetSymbolPeriod(panelChart,customSym,_Period)) Alert("Open a chart of ",customSym," and attach the EA there | چارت ",customSym," را باز و اکسپرت را روی آن بگذارید"); return INIT_SUCCEEDED; } // --- Stage 2: running on REPLAY_ symbol if(!SetupCustom()){ Alert("Symbol error | خطا سمبل"); return INIT_FAILED; } ArrayResize(tkOff,InpMaxTicks+16); ArrayResize(tkBid,InpMaxTicks+16); ArrayResize(wkb,InpMaxTicks+16); ArrayResize(outTk,256); if(!LoadBase()){ Alert("No data | داده‌ای یافت نشد"); return INIT_FAILED; } barDurUs = (double)PeriodSeconds(usedTF)*1e6; dataFirst = DayStart(rates[0].time); dataLast = DayStart(rates[total-1].time); currentTF = (ENUM_TIMEFRAMES)_Period; ChartSetInteger(panelChart,CHART_MODE,CHART_CANDLES); ChartSetInteger(panelChart,CHART_SHOW_GRID,false); ChartSetInteger(panelChart,CHART_AUTOSCROLL,true); ChartSetInteger(panelChart,CHART_SHIFT,true); ChartSetInteger(panelChart,CHART_SHOW_BID_LINE,true); ChartSetInteger(panelChart,CHART_SHOW_ASK_LINE,true); ChartSetInteger(panelChart,CHART_COLOR_BID,clrDodgerBlue); ChartSetInteger(panelChart,CHART_COLOR_ASK,clrRed); ChartSetInteger(panelChart,CHART_SHOW_OBJECT_DESCR,true); ChartSetInteger(panelChart,CHART_CONTEXT_MENU,false); // our own right-click menu ChartSetInteger(panelChart,CHART_EVENT_MOUSE_MOVE,true); if(!TryRestoreState()) { bal=InpBalance; eq=bal; ResetTradeState(true); endBar=total-1; StartAt(MathMax(1,total/5)); selFrom=DayStart(rates[cur].time); selTo=dataLast; selDirty=false; } if(selFrom<=0) selFrom=DayStart(rates[MathMin(cur,total-1)].time); if(selTo<=0) selTo=dataLast; BuildPanel(); if(trOpen){ BuildTrades(); } ready=true; btnDirty=true; linesDirty=true; lastUs=GetMicrosecondCount(); EventSetMillisecondTimer(playing?1:10); UpdateUI(); Print("Ready | آماده | Bars: ",total," | Base TF: ",TFName(usedTF)," | Ticks/bar up to ",InpMaxTicks); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { EventKillTimer(); if(ready && reason==REASON_CHARTCHANGE) SaveState(); if(reason!=REASON_CHARTCHANGE) { ChartSetInteger(panelChart,CHART_CONTEXT_MENU,true); ChartSetInteger(panelChart,CHART_EVENT_MOUSE_MOVE,false); ObjectsDeleteAll(panelChart,"RPH_"); } ChartSetInteger(panelChart,CHART_MOUSE_SCROLL,true); ObjectsDeleteAll(panelChart,P); ready=false; } //+------------------------------------------------------------------+ //| Timer: the heart of the replay | //+------------------------------------------------------------------+ void OnTimer() { if(!ready) return; ulong nowUs = GetMicrosecondCount(); if(playing) { double dt = (double)(nowUs - lastUs); lastUs = nowUs; if(dt > 1000000.0) dt = 1000000.0; Advance(dt * speedMult); } else lastUs = nowUs; if(trSizeDirty && (nowUs-trSizeUs)>=60000) { trSizeUs=nowUs; trSizeDirty=false; BuildTrades(); RefreshTrades(); ChartRedraw(panelChart); } if(uiForce || (nowUs - lastUiUs) >= (ulong)InpUiMs*1000) { lastUiUs = nowUs; UpdateUI(); } } //+------------------------------------------------------------------+ //| Advance the virtual clock by addUs and emit every due tick | //+------------------------------------------------------------------+ void Advance(const double addUs) { if(tkN<=0) return; ulong t0 = GetMicrosecondCount(); barClockUs += addUs; outN = 0; while(true) { while(tkIdx=256) FlushTicks(); } if(tkIdx>=tkN && barClockUs>=barDurUs) { if(cur>=endBar) { playing=false; btnDirty=true; barClockUs=barDurUs; EventSetMillisecondTimer(10); break; } barClockUs -= barDurUs; // carry remainder => zero drift cur++; UpdateStatsPeriod(rates[cur].time); BuildBarTicks(cur); tkIdx = 0; if((double)(GetMicrosecondCount()-t0) > BUDGET_US) break; // keep UI responsive continue; } break; } FlushTicks(); } void EmitTick(const int i) { curBid = tkBid[i]; curAsk = curBid + spreadPx; ulong msc = (ulong)rates[cur].time*1000 + (ulong)(tkOff[i]/1000.0); MqlTick t; t.time = (datetime)(msc/1000); curTime = t.time; t.time_msc = (long)msc; t.bid = curBid; t.ask = curAsk; t.last = 0; t.volume = 0; t.volume_real = 0; t.flags = TICK_FLAG_BID|TICK_FLAG_ASK; outTk[outN++] = t; if(openCnt>0 || ordLive>0) ManageTick(curBid,curAsk); // SL/TP/trailing on EVERY tick } void FlushTicks() { if(outN>0){ CustomTicksAdd(customSym,outTk,(uint)outN); outN=0; } } void BuildBarTicks(const int idx) { double o=rates[idx].open, h=rates[idx].high, l=rates[idx].low, c=rates[idx].close; if(hMathMin(o,c)) l=MathMin(o,c); int minT=MathMax(4,InpMinTicks); int maxT=MathMax(minT,InpMaxTicks); int n=(int)rates[idx].tick_volume; if(nmaxT) n=maxT; double rng=h-l; SeedRng((ulong)rates[idx].time); if(rng<=0.0) { n=2; tkBid[0]=NormalizeDouble(o,digitsN); tkBid[1]=NormalizeDouble(c,digitsN); tkOff[0]=0; tkOff[1]=MathFloor(barDurUs*0.5/100.0)*100.0; tkN=2; return; } double wp[4]; wp[0]=o; if(c>=o){ wp[1]=l; wp[2]=h; } else { wp[1]=h; wp[2]=l; } wp[3]=c; // distribute ticks over the 3 legs proportionally to their length int rem=n-1, m[3]; double len[3]; double S=0; int big=0; for(int k=0;k<3;k++){ len[k]=MathAbs(wp[k+1]-wp[k]); S+=len[k]; if(len[k]>len[big]) big=k; } int extra=rem-3, used=0; for(int k=0;k<3;k++){ m[k]=1+(int)MathFloor(extra*len[k]/S); used+=m[k]; } m[big]+=rem-used; tkBid[0]=NormalizeDouble(o,digitsN); int w=1; for(int k=0;k<3;k++) { int mk=m[k]; double p0=wp[k], p1=wp[k+1]; double sigma=InpPathNoise*rng/MathSqrt((double)mk); double acc=0; for(int s=1;s<=mk;s++){ acc+=(Rnd()*2.0-1.0)*1.7320508*sigma; wkb[s]=acc; } double wm=wkb[mk]; for(int s=1;s<=mk;s++) { double px; if(s==mk) px=p1; // legs end exactly on O/H/L/C else { px=p0+(p1-p0)*s/mk+(wkb[s]-wm*s/mk); // Brownian bridge if(px>h) px=h; if(px0) CheckOrders(bid,ask); if(openCnt>0 && StopOutHit()){ DoStopOut(); return; } if(InpTradeMode==MODE_SINGLE) { for(int i=ArraySize(pos)-1;i>=0;i--) { if(!pos[i].open_) continue; if(pos[i].dir>0) { if(pos[i].sl>0 && bid<=pos[i].sl){ ClosePos(i,bid); continue; } if(pos[i].tp>0 && bid>=pos[i].tp){ ClosePos(i,pos[i].tp); continue; } if(trailOn && trStart>0 && (bid-pos[i].open)/pt>=trStart) { double ns=NormalizeDouble(bid-trStep*pt,digitsN); if(ns>pos[i].sl){ pos[i].sl=ns; linesDirty=true; } } } else { if(pos[i].sl>0 && ask>=pos[i].sl){ ClosePos(i,ask); continue; } if(pos[i].tp>0 && ask<=pos[i].tp){ ClosePos(i,pos[i].tp); continue; } if(trailOn && trStart>0 && (pos[i].open-ask)/pt>=trStart) { double ns=NormalizeDouble(ask+trStep*pt,digitsN); if(pos[i].sl==0 || ns0) { if(bid>=basketTP){ CloseBasket(basketTP); return; } if(trailOn && trStart>0) { double pts=(bid-basketAvg)/pt; if(!basketTrailActive && pts>=trStart) { basketTrailActive=true; basketSL=NormalizeDouble(bid-trStep*pt,digitsN); linesDirty=true; } if(basketTrailActive) { double ns=NormalizeDouble(bid-trStep*pt,digitsN); if(ns>basketSL){ basketSL=ns; linesDirty=true; } if(bid<=basketSL){ CloseBasket(bid); return; } } } } else { if(ask<=basketTP){ CloseBasket(basketTP); return; } if(trailOn && trStart>0) { double pts=(basketAvg-ask)/pt; if(!basketTrailActive && pts>=trStart) { basketTrailActive=true; basketSL=NormalizeDouble(ask+trStep*pt,digitsN); linesDirty=true; } if(basketTrailActive) { double ns=NormalizeDouble(ask+trStep*pt,digitsN); if(ns=basketSL){ CloseBasket(ask); return; } } } } } string OrdTypeName(const int t) { if(t==1) return "buy limit"; if(t==2) return "sell limit"; if(t==3) return "buy stop"; return "sell stop"; } // pending orders trigger on Ask (buy) / Bid (sell), exactly like a real server void CheckOrders(const double bid,const double ask) { for(int i=0;i=pr){ hit=true; fp=ask; } else if(t==2 && bid>=pr){ hit=true; fp=MathMax(pr,bid); } else if(t==4 && bid<=pr){ hit=true; fp=bid; } if(!hit) continue; ords[i].live=false; ordLive--; ordCompact=true; linesDirty=true; int dir=(t==1||t==3)?1:-1; if(!OpenPos(dir,fp,ords[i].lots,ords[i].ticket)) pendLog+=StringFormat("Pending #%I64u cancelled (blocked)\n",ords[i].ticket); else pendLog+=StringFormat("Pending #%I64u %s filled @ %s\n",ords[i].ticket,OrdTypeName(t),Px(fp)); } } void ClosePos(const int i,const double price) { double pr=CalcProfit(pos[i].dir,pos[i].open,pos[i].lots,price); bal+=pr; dayProfit+=pr; weekProfit+=pr; monthProfit+=pr; closedToday+=pr; AddHevClose(pos[i],price,pr); pos[i].open_=false; openCnt--; needCompact=true; linesDirty=true; pendLog+=StringFormat("Closed #%I64u %s @ %s P/L=%+.2f\n",pos[i].ticket,(pos[i].dir>0?"BUY":"SELL"),Px(price),pr); } void CloseBasket(const double price) { double totalPr=0; for(int i=0;i0) { basketAvg=NormalizeDouble(sum/basketLots,digitsN); basketTP=NormalizeDouble(basketAvg+bDir*tpPts*InpPoint,digitsN); } else { basketSL=0; basketTP=0; basketTrailActive=false; } } //+------------------------------------------------------------------+ //| Leverage / margin / stop-out / balance control | //+------------------------------------------------------------------+ void Account(double &flo,double &used) { flo=0; used=0; for(int i=0;i0?curBid:curAsk); used+=pos[i].lots*InpContractSize*pos[i].open/leverage; } } double FreeMargin() { double f,u; Account(f,u); return bal+f-u; } // largest lot (on lot-step grid) that the free margin can afford at this price double MaxLotFor(const double freeM,const double price) { if(freeM<=0 || price<=0) return 0.0; double lot=freeM*leverage/(InpContractSize*price); double st=MathMax(InpLotStep,0.0001); lot=MathFloor(lot/st+1e-9)*st; lot=MathMin(lot,InpLotMax); if(lot5000) v=5000; leverage=(int)v; ObjectSetString(panelChart,P+"LEV",OBJPROP_TEXT,IntegerToString(leverage)); lastMsg="Leverage set to 1:"+IntegerToString(leverage); uiForce=true; } void ApplySLTPText() { long s=StringToInteger(ObjectGetString(panelChart,P+"SLP",OBJPROP_TEXT)); long t=StringToInteger(ObjectGetString(panelChart,P+"TPP",OBJPROP_TEXT)); if(s<0) s=0; if(t<0) t=0; slPts=(int)s; tpPts=(int)t; ObjectSetString(panelChart,P+"SLP",OBJPROP_TEXT,IntegerToString(slPts)); ObjectSetString(panelChart,P+"TPP",OBJPROP_TEXT,IntegerToString(tpPts)); lastMsg=StringFormat("SL=%d TP=%d points (new trades)",slPts,tpPts); uiForce=true; } void ApplyBalText() { string t=ObjectGetString(panelChart,P+"BALV",OBJPROP_TEXT); StringReplace(t,",","."); StringReplace(t," ",""); double v=StringToDouble(t); if(v<=0) v=resetBal; resetBal=NormalizeDouble(v,2); ObjectSetString(panelChart,P+"BALV",OBJPROP_TEXT,DoubleToString(resetBal,2)); uiForce=true; } void ApplySLTPOpen() { if(InpTradeMode==MODE_SINGLE) { for(int i=0;i0?NormalizeDouble(pos[i].open-pos[i].dir*slPts*InpPoint,digitsN):0; pos[i].tp=tpPts>0?NormalizeDouble(pos[i].open+pos[i].dir*tpPts*InpPoint,digitsN):0; } } else { double sl=basketSL; bool ta=basketTrailActive; CalcBasketAvg(); basketSL=sl; basketTrailActive=ta; } linesDirty=true; uiForce=true; lastMsg="SL/TP applied to open positions | اعمال شد"; } bool IsEditName(const string n) { return (n==P+"LOT"||n==P+"LEV"||n==P+"SLP"||n==P+"TPP"||n==P+"BALV"||n==P+"TRS"||n==P+"TRP"||n==P+"MOD_SL"||n==P+"MOD_TP"||n==P+"VOL_V"); } //+------------------------------------------------------------------+ //| BUY / SELL / pending - hot path, no UI work | //+------------------------------------------------------------------+ bool OpenPos(const int dir,const double price,const double baseLots,const ulong useTicket) { double lots=baseLots; int level=0; if(InpTradeMode==MODE_MARTINGALE) { if(openCnt>0 && martDir!=0 && martDir!=dir) { lastMsg="Opposite blocked - close basket first | جهت مخالف ممکن نیست، ابتدا سبد را ببندید"; uiForce=true; return false; } if(openCnt==0 && martDir!=dir) martLevel=0; martDir=dir; level=martLevel; int lv=MathMin(martLevel,InpMartMaxLevels); lots=NormalizeDouble(baseLots*MathPow(InpMartMultiplier,lv),2); } double need=lots*InpContractSize*price/leverage; double fm=FreeMargin(); if(need>fm+1e-9) { lastMsg=StringFormat("Not enough margin: need %s, free %s | مارجین کافی نیست",Money(need),Money(fm)); uiForce=true; return false; } Pos p; p.ticket=(useTicket>0)?useTicket:nextT++; p.dir=dir; p.lots=lots; p.open=price; p.time=rates[cur].time; p.open_=true; p.level=level; if(InpTradeMode==MODE_SINGLE) { p.sl=slPts>0 ? NormalizeDouble(price-dir*slPts*InpPoint,digitsN) : 0; p.tp=tpPts>0 ? NormalizeDouble(price+dir*tpPts*InpPoint,digitsN) : 0; } else { p.sl=0; p.tp=0; } int n=ArraySize(pos); ArrayResize(pos,n+1,64); pos[n]=p; openCnt++; AddHevOpen(p); dayLots+=lots; weekLots+=lots; monthLots+=lots; if(InpTradeMode==MODE_MARTINGALE){ CalcBasketAvg(); basketTrailActive=false; basketSL=0; } linesDirty=true; uiForce=true; lastMsg=""; pendLog+=StringFormat("%s #%I64u Lot=%.2f @ %s\n",(dir>0?"BUY":"SELL"),p.ticket,lots,Px(price)); return true; } void OpenTrade(const int dir) { if(curBid<=0) return; OpenPos(dir,(dir>0)?curAsk:curBid,lotSize,0); } void PlaceOrder(const int type,const double price0) { double price=NormalizeDouble(price0,digitsN); bool ok=false; if(type==1) ok=(pricecurBid); else if(type==3) ok=(price>curAsk); else if(type==4) ok=(price=ArraySize(ords) || !ords[i].live) return; ords[i].live=false; ordLive--; ordCompact=true; linesDirty=true; uiForce=true; pendLog+=StringFormat("Order #%I64u deleted\n",ords[i].ticket); } void CloseAll() { if(openCnt<=0) return; if(InpTradeMode==MODE_SINGLE) { for(int i=ArraySize(pos)-1;i>=0;i--) if(pos[i].open_) ClosePos(i,pos[i].dir>0?curBid:curAsk); } else CloseBasket(bDir>0?curBid:curAsk); uiForce=true; } void SetLot(double v) { v=MathMax(InpLotMin,MathMin(InpLotMax,v)); double st=MathMax(InpLotStep,0.0001); v=NormalizeDouble(MathRound(v/st)*st,2); if(v=rX[i] && x=rY[i] && y=calX && x=calY && y=calX+6 && x=calY+4 && y=calX+calW-34 && x=calY+4 && y=gx && x=gy && y=gx && x=gy+6*CAL_CH+4 && y=trX && x=trY && y=10) return MathRound(s); if(s>=1) return MathRound(s*10.0)/10.0; return MathRound(s*100.0)/100.0; } double PosFromSpeed(const double s) { double p=MathLog10(MathMax(s,SPD_MIN)/SPD_MIN)/MathLog10(SPD_MAX/SPD_MIN); return MathMax(0.0,MathMin(1.0,p)); } string SpeedTxt(const double s) { if(s==1.0) return "Live 1x"; if(s>=10) return DoubleToString(s,0)+"x"; if(s>=1) return DoubleToString(s,1)+"x"; return DoubleToString(s,2)+"x"; } void PlaceThumb() { double p=PosFromSpeed(speedMult); ObjectSetInteger(panelChart,P+"SL_TH",OBJPROP_XDISTANCE,slX+(int)MathRound(p*(slW-SL_TH))); } void SetSpeed(const double s) { speedMult=MathMax(SPD_MIN,MathMin(SPD_MAX,s)); PlaceThumb(); btnDirty=true; uiForce=true; } void SliderAt(const int x) { double p=(double)(x-slX-SL_TH/2)/(double)(slW-SL_TH); SetSpeed(SpeedFromPos(MathMax(0.0,MathMin(1.0,p)))); } void SpeedStep(const int dir) { double p=PosFromSpeed(speedMult)+dir*0.03; SetSpeed(SpeedFromPos(MathMax(0.0,MathMin(1.0,p)))); } //--- right-click context menu (Limit / Stop orders) ------------------ void CloseCtx() { if(!ctxOpen) return; ObjectsDeleteAll(panelChart,P+"CTX_"); ctxOpen=false; ChartRedraw(panelChart); } string HitCtx(const int x,const int y) { if(!ctxOpen) return ""; for(int i=0;i=ctxX+3 && x=ctxRY[i] && ycurAsk) { ctxId[0]="CTX_SLM"; ctxId[1]="CTX_BS"; ctxN=2; } else { lastMsg="Too close to market price | قیمت خیلی نزدیک بازار است"; uiForce=true; return; } int w=CTX_W, h=CTX_ROW*(ctxN+1)+6; int cw=(int)ChartGetInteger(panelChart,CHART_WIDTH_IN_PIXELS); int ch=(int)ChartGetInteger(panelChart,CHART_HEIGHT_IN_PIXELS); ctxX=MathMax(2,MathMin(x,cw-w-4)); ctxY=MathMax(2,MathMin(y,ch-h-4)); Rect("CTX_BG",ctxX,ctxY,w,h,C'30,36,54',C'0,150,255',2999); RawBtn(P+"CTX_H",StringFormat("Order @ %s | Lot %.2f",Px(price),lotSize),ctxX+3,ctxY+3,w-6,CTX_ROW-2, C'0,92,170',clrWhite,C'0,92,170',9,3000); for(int i=0;i=4 && x<4+panW && y>=4 && y<4+panH) return; // not over the panel if(InTrades(x,y)) return; OpenCtx(x,y); } //--- Trades window interaction --------------------------------------- void CloseRow(const int row) { if(row<0 || row>=twRows) return; if(twKind[row]==0) { int i=twIdx[row]; if(i>=0 && i0?curBid:curAsk); else CloseBasket(bDir>0?curBid:curAsk); } } else CancelOrder(twIdx[row]); uiForce=true; } void MoveTrades() { for(int i=0;i=trX+trW-28){ Act("TW_CLOSE"); return; } trDrag=true; trDX=x-trX; trDY=y-trY; ChartSetInteger(panelChart,CHART_MOUSE_SCROLL,false); return; } if(y>=trY+trH-TR_FOOT) { if(x>=trX+trW-22){ trSize=true; ChartSetInteger(panelChart,CHART_MOUSE_SCROLL,false); return; } if(x>=trX+trW-78 && x=trX+trW-52 && x=rowsTop && x>=trX+trW-30) { int r=(y-rowsTop)/TR_ROWH; if(r>=0 && r=rowsTop) { int r=(y-rowsTop)/TR_ROWH; if(r<0 || r>=twVis || trTop+r>=twRows) return; double tw=(double)(trW-2-30), acc=1.0; for(int c=0;c<10;c++) { double l=acc; acc+=twFr[c]*tw; if((c==6||c==7) && x>=trX+(int)l && x=trX+(int)l && xexit lines | //+------------------------------------------------------------------+ void AddHevOpen(const Pos &p) { int n=ArraySize(hev); if(n>3000) return; ArrayResize(hev,n+1,32); hev[n].ticket=p.ticket; hev[n].kind=0; hev[n].dir=p.dir; hev[n].t=p.time; hev[n].price=p.open; hev[n].t0=p.time; hev[n].p0=p.open; hev[n].profit=0; hev[n].part=0; hev[n].lots=p.lots; } void AddHevClose(const Pos &p,const double price,const double profit) { int n=ArraySize(hev); if(n>3000) return; ArrayResize(hev,n+1,32); hev[n].ticket=p.ticket; hev[n].kind=1; hev[n].dir=p.dir; hev[n].t=(curTime>0)?curTime:p.time; hev[n].price=price; hev[n].t0=p.time; hev[n].p0=p.open; hev[n].profit=profit; hev[n].part=0; hev[n].lots=p.lots; } // partial close: same exit mark + dotted line as a full close, but a unique object name per event void AddHevPartial(const Pos &p,const double closedLots,const double price,const double profit) { int n=ArraySize(hev); if(n>3000) return; ArrayResize(hev,n+1,32); hev[n].ticket=p.ticket; hev[n].kind=1; hev[n].dir=p.dir; hev[n].t=(curTime>0)?curTime:p.time; hev[n].price=price; hev[n].t0=p.time; hev[n].p0=p.open; hev[n].profit=profit; hev[n].part=++partSeq; hev[n].lots=closedLots; } void ProcessHistory() { int n=ArraySize(hev); if(n<=0) return; for(int k=0;k0?"p"+IntegerToString(hev[k].part):""); bool buy=(hev[k].dir>0); color col=buy?clrDodgerBlue:clrRed; if(hev[k].kind==0) { string nm=base+"_in"; if(ObjectCreate(panelChart,nm,buy?OBJ_ARROW_BUY:OBJ_ARROW_SELL,0,hev[k].t,hev[k].price)) { ObjectSetInteger(panelChart,nm,OBJPROP_COLOR,col); ObjectSetInteger(panelChart,nm,OBJPROP_SELECTABLE,false); ObjectSetString(panelChart,nm,OBJPROP_TOOLTIP,"#"+IntegerToString((long)hev[k].ticket)+(buy?" buy @ ":" sell @ ")+Px(hev[k].price)); } } else { string nx=base+"_out", nl=base+"_ln"; color pc=(hev[k].profit>=0)?clrLimeGreen:clrOrangeRed; if(ObjectCreate(panelChart,nx,OBJ_ARROW,0,hev[k].t,hev[k].price)) { ObjectSetInteger(panelChart,nx,OBJPROP_ARROWCODE,251); ObjectSetInteger(panelChart,nx,OBJPROP_COLOR,pc); ObjectSetInteger(panelChart,nx,OBJPROP_SELECTABLE,false); ObjectSetString(panelChart,nx,OBJPROP_TOOLTIP,"#"+IntegerToString((long)hev[k].ticket)+(hev[k].part>0?" partial close "+DoubleToString(hev[k].lots,2)+" @ ":" closed @ ")+Px(hev[k].price)+" "+MoneyS(hev[k].profit)); } if(ObjectCreate(panelChart,nl,OBJ_TREND,0,hev[k].t0,hev[k].p0,hev[k].t,hev[k].price)) { ObjectSetInteger(panelChart,nl,OBJPROP_COLOR,col); ObjectSetInteger(panelChart,nl,OBJPROP_STYLE,STYLE_DOT); ObjectSetInteger(panelChart,nl,OBJPROP_WIDTH,1); ObjectSetInteger(panelChart,nl,OBJPROP_RAY_RIGHT,false); ObjectSetInteger(panelChart,nl,OBJPROP_SELECTABLE,false); ObjectSetInteger(panelChart,nl,OBJPROP_BACK,true); } int m=ArraySize(hDrawn); ArrayResize(hDrawn,m+1,64); hDrawn[m]=base; if(m+1>300) // keep the chart light { string ob=hDrawn[0]; ObjectDelete(panelChart,ob+"_in"); ObjectDelete(panelChart,ob+"_out"); ObjectDelete(panelChart,ob+"_ln"); ArrayRemove(hDrawn,0,1); } } } ArrayResize(hev,0); } //+------------------------------------------------------------------+ //| Modify SL / TP: validation, drag on chart, Modify popup | //+------------------------------------------------------------------+ int FindPosByTicket(const ulong tk) { for(int i=0;i0) { bool buy=(pos[i].dir>0); bool ok; if(isSL) ok=buy?(pricecurAsk); else ok=buy?(price>curBid):(price drag it like in MetaTrader bool TryStartLineDrag(const int x,const int y) { if(dragKind!=0) return false; const int TOL=5; if(InpTradeMode==MODE_SINGLE) { for(int i=0;i0 && MathAbs(y-PriceToY(pos[i].sl))<=TOL){ BeginDrag(1,pos[i].ticket,P+"S_"+t); return true; } if(pos[i].tp>0 && MathAbs(y-PriceToY(pos[i].tp))<=TOL){ BeginDrag(2,pos[i].ticket,P+"T_"+t); return true; } } } else if(basketLots>0 && basketTP>0 && MathAbs(y-PriceToY(basketTP))<=TOL) { BeginDrag(4,0,P+"BTP"); return true; } for(int i=0;icurBid); else if(t==3) ok=(np>curAsk); else ok=(np0)?(np>basketAvg):(np=1) { tpPts=pts; ObjectSetString(panelChart,P+"TPP",OBJPROP_TEXT,IntegerToString(tpPts)); double sl=basketSL; bool ta=basketTrailActive; CalcBasketAvg(); basketSL=sl; basketTrailActive=ta; lastMsg=StringFormat("Basket TP = %d points",tpPts); } else lastMsg="Invalid basket TP | حد سود نامعتبر"; } } bool InMod(const int x,const int y) { return modOpen && x>=modX && x=modY && y=modY+72 && y=modX+10 && x=modX+100 && x=modX+196 && x=modY+108 && y=modX+196 && x=ArraySize(pos) || !pos[i].open_) return; CloseVol(); CloseMod(); modOpen=true; modTicket=pos[i].ticket; modX=MathMax(2,trX+40); modY=(trY>MOD_H+12)?trY-MOD_H-6:trY+trH+6; Rect("MOD_BG",modX,modY,MOD_W,MOD_H,C'30,36,54',C'0,150,255',3400); RawBtn(P+"MOD_T",StringFormat("Modify #%I64u %s %.2f",pos[i].ticket,(pos[i].dir>0?"buy":"sell"),pos[i].lots), modX+3,modY+3,MOD_W-6,24,C'0,92,170',clrWhite,C'0,92,170',9,3500); Lbl("MOD_LS","S/L",modX+10,modY+42,clrWhite,9); EditBox("MOD_SL",pos[i].sl>0?Px(pos[i].sl):"0",modX+40,modY+36,100,26); Lbl("MOD_LT","T/P",modX+156,modY+42,clrWhite,9); EditBox("MOD_TP",pos[i].tp>0?Px(pos[i].tp):"0",modX+186,modY+36,100,26); ObjectSetInteger(panelChart,P+"MOD_SL",OBJPROP_ZORDER,3500); ObjectSetInteger(panelChart,P+"MOD_TP",OBJPROP_ZORDER,3500); RawBtn(P+"MOD_OK", "Apply", modX+10, modY+72,84, 26,clrForestGreen,clrWhite,Lighten(clrForestGreen,55),9,3500); RawBtn(P+"MOD_NOSL", "Remove SL", modX+100,modY+72,90, 26,clrFireBrick, clrWhite,Lighten(clrFireBrick,55), 9,3500); RawBtn(P+"MOD_NOTP", "Remove TP", modX+196,modY+72,90, 26,clrFireBrick, clrWhite,Lighten(clrFireBrick,55), 9,3500); RawBtn(P+"MOD_X", "Close", modX+196,modY+108,90,26,clrDimGray, clrWhite,Lighten(clrDimGray,55), 9,3500); Lbl("MOD_H","0 = no level | صفر یعنی بدون سطح",modX+10,modY+114,C'140,152,185',8); ChartRedraw(panelChart); } void ApplyMod() { int i=FindPosByTicket(modTicket); if(i<0){ CloseMod(); return; } string t1=ObjectGetString(panelChart,P+"MOD_SL",OBJPROP_TEXT), t2=ObjectGetString(panelChart,P+"MOD_TP",OBJPROP_TEXT); StringReplace(t1,",","."); StringReplace(t2,",","."); string e1="", e2=""; bool a=SetPosLevel(i,true,StringToDouble(t1),e1); bool b=SetPosLevel(i,false,StringToDouble(t2),e2); if(a && b){ lastMsg=StringFormat("#%I64u SL/TP updated",modTicket); CloseMod(); } else lastMsg=(a?e2:e1); uiForce=true; } void ModRemove(const bool isSL) { int i=FindPosByTicket(modTicket); if(i<0){ CloseMod(); return; } string e=""; SetPosLevel(i,isSL,0,e); ObjectSetString(panelChart,P+(isSL?"MOD_SL":"MOD_TP"),OBJPROP_TEXT,"0"); lastMsg=StringFormat("#%I64u %s removed",modTicket,(isSL?"SL":"TP")); uiForce=true; } //+------------------------------------------------------------------+ //| Change Volume of an open position = PARTIAL CLOSE (MT5 style) | //| the removed part is closed at market, its P/L goes to balance | //+------------------------------------------------------------------+ bool PartialClose(const int i,const double newLots0,string &err) { err=""; if(i<0 || i>=ArraySize(pos) || !pos[i].open_){ err="Position not found | معامله پیدا نشد"; return false; } double st=MathMax(InpLotStep,0.0001); double nl=NormalizeDouble(MathRound(newLots0/st)*st,2); if(newLots0<=0 || nlpos[i].lots+1e-9) { err=StringFormat("Volume can only be reduced (now %.2f) | حجم فقط کاهش می‌یابد",pos[i].lots); return false; } double cl=NormalizeDouble(pos[i].lots-nl,2); if(cl<=1e-9){ err="Same volume - nothing changed | حجم تغییری نکرد"; return false; } double price=(pos[i].dir>0)?curBid:curAsk; double pr=CalcProfit(pos[i].dir,pos[i].open,cl,price); // P/L of the closed part only bal+=pr; dayProfit+=pr; weekProfit+=pr; monthProfit+=pr; closedToday+=pr; AddHevPartial(pos[i],cl,price,pr); pos[i].lots=nl; if(InpTradeMode==MODE_MARTINGALE) { double sl=basketSL; bool ta=basketTrailActive; CalcBasketAvg(); basketSL=sl; basketTrailActive=ta; } linesDirty=true; uiForce=true; lastMsg=StringFormat("#%I64u closed %.2f lot, P/L %s | %.2f remains",pos[i].ticket,cl,MoneyS(pr),nl); pendLog+=StringFormat("Partial close #%I64u %s %.2f @ %s P/L=%+.2f | remaining %.2f\n", pos[i].ticket,(pos[i].dir>0?"BUY":"SELL"),cl,Px(price),pr,nl); return true; } bool InVol(const int x,const int y) { return volOpen && x>=volX && x=volY && y=volY+72 && y=volX+10 && x=volX+196 && x=ArraySize(pos) || !pos[i].open_) return; CloseMod(); CloseVol(); volOpen=true; volTicket=pos[i].ticket; volX=MathMax(2,trX+40); volY=(trY>MOD_H+12)?trY-MOD_H-6:trY+trH+6; Rect("VOL_BG",volX,volY,MOD_W,MOD_H,C'30,36,54',C'0,150,255',3400); RawBtn(P+"VOL_T",StringFormat("Volume #%I64u %s %.2f",pos[i].ticket,(pos[i].dir>0?"buy":"sell"),pos[i].lots), volX+3,volY+3,MOD_W-6,24,C'0,92,170',clrWhite,C'0,92,170',9,3500); Lbl("VOL_LV","Volume",volX+10,volY+42,clrWhite,9); EditBox("VOL_V",DoubleToString(pos[i].lots,2),volX+70,volY+36,100,26); ObjectSetInteger(panelChart,P+"VOL_V",OBJPROP_ZORDER,3500); Lbl("VOL_LC",StringFormat("max %.2f",pos[i].lots),volX+182,volY+42,C'140,152,185',9); RawBtn(P+"VOL_OK","Apply",volX+10,volY+72,84,26,clrForestGreen,clrWhite,Lighten(clrForestGreen,55),9,3500); RawBtn(P+"VOL_X","Close",volX+196,volY+72,90,26,clrDimGray,clrWhite,Lighten(clrDimGray,55),9,3500); Lbl("VOL_H","New volume < current: the rest is closed at market | حجم کمتر یعنی بستن بخشی از معامله",volX+10,volY+108,C'140,152,185',7); ChartRedraw(panelChart); } void ApplyVol() { int i=FindPosByTicket(volTicket); if(i<0){ CloseVol(); return; } string t=ObjectGetString(panelChart,P+"VOL_V",OBJPROP_TEXT); StringReplace(t,",","."); StringTrimLeft(t); StringTrimRight(t); string e=""; if(PartialClose(i,StringToDouble(t),e)) CloseVol(); else { lastMsg=e; uiForce=true; } } void ApplyTrailText() { long a=StringToInteger(ObjectGetString(panelChart,P+"TRS",OBJPROP_TEXT)); long b=StringToInteger(ObjectGetString(panelChart,P+"TRP",OBJPROP_TEXT)); if(a<0) a=0; if(b<0) b=0; trStart=(int)a; trStep=(int)b; ObjectSetString(panelChart,P+"TRS",OBJPROP_TEXT,IntegerToString(trStart)); ObjectSetString(panelChart,P+"TRP",OBJPROP_TEXT,IntegerToString(trStep)); lastMsg=StringFormat("Trailing: start %d step %d points",trStart,trStep); uiForce=true; } void ToggleTrail() { trailOn=!trailOn; if(!trailOn && InpTradeMode==MODE_MARTINGALE){ basketTrailActive=false; basketSL=0; linesDirty=true; } lastMsg=trailOn?"Trailing stop ON | تریلینگ روشن":"Trailing stop OFF | تریلینگ خاموش"; btnDirty=true; uiForce=true; } void EndDrags() { if(dragKind!=0) ApplyDrag(); if(trSize) trSizeDirty=true; sliderDrag=false; trDrag=false; trSize=false; ChartSetInteger(panelChart,CHART_MOUSE_SCROLL,true); } //--- press dispatcher ------------------------------------------------- void PressAt(const int x,const int y) { if(ctxOpen) { string c=HitCtx(x,y); CloseCtx(); if(c!=""){ MarkPressed(c); Act(c); return; } } if(calOpen) { if(InCal(x,y)) { string c=HitCal(x,y); if(c!=""){ MarkPressed(c); Act(c); } return; } string pid=HitPanel(x,y); bool same=(calTarget==0 && (pid=="FROM"||pid=="FROM_I")) || (calTarget==1 && (pid=="TO"||pid=="TO_I")); CloseCal(); if(same){ MarkPressed(pid); return; } if(pid!=""){ MarkPressed(pid); Act(pid); } return; } if(volOpen) { if(InVol(x,y)){ string vc=HitVol(x,y); if(vc!=""){ MarkPressed(vc); Act(vc); } return; } CloseVol(); } if(modOpen) { if(InMod(x,y)){ string mc=HitMod(x,y); if(mc!=""){ MarkPressed(mc); Act(mc); } return; } CloseMod(); } if(InTrades(x,y)){ HandleTrades(x,y); return; } if(x>=slX-6 && x=slY-4 && y=4 && x<4+panW && y>=4 && y<4+panH) && TryStartLineDrag(x,y)) return; if(!InpFireOnPress) return; string id=HitPanel(x,y); if(id!=""){ MarkPressed(id); Act(id); } } void SetPlay(const bool p) { playing=p; lastUs=GetMicrosecondCount(); EventSetMillisecondTimer(p?1:10); btnDirty=true; uiForce=true; } // Play applies the chosen date range first (if it changed or replay finished) void StartPlay() { bool atEnd=(cur>=endBar && tkIdx>=tkN && barClockUs>=barDurUs); if(selDirty || atEnd) ApplyRange(); SetPlay(true); } void Act(const string b) { if(b=="BUY") OpenTrade(1); else if(b=="SELL") OpenTrade(-1); else if(b=="CLOSE") CloseAll(); else if(b=="PLAY") { StartPlay(); UpdateButtonStates(true); btnDirty=false; } else if(b=="PAUSE") { SetPlay(false); UpdateButtonStates(true); btnDirty=false; } else if(b=="LIVE") { SetSpeed(1.0); UpdateButtonStates(true); btnDirty=false; } else if(b=="LOT_M") SetLot(lotSize-InpLotStep); else if(b=="LOT_P") SetLot(lotSize+InpLotStep); else if(b=="MAXLOT") SetLot(MaxLotFor(FreeMargin(),curAsk)); else if(b=="APPLY") ApplySLTPOpen(); else if(b=="BAL_RESET") ResetBalance(); else if(b=="BAL_DEP") Deposit(); else if(b=="TRAIL_T") ToggleTrail(); else if(b=="MOD_OK") ApplyMod(); else if(b=="MOD_NOSL") ModRemove(true); else if(b=="MOD_NOTP") ModRemove(false); else if(b=="MOD_X") CloseMod(); else if(b=="VOL_OK") ApplyVol(); else if(b=="VOL_X") CloseVol(); else if(b=="TRADES"){ if(trOpen) CloseTrades(); else OpenTrades(); UpdateButtonStates(true); btnDirty=false; } else if(b=="TW_CLOSE"){ CloseTrades(); UpdateButtonStates(true); btnDirty=false; } else if(b=="CTX_BL") { PlaceOrder(1,ctxPrice); CloseCtx(); } else if(b=="CTX_SLM") { PlaceOrder(2,ctxPrice); CloseCtx(); } else if(b=="CTX_BS") { PlaceOrder(3,ctxPrice); CloseCtx(); } else if(b=="CTX_SS") { PlaceOrder(4,ctxPrice); CloseCtx(); } else if(b=="TF_M1") SwitchTF(PERIOD_M1); else if(b=="TF_M5") SwitchTF(PERIOD_M5); else if(b=="TF_M15") SwitchTF(PERIOD_M15); else if(b=="TF_M30") SwitchTF(PERIOD_M30); else if(b=="TF_H1") SwitchTF(PERIOD_H1); else if(b=="TF_H4") SwitchTF(PERIOD_H4); else if(b=="TF_D1") SwitchTF(PERIOD_D1); else if(b=="TF_W1") SwitchTF(PERIOD_W1); else if(b=="FROM"||b=="FROM_I") { if(calOpen && calTarget==0) CloseCal(); else OpenCal(0); } else if(b=="TO"||b=="TO_I") { if(calOpen && calTarget==1) CloseCal(); else OpenCal(1); } else if(b=="CAL_PREV"){ calMon--; if(calMon<1){calMon=12;calYear--;} RefreshCal(); ChartRedraw(panelChart); } else if(b=="CAL_NEXT"){ calMon++; if(calMon>12){calMon=1;calYear++;} RefreshCal(); ChartRedraw(panelChart); } else if(b=="CAL_TODAY"){ PickDate(MathMax(dataFirst,MathMin(dataLast,DayStart(TimeLocal())))); } else if(StringFind(b,"CAL_D")==0) { int i=(int)StringToInteger(StringSubstr(b,5)); if(i>=0 && i<42 && calOn[i]) PickDate(calDate[i]); } uiForce=true; } void OnChartEvent(const int id,const long &lp,const double &dp,const string &sp) { if(!ready) return; if(id==CHARTEVENT_MOUSE_MOVE) { int x=(int)lp, y=(int)dp; uint fl=(uint)StringToInteger(sp); bool ld=((fl&1)!=0), rd=((fl&2)!=0); if(ld) { if(sliderDrag) SliderAt(x); else if(trDrag){ trX=MathMax(0,x-trDX); trY=MathMax(0,y-trDY); MoveTrades(); ChartRedraw(panelChart); } else if(trSize){ trW=MathMax(600,x-trX+10); trH=MathMax(160,y-trY+10); trSizeDirty=true; } else if(dragKind!=0) DragMove(y); } else if(sliderDrag||trDrag||trSize||dragKind!=0) EndDrags(); if(ld && !mouseDown) PressAt(x,y); if(rd && !rightDown) RightClickAt(x,y); mouseDown=ld; rightDown=rd; return; } if(id==CHARTEVENT_OBJECT_ENDEDIT) { editing=false; if(sp==P+"LOT") ApplyLotText(); else if(sp==P+"LEV") ApplyLevText(); else if(sp==P+"SLP"||sp==P+"TPP") ApplySLTPText(); else if(sp==P+"BALV") ApplyBalText(); else if(sp==P+"TRS"||sp==P+"TRP") ApplyTrailText(); return; } // hotkeys: B = Buy, S = Sell, C = Close all, Space = Play/Pause, +/- = speed, Esc = close popups if(id==CHARTEVENT_KEYDOWN) { if(editing) return; if(lp==66) OpenTrade(1); else if(lp==83) OpenTrade(-1); else if(lp==67) CloseAll(); else if(lp==32) { if(playing) Act("PAUSE"); else Act("PLAY"); } else if(lp==107||lp==187) SpeedStep(1); else if(lp==109||lp==189) SpeedStep(-1); else if(lp==27) { CloseCtx(); CloseCal(); CloseMod(); CloseVol(); } return; } // fallback path (click on release) - skipped when the press already fired if(id!=CHARTEVENT_OBJECT_CLICK) return; if(StringFind(sp,P)!=0) return; if(IsEditName(sp)){ editing=true; return; } string b=StringSubstr(sp,StringLen(P)); if(b=="") return; bool dup=(b==pressId && (GetTickCount64()-pressMs)<1500); if(dup) pressId=""; else Act(b); ObjectSetInteger(panelChart,sp,OBJPROP_STATE,false); btnDirty=true; uiForce=true; } //+------------------------------------------------------------------+ //| TF switch: terminal rebuilds every TF from ticks, so no reload. | //+------------------------------------------------------------------+ void SwitchTF(ENUM_TIMEFRAMES tf) { if(tf==currentTF) return; FlushTicks(); ChartSetSymbolPeriod(panelChart,customSym,tf); } datetime DayStart(const datetime t){ return (datetime)(((long)t/86400)*86400); } int FindFirst(const datetime t) // first bar with time >= t { int lo=0,hi=total-1; while(lot) hi=mid-1; else lo=mid; } return lo; } void ApplyRange() { if(selTo=0;s--){ ArrayCopy(all,cser,w,segS[s],segC[s]); w+=segC[s]; } if(idx-from>0) ArrayCopy(all,rates,w,from,idx-from); CustomRatesUpdate(customSym,all); } void StartAt(const int idx) { cur=idx; PushHistory(idx); curBid=(idx>0)?rates[idx-1].close:rates[0].open; curAsk=curBid+spreadPx; UpdateStatsPeriod(rates[cur].time); BuildBarTicks(cur); tkIdx=0; barClockUs=0; lastUs=GetMicrosecondCount(); Advance(0.0); // emit opening tick so the first candle is visible } void UpdateStatsPeriod(const datetime t) { datetime dayStart=(datetime)(((long)t/86400)*86400); if(dayStart==lastDay && lastWeek!=0 && lastMonth!=0) return; MqlDateTime d; TimeToStruct(dayStart,d); long dn=(long)dayStart/86400; datetime weekStart =(datetime)((dn-((dn+3)%7))*86400); // Monday based datetime monthStart=(datetime)(((long)dayStart)-(long)(d.day-1)*86400); if(lastDay==0) lastDay=dayStart; else if(dayStart!=lastDay){ ydayProfit=dayProfit; dayProfit=0; dayLots=0; closedToday=0; lastDay=dayStart; } if(lastWeek==0) lastWeek=weekStart; else if(weekStart!=lastWeek){ weekProfit=0; weekLots=0; lastWeek=weekStart; } if(lastMonth==0) lastMonth=monthStart; else if(monthStart!=lastMonth){ monthProfit=0; monthLots=0; lastMonth=monthStart; } } //+------------------------------------------------------------------+ //| Data: full multi-TF export / cached load | //+------------------------------------------------------------------+ string CsvPath(ENUM_TIMEFRAMES tf){ return folderName+"\\"+baseSym+"+"+TFName(tf)+".csv"; } string BinPath(ENUM_TIMEFRAMES tf){ return folderName+"\\"+baseSym+"+"+TFName(tf)+".bin"; } int TfCandidates(ENUM_TIMEFRAMES &c[]) { ArrayResize(c,8); c[0]=PERIOD_M1; c[1]=PERIOD_M5; c[2]=PERIOD_M15; c[3]=PERIOD_M30; c[4]=PERIOD_H1; c[5]=PERIOD_H4; c[6]=PERIOD_D1; c[7]=PERIOD_W1; int s=0; for(int i=0;i<8;i++) if(c[i]==InpBaseTF){ s=i; break; } return s; } // make sure the terminal really has the whole history before exporting int FetchFull(const string sym,ENUM_TIMEFRAMES tf,MqlRates &r[]) { for(int w=0;w<25;w++){ if((bool)SeriesInfoInteger(sym,tf,SERIES_SYNCHRONIZED)) break; Sleep(200); } int prev=-1, n=0; for(int k=0;k<8;k++) { n=CopyRates(sym,tf,0,InpBarsToSave,r); if(n<=0){ Sleep(250); continue; } if(n==prev || n>=InpBarsToSave) break; // stopped growing => fully loaded prev=n; Sleep(250); } return n; } void SaveSource() { ENUM_TIMEFRAMES c[]; int s=TfCandidates(c); SymbolSelect(_Symbol,true); for(int k=s;k<8;k++) { if(!InpResaveOnInit && FileIsExist(CsvPath(c[k]))) continue; MqlRates r[]; int n=FetchFull(_Symbol,c[k],r); if(n<=0){ Print("No history for ",TFName(c[k])," on ",_Symbol); continue; } if(r[n-1].time+PeriodSeconds(c[k])>TimeCurrent()) n--; // drop the still-forming bar if(n<=0) continue; int h=FileOpen(CsvPath(c[k]),FILE_WRITE|FILE_CSV|FILE_ANSI,','); if(h==INVALID_HANDLE){ Print("Cannot write ",CsvPath(c[k])); continue; } FileWrite(h,"DateTime","Open","High","Low","Close","TickVolume","Spread","RealVolume"); for(int j=0;j ",TimeToString(r[n-1].time)); } } bool SetupCustom() { if(!SymbolInfoInteger(customSym,SYMBOL_EXIST)) { if(!CustomSymbolCreate(customSym,"Custom\\Replay")) return false; } CustomSymbolSetInteger(customSym,SYMBOL_DIGITS,digitsN); CustomSymbolSetDouble(customSym,SYMBOL_POINT,InpPoint); CustomSymbolSetDouble(customSym,SYMBOL_TRADE_TICK_SIZE,InpPoint); CustomSymbolSetDouble(customSym,SYMBOL_TRADE_CONTRACT_SIZE,InpContractSize); CustomSymbolSetInteger(customSym,SYMBOL_TRADE_CALC_MODE,SYMBOL_CALC_MODE_CFD); CustomSymbolSetInteger(customSym,SYMBOL_TRADE_MODE,SYMBOL_TRADE_MODE_FULL); SymbolSelect(customSym,true); // required for CustomTicksAdd return true; } // binary cache: loads hundreds of thousands of bars in milliseconds bool LoadBin(ENUM_TIMEFRAMES tf,MqlRates &out[]) { if(!FileIsExist(BinPath(tf)) || !FileIsExist(CsvPath(tf))) return false; int hc=FileOpen(CsvPath(tf),FILE_READ|FILE_TXT|FILE_ANSI|FILE_SHARE_READ); if(hc==INVALID_HANDLE) return false; long dCsv=FileGetInteger(hc,FILE_MODIFY_DATE); FileClose(hc); int h=FileOpen(BinPath(tf),FILE_READ|FILE_BIN); if(h==INVALID_HANDLE) return false; bool ok=false; if(FileGetInteger(h,FILE_MODIFY_DATE)>=dCsv && FileReadInteger(h)==0x42494E32 && FileReadInteger(h)==InpBarsToLoad) { int n=FileReadInteger(h); if(n>=10 && n<=20000000) { ArrayResize(out,n); ok=((int)FileReadArray(h,out,0,n)==n); } } FileClose(h); return ok; } void SaveBin(ENUM_TIMEFRAMES tf,MqlRates &src[]) { int n=ArraySize(src); int h=FileOpen(BinPath(tf),FILE_WRITE|FILE_BIN); if(h==INVALID_HANDLE) return; FileWriteInteger(h,0x42494E32); FileWriteInteger(h,InpBarsToLoad); FileWriteInteger(h,n); FileWriteArray(h,src,0,n); FileClose(h); } bool ReadSeries(ENUM_TIMEFRAMES tf,MqlRates &out[]) { if(LoadBin(tf,out)) return ArraySize(out)>=10; if(!FileIsExist(CsvPath(tf))) { int n0=CopyRates(baseSym,tf,0,InpBarsToLoad,out); return n0>=10; } int h=FileOpen(CsvPath(tf),FILE_READ|FILE_TXT|FILE_ANSI|FILE_SHARE_READ); if(h==INVALID_HANDLE) return false; MqlRates tmp[]; ArrayResize(tmp,0,MathMin(InpBarsToSave,3000000)+16); int n=0; string line; string p[]; datetime lastT=0; while(!FileIsEnding(h)) { line=FileReadString(h); if(StringLen(line)<16) continue; int k=StringSplit(line,',',p); if(k<6) continue; datetime t=StringToTime(p[0]); if(t<=0 || t<=lastT) continue; // header / duplicates / disorder double o=StringToDouble(p[1]), hi=StringToDouble(p[2]), lo=StringToDouble(p[3]), cl=StringToDouble(p[4]); if(o<=0 || hi<=0 || lo<=0 || cl<=0) continue; MqlRates rr; ZeroMemory(rr); rr.time=t; rr.open=o; rr.high=hi; rr.low=lo; rr.close=cl; rr.tick_volume=(long)StringToInteger(p[4+1]); if(rr.tick_volume<=0) rr.tick_volume=1; if(k>=7) rr.spread=(int)StringToInteger(p[6]); if(k>=8) rr.real_volume=(long)StringToInteger(p[7]); ArrayResize(tmp,n+1); tmp[n++]=rr; lastT=t; } FileClose(h); if(n<10) return false; int keep=MathMin(n,InpBarsToLoad); // newest bars win ArrayResize(out,keep); ArrayCopy(out,tmp,0,n-keep,keep); SaveBin(tf,out); return true; } bool LoadBase() { ENUM_TIMEFRAMES c[]; int s=TfCandidates(c); int kb=-1; for(int k=s;k<8;k++) if(ReadSeries(c[k],rates)){ kb=k; break; } if(kb<0) return false; total=ArraySize(rates); usedTF=c[kb]; ArrayResize(cser,0); cK=0; int w=0; for(int k=kb+1;k<8 && cK<8;k++) { MqlRates t[]; if(!ReadSeries(c[k],t)) continue; int m=ArraySize(t); ArrayResize(cser,w+m); ArrayCopy(cser,t,w,0,m); cTF[cK]=c[k]; cOff[cK]=w; cCnt[cK]=m; cK++; w+=m; } Print("Loaded base ",TFName(usedTF)," bars: ",total," | ",TimeToString(rates[0].time)," -> ",TimeToString(rates[total-1].time), " | stitched coarser TFs: ",cK); return total>=10; } //+------------------------------------------------------------------+ //| Persist / restore state across TF changes (MT5 re-inits the EA) | //+------------------------------------------------------------------+ void SaveState() { int h=FileOpen(StatePath(),FILE_WRITE|FILE_BIN); if(h==INVALID_HANDLE) return; FileWriteInteger(h,0x52504C5C); FileWriteLong(h,(long)TimeLocal()); FileWriteInteger(h,cur); FileWriteInteger(h,endBar); FileWriteInteger(h,tkIdx); FileWriteDouble(h,barClockUs); FileWriteDouble(h,bal); FileWriteDouble(h,speedMult); FileWriteDouble(h,lotSize); FileWriteInteger(h,leverage); FileWriteInteger(h,slPts); FileWriteInteger(h,tpPts); FileWriteDouble(h,resetBal); FileWriteInteger(h,trailOn?1:0); FileWriteInteger(h,trStart); FileWriteInteger(h,trStep); FileWriteInteger(h,playing?1:0); FileWriteLong(h,(long)nextT); FileWriteInteger(h,martLevel); FileWriteInteger(h,martDir); FileWriteDouble(h,basketSL); FileWriteInteger(h,basketTrailActive?1:0); FileWriteDouble(h,curBid); FileWriteDouble(h,dayProfit); FileWriteDouble(h,ydayProfit); FileWriteDouble(h,weekProfit); FileWriteDouble(h,monthProfit); FileWriteDouble(h,dayLots); FileWriteDouble(h,weekLots); FileWriteDouble(h,monthLots); FileWriteDouble(h,closedToday); FileWriteLong(h,(long)lastDay); FileWriteLong(h,(long)lastWeek); FileWriteLong(h,(long)lastMonth); FileWriteLong(h,(long)selFrom); FileWriteLong(h,(long)selTo); FileWriteInteger(h,selDirty?1:0); FileWriteInteger(h,trOpen?1:0); FileWriteInteger(h,trX); FileWriteInteger(h,trY); FileWriteInteger(h,trW); FileWriteInteger(h,trH); int n=ArraySize(pos); FileWriteInteger(h,n); for(int i=0;i=0 && n<100000) { ArrayResize(pos,n,64); for(int i=0;i=0 && m<100000) { ArrayResize(ords,m,16); for(int i=0;i=0 && curtkN) tkIdx=tkN; eq=bal; Print("State restored after TF change | وضعیت حفظ شد. Bar=",cur); return true; } //+------------------------------------------------------------------+ //| Chart lines - MetaTrader 5 standard look | //| dotted lines, labels "#ticket buy 0.10" / "#ticket sl|tp" | //+------------------------------------------------------------------+ void DrawHLine(const string name,const double price,const color clr,const ENUM_LINE_STYLE st,const int w,const string txt) { if(dragKind!=0 && name==dragName) return; // do not fight the user while a line is being dragged if(ObjectFind(panelChart,name)<0) { ObjectCreate(panelChart,name,OBJ_HLINE,0,0,price); ObjectSetInteger(panelChart,name,OBJPROP_COLOR,clr); ObjectSetInteger(panelChart,name,OBJPROP_STYLE,st); ObjectSetInteger(panelChart,name,OBJPROP_WIDTH,w); ObjectSetInteger(panelChart,name,OBJPROP_BACK,true); ObjectSetInteger(panelChart,name,OBJPROP_SELECTABLE,false); } else ObjectSetDouble(panelChart,name,OBJPROP_PRICE,price); ObjectSetString(panelChart,name,OBJPROP_TEXT,txt); } void DeletePosLines(const ulong ticket) { string t=IntegerToString((long)ticket); ObjectDelete(panelChart,P+"E_"+t); ObjectDelete(panelChart,P+"S_"+t); ObjectDelete(panelChart,P+"T_"+t); } void ClearBasketLines() { ObjectDelete(panelChart,P+"BAVG"); ObjectDelete(panelChart,P+"BTP"); ObjectDelete(panelChart,P+"BSL"); } void ClearAllLines() { ObjectsDeleteAll(panelChart,P+"E_"); ObjectsDeleteAll(panelChart,P+"S_"); ObjectsDeleteAll(panelChart,P+"T_"); ObjectsDeleteAll(panelChart,P+"O_"); ObjectsDeleteAll(panelChart,"RPH_"); ArrayResize(hev,0); ArrayResize(hDrawn,0); ClearBasketLines(); } void CompactPositions() { int w=0, n=ArraySize(pos); for(int i=0;i0?"buy ":"sell ")+DoubleToString(pos[i].lots,2)); if(InpTradeMode!=MODE_SINGLE) continue; if(pos[i].sl>0) DrawHLine(P+"S_"+t,pos[i].sl,InpColSL,STYLE_DOT,1,"#"+t+" sl"); else ObjectDelete(panelChart,P+"S_"+t); if(pos[i].tp>0) DrawHLine(P+"T_"+t,pos[i].tp,InpColTP,STYLE_DOT,1,"#"+t+" tp"); else ObjectDelete(panelChart,P+"T_"+t); } } void UpdateBasketLines() { if(basketLots<=0 || basketAvg<=0){ ClearBasketLines(); return; } DrawHLine(P+"BAVG",basketAvg,InpColEntry,STYLE_SOLID,2,StringFormat("basket %s %.2f",(bDir>0?"buy":"sell"),basketLots)); DrawHLine(P+"BTP",basketTP,InpColTP,STYLE_SOLID,2,"basket tp"); if(basketTrailActive && basketSL>0) DrawHLine(P+"BSL",basketSL,InpColSL,STYLE_SOLID,2,"basket sl"); else ObjectDelete(panelChart,P+"BSL"); } //+------------------------------------------------------------------+ //| UI refresh (throttled, never inside the tick loop) | //+------------------------------------------------------------------+ void SetT(const int k,const string id,const string t0) { string t=(t0=="")?" ":t0; if(lcT[k]==t) return; lcT[k]=t; ObjectSetString(panelChart,P+id,OBJPROP_TEXT,t); } void SetCol(const int k,const string id,const color c) { if(lcC[k]==c) return; lcC[k]=c; ObjectSetInteger(panelChart,P+id,OBJPROP_COLOR,c); } void UpdateUI() { uiForce=false; double flo=0, used=0, buyL=0, sellL=0; int nb=0, ns=0; for(int i=0;i0?curBid:curAsk); flo+=fl; used+=pos[i].lots*InpContractSize*pos[i].open/leverage; if(pos[i].dir>0){ buyL+=pos[i].lots; nb++; } else { sellL+=pos[i].lots; ns++; } ObjectSetString(panelChart,P+"E_"+IntegerToString((long)pos[i].ticket),OBJPROP_TEXT, StringFormat("#%I64u %s %.2f %s",pos[i].ticket,(pos[i].dir>0?"buy":"sell"),pos[i].lots,MoneyS(fl))); } eq=bal+flo; double freeM=eq-used; double mlvl=(used>0)?eq/used*100.0:0; if(needCompact) CompactPositions(); if(ordCompact) CompactOrders(); if(linesDirty) { linesDirty=false; UpdateOrderLines(); UpdateTradeLines(); if(InpTradeMode==MODE_MARTINGALE) UpdateBasketLines(); } ProcessHistory(); if(btnDirty){ btnDirty=false; UpdateButtonStates(false); } if(pendLog!=""){ Print(pendLog); pendLog=""; } // simulated time & candle countdown double simSec=(double)rates[cur].time+barClockUs/1e6; int tfs=PeriodSeconds(currentTF); if(tfs<=0) tfs=300; double tfOff=(currentTF==PERIOD_W1)?3.0*86400.0:0.0; double remReal=(tfs-MathMod(simSec-tfOff,(double)tfs))/MathMax(0.01,speedMult); int rm=(int)(remReal/60.0), rs=(int)MathMod(remReal,60.0), rt=(int)(MathMod(remReal,1.0)*10.0); // --- big quote buttons SetT(K_SELL,"SELL","SELL "+Px(curBid)); SetT(K_BUY, "BUY", "BUY "+Px(curAsk)); SetT(K_SPEED,"SPDV",SpeedTxt(speedMult)); // --- account cells SetT(K_BAL,"VB",Money(bal)); SetT(K_EQ, "VE",Money(eq)); SetCol(K_EQ,"VE",PnlCol(eq-bal)); SetT(K_FLO,"VF",MoneyS(flo)); SetCol(K_FLO,"VF",PnlCol(flo)); SetT(K_MG,"VMG",Money(used)); SetT(K_FM,"VFM",Money(freeM)); SetCol(K_FM,"VFM",(freeM>=0)?clrWhite:C'255,100,100'); SetT(K_ML,"VML",(used>0)?DoubleToString(mlvl,1)+"%":"--"); SetCol(K_ML,"VML",(used<=0)?clrWhite:(mlvl0)?(int)MathFloor(MathMax(0.0,freeM)/(mpl*lotSize)):0; SetT(K_LI,"LI",StringFormat("Margin/1 lot %s | This lot %s | Max lot %.2f | Fits %d trade(s)", Money(mpl),Money(mpl*lotSize),MaxLotFor(freeM,curAsk),fit)); int op=nb+ns; SetT(K_POS,"POS",(op==0)?StringFormat("No open positions | Pending: %d",ordLive) :StringFormat("Open %d | Buy %.2f (%d) Sell %.2f (%d) Net %+.2f | Pending: %d",op,buyL,nb,sellL,ns,buyL-sellL,ordLive)); SetT(K_TD,"VT",MoneyS(dayProfit)); SetCol(K_TD,"VT",PnlCol(dayProfit)); SetT(K_YD,"VY",MoneyS(ydayProfit)); SetCol(K_YD,"VY",PnlCol(ydayProfit)); SetT(K_WK,"VW",MoneyS(weekProfit)); SetCol(K_WK,"VW",PnlCol(weekProfit)); SetT(K_MO,"VM",MoneyS(monthProfit)); SetCol(K_MO,"VM",PnlCol(monthProfit)); SetT(K_LOTS,"LOTS",StringFormat("Lots traded Today %.2f | Week %.2f | Month %.2f",dayLots,weekLots,monthLots)); if(InpTradeMode==MODE_SINGLE) SetT(K_MODE,"MODE",StringFormat("Mode: Single | تک معامله SL %d TP %d Trail %s %d/%d Lev 1:%d",slPts,tpPts,(trailOn?"ON":"OFF"),trStart,trStep,leverage)); else SetT(K_MODE,"MODE",StringFormat("Mode: Martingale | مارتینگل Lvl %d Avg %s Lots %.2f Trail %s", martLevel,(basketAvg>0?Px(basketAvg):"--"),basketLots,basketTrailActive?"ON":"OFF")); SetT(K_INFO,"INFO",StringFormat("%s %s %s Bid %s Ask %s Spr %d",(playing?"▶":"⏸"),SpeedTxt(speedMult), TimeToString((datetime)simSec,TIME_DATE|TIME_SECONDS),Px(curBid),Px(curAsk),InpSpreadPts)); SetT(K_INFO2,"INFO2",StringFormat("TF %s | bar %d/%d | tick %d/%d | ⏱ %d:%02d.%d", TFName(currentTF),cur+1,total,tkIdx,tkN,rm,rs,rt)); SetT(K_MSG,"MSG",lastMsg); if(trOpen) RefreshTrades(); ChartRedraw(panelChart); } //+------------------------------------------------------------------+ //| Panel: framed, 3D-style buttons | //+------------------------------------------------------------------+ color Lighten(const color c,const int a) { int r=(int)(((uint)c)&0xFF), g=(int)((((uint)c)>>8)&0xFF), b=(int)((((uint)c)>>16)&0xFF); r=MathMin(255,r+a); g=MathMin(255,g+a); b=MathMin(255,b+a); return (color)(r|(g<<8)|(b<<16)); } void Rect(const string id,const int x,const int y,const int w,const int h,const color bg,const color border,const int z=0) { string n=P+id; ObjectCreate(panelChart,n,OBJ_RECTANGLE_LABEL,0,0,0); ObjectSetInteger(panelChart,n,OBJPROP_CORNER,CORNER_LEFT_UPPER); ObjectSetInteger(panelChart,n,OBJPROP_XDISTANCE,x); ObjectSetInteger(panelChart,n,OBJPROP_YDISTANCE,y); ObjectSetInteger(panelChart,n,OBJPROP_XSIZE,w); ObjectSetInteger(panelChart,n,OBJPROP_YSIZE,h); ObjectSetInteger(panelChart,n,OBJPROP_BGCOLOR,bg); ObjectSetInteger(panelChart,n,OBJPROP_BORDER_TYPE,BORDER_FLAT); ObjectSetInteger(panelChart,n,OBJPROP_COLOR,border); ObjectSetInteger(panelChart,n,OBJPROP_BACK,false); ObjectSetInteger(panelChart,n,OBJPROP_SELECTABLE,false); ObjectSetInteger(panelChart,n,OBJPROP_ZORDER,z); } void Reg(const string id,const int x,const int y,const int w,const int h) { if(rN>=48) return; rId[rN]=id; rX[rN]=x; rY[rN]=y; rW[rN]=w; rH[rN]=h; rN++; } void RawBtn(const string n,const string t,const int x,const int y,const int w,const int h, const color bg,const color tc,const color border,const int fs,const int z) { ObjectCreate(panelChart,n,OBJ_BUTTON,0,0,0); ObjectSetInteger(panelChart,n,OBJPROP_XDISTANCE,x); ObjectSetInteger(panelChart,n,OBJPROP_YDISTANCE,y); ObjectSetInteger(panelChart,n,OBJPROP_XSIZE,w); ObjectSetInteger(panelChart,n,OBJPROP_YSIZE,h); ObjectSetString(panelChart,n,OBJPROP_TEXT,t); ObjectSetString(panelChart,n,OBJPROP_FONT,"Segoe UI Semibold"); ObjectSetInteger(panelChart,n,OBJPROP_FONTSIZE,fs); ObjectSetInteger(panelChart,n,OBJPROP_COLOR,tc); ObjectSetInteger(panelChart,n,OBJPROP_BGCOLOR,bg); ObjectSetInteger(panelChart,n,OBJPROP_BORDER_COLOR,border); ObjectSetInteger(panelChart,n,OBJPROP_CORNER,CORNER_LEFT_UPPER); ObjectSetInteger(panelChart,n,OBJPROP_SELECTABLE,false); ObjectSetInteger(panelChart,n,OBJPROP_ZORDER,z); ObjectSetInteger(panelChart,n,OBJPROP_STATE,false); } void Btn3D(const string id,const string t,const int x,const int y,const int w,const int h,const color bg,const int fs=9) { Rect(id+"_sh",x+2,y+3,w,h,C'6,8,14',C'6,8,14'); RawBtn(P+id,t,x,y,w,h,bg,clrWhite,Lighten(bg,55),fs,500); Rect(id+"_hl",x+2,y+2,w-4,1,Lighten(bg,110),Lighten(bg,110)); Reg(id,x,y,w,h); } void SetBtn(const string id,const color bg,const color tc) { ObjectSetInteger(panelChart,P+id,OBJPROP_BGCOLOR,bg); ObjectSetInteger(panelChart,P+id,OBJPROP_COLOR,tc); ObjectSetInteger(panelChart,P+id,OBJPROP_BORDER_COLOR,Lighten(bg,55)); ObjectSetInteger(panelChart,P+id+"_hl",OBJPROP_BGCOLOR,Lighten(bg,110)); ObjectSetInteger(panelChart,P+id+"_hl",OBJPROP_COLOR,Lighten(bg,110)); } void Lbl(const string id,const string t,const int x,const int y,const color c=clrYellow, const int fs=9,const string font="Segoe UI") { string n=P+id; ObjectCreate(panelChart,n,OBJ_LABEL,0,0,0); ObjectSetInteger(panelChart,n,OBJPROP_XDISTANCE,x); ObjectSetInteger(panelChart,n,OBJPROP_YDISTANCE,y); ObjectSetString(panelChart,n,OBJPROP_TEXT,(t=="")?" ":t); // empty text would show "Label" ObjectSetString(panelChart,n,OBJPROP_FONT,font); ObjectSetInteger(panelChart,n,OBJPROP_COLOR,c); ObjectSetInteger(panelChart,n,OBJPROP_CORNER,CORNER_LEFT_UPPER); ObjectSetInteger(panelChart,n,OBJPROP_FONTSIZE,fs); ObjectSetInteger(panelChart,n,OBJPROP_SELECTABLE,false); ObjectSetInteger(panelChart,n,OBJPROP_ZORDER,0); } void EditBox(const string id,const string t,const int x,const int y,const int w,const int h) { string n=P+id; ObjectCreate(panelChart,n,OBJ_EDIT,0,0,0); ObjectSetInteger(panelChart,n,OBJPROP_XDISTANCE,x); ObjectSetInteger(panelChart,n,OBJPROP_YDISTANCE,y); ObjectSetInteger(panelChart,n,OBJPROP_XSIZE,w); ObjectSetInteger(panelChart,n,OBJPROP_YSIZE,h); ObjectSetString(panelChart,n,OBJPROP_TEXT,t); ObjectSetString(panelChart,n,OBJPROP_FONT,"Segoe UI Semibold"); ObjectSetInteger(panelChart,n,OBJPROP_FONTSIZE,11); ObjectSetInteger(panelChart,n,OBJPROP_COLOR,clrBlack); ObjectSetInteger(panelChart,n,OBJPROP_BGCOLOR,clrWhite); ObjectSetInteger(panelChart,n,OBJPROP_BORDER_COLOR,C'0,120,215'); ObjectSetInteger(panelChart,n,OBJPROP_CORNER,CORNER_LEFT_UPPER); ObjectSetInteger(panelChart,n,OBJPROP_ALIGN,ALIGN_CENTER); ObjectSetInteger(panelChart,n,OBJPROP_ZORDER,500); } void DateField(const string id,const datetime d,const int x,const int y) { RawBtn(P+id,TimeToString(d,TIME_DATE),x,y,104,26,clrWhite,clrBlack,C'0,120,215',10,500); Reg(id,x,y,104,26); Btn3D(id+"_I","▦ ▾",x+104,y,28,26,C'96,110,140',9); } void BuildPanel() { ObjectsDeleteAll(panelChart,P); rN=0; calOpen=false; ctxOpen=false; for(int i=0;i<40;i++){ lcC[i]=clrNONE; lcT[i]="~~"; } const int PX=4, PY=4; panW=480; const int x=PX+14; const color KEY=C'140,152,185', LAB=C'190,200,225'; int y=PY+34; Rect("FRAME", PX, PY, panW, 400,C'24,30,46',C'0,150,255'); Rect("FRAME2",PX+3,PY+3,panW-6, 394,C'24,30,46',C'52,68,104'); Rect("TBAR", PX+4,PY+4,panW-8, 24, C'0,92,170',C'0,92,170'); Lbl("TTL","◆ MARKET REPLAY v9.1 | ریپلی بازار",x,PY+8,clrWhite,10,"Segoe UI Semibold"); // --- row 1: transport Btn3D("PLAY", "▶ Play", x, y,104,30,clrDarkGreen,10); Btn3D("PAUSE", "⏸ Pause", x+112, y,104,30,clrDarkOrange,10); Btn3D("TRADES","▤ Trades",x+224, y,124,30,clrSteelBlue,10); // --- row 2: speed slider y+=40; Lbl("LS","Speed",x,y+4,LAB,9); slX=x+50; slY=y; Rect("SL_TRK",slX,slY+SL_H/2-5,slW,10,C'208,213,226',C'96,108,140',600); Rect("SL_GRV",slX+3,slY+SL_H/2-1,slW-6,2,C'120,132,165',C'120,132,165',600); for(int i=0;i<5;i++) { string tx=(i==0)?"0.1x":(i==1)?"1x":(i==2)?"10x":(i==3)?"100x":"1000x"; int tx_x=slX+(int)MathRound(i*0.25*(slW-SL_TH))+SL_TH/2-(StringLen(tx)*3); Lbl("SK"+IntegerToString(i),tx,tx_x,y+SL_H+3,KEY,7); Rect("ST"+IntegerToString(i),slX+(int)MathRound(i*0.25*(slW-SL_TH))+SL_TH/2,slY+SL_H/2+6,1,4,C'96,108,140',C'96,108,140',600); } RawBtn(P+"SL_TH","",slX,slY,SL_TH,SL_H,C'238,241,248',clrBlack,C'70,80,115',8,700); PlaceThumb(); Lbl("SPDV","Live 1x",x+322,y+3,clrGold,11,"Segoe UI Semibold"); Btn3D("LIVE","Live",x+400,y,52,26,clrTeal); // --- row 3: timeframes y+=50; Lbl("LTF","TF",x,y+5,LAB); Btn3D("TF_M1", "M1", x+26, y,48,26,clrSteelBlue); Btn3D("TF_M5", "M5", x+78, y,48,26,clrSteelBlue); Btn3D("TF_M15","M15", x+130, y,48,26,clrSteelBlue); Btn3D("TF_M30","M30", x+182, y,48,26,clrSteelBlue); Btn3D("TF_H1", "H1", x+234, y,48,26,clrSteelBlue); Btn3D("TF_H4", "H4", x+286, y,48,26,clrSteelBlue); Btn3D("TF_D1", "D1", x+338, y,48,26,clrSteelBlue); Btn3D("TF_W1", "W1", x+390, y,48,26,clrSteelBlue); // --- row 4: date range y+=38; dateRowY=y; fromFieldX=x+44; toFieldX=x+246; Lbl("LF","From:",x,y+5,LAB); DateField("FROM",selFrom,fromFieldX,y); Lbl("LT","To:",x+206,y+5,LAB); DateField("TO",selTo,toFieldX,y); // --- row 5: lot + leverage y+=38; Lbl("LLOT","Lot:",x,y+5,LAB); Btn3D("LOT_M","−",x+34, y,30,26,clrDimGray,12); EditBox("LOT",DoubleToString(lotSize,2),x+68,y,78,26); Btn3D("LOT_P","+",x+150,y,30,26,clrDimGray,12); Btn3D("MAXLOT","Max",x+186,y,46,26,clrSlateGray,9); Lbl("LLEV","Leverage 1 :",x+250,y+5,LAB); EditBox("LEV",IntegerToString(leverage),x+346,y,70,26); y+=31; Lbl("LI","",x,y,KEY,8); y+=22; // --- row 6: SL / TP Lbl("LSL","SL pts:",x,y+5,LAB); EditBox("SLP",IntegerToString(slPts),x+50,y,76,26); Lbl("LTP",(InpTradeMode==MODE_SINGLE)?"TP pts:":"Basket TP:",x+140,y+5,LAB); EditBox("TPP",IntegerToString(tpPts),x+206,y,76,26); Btn3D("APPLY","Apply to open",x+296,y,120,26,clrSteelBlue,9); // --- row 6b: trailing stop y+=36; Lbl("LTR",(InpTradeMode==MODE_SINGLE)?"Trailing:":"Basket trail:",x,y+5,LAB); Btn3D("TRAIL_T",trailOn?"ON":"OFF",x+84,y,52,26,trailOn?clrForestGreen:clrDimGray,9); Lbl("LTS","Start pts:",x+146,y+5,LAB); EditBox("TRS",IntegerToString(trStart),x+214,y,66,26); Lbl("LTP2","Step pts:",x+290,y+5,LAB); EditBox("TRP",IntegerToString(trStep),x+354,y,66,26); // --- row 7: balance control y+=36; Lbl("LBV","Amount:",x,y+5,LAB); EditBox("BALV",DoubleToString(resetBal,2),x+56,y,110,26); Btn3D("BAL_RESET","Reset balance",x+174,y,130,26,clrDarkOrange,9); Btn3D("BAL_DEP","+ Deposit",x+312,y,110,26,clrTeal,9); // --- row 8: SELL (left) CLOSE BUY (right) y+=40; Btn3D("SELL", "SELL", x, y,166,46,clrFireBrick,12); Btn3D("CLOSE","Close All",x+174, y,104,46,clrDimGray,10); Btn3D("BUY", "BUY", x+286, y,166,46,clrForestGreen,12); // --- stats block y+=58; int statTop=y-6; Rect("STATS",x-6,statTop,panW-16,200,C'12,16,28',C'60,74,108'); Lbl("KB","BALANCE",x,y,KEY,8); Lbl("VB","",x,y+12,clrWhite,13,"Segoe UI Semibold"); Lbl("KE","EQUITY",x+156,y,KEY,8); Lbl("VE","",x+156,y+12,clrWhite,13,"Segoe UI Semibold"); Lbl("KF","FLOATING P/L",x+312,y,KEY,8); Lbl("VF","",x+312,y+12,clrWhite,13,"Segoe UI Semibold"); y+=42; Lbl("KG","USED MARGIN",x,y,KEY,8); Lbl("VMG","",x,y+12,clrWhite,11,"Segoe UI Semibold"); Lbl("KH","FREE MARGIN",x+156,y,KEY,8); Lbl("VFM","",x+156,y+12,clrWhite,11,"Segoe UI Semibold"); Lbl("KL","MARGIN LEVEL",x+312,y,KEY,8); Lbl("VML","",x+312,y+12,clrWhite,11,"Segoe UI Semibold"); y+=40; Lbl("POS","",x,y,clrWhite,9); y+=22; Rect("SEP1",x,y-2,panW-28,1,C'60,74,108',C'60,74,108'); Lbl("KT","TODAY",x,y+2,KEY,8); Lbl("VT","",x,y+14,clrWhite,11,"Segoe UI Semibold"); Lbl("KY","YESTERDAY",x+114,y+2,KEY,8); Lbl("VY","",x+114,y+14,clrWhite,11,"Segoe UI Semibold"); Lbl("KW","WEEK",x+228,y+2,KEY,8); Lbl("VW","",x+228,y+14,clrWhite,11,"Segoe UI Semibold"); Lbl("KM","MONTH",x+342,y+2,KEY,8); Lbl("VM","",x+342,y+14,clrWhite,11,"Segoe UI Semibold"); y+=40; Lbl("LOTS","",x,y,LAB,9); y+=20; Lbl("MODE","",x,y,LAB,9); y+=22; Rect("SEP2",x,y-2,panW-28,1,C'60,74,108',C'60,74,108'); Lbl("INFO","",x,y+2,clrWhite,9); y+=19; Lbl("INFO2","",x,y+2,clrWhite,9); y+=19; Lbl("MSG","",x,y+2,clrTomato,9); y+=19; Lbl("HINT","Keys: B=Buy S=Sell C=Close Space=Play/Pause +/-=Speed Esc=Cancel",x,y+2,C'110,124,160',8); y+=26; panH=y-PY; ObjectSetInteger(panelChart,P+"FRAME", OBJPROP_YSIZE,panH); ObjectSetInteger(panelChart,P+"FRAME2",OBJPROP_YSIZE,panH-6); ObjectSetInteger(panelChart,P+"STATS", OBJPROP_YSIZE,y-26-statTop+22); } void SpBtn(const string id,const bool on,const color idle) { if(on) SetBtn(id,clrGold,clrBlack); else SetBtn(id,idle,clrWhite); } void TfBtn(const string id,const ENUM_TIMEFRAMES tf) { if(currentTF==tf) SetBtn(id,clrDodgerBlue,clrWhite); else SetBtn(id,clrSteelBlue,clrWhite); } void UpdateButtonStates(const bool redraw) { SetBtn("PLAY", playing?clrLimeGreen:clrDarkGreen, playing?clrBlack:clrWhite); SetBtn("PAUSE",playing?clrDarkOrange:clrOrange, clrWhite); ObjectSetString(panelChart,P+"PLAY", OBJPROP_TEXT,playing?"▶ Playing":"▶ Play"); ObjectSetString(panelChart,P+"PAUSE",OBJPROP_TEXT,playing?"⏸ Pause":"⏸ Paused"); if(trOpen) SetBtn("TRADES",clrDodgerBlue,clrWhite); else SetBtn("TRADES",clrSteelBlue,clrWhite); SpBtn("LIVE",speedMult==1.0,clrTeal); SetBtn("TRAIL_T",trailOn?clrForestGreen:clrDimGray,clrWhite); ObjectSetString(panelChart,P+"TRAIL_T",OBJPROP_TEXT,trailOn?"ON":"OFF"); TfBtn("TF_M1",PERIOD_M1); TfBtn("TF_M5",PERIOD_M5); TfBtn("TF_M15",PERIOD_M15); TfBtn("TF_M30",PERIOD_M30); TfBtn("TF_H1",PERIOD_H1); TfBtn("TF_H4",PERIOD_H4); TfBtn("TF_D1",PERIOD_D1); TfBtn("TF_W1",PERIOD_W1); SetT(K_SPEED,"SPDV",SpeedTxt(speedMult)); if(redraw) ChartRedraw(panelChart); } //+------------------------------------------------------------------+ //| Calendar popup (month grid, Sun..Sat, like a Windows date picker)| //+------------------------------------------------------------------+ void CalBtn(const string id,const string t,const int x,const int y,const int w,const int h, const color bg,const color tc,const color border,const int fs=9) { RawBtn(P+"CAL_"+id,t,x,y,w,h,bg,tc,border,fs,2000); } void OpenCal(const int target) { CloseCal(); calTarget=target; calOpen=true; datetime d=(target==0)?selFrom:selTo; MqlDateTime s; TimeToStruct(d,s); calYear=s.year; calMon=s.mon; int fx=(target==0)?fromFieldX:toFieldX; calX=MathMin(fx,4+panW-calW-6); calY=dateRowY+28; Rect("CAL_BG",calX,calY,calW,calH,clrWhite,C'110,110,110',1999); CalBtn("PREV","◀",calX+6,calY+4,28,24,clrWhite,C'60,60,60',clrWhite,10); CalBtn("NEXT","▶",calX+calW-34,calY+4,28,24,clrWhite,C'60,60,60',clrWhite,10); CalBtn("TITLE","",calX+40,calY+4,calW-80,24,clrWhite,clrBlack,clrWhite,10); string wd[7]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; int gx=calX+8, gy=calY+CAL_HDR+CAL_WK; for(int c=0;c<7;c++) CalBtn("W"+IntegerToString(c),wd[c],gx+c*CAL_CW,calY+CAL_HDR,CAL_CW,CAL_WK,clrWhite,C'40,40,40',clrWhite,9); for(int i=0;i<42;i++) CalBtn("D"+IntegerToString(i),"",gx+(i%7)*CAL_CW+1,gy+(i/7)*CAL_CH+1,CAL_CW-2,CAL_CH-2,clrWhite,clrBlack,clrWhite,10); CalBtn("TODAY","Today: "+TimeToString(DayStart(TimeLocal()),TIME_DATE),gx,gy+6*CAL_CH+4,210,24,clrWhite,C'30,30,30',C'0,120,215',9); RefreshCal(); ChartRedraw(panelChart); } void RefreshCal() { ObjectSetString(panelChart,P+"CAL_TITLE",OBJPROP_TEXT,MonName[calMon-1]+" "+IntegerToString(calYear)); datetime first=StringToTime(StringFormat("%04d.%02d.01",calYear,calMon)); MqlDateTime f; TimeToStruct(first,f); datetime start=first-(datetime)((long)f.day_of_week*86400); datetime sel=(calTarget==0)?selFrom:selTo; datetime today=DayStart(TimeLocal()); for(int i=0;i<42;i++) { datetime d=start+(datetime)((long)i*86400); MqlDateTime m; TimeToStruct(d,m); bool inMon=(m.mon==calMon); bool on=(d>=dataFirst && d<=dataLast); calDate[i]=d; calOn[i]=on; string n=P+"CAL_D"+IntegerToString(i); ObjectSetString(panelChart,n,OBJPROP_TEXT,IntegerToString(m.day)); color tc=!on?C'205,205,205':(inMon?clrBlack:C'140,140,140'); color bg=(d==sel)?C'204,228,247':clrWhite; color bd=(d==sel)?C'0,120,215':((d==today)?clrCrimson:clrWhite); ObjectSetInteger(panelChart,n,OBJPROP_COLOR,tc); ObjectSetInteger(panelChart,n,OBJPROP_BGCOLOR,bg); ObjectSetInteger(panelChart,n,OBJPROP_BORDER_COLOR,bd); } } void CloseCal() { if(!calOpen) return; ObjectsDeleteAll(panelChart,P+"CAL_"); calOpen=false; ChartRedraw(panelChart); } void PickDate(const datetime d) { if(calTarget==0){ selFrom=d; if(selToselTo) selFrom=selTo; } selDirty=true; ObjectSetString(panelChart,P+"FROM",OBJPROP_TEXT,TimeToString(selFrom,TIME_DATE)); ObjectSetString(panelChart,P+"TO", OBJPROP_TEXT,TimeToString(selTo,TIME_DATE)); lastMsg="Range set - press Play to apply | محدوده انتخاب شد، Play را بزنید"; CloseCal(); } //+------------------------------------------------------------------+ //| Trades window: floating, draggable, resizable, MT5-like table | //+------------------------------------------------------------------+ double twFr[10]={0.09,0.08,0.15,0.10,0.07,0.11,0.10,0.10,0.10,0.10}; void TwReg(const string n,const int ox,const int oy) { if(twCnt>=600) return; twN[twCnt]=n; twOX[twCnt]=ox; twOY[twCnt]=oy; twCnt++; } void TwRect(const string id,const int ox,const int oy,const int w,const int h,const color bg,const color bd) { Rect(id,trX+ox,trY+oy,w,h,bg,bd,3000); TwReg(P+id,ox,oy); } void TwLbl(const string id,const string t,const int ox,const int oy,const color c,const int fs,const bool right, const string font="Segoe UI") { string n=P+id; ObjectCreate(panelChart,n,OBJ_LABEL,0,0,0); ObjectSetInteger(panelChart,n,OBJPROP_CORNER,CORNER_LEFT_UPPER); ObjectSetInteger(panelChart,n,OBJPROP_ANCHOR,right?ANCHOR_RIGHT_UPPER:ANCHOR_LEFT_UPPER); ObjectSetInteger(panelChart,n,OBJPROP_XDISTANCE,trX+ox); ObjectSetInteger(panelChart,n,OBJPROP_YDISTANCE,trY+oy); ObjectSetString(panelChart,n,OBJPROP_TEXT,(t=="")?" ":t); // empty text would show "Label" ObjectSetString(panelChart,n,OBJPROP_FONT,font); ObjectSetInteger(panelChart,n,OBJPROP_FONTSIZE,fs); ObjectSetInteger(panelChart,n,OBJPROP_COLOR,c); ObjectSetInteger(panelChart,n,OBJPROP_SELECTABLE,false); ObjectSetInteger(panelChart,n,OBJPROP_ZORDER,0); TwReg(n,ox,oy); } void BuildTrades() { ObjectsDeleteAll(panelChart,P+"TW_"); twCnt=0; trOpen=true; int cw=(int)ChartGetInteger(panelChart,CHART_WIDTH_IN_PIXELS); int ch=(int)ChartGetInteger(panelChart,CHART_HEIGHT_IN_PIXELS); trW=MathMax(600,MathMin(trW,cw-4)); trH=MathMax(160,MathMin(trH,ch-4)); trX=MathMax(0,MathMin(trX,cw-trW)); trY=MathMax(0,MathMin(trY,ch-trH)); twVis=MathMax(1,MathMin(TR_MAXROWS,(trH-TR_TITLE-TR_HEAD-TR_FOOT)/TR_ROWH)); for(int i=0;i<500;i++){ twC[i]="~~"; twCC[i]=clrNONE; } for(int i=36;i<40;i++){ lcT[i]="~~"; lcC[i]=clrNONE; } TwRect("TW_BG",0,0,trW,trH,C'252,252,254',C'80,92,130'); TwRect("TW_TBAR",1,1,trW-2,TR_TITLE-1,C'0,92,170',C'0,92,170'); TwLbl("TW_TTL","Trades | معاملات",8,4,clrWhite,9,false,"Arial Bold"); TwLbl("TW_XC","✕",trW-20,3,clrWhite,10,false); TwRect("TW_HD",1,TR_TITLE,trW-2,TR_HEAD,C'208,216,236',C'160,172,204'); // bold, large, dark column titles string hd[10]={"Symbol","Ticket","Time","Type","Volume","Price","S / L","T / P","Price","Profit"}; double tw=(double)(trW-2-30); double acc=1.0; double colR[10]; double colL[10]; for(int c=0;c<10;c++){ colL[c]=acc; acc+=twFr[c]*tw; colR[c]=acc; } for(int c=0;c<10;c++) TwLbl("TW_H"+IntegerToString(c),hd[c],(int)(c<4?colL[c]+6:colR[c]-6),TR_TITLE+6,C'10,20,70',10,c>=4,"Arial Bold"); for(int r=0;r=4); TwLbl("TW_C"+IntegerToString(r)+"_10"," ",trW-24,yy+2,C'200,40,40',10,false); } // footer: big bold Balance / Equity / Profit int fy=trH-TR_FOOT; TwRect("TW_FT",1,fy,trW-2,TR_FOOT-1,C'208,216,236',C'160,172,204'); TwLbl("TW_FBK","Balance:",10,fy+8,C'40,50,90',10,false,"Arial Bold"); TwLbl("TW_FBV"," ",84,fy+7,C'8,16,60',12,false,"Arial Bold"); TwLbl("TW_FEK","Equity:",210,fy+8,C'40,50,90',10,false,"Arial Bold"); TwLbl("TW_FEV"," ",272,fy+7,C'8,16,60',12,false,"Arial Bold"); TwLbl("TW_FPK","Profit:",390,fy+8,C'40,50,90',10,false,"Arial Bold"); TwLbl("TW_FPV"," ",446,fy+7,C'0,130,50',12,false,"Arial Bold"); TwLbl("TW_UP","▲",trW-72,fy+8,C'70,80,115',10,false); TwLbl("TW_DN","▼",trW-46,fy+8,C'70,80,115',10,false); TwLbl("TW_GR","◢",trW-15,trH-17,C'110,120,150',9,false); } void TwSet(const int r,const int c,const string txt0,const color col) { int k=r*11+c; if(k>=500) return; string txt=(txt0=="")?" ":txt0; // never leave a label empty string n=P+"TW_C"+IntegerToString(r)+"_"+IntegerToString(c); if(twC[k]!=txt){ twC[k]=txt; ObjectSetString(panelChart,n,OBJPROP_TEXT,txt); } if(twCC[k]!=col){ twCC[k]=col; ObjectSetInteger(panelChart,n,OBJPROP_COLOR,col); } } void RefreshTrades() { if(!trOpen) return; twRows=0; for(int i=0;i=twRows){ for(int c=0;c<=10;c++) TwSet(r,c,"",TX); continue; } if(twKind[row]==0) { int i=twIdx[row]; double pr=(pos[i].dir>0)?curBid:curAsk; double fl=CalcProfit(pos[i].dir,pos[i].open,pos[i].lots,pr); double ssl=pos[i].sl, stp=pos[i].tp; if(InpTradeMode==MODE_MARTINGALE){ ssl=basketTrailActive?basketSL:0; stp=basketTP; } TwSet(r,0,baseSym,TX); TwSet(r,1,IntegerToString((long)pos[i].ticket),TX); TwSet(r,2,TimeToString(pos[i].time,TIME_DATE|TIME_SECONDS),TX); TwSet(r,3,(pos[i].dir>0)?"buy":"sell",(pos[i].dir>0)?BUYC:SELLC); TwSet(r,4,DoubleToString(pos[i].lots,2),TX); TwSet(r,5,Px(pos[i].open),TX); TwSet(r,6,ssl>0?Px(ssl):"",TX); TwSet(r,7,stp>0?Px(stp):"",TX); TwSet(r,8,Px(pr),TX); TwSet(r,9,Money(fl),(fl>=0)?GRN:RED); TwSet(r,10,"✕",RED); } else { int i=twIdx[row]; int t=ords[i].type; bool buy=(t==1||t==3); TwSet(r,0,baseSym,TX); TwSet(r,1,IntegerToString((long)ords[i].ticket),TX); TwSet(r,2,TimeToString(ords[i].time,TIME_DATE|TIME_SECONDS),TX); TwSet(r,3,OrdTypeName(t),buy?BUYC:SELLC); TwSet(r,4,DoubleToString(ords[i].lots,2),TX); TwSet(r,5,Px(ords[i].price),TX); TwSet(r,6,"",TX); TwSet(r,7,"",TX); TwSet(r,8,Px(buy?curAsk:curBid),TX); TwSet(r,9,"",TX); TwSet(r,10,"✕",RED); } } // footer values: large, bold, strong colours string vb=Money(bal), ve=Money(eq), vp=MoneyS(eq-bal); color pc=(eq-bal>=0)?C'0,130,50':C'200,30,30'; if(lcT[36]!=vb){ lcT[36]=vb; ObjectSetString(panelChart,P+"TW_FBV",OBJPROP_TEXT,vb); } if(lcT[37]!=ve){ lcT[37]=ve; ObjectSetString(panelChart,P+"TW_FEV",OBJPROP_TEXT,ve); } if(lcT[38]!=vp){ lcT[38]=vp; ObjectSetString(panelChart,P+"TW_FPV",OBJPROP_TEXT,vp); } if(lcC[38]!=pc){ lcC[38]=pc; ObjectSetInteger(panelChart,P+"TW_FPV",OBJPROP_COLOR,pc); } } void OpenTrades() { trOpen=true; BuildTrades(); RefreshTrades(); ChartRedraw(panelChart); } void CloseTrades() { trOpen=false; trDrag=false; trSize=false; ObjectsDeleteAll(panelChart,P+"TW_"); twCnt=0; ChartSetInteger(panelChart,CHART_MOUSE_SCROLL,true); ChartRedraw(panelChart); }