mql5-execution-microstructu.../Include/Dependencies/Article01/Experts/OrderLifecycle/OrderLifecycleRecorder.mq5

894 lines
No EOL
34 KiB
MQL5

//+------------------------------------------------------------------+
//| OrderLifecycleRecorder.mq5 |
//| Copyright 2000-2026, MetaQuotes Ltd. |
//| www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2000-2026, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.00"
#property description "Вывод полного жизненного цикла заявки:"
#property description "запрос, ордер, сделка, позиция."
#property description "Накопление событий, reconciliation, CSV, timeline."
#include "TradeLifecycle.mqh"
//+------------------------------------------------------------------+
//| Входные параметры |
//+------------------------------------------------------------------+
//--- Magic Number
input ulong InpMagic=20260806;
//--- Объем тестовой заявки,лот
input double InpVolume=0.01;
//--- символ (пусто=текущий график)
input string InpSymbol="";
//--- OrderSendAsync() вместо OrderSend()
input bool InpUseAsync=true;
//--- таймаут ожидания события,мс
input int InpReconcileTimeoutMs=5000;
//--- число повторов запроса при отказе (0=без ограничений)
input int InpRetries=0;
//--- пауза между повторами,мс
input int InpRetryDelayMs=5000;
//--- entry/reason на всю цепочку в CSV (не только DEAL_ADD)
input bool InpCsvChainEntry=true;
//--- размер шрифта кнопок (базовый,масштабируется по ширине окна)
input int InpBtnFontSize=10;
//--- доп. множитель шрифта поверх DPI (1.0=только авто)
input double InpFontScale=1.24;
//--- торговые константы
#define TRADE_DEVIATION_POINTS 20 // допустимое отклонение для рыночного ордера, пункты
#define PENDING_DISTANCE_POINTS 100 // расстояние до отложенного ордера от рынка, пункты
//+------------------------------------------------------------------+
//| Имена кнопок |
//+------------------------------------------------------------------+
const string BUTTON_NAMES[]=
{
"Market Buy", // 0 — покупка по рынку
"Market Sell", // 1 — продажа по рынку
"Pending Buy Limit", // 2 — отложенный ордер Buy Limit
"Cancel", // 3 — отмена первого отложенного ордера
"Modify", // 4 — модификация отложенного ордера
"Export CSV", // 5 — экспорт событий в CSV
"Draw Timeline", // 6 — построение timeline на графике
"Close" // 7 — закрытие позиций по одной за нажатие
};
//+------------------------------------------------------------------+
//| Базовые размеры панели (при макете окна ~900px) |
//+------------------------------------------------------------------+
#define UI_BTN_BASE_WIDTH 70 // минимальная ширина кнопки
#define UI_BTN_BASE_HEIGHT 36 // высота кнопки
#define UI_BTN_BASE_Y 44 // отступ сверху
#define UI_BTN_PADDING 16 // внутреннее поле кнопки слева/справа
#define UI_BTN_GAP 6 // зазор между кнопками
#define UI_PANEL_BASE_W 720 // эталонная ширина панели при scale=1.0
#define UI_SCALE_MIN 0.75 // нижняя граница масштаба
#define UI_SCALE_MAX 1.50 // верхняя граница масштаба
//+------------------------------------------------------------------+
//| Глобальные переменные |
//+------------------------------------------------------------------+
CTradeLifecycle ExtLifecycle; // накопитель событий+реестр запросов
string ExtSymbol; // рабочий символ
ulong ExtStartUs; // GetMicrosecondCount() для стартовой метки
bool ExtHasEvents; // появились ли события
ulong ExtLastEventUs; // GetMicrosecondCount() последнего события
bool ExtWatchdogFired; // watchdog уже сработал в этом «тихом» периоде
double ExtUiScale; // текущий масштаб панели
uint ExtLocalSeq; // локальный порядковый номер отправленных запросов
//+------------------------------------------------------------------+
//| Состояние повторов запроса (InpRetries) |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE ExtRetryType; // тип ордера для повтора
bool ExtRetryPending; // запланирован ли повтор
ulong ExtRetryAtUs; // момент повтора (GetMicrosecondCount)
int ExtRetriesLeft; // осталось повторов (-1=без ограничений)
//+------------------------------------------------------------------+
//| Масштаб UI по ширине окна графика: панель не должна выходить |
//| За пределы окна (UI_PANEL_BASE_W — ширина панели при scale=1.0),|
//| С границами UI_SCALE_MIN..UI_SCALE_MAX. |
//+------------------------------------------------------------------+
double GetUIScale()
{
long chart_w=ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
double s=(chart_w>0?(double)chart_w/UI_PANEL_BASE_W:1.0);
if(s<UI_SCALE_MIN)
s=UI_SCALE_MIN;
if(s>UI_SCALE_MAX)
s=UI_SCALE_MAX;
return(s);
}
//+------------------------------------------------------------------+
//| Итоговый коэффициент масштабирования шрифта |
//+------------------------------------------------------------------+
double GetFontScale()
{
double base=(InpFontScale>0.1?InpFontScale:1.0);
int dpi=(int)TerminalInfoInteger(TERMINAL_SCREEN_DPI);
if(dpi<=0)
return(base);
return(base*(double)dpi/96.0);
}
//+------------------------------------------------------------------+
//| Измерение ширины надписи по метрикам шрифта MQL5 |
//+------------------------------------------------------------------+
int TextWidthPx(const string text,const int font_size)
{
//--- шрифт не задался — грубая оценка по длине строки
if(!TextSetFont("Arial",font_size,0))
return(StringLen(text)*font_size/2+20);
uint tw=0;
uint th=0;
//--- размер метки не получен — грубая оценка
if(!TextGetSize(text,tw,th))
return(StringLen(text)*font_size/2+20);
return((int)tw);
}
//+------------------------------------------------------------------+
//| Нормализация цены по SYMBOL_DIGITS |
//+------------------------------------------------------------------+
double NormalizePrice(const string symbol,const double price)
{
int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
return(NormalizeDouble(price,digits));
}
//+------------------------------------------------------------------+
//| Нормализация объема: диапазон SYMBOL_VOLUME_MIN..MAX и шаг STEP |
//+------------------------------------------------------------------+
double NormalizeVolume(const string symbol,const double volume)
{
double min_v=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN);
double max_v=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX);
double step_v=SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP);
double v=volume;
if(v<min_v)
v=min_v;
if(v>max_v)
v=max_v;
if(step_v>0)
{
v=MathRound(v/step_v)*step_v;
int vdigits=(int)MathCeil(-MathLog10(step_v));
if(vdigits<0)
vdigits=0;
v=NormalizeDouble(v,vdigits);
}
return(v);
}
//+------------------------------------------------------------------+
//| Выбор режима исполнения из SYMBOL_FILLING_MODE |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE_FILLING SelectFilling(const string symbol)
{
long mode=SymbolInfoInteger(symbol,SYMBOL_FILLING_MODE);
if((mode&SYMBOL_FILLING_FOK)!=0)
return(ORDER_FILLING_FOK);
if((mode&SYMBOL_FILLING_IOC)!=0)
return(ORDER_FILLING_IOC);
return(ORDER_FILLING_RETURN);
}
//+------------------------------------------------------------------+
//| BuildRequest — заполнение MqlTradeRequest |
//+------------------------------------------------------------------+
bool BuildRequest(const string symbol,const ENUM_ORDER_TYPE type,
const double volume,MqlTradeRequest &request)
{
ZeroMemory(request);
bool is_market=(type==ORDER_TYPE_BUY||type==ORDER_TYPE_SELL);
request.action=(is_market?TRADE_ACTION_DEAL:TRADE_ACTION_PENDING);
request.magic=InpMagic;
request.symbol=symbol;
request.type=type;
request.volume=NormalizeVolume(symbol,volume);
request.deviation=TRADE_DEVIATION_POINTS;
request.type_time=ORDER_TIME_GTC;
request.type_filling=SelectFilling(symbol);
request.comment=StringFormat("LC_%u",ExtLocalSeq+1);
//--- цена: рыночная — Ask/Bid; отложенная — с отступом от рынка
double ask=SymbolInfoDouble(symbol,SYMBOL_ASK);
double bid=SymbolInfoDouble(symbol,SYMBOL_BID);
double point=SymbolInfoDouble(symbol,SYMBOL_POINT);
if(point<=0)
point=1e-6;
switch(type)
{
case ORDER_TYPE_BUY:
request.price=ask;
break;
case ORDER_TYPE_SELL:
request.price=bid;
break;
case ORDER_TYPE_BUY_LIMIT:
request.price=ask-PENDING_DISTANCE_POINTS*point;
break;
case ORDER_TYPE_SELL_LIMIT:
request.price=bid+PENDING_DISTANCE_POINTS*point;
break;
default:
PrintFormat("BuildRequest: тип '%s' пока не поддерживается",EnumToString(type));
return(false);
}
request.price=NormalizePrice(symbol,request.price);
PrintFormat("BuildRequest: %s %s vol=%.2f price=%.5f filling=%s",
EnumToString(type),symbol,request.volume,request.price,
EnumToString(request.type_filling));
return(true);
}
//+------------------------------------------------------------------+
//| PreCheck — предварительная проверка OrderCheck |
//+------------------------------------------------------------------+
bool PreCheck(const MqlTradeRequest &request,MqlTradeCheckResult &check_result)
{
ZeroMemory(check_result);
if(!OrderCheck(request,check_result))
{
PrintFormat("PreCheck: отказ | retcode=%u '%s'",check_result.retcode,check_result.comment);
return(false);
}
PrintFormat("PreCheck: ок | retcode=%u margin_free=%.2f",check_result.retcode,check_result.margin_free);
return(true);
}
//+------------------------------------------------------------------+
//| ScheduleRetry — планирование повтора запроса (InpRetries) |
//+------------------------------------------------------------------+
void ScheduleRetry(const ENUM_ORDER_TYPE type)
{
if(InpRetries==0||ExtRetriesLeft>0)
{
if(ExtRetriesLeft>0)
ExtRetriesLeft--;
ExtRetryType=type;
ExtRetryPending=true;
ExtRetryAtUs=GetMicrosecondCount()+(ulong)InpRetryDelayMs*1000;
PrintFormat("ScheduleRetry: %s через %d мс | осталось повторов: %s",
EnumToString(type),InpRetryDelayMs,
(InpRetries==0?"без ограничений":(string)ExtRetriesLeft));
}
else
{
PrintFormat("ScheduleRetry: лимит повторов исчерпан (%d), %s не повторяем",
InpRetries,EnumToString(type));
}
}
//+------------------------------------------------------------------+
//| SendTradeRequest — BuildRequest -> PreCheck -> OrderSend/Async |
//+------------------------------------------------------------------+
bool SendTradeRequest(const ENUM_ORDER_TYPE type,const double volume)
{
MqlTradeRequest request;
if(!BuildRequest(ExtSymbol,type,volume,request))
return(false);
MqlTradeCheckResult check_result;
if(!PreCheck(request,check_result))
{
ScheduleRetry(type);
return(false);
}
MqlTradeResult result;
ulong t0=GetMicrosecondCount();
bool ok=(InpUseAsync?OrderSendAsync(request,result):OrderSend(request,result));
PrintFormat("SendTradeRequest: %s | ok=%s retcode=%u '%s' request_id=%u dt=%I64u мкс",
EnumToString(type),
(ok?"true":"false"),
result.retcode,result.comment,result.request_id,
GetMicrosecondCount()-t0);
if(ok)
{
ExtLocalSeq++;
ExtLifecycle.RegisterRequest(ExtLocalSeq,result.request_id,InpMagic,ExtSymbol,
request.volume,request.comment,t0,type);
}
else
{
//--- отправка не удалась (нет соединения и т.п.) — планируем повтор
ScheduleRetry(type);
}
return(ok);
}
//+------------------------------------------------------------------+
//| FindFirstPendingOrder — первый активный отложенный ордер |
//+------------------------------------------------------------------+
ulong FindFirstPendingOrder()
{
for(int i=0;i<OrdersTotal();i++)
{
ulong ticket=OrderGetTicket(i);
if(ticket==0)
continue;
if(OrderGetString(ORDER_SYMBOL)!=ExtSymbol)
continue;
if((ulong)OrderGetInteger(ORDER_MAGIC)!=InpMagic)
continue;
long type=OrderGetInteger(ORDER_TYPE);
if(type==ORDER_TYPE_BUY_LIMIT||type==ORDER_TYPE_SELL_LIMIT||
type==ORDER_TYPE_BUY_STOP||type==ORDER_TYPE_SELL_STOP||
type==ORDER_TYPE_BUY_STOP_LIMIT||type==ORDER_TYPE_SELL_STOP_LIMIT)
return(ticket);
}
return(0);
}
//+------------------------------------------------------------------+
//| SendManageRequest — отправка управляющего запроса (REMOVE/MODIFY)|
//+------------------------------------------------------------------+
bool SendManageRequest(MqlTradeRequest &request,const double volume,
const ENUM_ORDER_TYPE order_type)
{
MqlTradeCheckResult check_result;
if(!PreCheck(request,check_result))
return(false);
MqlTradeResult result;
ulong t0=GetMicrosecondCount();
bool ok=(InpUseAsync?OrderSendAsync(request,result):OrderSend(request,result));
PrintFormat("SendManageRequest: %s order=%I64u | ok=%s retcode=%u '%s' request_id=%u dt=%I64u мкс",
EnumToString(request.action),request.order,
(ok?"true":"false"),
result.retcode,result.comment,result.request_id,
GetMicrosecondCount()-t0);
if(ok)
{
ExtLocalSeq++;
ExtLifecycle.RegisterRequest(ExtLocalSeq,result.request_id,InpMagic,ExtSymbol,
volume,request.comment,t0,order_type);
}
return(ok);
}
//+------------------------------------------------------------------+
//| OnInit |
//+------------------------------------------------------------------+
int OnInit()
{
ExtSymbol=(InpSymbol==""?_Symbol:InpSymbol);
ExtStartUs=GetMicrosecondCount();
ExtHasEvents=false;
ExtLastEventUs=0;
ExtWatchdogFired=false;
ExtUiScale=0.0;
ExtLocalSeq=0;
//--- состояние повторов
ExtRetryPending=false;
ExtRetryAtUs=0;
ExtRetriesLeft=(InpRetries==0?-1:InpRetries);
//--- подписка на символ
if(!SymbolSelect(ExtSymbol,true))
PrintFormat("OnInit: не удалось выбрать символ %s (error=%d)",ExtSymbol,GetLastError());
//--- контекст для reconciliation (символ+magic)
ExtLifecycle.SetContext(ExtSymbol,InpMagic);
//--- создание кнопок
CreateButtons();
ExtUiScale=GetUIScale();
LogButtonWidths();
//--- таймер для запуска reconciliation
EventSetTimer(1);
PrintFormat("OrderLifecycleRecorder: init | symbol=%s magic=%I64u volume=%.2f async=%s ui_scale=%.2f font_scale=%.2f (dpi=%d, mult=%.2f)",
ExtSymbol,InpMagic,InpVolume,(InpUseAsync?"true":"false"),ExtUiScale,
GetFontScale(),(int)TerminalInfoInteger(TERMINAL_SCREEN_DPI),
(InpFontScale>0.1?InpFontScale:1.0));
//--- модель учета позиций
PrintFormat("Модель учета позиций: %s",
EnumToString((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)));
//--- сводка для проверки доступности
PrintEntityInfo(ExtSymbol);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| OnDeinit |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- экспорт журнала при завершении
if(reason!=REASON_CHARTCHANGE&&reason!=REASON_RECOMPILE)
{
string file_name=MakeExportFileName();
ExtLifecycle.ExportCsv(file_name,InpCsvChainEntry);
}
EventKillTimer();
//--- удаление графических объектов (кнопки+timeline)
int total=ArraySize(BUTTON_NAMES);
for(int i=0;i<total;i++)
ObjectDelete(0,"LC_BTN_"+(string)i);
ObjectsDeleteAll(0,"LC_TL_");
Print("OrderLifecycleRecorder: завершение");
}
//+------------------------------------------------------------------+
//| OnTradeTransaction — регистрация событий жизненного цикла заявки |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request,
const MqlTradeResult &result)
{
//--- фильтр по символу (если транзакция не глобальная)
if(trans.symbol!=""&&trans.symbol!=ExtSymbol)
return;
//--- глобальные события (symbol='') — принимаем только запросы и локального реестра по request_id,иначе это чужие запросы
if(trans.symbol=="")
{
PendingRequest _pr;
if(result.request_id==0||!ExtLifecycle.FindRequest(result.request_id,_pr))
return;
}
//--- формирование события
TradeEvent e;
ZeroMemory(e);
e.request_id=result.request_id;
e.order_ticket=trans.order;
e.deal_ticket=trans.deal;
e.position_id=trans.position;
e.type=trans.type;
e.local_time_us=GetMicrosecondCount();
//--- берем время сервера на момент обработки
e.server_time=TimeTradeServer();
e.price=trans.price;
e.volume=trans.volume;
e.retcode=result.retcode;
//--- для сделок: направление и причина
if(trans.type==TRADE_TRANSACTION_DEAL_ADD&&trans.deal!=0)
{
HistorySelect(0,TimeCurrent());
if(HistoryDealSelect(trans.deal))
{
e.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(trans.deal,DEAL_ENTRY);
e.reason=(ENUM_DEAL_REASON)HistoryDealGetInteger(trans.deal,DEAL_REASON);
}
}
//--- добавление в буфер
ExtLifecycle.Add(e);
ExtHasEvents=true;
ExtLastEventUs=e.local_time_us;
ExtWatchdogFired=false;
PrintFormat("OnTradeTransaction: %s | order=%I64u deal=%I64u pos=%I64u price=%.5f vol=%.2f retcode=%u dt=%I64u мкс",
EnumToString(trans.type),
trans.order,
trans.deal,
trans.position,
trans.price,
trans.volume,
result.retcode,
e.local_time_us-ExtStartUs);
//--- для сделок: вывод в лог
if(e.deal_ticket!=0)
PrintFormat(" -> сделка: entry=%s reason=%s",
EnumToString(e.entry),EnumToString(e.reason));
//--- привязка события к локальному реестру по request_id
if(result.request_id!=0)
{
PendingRequest pr;
if(ExtLifecycle.FindRequest(result.request_id,pr))
{
PrintFormat(" -> локальный запрос #%u | %s vol=%.2f '%s' | с момента отправки %I64u мкс",
pr.local_seq,pr.symbol,pr.volume,pr.comment,
e.local_time_us-pr.send_time_us);
//--- при отказе сервера (REQUEST с ошибкой) — планируем повтор
if(trans.type==TRADE_TRANSACTION_REQUEST)
{
uint rc=result.retcode;
if(rc!=0&&rc!=TRADE_RETCODE_DONE&&rc!=TRADE_RETCODE_PLACED&&rc!=TRADE_RETCODE_DONE_PARTIAL)
{
//--- бессмысленные повторы не планируем: рынок закрыт или торговля отключена
if(rc==TRADE_RETCODE_MARKET_CLOSED||rc==TRADE_RETCODE_TRADE_DISABLED)
PrintFormat(" -> повтор пропущен: %s (бессмысленно до изменения условий)",
RetcodeName(rc));
else
ScheduleRetry(pr.order_type);
}
}
}
}
}
//+------------------------------------------------------------------+
//| OnTimer — повторы запросов+reconciliation по таймауту |
//+------------------------------------------------------------------+
void OnTimer()
{
ulong now_us=GetMicrosecondCount();
//--- повтор запроса по расписанию (InpRetries)
if(ExtRetryPending&&now_us>=ExtRetryAtUs)
{
ExtRetryPending=false;
PrintFormat("OnTimer: повтор запроса %s",EnumToString(ExtRetryType));
SendTradeRequest(ExtRetryType,InpVolume);
}
//--- запуск reconciliation по таймауту только при появлении новых событий
if(!ExtHasEvents)
return;
static ulong last_reconcile_us=0;
static int last_reconcile_cnt=-1;
if(now_us-last_reconcile_us>=(ulong)InpReconcileTimeoutMs*1000)
{
int cnt=ExtLifecycle.EventCount();
if(cnt!=last_reconcile_cnt)
{
ExtLifecycle.Reconcile();
last_reconcile_cnt=cnt;
}
last_reconcile_us=now_us;
}
//--- watchdog: если события не приходили InpReconcileTimeoutMs,а цепочка не завершена — принудительная сверка (обнаружение потери последнего события)
if(ExtHasEvents&&ExtLastEventUs!=0&&
now_us-ExtLastEventUs>=(ulong)InpReconcileTimeoutMs*1000&&
!ExtLifecycle.IsComplete())
{
if(!ExtWatchdogFired)
{
ExtWatchdogFired=true;
PrintFormat("OnTimer: watchdog | событий не было %d мс, цепочка не завершена — принудительная сверка",
InpReconcileTimeoutMs);
ExtLifecycle.Reconcile();
}
}
}
//+------------------------------------------------------------------+
//| OnChartEvent — обработка событий графика |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
//--- при изменении размеров окна обновляется панель,если изменился масштаб
if(id==CHARTEVENT_CHART_CHANGE)
{
double s=GetUIScale();
if(MathAbs(s-ExtUiScale)>0.02)
{
ExtUiScale=s;
CreateButtons();
}
return;
}
if(id!=CHARTEVENT_OBJECT_CLICK)
return;
int total=ArraySize(BUTTON_NAMES);
for(int i=0;i<total;i++)
{
if(sparam!="LC_BTN_"+(string)i)
continue;
PrintFormat("OnChartEvent: нажата кнопка '%s'",BUTTON_NAMES[i]);
//--- сброс нажатого состояния кнопки
ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
ChartRedraw(0);
switch(i)
{
case 0:
OnButtonMarketBuy();
break;
case 1:
OnButtonMarketSell();
break;
case 2:
OnButtonPendingBuyLimit();
break;
case 3:
OnButtonCancel();
break;
case 4:
OnButtonModify();
break;
case 5:
OnButtonExportCsv();
break;
case 6:
OnButtonDrawTimeline();
break;
case 7:
OnButtonClose();
break;
}
break;
}
}
//+------------------------------------------------------------------+
//| Вывод фактической ширины каждой кнопки в лог |
//+------------------------------------------------------------------+
void LogButtonWidths()
{
int total=ArraySize(BUTTON_NAMES);
Print("Фактическая ширина кнопок (px):");
for(int i=0;i<total;i++)
{
string name="LC_BTN_"+(string)i;
long w=ObjectGetInteger(0,name,OBJPROP_XSIZE);
PrintFormat(" [%d] %-20s width=%d",i,BUTTON_NAMES[i],(int)w);
}
}
//+------------------------------------------------------------------+
//| Размеры и шрифт масштабируются по ширине окна |
//+------------------------------------------------------------------+
void CreateButtons()
{
double scale=GetUIScale();
double fscale=GetFontScale();
int btn_h=(int)MathRound(UI_BTN_BASE_HEIGHT*scale);
int btn_y=(int)MathRound(UI_BTN_BASE_Y*scale);
int btn_fs=(int)MathRound(InpBtnFontSize*scale);
int gap=(int)MathRound(UI_BTN_GAP*scale);
int padding=(int)MathRound(UI_BTN_PADDING*scale);
if(btn_fs<7)
btn_fs=7;
if(btn_h<24)
btn_h=24;
if(gap<4)
gap=4;
if(padding<8)
padding=8;
int x_pos=(int)MathRound(10*scale);
int total=ArraySize(BUTTON_NAMES);
for(int i=0;i<total;i++)
{
string name="LC_BTN_"+(string)i;
//--- Создать объект,если его еще нет
if(ObjectFind(0,name)<0)
{
if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0))
continue;
}
int text_w=TextWidthPx(BUTTON_NAMES[i],btn_fs);
int btn_w=(int)MathRound((text_w+2*padding)*fscale);
int min_w=(int)MathRound(UI_BTN_BASE_WIDTH*scale*fscale);
if(btn_w<min_w)
btn_w=min_w;
ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x_pos);
ObjectSetInteger(0,name,OBJPROP_YDISTANCE,btn_y);
ObjectSetInteger(0,name,OBJPROP_XSIZE,btn_w);
ObjectSetInteger(0,name,OBJPROP_YSIZE,btn_h);
ObjectSetString(0,name,OBJPROP_FONT,"Arial");
ObjectSetString(0,name,OBJPROP_TEXT,BUTTON_NAMES[i]);
ObjectSetInteger(0,name,OBJPROP_FONTSIZE,btn_fs);
ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
ObjectSetInteger(0,name,OBJPROP_STATE,false);
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
//--- Следующая кнопка сдвигается с учетом фактической ширины
x_pos+=btn_w+gap;
}
//--- Принудительная перерисовка,чтобы панель появилась сразу
ChartRedraw(0);
}
//+------------------------------------------------------------------+
//| Имя файла экспорта: lifecycle_export_YYYYMMDD_HHMMSS.csv |
//+------------------------------------------------------------------+
string MakeExportFileName()
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(),dt);
return(StringFormat("lifecycle_export_%04d%02d%02d_%02d%02d%02d.csv",
dt.year,dt.mon,dt.day,dt.hour,dt.min,dt.sec));
}
//+------------------------------------------------------------------+
//| Обработчики кнопок |
//+------------------------------------------------------------------+
void OnButtonMarketBuy()
{
Print("Market Buy: BuildRequest -> PreCheck -> OrderSend");
SendTradeRequest(ORDER_TYPE_BUY,InpVolume);
}
void OnButtonMarketSell()
{
Print("Market Sell: BuildRequest -> PreCheck -> OrderSend");
SendTradeRequest(ORDER_TYPE_SELL,InpVolume);
}
void OnButtonPendingBuyLimit()
{
Print("Pending Buy Limit: BuildRequest -> PreCheck -> OrderSend");
SendTradeRequest(ORDER_TYPE_BUY_LIMIT,InpVolume);
}
//+------------------------------------------------------------------+
//| Cancel — отмена отложенного ордера |
//+------------------------------------------------------------------+
void OnButtonCancel()
{
ulong ticket=FindFirstPendingOrder();
if(ticket==0||!OrderSelect(ticket))
{
Print("Cancel: нет активных отложенных ордеров");
return;
}
ENUM_ORDER_TYPE otype=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
double vol=OrderGetDouble(ORDER_VOLUME_CURRENT);
MqlTradeRequest request;
ZeroMemory(request);
request.action=TRADE_ACTION_REMOVE;
request.order=ticket;
request.symbol=ExtSymbol;
request.magic=InpMagic;
request.comment=StringFormat("LC_%u",ExtLocalSeq+1);
PrintFormat("Cancel: отмена ордера %I64u (%s, vol=%.2f)",ticket,EnumToString(otype),vol);
SendManageRequest(request,vol,otype);
}
//+------------------------------------------------------------------+
//| Modify — модификация отложенного ордера |
//+------------------------------------------------------------------+
void OnButtonModify()
{
ulong ticket=FindFirstPendingOrder();
if(ticket==0||!OrderSelect(ticket))
{
Print("Modify: нет активных отложенных ордеров");
return;
}
ENUM_ORDER_TYPE otype=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
double vol=OrderGetDouble(ORDER_VOLUME_CURRENT);
double old_price=OrderGetDouble(ORDER_PRICE_OPEN);
double point=SymbolInfoDouble(ExtSymbol,SYMBOL_POINT);
if(point<=0)
point=1e-6;
//--- сдвиг в сторону исполнения: buy-лимиты ниже,sell-лимиты выше
bool buy_like=(otype==ORDER_TYPE_BUY_LIMIT||otype==ORDER_TYPE_BUY_STOP);
double new_price=old_price+(buy_like?-PENDING_DISTANCE_POINTS:PENDING_DISTANCE_POINTS)*point;
new_price=NormalizePrice(ExtSymbol,new_price);
MqlTradeRequest request;
ZeroMemory(request);
request.action=TRADE_ACTION_MODIFY;
request.order=ticket;
request.symbol=ExtSymbol;
request.price=new_price;
request.magic=InpMagic;
request.comment=StringFormat("LC_%u",ExtLocalSeq+1);
PrintFormat("Modify: ордер %I64u (%s) цена %.5f -> %.5f",
ticket,EnumToString(otype),old_price,new_price);
SendManageRequest(request,vol,otype);
}
void OnButtonExportCsv()
{
string file_name=MakeExportFileName();
ExtLifecycle.ExportCsv(file_name,InpCsvChainEntry);
}
void OnButtonDrawTimeline()
{
//--- режим OBJ_LABEL (метки)
ExtLifecycle.DrawTimeline(ChartID());
}
//+------------------------------------------------------------------+
//| OnButtonClose - закрытие одной позиции за нажатие |
//+------------------------------------------------------------------+
void OnButtonClose()
{
int total=PositionsTotal();
if(total==0)
{
Print("OnButtonClose: no open positions");
return;
}
//--- close position index 0; next press closes the next one
ulong ticket=PositionGetTicket(0);
if(ticket==0)
{
Print("OnButtonClose: failed to get position");
return;
}
string symbol=PositionGetString(POSITION_SYMBOL);
double volume=PositionGetDouble(POSITION_VOLUME);
long type=PositionGetInteger(POSITION_TYPE);
double price=(type==POSITION_TYPE_BUY?SymbolInfoDouble(symbol,SYMBOL_BID):SymbolInfoDouble(symbol,SYMBOL_ASK));
MqlTradeRequest req={};
MqlTradeResult res={};
req.action=TRADE_ACTION_DEAL;
req.symbol=symbol;
req.volume=volume;
req.type=(type==POSITION_TYPE_BUY?ORDER_TYPE_SELL:ORDER_TYPE_BUY);
req.price=price;
req.deviation=30;
req.magic=InpMagic;
req.position=ticket;
req.comment="LC_CLOSE";
bool ok=(InpUseAsync?OrderSendAsync(req,res):OrderSend(req,res));
PrintFormat("OnButtonClose: %s pos=%I64u vol=%.2f | ok=%s retcode=%u '%s'",
symbol,ticket,volume,(ok?"true":"false"),res.retcode,res.comment);
if(ok)
{
ExtLocalSeq++;
ExtLifecycle.RegisterRequest(ExtLocalSeq,res.request_id,InpMagic,symbol,
volume,"LC_CLOSE",GetMicrosecondCount(),req.type);
}
}
//+------------------------------------------------------------------+