// ===============CodeLock-Dashboard=========================== //============================================================================ // НОРМАТИВНЫЙ ДОКУМЕНТ // Полный стандарт системы: CodeLock-README.md (корень репозитория). // Термины, FSM, события, HISTORY/LIVE, GUI, ALERT и требования проверки // должны соответствовать CodeLock-README.md. При изменении логики сначала // обновляется/проверяется стандарт, затем изменяется код и выполняется // проверка потребителей и регрессий. // // АРХИТЕКТУРНАЯ РОЛЬ // CodeLock-Dashboard отвечает за визуальное отображение состояний LEVEL/ // DIRECTION и управление ALERT. Торговую логику LOCK/SIGNAL он не исполняет. // CODELOCK DASHBOARD — ЕДИНЫЙ СТАНДАРТ МОДЕЛИ НАПРАВЛЕНИЯ // // НАЗНАЧЕНИЕ // Индикатор восстанавливает и отображает состояние BID относительно ценовой // сетки. Для каждого LEVEL используется независимое состояние DIRECTION. // // ЕДИНЫЕ ТЕРМИНЫ // POINT = _Point. // LEVEL = расстояние сетки в points. // GRID RAY = цена StartPrice + GRID * stepPrice. // DIRECTION = 0 WAIT, +1 UP, -1 DOWN. // CONTROL RAY = directionRay — последний луч, подтвердивший DIRECTION. // LAST CROSS = lastCrossedRay / lastCrossingDirection. // REVERSAL = противоположное пересечение только соседнего CONTROL RAY: // UP на N -> DOWN только на N-1; // DOWN на N -> UP только на N+1. // RETOUCH = обратное пересечение любого другого луча; DIRECTION не меняется. // // ЕДИНЫЙ АЛГОРИТМ // TICK -> ProcessBidTick() -> ProcessDistanceTick() // -> RegisterCrossing() -> изменение DIRECTION -> ALERT/GUI. // // Первое пересечение LEVEL определяет DIRECTION. // Пересечение в том же направлении подтверждает DIRECTION. // Противоположное пересечение разрешает REVERSAL только через соседний // CONTROL RAY. Поэтому случайное повторное касание уровня не является // подтверждением REVERSAL. // // HISTORY / LIVE // HISTORY восстанавливает состояние до момента AttachMsc. // LIVE обрабатывает только новые тики после восстановленного курсора. // Оба режима используют один маршрут ProcessBidTick() -> ProcessDistanceTick() // -> RegisterCrossing(). Одно событие не должно обрабатываться повторно. // // ИСТОЧНИК ИСТИНЫ // States[] содержит расчётное состояние. GUI только отображает его и управляет // ALERT. Изменение внешнего вида не считается изменением модели. // // ALERT // ALERT реагирует на подтверждённое пересечение, отключается при срабатывании // и не изменяет правила определения DIRECTION. // // GUI // Жизненный цикл объектов: CREATE -> CONFIGURE -> UPDATE -> RESIZE // -> EVENT -> DELETE. GUI не является источником торговой логики. // ============================================================================ #property strict #property indicator_chart_window #property indicator_plots 0 #property version "1.00" #property description "Standalone 30-level UP/DOWN price dashboard" #define DASHBOARD_ROWS 30 #define DEFAULT_VISIBLE_ROWS 5 #define PANEL_PREFIX "TPDRD_" #define ROWS_STATE_PREFIX "TPDR_ROWS_" input double StartPrice=1.0; // Начало сетки ценовых уровней input string DashboardLevels="20,40,60,80,100,120,140,160,180,200,220,240,260,280,300,320,340,360,380,400,420,440,460,480,500,520,540,560,580,600"; // Уровни dashboard в points, каждый уровень задаётся отдельно input int HistoryBootstrapTicks=100000; // Расширенная история для восстановления состояний input int HistoryChunkSize=5000; // Размер блока тиков LiveMode input int UpdateIntervalMs=50; // Быстрое обновление dashboard input int PanelLeftMargin=12; // Отступ слева input int PanelTopMargin=12; // Отступ сверху input int RowHeight=25; // Высота строки input int HeaderHeight=72; // Высота заголовка input int DashboardFontSize=11; // Единый размер шрифта всей таблицы, включая заголовки, цифры и кнопки input color AlertOnColor=clrOrange; // Цвет включенного ALERT, ожидающего следующего пересечения input color AlertOffColor=C'55,62,74'; // Цвет выключенного ALERT input color AlertActivityColor=clrLimeGreen; // Цвет краткой индикации фактического срабатывания ALERT input int AlertActivityMs=500; // Длительность индикации фактического срабатывания ALERT в миллисекундах input color AlertTextColor=clrWhite; // Цвет текста кнопки ALERT input color PanelBackground=C'16,20,28'; // Фон панели input color RowBackground=C'29,35,46'; // Фон строки input color RowBackgroundAlt=C'24,30,40'; // Чередующийся фон input color BorderColor=C'75,88,108'; // Граница input color HeaderColor=clrDeepSkyBlue; // Акцент заголовка input color TextColor=clrWhiteSmoke; // Основной текст input color MutedTextColor=C'155,165,180'; // Вторичный текст input color UpColor=clrLimeGreen; // UP input color DownColor=clrTomato; // DOWN input color WaitColor=clrSilver; // WAIT struct DistanceState { int stepPoints; double stepPrice; int direction; int lastCrossedRay; int lastCrossingDirection; int consecutiveCrossings; int directionRay; double lastDirectionChangePrice; }; DistanceState States[DASHBOARD_ROWS]; bool AlertEnabled[DASHBOARD_ROWS]; ulong AlertActivityUntil[DASHBOARD_ROWS]; string Prefix=PANEL_PREFIX; string ShortName="Tick Price Dashboard 30"; long AttachMsc=0; long CursorMsc=1; int CursorSameMsc=0; bool BootstrapComplete=false; bool HavePreviousBid=false; double PreviousBid=0.0; int LastTickCount=0; int VisibleRows=DEFAULT_VISIBLE_ROWS; string RowsStateKey=""; int ColLevelX=16; int ColStatusX=0; int ColTriggerX=0; int ColAlertX=0; int ColDistanceX=0; int ColLastX=0; int AutoPanelWidth=0; int MeasureTextWidth(const string text) { uint width=0; uint height=0; TextSetFont("Segoe UI",-DashboardFontSize*10,FW_NORMAL); if(!TextGetSize(text,width,height)) return MathMax(1,StringLen(text)*DashboardFontSize); return (int)width; } void CalculateAutoLayout(const double bid) { int levelWidth=MeasureTextWidth("LEVEL"); int statusWidth=MeasureTextWidth("STATUS"); int triggerWidth=MeasureTextWidth("REVERSE AT"); int alertWidth=MeasureTextWidth("ALERT")+16; int distanceWidth=MeasureTextWidth("DIST"); int lastWidth=MeasureTextWidth("LAST"); int levelCount=MathMin(GetDashboardLevelCount(),VisibleRows); for(int i=0;i0.0 ? DoubleToString(States[i].lastDirectionChangePrice,_Digits) : "—"; lastWidth=MathMax(lastWidth,MeasureTextWidth(lastText)); } int gap=6; int pad=8; ColLevelX=16; ColStatusX=ColLevelX+levelWidth+gap; ColTriggerX=ColStatusX+statusWidth+gap; ColAlertX=ColTriggerX+triggerWidth+gap; ColDistanceX=ColAlertX+alertWidth+gap; ColLastX=ColDistanceX+distanceWidth+gap; AutoPanelWidth=ColLastX+lastWidth+16; } int GetDashboardLevelCount() { string parts[]; int count=StringSplit(DashboardLevels,',',parts); return MathMin(MathMax(count,0),DASHBOARD_ROWS); } int GetDashboardLevelPoints(const int index) { if(index<0 || index>=DASHBOARD_ROWS) return 0; string parts[]; int count=StringSplit(DashboardLevels,',',parts); if(index>=count) return 0; return (int)StringToInteger(parts[index]); } int FloorGridForStep(const double price,const double stepPrice) { if(stepPrice<=0.0) return 0; double position=(NormalizeDouble(price,_Digits)-NormalizeDouble(StartPrice,_Digits))/stepPrice; return (int)MathFloor(position+1.0e-9); } int CeilGridForStep(const double price,const double stepPrice) { if(stepPrice<=0.0) return 0; double position=(NormalizeDouble(price,_Digits)-NormalizeDouble(StartPrice,_Digits))/stepPrice; return (int)MathCeil(position-1.0e-9); } double RayPriceForStep(const int grid,const double stepPrice) { return NormalizeDouble(StartPrice+(double)grid*stepPrice,_Digits); } void RegisterCrossing(DistanceState &state,const int grid,const int direction) { state.lastCrossedRay=grid; state.lastCrossingDirection=direction; // Один принцип разворота с гистерезисом: // UP после пересечения уровня N разворачивается только на N-1. // DOWN после пересечения уровня N разворачивается только на N+1. // Повторное касание/пересечение того же уровня в обратную сторону // само по себе направление не меняет. if(state.direction==0) { state.direction=direction; state.directionRay=grid; state.consecutiveCrossings=1; state.lastDirectionChangePrice=RayPriceForStep(grid,state.stepPrice); return; } if(direction==state.direction) { state.directionRay=grid; state.consecutiveCrossings++; return; } int reversalGrid=state.directionRay-state.direction; if(grid!=reversalGrid) return; state.direction=direction; state.directionRay=grid; state.consecutiveCrossings=1; state.lastDirectionChangePrice=RayPriceForStep(grid,state.stepPrice); } void ProcessDistanceTick(DistanceState &state,const int stateIndex, const double oldBid,const double bid,const bool emitAlert) { if(state.stepPrice<=0.0 || oldBid<=0.0 || bid<=0.0 || bid==oldBid) return; if(bid>oldBid) { int first=FloorGridForStep(oldBid,state.stepPrice)+1; int last=FloorGridForStep(bid,state.stepPrice); for(int grid=first;grid<=last;grid++) { bool isAlertCrossing=false; if(emitAlert && AlertEnabled[stateIndex] && state.direction<0 && state.directionRay!=0) { int alertGrid=state.directionRay+1; isAlertCrossing=(grid==alertGrid); } RegisterCrossing(state,grid,1); if(isAlertCrossing) FireLevelAlert(stateIndex,grid,1); } } else { int first=CeilGridForStep(oldBid,state.stepPrice)-1; int last=CeilGridForStep(bid,state.stepPrice); for(int grid=first;grid>=last;grid--) { bool isAlertCrossing=false; if(emitAlert && AlertEnabled[stateIndex] && state.direction>0 && state.directionRay!=0) { int alertGrid=state.directionRay-1; isAlertCrossing=(grid==alertGrid); } RegisterCrossing(state,grid,-1); if(isAlertCrossing) FireLevelAlert(stateIndex,grid,-1); } } } void FireLevelAlert(const int stateIndex,const int grid,const int direction) { if(stateIndex<0 || stateIndex>=DASHBOARD_ROWS) return; double alertPrice=RayPriceForStep(grid,States[stateIndex].stepPrice); string message=StringFormat("%s: ALERT %s %d pt %s price=%s", _Symbol, (direction>0 ? "UP" : "DOWN"), States[stateIndex].stepPoints, "REVERSE AT", DoubleToString(alertPrice,_Digits)); // ALERT одноразовый: после фактического пробоя он немедленно деактивируется. // Перед выключением кратко показываем отдельное состояние ACTIVITY. // Деактивация внутреннего состояния происходит ДО Alert(). AlertEnabled[stateIndex]=false; AlertActivityUntil[stateIndex]=(ulong)GetTickCount()+(ulong)MathMax(0,AlertActivityMs); SetAlertButtonState(stateIndex); ChartRedraw(0); Alert(message); PlaySound("alert.wav"); } bool IsAlertButtonName(const string name,int &stateIndex) { string prefix=Prefix+"ALERT_"; if(StringFind(name,prefix)!=0) return false; string suffix=StringSubstr(name,StringLen(prefix)); if(suffix=="") return false; int index=(int)StringToInteger(suffix); if(index<0 || index>=DASHBOARD_ROWS) return false; stateIndex=index; return true; } void SetAlertButtonState(const int index) { if(index<0 || index>=DASHBOARD_ROWS) return; string name=Prefix+"ALERT_"+IntegerToString(index); if(ObjectFind(0,name)<0) return; bool enabled=AlertEnabled[index]; ulong now=(ulong)GetTickCount(); bool activity=(AlertActivityUntil[index]>now); color buttonColor=activity ? AlertActivityColor : (enabled ? AlertOnColor : AlertOffColor); ObjectSetInteger(0,name,OBJPROP_BGCOLOR,buttonColor); ObjectSetInteger(0,name,OBJPROP_COLOR,AlertTextColor); ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR, activity ? AlertActivityColor : (enabled ? AlertOnColor : BorderColor)); ObjectSetString(0,name,OBJPROP_TEXT,"ALERT"); ObjectSetInteger(0,name,OBJPROP_STATE,enabled); } void UpdateAlertButton(const int index,const int x,const int y,const int width,const int height) { string name=Prefix+"ALERT_"+IntegerToString(index); if(ObjectFind(0,name)<0) { ResetLastError(); if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) { Print("TPDR Dashboard: alert button creation failed. Error=",GetLastError(), " Name=",name); return; } ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER); ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,name,OBJPROP_SELECTED,false); ObjectSetInteger(0,name,OBJPROP_HIDDEN,true); ObjectSetInteger(0,name,OBJPROP_ZORDER,140); ObjectSetString(0,name,OBJPROP_FONT,"Segoe UI"); } ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x); ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y); ObjectSetInteger(0,name,OBJPROP_XSIZE,width); ObjectSetInteger(0,name,OBJPROP_YSIZE,height); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,DashboardFontSize); SetAlertButtonState(index); } void ToggleAlert(const int index) { if(index<0 || index>=DASHBOARD_ROWS) return; // Каждый клик заново устанавливает/снимает режим ожидания. // После повторной активации следующий подходящий crossing снова обязан вызвать ALERT. AlertEnabled[index]=!AlertEnabled[index]; AlertActivityUntil[index]=0; string stateText=(AlertEnabled[index] ? "включен" : "выключен"); Print("TPDR Dashboard: ALERT ",States[index].stepPoints," pt ",stateText); SetAlertButtonState(index); ChartRedraw(0); } // Единый вход модели для одного BID-события. // HISTORY и LIVE вызывают одну и ту же функцию. // Порядок: входной BID -> сравнение с PreviousBid -> все LEVEL // -> ProcessDistanceTick -> RegisterCrossing -> состояние -> ALERT/GUI. void ProcessBidTick(const double bid,const bool emitAlert=false) { if(bid<=0.0) return; double price=NormalizeDouble(bid,_Digits); if(!HavePreviousBid) { PreviousBid=price; HavePreviousBid=true; return; } if(price==PreviousBid) return; int levelCount=GetDashboardLevelCount(); for(int i=0;i0) return UpColor; if(direction<0) return DownColor; return WaitColor; } string TriggerText(const DistanceState &state) { bool valid=false; bool up=false; int grid=GetTriggerGrid(state,valid,up); if(!valid) return "—"; double price=RayPriceForStep(grid,state.stepPrice); return (up ? "↑ " : "↓ ")+DoubleToString(price,_Digits); } void DeleteDashboardObjects() { ObjectsDeleteAll(0,Prefix,-1,-1); } void EnsureRect(const string name,const int x,const int y,const int width,const int height, const color background,const color border) { if(ObjectFind(0,name)<0) { ResetLastError(); if(!ObjectCreate(0,name,OBJ_RECTANGLE_LABEL,0,0,0)) { Print("TPDR Dashboard: object creation failed. Error=",GetLastError(), " Name=",name); return; } ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER); ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,name,OBJPROP_SELECTED,false); ObjectSetInteger(0,name,OBJPROP_HIDDEN,true); ObjectSetInteger(0,name,OBJPROP_BACK,false); } ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x); ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y); ObjectSetInteger(0,name,OBJPROP_XSIZE,width); ObjectSetInteger(0,name,OBJPROP_YSIZE,height); ObjectSetInteger(0,name,OBJPROP_BGCOLOR,background); ObjectSetInteger(0,name,OBJPROP_COLOR,border); ObjectSetInteger(0,name,OBJPROP_BORDER_TYPE,BORDER_FLAT); ObjectSetInteger(0,name,OBJPROP_ZORDER,100); } void EnsureLabel(const string name,const int x,const int y,const int fontSize, const color textColor,const string textValue) { if(ObjectFind(0,name)<0) { ResetLastError(); if(!ObjectCreate(0,name,OBJ_LABEL,0,0,0)) { Print("TPDR Dashboard: label creation failed. Error=",GetLastError(), " Name=",name); return; } ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER); ObjectSetString(0,name,OBJPROP_FONT,"Segoe UI"); ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,name,OBJPROP_SELECTED,false); ObjectSetInteger(0,name,OBJPROP_HIDDEN,true); ObjectSetInteger(0,name,OBJPROP_ZORDER,110); } ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x); ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,fontSize); ObjectSetInteger(0,name,OBJPROP_COLOR,textColor); ObjectSetString(0,name,OBJPROP_TEXT,textValue); } void CreateRowsControl(const int panelX,const int y,const int panelWidth) { int buttonHeight=RowHeight-3; int minusWidth=28; int valueWidth=92; int plusWidth=28; int totalWidth=minusWidth+valueWidth+plusWidth; int startX=panelX+(panelWidth-totalWidth)/2; string names[3]={Prefix+"ROWS_MINUS",Prefix+"ROWS_VALUE",Prefix+"ROWS_PLUS"}; int xs[3]={startX,startX+minusWidth,startX+minusWidth+valueWidth}; int widths[3]={minusWidth,valueWidth,plusWidth}; string texts[3]={"-","ROWS: "+IntegerToString(VisibleRows),"+"}; for(int i=0;i<3;i++) { if(ObjectFind(0,names[i])<0) { ResetLastError(); if(!ObjectCreate(0,names[i],OBJ_BUTTON,0,0,0)) { Print("TPDR Dashboard: rows control creation failed. Error=",GetLastError(), " Name=",names[i]); continue; } ObjectSetInteger(0,names[i],OBJPROP_CORNER,CORNER_LEFT_UPPER); ObjectSetInteger(0,names[i],OBJPROP_SELECTABLE,false); ObjectSetInteger(0,names[i],OBJPROP_SELECTED,false); ObjectSetInteger(0,names[i],OBJPROP_HIDDEN,true); ObjectSetInteger(0,names[i],OBJPROP_ZORDER,150); ObjectSetString(0,names[i],OBJPROP_FONT,"Segoe UI"); } ObjectSetInteger(0,names[i],OBJPROP_XDISTANCE,xs[i]); ObjectSetInteger(0,names[i],OBJPROP_YDISTANCE,y); ObjectSetInteger(0,names[i],OBJPROP_XSIZE,widths[i]); ObjectSetInteger(0,names[i],OBJPROP_YSIZE,buttonHeight); ObjectSetInteger(0,names[i],OBJPROP_FONTSIZE,DashboardFontSize); ObjectSetInteger(0,names[i],OBJPROP_BGCOLOR,C'42,50,64'); ObjectSetInteger(0,names[i],OBJPROP_BORDER_COLOR,BorderColor); ObjectSetInteger(0,names[i],OBJPROP_COLOR,TextColor); ObjectSetString(0,names[i],OBJPROP_TEXT,texts[i]); } } void DeleteHiddenDashboardRows(const int visibleCount) { for(int i=visibleCount;i=1 && saved<=configured) VisibleRows=saved; } // Если конфигурация уровней изменилась и сохранённое значение больше // доступного количества строк, используем максимально допустимое. VisibleRows=MathMax(1,MathMin(VisibleRows,configured)); } void SetVisibleRows(const int delta) { int configured=GetDashboardLevelCount(); int newRows=VisibleRows+delta; if(newRows<1) newRows=1; if(newRows>configured) newRows=configured; if(newRows==VisibleRows) return; VisibleRows=newRows; // Сохраняем выбор пользователя сразу после нажатия +/−. // Это значение будет восстановлено после смены периода графика. SaveVisibleRows(); RenderDashboard(SymbolInfoDouble(_Symbol,SYMBOL_BID)); Print("TPDR Dashboard: visual rows changed to ",VisibleRows, " of ",configured); } bool IsRowsControlName(const string name) { return name==Prefix+"ROWS_MINUS" || name==Prefix+"ROWS_PLUS"; } void RenderDashboard(const double bid) { int levelCount=MathMin(GetDashboardLevelCount(),VisibleRows); CalculateAutoLayout(bid); int controlsHeight=RowHeight+8; // Управление количеством строк находится сверху таблицы, // между заголовками и первой строкой. int panelHeight=HeaderHeight+controlsHeight+levelCount*RowHeight+12; int panelX=MathMax(4,PanelLeftMargin); int panelY=MathMax(4,PanelTopMargin); EnsureRect(Prefix+"BG",panelX,panelY,AutoPanelWidth,panelHeight, PanelBackground,BorderColor); EnsureRect(Prefix+"HEADER",panelX+2,panelY+2,AutoPanelWidth-4,HeaderHeight-4, PanelBackground,PanelBackground); EnsureLabel(Prefix+"TITLE",panelX+16,panelY+8,DashboardFontSize,TextColor, "TICK PRICE • "+IntegerToString(levelCount)+" LEVELS"); int bidWidth=MeasureTextWidth("BID "+DoubleToString(bid,_Digits)); EnsureLabel(Prefix+"BID",panelX+AutoPanelWidth-bidWidth-16,panelY+8,DashboardFontSize,HeaderColor, "BID "+DoubleToString(bid,_Digits)); int headerY=panelY+HeaderHeight-20; EnsureLabel(Prefix+"COL_LEVEL",panelX+ColLevelX,headerY,DashboardFontSize,MutedTextColor,"LEVEL"); EnsureLabel(Prefix+"COL_STATUS",panelX+ColStatusX,headerY,DashboardFontSize,MutedTextColor,"STATUS"); EnsureLabel(Prefix+"COL_TRIGGER",panelX+ColTriggerX,headerY,DashboardFontSize,MutedTextColor,"REVERSE AT"); EnsureLabel(Prefix+"COL_ALERT",panelX+ColAlertX,headerY,DashboardFontSize,MutedTextColor,"ALERT"); EnsureLabel(Prefix+"COL_DISTANCE",panelX+ColDistanceX,headerY,DashboardFontSize,MutedTextColor,"DIST"); EnsureLabel(Prefix+"COL_LAST",panelX+ColLastX,headerY,DashboardFontSize,MutedTextColor,"LAST"); for(int row=0;row0.0 ? DoubleToString(States[index].lastDirectionChangePrice,_Digits) : "—"; EnsureLabel(Prefix+"LAST_"+suffix,panelX+ColLastX,y+3,DashboardFontSize,MutedTextColor,lastText); } DeleteHiddenDashboardRows(levelCount); // Кнопки изменения количества строк размещены сверху таблицы. CreateRowsControl(panelX,panelY+HeaderHeight+2,AutoPanelWidth); ChartRedraw(0); } int OnInit() { int levelCount=GetDashboardLevelCount(); if(levelCount<1 || HistoryBootstrapTicks<100 || HistoryChunkSize<100 || UpdateIntervalMs<10 || UpdateIntervalMs>2000 || RowHeight<20 || HeaderHeight<45 || DashboardFontSize<7 || DashboardFontSize>18 || PanelLeftMargin<0 || PanelTopMargin<0) return INIT_PARAMETERS_INCORRECT; if(AlertActivityMs<0 || AlertActivityMs>10000) return INIT_PARAMETERS_INCORRECT; for(int i=0;i0 && GetDashboardLevelPoints(i)<=GetDashboardLevelPoints(i-1))) return INIT_PARAMETERS_INCORRECT; } IndicatorSetString(INDICATOR_SHORTNAME,ShortName); MqlTick attachTick; if(!SymbolInfoTick(_Symbol,attachTick) || attachTick.bid<=0.0) return INIT_FAILED; AttachMsc=attachTick.time_msc; CursorMsc=AttachMsc; for(int i=0;i