RhinoQuant
FreeMT5MQL5 source code

TradingView Style Chart Drag and Zoom for MT5

TradingView Style Chart Drag and Zoom for MT5

TradingView-style chart navigation for MetaTrader 5

If you move between TradingView and MetaTrader 5, you know the feeling: in TradingView you grab the chart and move it anywhere, up, down, left or right. In MetaTrader 5 the chart only scrolls sideways, and the price scale keeps fitting itself to the candles, so you cannot simply pull the candles to where you want to look.

This indicator brings that free movement to MetaTrader 5. Attach it to a chart and the left mouse button behaves the way TradingView users expect.

What it does

  • Drag the candles in any direction. Left and right scrolls through bars, up and down moves the price range.
  • Drag the price scale up or down to stretch or compress the prices.
  • Drag the time axis left or right to zoom the bars out or in.
  • Shift + mouse wheel over the chart zooms the price around the cursor.
  • Mouse wheel over the price scale zooms the price around the middle of the chart.
  • Optional: the plain mouse wheel zooms bars instead of scrolling them.
  • AUTO button in the bottom-right corner, or a double-click on the price scale, returns to the normal auto-fit scale.
  • ON/OFF button next to AUTO. OFF gives you the normal MetaTrader chart back right away, with every setting as it was. ON switches the TradingView-style navigation on again.

The AUTO button is blue while auto-fit is on and grey after you have moved or stretched the prices.

Safe with your trades and drawings

  • It is an indicator, not an Expert Advisor, so it can run on the same chart as your EA.
  • It never opens, changes or closes trades.
  • When you grab an entry, Stop Loss or Take Profit line, the chart does not move, so you can still drag those levels as usual.
  • When a drawing object is selected, the chart does not move, so you can still move your trend lines and other objects.
  • Indicator subwindows scroll left and right only.
  • When you remove the indicator, every chart setting it changed is put back exactly as it was.

Inputs

Dragging

  • Drag candles up/down: turn vertical movement on or off.
  • Drag price scale to stretch/compress: on or off.
  • Drag time axis to zoom bars: on or off.
  • Don't pan when grabbing an order/SL/TP line: keeps trade levels draggable.
  • Trade-level grab tolerance (px): how close to a line counts as grabbing it.

Zoom

  • Plain mouse wheel: Native MT5 (scroll bars) or Zoom bars (TradingView style).
  • Shift+wheel / wheel on price scale zooms price: on or off.
  • Price-scale drag sensitivity (per px): higher value = faster stretching.
  • Price zoom per wheel notch: 0.10 means 10 percent per notch.
  • Time-axis drag px per zoom step: how far to drag for one zoom step.

Behaviour

  • Don't snap to latest bar on new ticks: switches off chart auto-scroll so the chart stays where you put it.
  • Show AUTO scale button: on or off.
  • Double-click time (ms): maximum time between the two clicks of a double-click.

How to use

  1. Copy the file to the MQL5\Indicators folder and compile it in MetaEditor (or restart MetaTrader).
  2. Drag TradingView_Chart_Navigation from the Navigator onto a chart.
  3. Use the mouse as described above. Remove the indicator to get the normal MetaTrader behaviour back.

How it works

MetaTrader's own left-button scrolling is switched off and the indicator reads the mouse itself through chart events. Horizontal movement is turned into whole-bar steps with ChartNavigate. For vertical movement and price zoom, the chart is switched to a fixed scale starting from exactly what is visible, so nothing jumps, and the fixed maximum and minimum are then moved or scaled. Time zoom uses the normal MetaTrader zoom levels.

The code is a single file with no includes, commented throughout, so it is also a useful example of working with mouse events, chart coordinates and chart properties in MQL5.

Notes

  • MetaTrader has six zoom levels for bars, so time zoom moves in steps rather than smoothly.
  • MQL5 cannot block MetaTrader's own mouse wheel action. If you choose "Zoom bars" for the plain wheel, MetaTrader may still scroll a little at the same time.
  • Use one copy of the indicator per chart.

Source code

Copy the code into MetaEditor, save it as TradingView_Chart_Navigation.mq5 and compile (F7).

TradingView_Chart_Navigation.mq5
//+------------------------------------------------------------------+
//|                                 TradingView_Chart_Navigation.mq5 |
//|                                Copyright 2026, Sandro Begashvili |
//|                                                                  |
//| TradingView-style free chart navigation for MetaTrader 5.        |
//|                                                                  |
//| - Drag the candles in any direction: left/right scrolls bars,    |
//|   up/down moves the price range.                                 |
//| - Drag the price scale up/down to stretch or compress prices.    |
//| - Drag the time axis left/right to zoom bars out or in.          |
//| - Shift+wheel on the chart zooms price around the cursor; the    |
//|   wheel over the price scale zooms around the middle.            |
//| - AUTO button or double-click on the price scale = auto-fit.     |
//| - ON/OFF button: OFF restores the normal chart, ON resumes.      |
//|   Removing the indicator also restores the normal chart.         |
//|                                                                  |
//| How it works: native left-button scrolling is switched off and   |
//| the indicator reads CHARTEVENT_MOUSE_MOVE itself. Horizontal     |
//| moves become ChartNavigate() calls, vertical moves and price     |
//| zoom switch the chart to a fixed scale (CHART_SCALEFIX) and move |
//| CHART_FIXED_MAX / CHART_FIXED_MIN. Every chart property that is  |
//| changed is saved in OnInit and restored in OnDeinit.             |
//|                                                                  |
//| It is an indicator (not an EA) so it can run next to any Expert  |
//| Advisor on the same chart. It draws nothing and never trades.    |
//+------------------------------------------------------------------+
#property copyright   "Copyright 2026, Sandro Begashvili"
#property link        "https://www.mql5.com/en/users/sandrobegashvil"
#property version     "1.00"
#property description "TradingView-style chart navigation for MetaTrader 5:"
#property description "drag candles in any direction, drag the price scale to stretch it,"
#property description "drag the time axis to zoom, Shift+wheel zooms price at the cursor,"
#property description "AUTO button or double-click on the price scale restores auto-fit."

#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots   0

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
enum ENUM_WHEEL_MODE
  {
   WHEEL_NATIVE    = 0, // Native MT5 (scroll bars)
   WHEEL_ZOOM_TIME = 1  // Zoom bars (TradingView style)
  };

input group "Dragging"
input bool   InpVerticalPan        = true;   // Drag candles up/down
input bool   InpPriceScaleDrag     = true;   // Drag price scale to stretch/compress
input bool   InpTimeScaleDrag      = true;   // Drag time axis to zoom bars
input bool   InpProtectTradeLevels = true;   // Don't pan when grabbing an order/SL/TP line
input int    InpTradeLevelTolPx    = 5;      // Trade-level grab tolerance (px)

input group "Zoom"
input ENUM_WHEEL_MODE InpWheelMode = WHEEL_NATIVE; // Plain mouse wheel
input bool   InpShiftWheelPrice    = true;   // Shift+wheel / wheel on price scale zooms price
input double InpPriceDragSpeed     = 0.005;  // Price-scale drag sensitivity (per px)
input double InpPriceWheelStep     = 0.10;   // Price zoom per wheel notch (0.10 = 10%)
input int    InpTimeZoomPixels     = 40;     // Time-axis drag px per zoom step

input group "Behaviour"
input bool   InpDisableAutoScroll  = true;   // Don't snap to latest bar on new ticks
input bool   InpShowAutoButton     = true;   // Show AUTO scale button
input int    InpDoubleClickMs      = 350;    // Double-click time (ms)

//+------------------------------------------------------------------+
//| Constants                                                        |
//+------------------------------------------------------------------+
#define TVN_SHORT_NAME   "TV Chart Navigation"
#define TVN_BTN_NAME     "TVNav_AutoBtn"
#define TVN_TGL_NAME     "TVNav_ToggleBtn"
#define TVN_TGL_W        44
#define TVN_BTN_W        54
#define TVN_BTN_H        20
#define TVN_BTN_MARGIN   6
#define TVN_DBL_CLICK_PX 6     // max cursor travel between the two clicks
#define TVN_MIN_RANGE_PT 10    // smallest allowed visible price range (points)
#define TVN_MAX_SCALE    5     // CHART_SCALE goes from 0 to 5

//--- mouse/keyboard state flags sent with CHARTEVENT_MOUSE_MOVE/_WHEEL
#define TVN_MK_LEFT   1
#define TVN_MK_SHIFT  4

//--- which part of the chart the cursor is over
enum ENUM_TVN_ZONE
  {
   ZONE_NONE,
   ZONE_MAIN,        // main price window
   ZONE_SUB,         // indicator subwindow
   ZONE_PRICE_SCALE, // price scale of the main window
   ZONE_TIME_SCALE   // time axis at the bottom
  };

//--- what the current left-button drag is doing
enum ENUM_TVN_DRAG
  {
   DRAG_NONE,
   DRAG_PAN,
   DRAG_PRICE_SCALE,
   DRAG_TIME_SCALE
  };

//--- chart properties we change, saved so they can be restored
struct STvnChartState
  {
   bool              mouseScroll;
   bool              autoScroll;
   bool              scaleFix;
   bool              evMouseMove;
   bool              evMouseWheel;
   double            fixedMax;
   double            fixedMin;
  };

//+------------------------------------------------------------------+
//| Globals                                                          |
//+------------------------------------------------------------------+
long           g_chart       = 0;
STvnChartState g_saved;
bool           g_enabled     = true;    // ON/OFF toggle state
ENUM_TVN_DRAG  g_mode        = DRAG_NONE;
bool           g_panVertical = false;
bool           g_prevLeft    = false;   // left button state on the previous event
int            g_lastX       = 0;
int            g_lastY       = 0;
double         g_accumX      = 0.0;     // sub-bar pixels not yet scrolled
double         g_accumZoom   = 0.0;     // pixels not yet turned into a zoom step
uint           g_lastClickMs = 0;
int            g_lastClickX  = -100;
int            g_lastClickY  = -100;

//--- validated copies of the inputs
double         g_dragSpeed   = 0.005;
double         g_wheelStep   = 0.10;
int            g_zoomPx      = 40;
int            g_dblClickMs  = 350;
int            g_levelTolPx  = 5;

//+------------------------------------------------------------------+
//| Initialization                                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   g_chart = ChartID();
   IndicatorSetString(INDICATOR_SHORTNAME, TVN_SHORT_NAME);

//--- clamp inputs to sane ranges instead of refusing to start
   g_dragSpeed  = MathMax(0.0001, MathMin(0.1, InpPriceDragSpeed));
   g_wheelStep  = MathMax(0.01,   MathMin(0.9, InpPriceWheelStep));
   g_zoomPx     = MathMax(5,      InpTimeZoomPixels);
   g_dblClickMs = MathMax(100,    InpDoubleClickMs);
   g_levelTolPx = MathMax(1,      InpTradeLevelTolPx);

   CreateToggleButton();
   Enable();
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Deinitialization: put every chart property back                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(g_enabled)
      Disable();
   ObjectDelete(g_chart, TVN_TGL_NAME);
   ChartRedraw(g_chart);
  }

//+------------------------------------------------------------------+
//| ON: remember the chart as it is now, then take over the mouse    |
//+------------------------------------------------------------------+
void Enable()
  {
   g_saved.mouseScroll  = (bool)ChartGetInteger(g_chart, CHART_MOUSE_SCROLL);
   g_saved.autoScroll   = (bool)ChartGetInteger(g_chart, CHART_AUTOSCROLL);
   g_saved.scaleFix     = (bool)ChartGetInteger(g_chart, CHART_SCALEFIX);
   g_saved.evMouseMove  = (bool)ChartGetInteger(g_chart, CHART_EVENT_MOUSE_MOVE);
   g_saved.evMouseWheel = (bool)ChartGetInteger(g_chart, CHART_EVENT_MOUSE_WHEEL);
   g_saved.fixedMax     = ChartGetDouble(g_chart, CHART_FIXED_MAX);
   g_saved.fixedMin     = ChartGetDouble(g_chart, CHART_FIXED_MIN);

   ChartSetInteger(g_chart, CHART_EVENT_MOUSE_MOVE,  true);
   ChartSetInteger(g_chart, CHART_EVENT_MOUSE_WHEEL, true);
   ChartSetInteger(g_chart, CHART_MOUSE_SCROLL,      false);
   if(InpDisableAutoScroll)
      ChartSetInteger(g_chart, CHART_AUTOSCROLL, false);

   if(InpShowAutoButton)
      CreateAutoButton();
   UpdateAutoButton();

   g_enabled = true;
   UpdateToggleButton();
   ChartRedraw(g_chart);
  }

//+------------------------------------------------------------------+
//| OFF: restore the chart exactly as it was before Enable()         |
//+------------------------------------------------------------------+
void Disable()
  {
   EndDrag();
   g_prevLeft = false;
   ObjectDelete(g_chart, TVN_BTN_NAME);

   ChartSetInteger(g_chart, CHART_MOUSE_SCROLL,      g_saved.mouseScroll);
   ChartSetInteger(g_chart, CHART_AUTOSCROLL,        g_saved.autoScroll);
   ChartSetInteger(g_chart, CHART_EVENT_MOUSE_MOVE,  g_saved.evMouseMove);
   ChartSetInteger(g_chart, CHART_EVENT_MOUSE_WHEEL, g_saved.evMouseWheel);
   if(g_saved.scaleFix)
     {
      ChartSetDouble(g_chart, CHART_FIXED_MAX, g_saved.fixedMax);
      ChartSetDouble(g_chart, CHART_FIXED_MIN, g_saved.fixedMin);
     }
   ChartSetInteger(g_chart, CHART_SCALEFIX, g_saved.scaleFix);

   g_enabled = false;
   UpdateToggleButton();
   ChartRedraw(g_chart);
  }

//+------------------------------------------------------------------+
//| No calculation: the indicator only handles chart events          |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   return(rates_total);
  }

//+------------------------------------------------------------------+
//| Chart events                                                     |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
//--- the ON/OFF button works in both states
   if(id == CHARTEVENT_OBJECT_CLICK && sparam == TVN_TGL_NAME)
     {
      if(g_enabled)
         Disable();
      else
         Enable();
      return;
     }
//--- while OFF the chart behaves as normal MetaTrader
   if(!g_enabled)
      return;

   switch(id)
     {
      case CHARTEVENT_MOUSE_MOVE:
         OnMouseMove((int)lparam, (int)dparam, (uint)StringToInteger(sparam));
         break;

      case CHARTEVENT_MOUSE_WHEEL:
         OnMouseWheel(lparam, dparam);
         break;

      case CHARTEVENT_OBJECT_CLICK:
         if(sparam == TVN_BTN_NAME)
           {
            if(IsAutoScale())
               EnsureFixedScale();
            else
               SetAutoScale();
            UpdateAutoButton();
            ChartRedraw(g_chart);
           }
         break;

      case CHARTEVENT_CHART_CHANGE:
         // the scale may have been changed natively (chart properties, F8 ...)
         UpdateAutoButton();
         break;
     }
  }

//+------------------------------------------------------------------+
//| Mouse move: turn button state changes into drag begin/move/end   |
//+------------------------------------------------------------------+
void OnMouseMove(const int x, const int y, const uint state)
  {
   bool left = (state & TVN_MK_LEFT) != 0;

   if(left && !g_prevLeft)
      BeginDrag(x, y);
   else
      if(left && g_mode != DRAG_NONE)
         ContinueDrag(x, y);
      else
         if(!left && g_prevLeft)
            EndDrag();

   g_prevLeft = left;
  }

//+------------------------------------------------------------------+
//| Left button pressed: decide what this drag will do               |
//+------------------------------------------------------------------+
void BeginDrag(const int x, const int y)
  {
   g_mode      = DRAG_NONE;
   g_lastX     = x;
   g_lastY     = y;
   g_accumX    = 0.0;
   g_accumZoom = 0.0;

   ENUM_TVN_ZONE zone = GetZone(x, y);

//--- double-click on the price scale -> auto-fit
   uint now = GetTickCount();
   bool dbl = (now - g_lastClickMs) <= (uint)g_dblClickMs &&
              MathAbs(x - g_lastClickX) < TVN_DBL_CLICK_PX &&
              MathAbs(y - g_lastClickY) < TVN_DBL_CLICK_PX;
   g_lastClickMs = now;
   g_lastClickX  = x;
   g_lastClickY  = y;

   if(dbl && zone == ZONE_PRICE_SCALE)
     {
      SetAutoScale();
      UpdateAutoButton();
      ChartRedraw(g_chart);
      g_lastClickMs = 0;   // a third click must not count as another double-click
      return;
     }

//--- leave clicks meant for our button, selected objects or trade levels alone
   if(IsOverButton(TVN_BTN_NAME, x, y) || IsOverButton(TVN_TGL_NAME, x, y) ||
      AnyObjectSelected())
      return;
   if(InpProtectTradeLevels && zone == ZONE_MAIN && IsNearTradeLevel(y))
      return;

   switch(zone)
     {
      case ZONE_MAIN:
         g_mode        = DRAG_PAN;
         g_panVertical = InpVerticalPan;
         break;
      case ZONE_SUB:
         g_mode        = DRAG_PAN;
         g_panVertical = false;   // subwindows scroll horizontally only
         break;
      case ZONE_PRICE_SCALE:
         if(InpPriceScaleDrag)
            g_mode = DRAG_PRICE_SCALE;
         break;
      case ZONE_TIME_SCALE:
         if(InpTimeScaleDrag)
            g_mode = DRAG_TIME_SCALE;
         break;
      default:
         break;
     }
  }

//+------------------------------------------------------------------+
//| Mouse moved with the left button held                            |
//+------------------------------------------------------------------+
void ContinueDrag(const int x, const int y)
  {
   int dx = x - g_lastX;
   int dy = y - g_lastY;
   g_lastX = x;
   g_lastY = y;
   if(dx == 0 && dy == 0)
      return;

   bool changed = false;

   switch(g_mode)
     {
      case DRAG_PAN:
         if(dx != 0 && PanHorizontal(dx))
            changed = true;
         if(g_panVertical && dy != 0 && PanVertical(dy))
            changed = true;
         break;

      case DRAG_PRICE_SCALE:
         if(dy != 0)
           {
            // drag down = compress (zoom out), drag up = stretch (zoom in)
            double factor = MathExp(dy * g_dragSpeed);
            double mx = 0, mn = 0;
            EnsureFixedScale();
            GetFixedRange(mx, mn);
            changed = ZoomPriceAt((mx + mn) / 2.0, factor);
           }
         break;

      case DRAG_TIME_SCALE:
         if(dx != 0)
            changed = ZoomTimeDrag(dx);
         break;

      default:
         break;
     }

   if(changed)
      ChartRedraw(g_chart);
  }

//+------------------------------------------------------------------+
//| Left button released                                             |
//+------------------------------------------------------------------+
void EndDrag()
  {
   g_mode      = DRAG_NONE;
   g_accumX    = 0.0;
   g_accumZoom = 0.0;
  }

//+------------------------------------------------------------------+
//| Mouse wheel: price zoom, optional time zoom                      |
//+------------------------------------------------------------------+
void OnMouseWheel(const long lparam, const double dparam)
  {
   int keys  = (int)(lparam >> 32);
   int x     = (int)(short)lparam;
   int y     = (int)(short)(lparam >> 16);
   int delta = (int)dparam;
   if(delta == 0)
      return;

   ENUM_TVN_ZONE zone = GetZone(x, y);
   bool changed = false;

//--- price zoom: wheel over the price scale, or Shift+wheel over the main chart
   if(InpShiftWheelPrice &&
      (zone == ZONE_PRICE_SCALE || (zone == ZONE_MAIN && (keys & TVN_MK_SHIFT) != 0)))
     {
      double factor = (delta > 0) ? (1.0 - g_wheelStep) : 1.0 / (1.0 - g_wheelStep);

      EnsureFixedScale();
      double mx = 0, mn = 0;
      GetFixedRange(mx, mn);

      double anchor = (mx + mn) / 2.0;
      if(zone == ZONE_MAIN)
         anchor = YToPrice(y, mx, mn);   // keep the price under the cursor still

      changed = ZoomPriceAt(anchor, factor);
     }
//--- time zoom
   else
      if(InpWheelMode == WHEEL_ZOOM_TIME &&
         (zone == ZONE_MAIN || zone == ZONE_SUB || zone == ZONE_TIME_SCALE))
        {
         changed = ChangeChartScale(delta > 0 ? 1 : -1);
        }

   if(changed)
      ChartRedraw(g_chart);
  }

//+------------------------------------------------------------------+
//| Scroll by whole bars; keeps the leftover pixels for next time    |
//+------------------------------------------------------------------+
bool PanHorizontal(const int dx)
  {
   double barW = BarWidthPx();
   g_accumX += dx;

   int bars = (int)(g_accumX / barW);   // truncates toward zero
   if(bars == 0)
      return false;

   g_accumX -= bars * barW;
// mouse moves right -> older bars come into view
   return ChartNavigate(g_chart, CHART_CURRENT_POS, -bars);
  }

//+------------------------------------------------------------------+
//| Move the fixed price range so the candles follow the mouse       |
//+------------------------------------------------------------------+
bool PanVertical(const int dy)
  {
   EnsureFixedScale();

   double mx = 0, mn = 0;
   GetFixedRange(mx, mn);

   int h = (int)ChartGetInteger(g_chart, CHART_HEIGHT_IN_PIXELS, 0);
   if(h <= 0 || mx <= mn)
      return false;

// mouse moves down -> candles move down -> the visible range shifts up
   double shift = dy * (mx - mn) / h;
   ChartSetDouble(g_chart, CHART_FIXED_MAX, mx + shift);
   ChartSetDouble(g_chart, CHART_FIXED_MIN, mn + shift);
   return true;
  }

//+------------------------------------------------------------------+
//| Scale the fixed price range around an anchor price               |
//| factor < 1 stretches (zoom in), factor > 1 compresses            |
//+------------------------------------------------------------------+
bool ZoomPriceAt(const double anchor, const double factor)
  {
   double mx = 0, mn = 0;
   GetFixedRange(mx, mn);

   double newMax = anchor + (mx - anchor) * factor;
   double newMin = anchor - (anchor - mn) * factor;

   if(newMax - newMin < TVN_MIN_RANGE_PT * _Point)
      return false;

   ChartSetDouble(g_chart, CHART_FIXED_MAX, newMax);
   ChartSetDouble(g_chart, CHART_FIXED_MIN, newMin);
   return true;
  }

//+------------------------------------------------------------------+
//| Time-axis drag: every g_zoomPx pixels = one CHART_SCALE step     |
//+------------------------------------------------------------------+
bool ZoomTimeDrag(const int dx)
  {
   g_accumZoom += dx;

   int steps = (int)(g_accumZoom / g_zoomPx);
   if(steps == 0)
      return false;

   g_accumZoom -= steps * g_zoomPx;
// drag right = zoom in (wider bars), drag left = zoom out
   return ChangeChartScale(steps);
  }

//+------------------------------------------------------------------+
bool ChangeChartScale(const int steps)
  {
   int scale    = (int)ChartGetInteger(g_chart, CHART_SCALE);
   int newScale = MathMax(0, MathMin(TVN_MAX_SCALE, scale + steps));
   if(newScale == scale)
      return false;
   return ChartSetInteger(g_chart, CHART_SCALE, newScale);
  }

//+------------------------------------------------------------------+
//| Scale helpers                                                    |
//+------------------------------------------------------------------+
bool IsAutoScale()
  {
   return !(bool)ChartGetInteger(g_chart, CHART_SCALEFIX);
  }

//--- switch to a fixed scale that starts from what is visible now,
//--- so the chart does not jump
void EnsureFixedScale()
  {
   if(!IsAutoScale())
      return;

   double mx = ChartGetDouble(g_chart, CHART_PRICE_MAX, 0);
   double mn = ChartGetDouble(g_chart, CHART_PRICE_MIN, 0);
   if(mx <= mn)
      return;

   ChartSetDouble(g_chart, CHART_FIXED_MAX, mx);
   ChartSetDouble(g_chart, CHART_FIXED_MIN, mn);
   ChartSetInteger(g_chart, CHART_SCALEFIX, true);
   UpdateAutoButton();
  }

//--- back to MetaTrader's auto-fit
void SetAutoScale()
  {
   ChartSetInteger(g_chart, CHART_SCALEFIX, false);
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void GetFixedRange(double &mx, double &mn)
  {
   mx = ChartGetDouble(g_chart, CHART_FIXED_MAX);
   mn = ChartGetDouble(g_chart, CHART_FIXED_MIN);
  }

//--- pixel row in the main window -> price
double YToPrice(const int y, const double mx, const double mn)
  {
   int h = (int)ChartGetInteger(g_chart, CHART_HEIGHT_IN_PIXELS, 0);
   if(h <= 0)
      return (mx + mn) / 2.0;
   return mx - (mx - mn) * (double)y / h;
  }

//--- width of one bar in pixels at the current zoom
double BarWidthPx()
  {
   long px   = ChartGetInteger(g_chart, CHART_WIDTH_IN_PIXELS);
   long bars = ChartGetInteger(g_chart, CHART_WIDTH_IN_BARS);
   if(bars > 0 && px > 0)
      return (double)px / (double)bars;
   return (double)(1 << (int)ChartGetInteger(g_chart, CHART_SCALE));
  }

//+------------------------------------------------------------------+
//| Which part of the chart is under (x,y)?                          |
//+------------------------------------------------------------------+
ENUM_TVN_ZONE GetZone(const int x, const int y)
  {
   if(x < 0 || y < 0)
      return ZONE_NONE;

   int w     = (int)ChartGetInteger(g_chart, CHART_WIDTH_IN_PIXELS);   // excludes the price scale
   int wins  = (int)ChartGetInteger(g_chart, CHART_WINDOWS_TOTAL);
   int mainH = (int)ChartGetInteger(g_chart, CHART_HEIGHT_IN_PIXELS, 0);

   int lastTop = (int)ChartGetInteger(g_chart, CHART_WINDOW_YDISTANCE, wins - 1);
   int lastH   = (int)ChartGetInteger(g_chart, CHART_HEIGHT_IN_PIXELS, wins - 1);
   int bottom  = lastTop + lastH;   // top edge of the time axis

   if(y >= bottom)
      return (x < w) ? ZONE_TIME_SCALE : ZONE_NONE;
   if(x >= w)
      return (y < mainH) ? ZONE_PRICE_SCALE : ZONE_NONE;
   return (y < mainH) ? ZONE_MAIN : ZONE_SUB;
  }

//+------------------------------------------------------------------+
//| A selected object is about to be moved - do not pan              |
//+------------------------------------------------------------------+
bool AnyObjectSelected()
  {
   int total = ObjectsTotal(g_chart, -1, -1);
   for(int i = 0; i < total; i++)
     {
      string name = ObjectName(g_chart, i, -1, -1);
      if(name == TVN_BTN_NAME || name == TVN_TGL_NAME)
         continue;
      if((bool)ObjectGetInteger(g_chart, name, OBJPROP_SELECTED))
         return true;
     }
   return false;
  }

//+------------------------------------------------------------------+
//| Is y close to an entry / SL / TP line of this symbol?            |
//| Only matters when trade levels are shown and draggable.          |
//+------------------------------------------------------------------+
bool IsNearTradeLevel(const int y)
  {
   if(!(bool)ChartGetInteger(g_chart, CHART_SHOW_TRADE_LEVELS) ||
      !(bool)ChartGetInteger(g_chart, CHART_DRAG_TRADE_LEVELS))
      return false;

   datetime t = TimeCurrent();

//--- open positions
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(PositionGetSymbol(i) != _Symbol)
         continue;
      if(PriceNearY(t, PositionGetDouble(POSITION_PRICE_OPEN), y) ||
         PriceNearY(t, PositionGetDouble(POSITION_SL), y) ||
         PriceNearY(t, PositionGetDouble(POSITION_TP), y))
         return true;
     }

//--- pending orders
   for(int i = OrdersTotal() - 1; i >= 0; i--)
     {
      ulong ticket = OrderGetTicket(i);
      if(ticket == 0 || OrderGetString(ORDER_SYMBOL) != _Symbol)
         continue;
      if(PriceNearY(t, OrderGetDouble(ORDER_PRICE_OPEN), y) ||
         PriceNearY(t, OrderGetDouble(ORDER_SL), y) ||
         PriceNearY(t, OrderGetDouble(ORDER_TP), y))
         return true;
     }
   return false;
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool PriceNearY(const datetime t, const double price, const int y)
  {
   if(price <= 0.0)
      return false;
   int px = 0, py = 0;
   if(!ChartTimePriceToXY(g_chart, 0, t, price, px, py))
      return false;
   return MathAbs(py - y) <= g_levelTolPx;
  }

//+------------------------------------------------------------------+
//| AUTO button, bottom-right of the main window                     |
//+------------------------------------------------------------------+
void CreateAutoButton()
  {
   if(ObjectFind(g_chart, TVN_BTN_NAME) >= 0)
      ObjectDelete(g_chart, TVN_BTN_NAME);

   ObjectCreate(g_chart, TVN_BTN_NAME, OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(g_chart, TVN_BTN_NAME, OBJPROP_CORNER,     CORNER_RIGHT_LOWER);
   ObjectSetInteger(g_chart, TVN_BTN_NAME, OBJPROP_XDISTANCE,  TVN_BTN_W + TVN_BTN_MARGIN);
   ObjectSetInteger(g_chart, TVN_BTN_NAME, OBJPROP_YDISTANCE,  TVN_BTN_H + TVN_BTN_MARGIN);
   ObjectSetInteger(g_chart, TVN_BTN_NAME, OBJPROP_XSIZE,      TVN_BTN_W);
   ObjectSetInteger(g_chart, TVN_BTN_NAME, OBJPROP_YSIZE,      TVN_BTN_H);
   ObjectSetString(g_chart,  TVN_BTN_NAME, OBJPROP_TEXT,       "AUTO");
   ObjectSetString(g_chart,  TVN_BTN_NAME, OBJPROP_FONT,       "Arial");
   ObjectSetInteger(g_chart, TVN_BTN_NAME, OBJPROP_FONTSIZE,   8);
   ObjectSetInteger(g_chart, TVN_BTN_NAME, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(g_chart, TVN_BTN_NAME, OBJPROP_HIDDEN,     true);
   ObjectSetInteger(g_chart, TVN_BTN_NAME, OBJPROP_ZORDER,     100);
   ObjectSetString(g_chart,  TVN_BTN_NAME, OBJPROP_TOOLTIP,
                   "Auto-fit price scale (or double-click the price scale)");
  }

//--- blue = auto-fit on, grey = fixed scale
void UpdateAutoButton()
  {
   if(ObjectFind(g_chart, TVN_BTN_NAME) < 0)
      return;

   bool autoOn = IsAutoScale();
   ObjectSetInteger(g_chart, TVN_BTN_NAME, OBJPROP_STATE,        autoOn);
   ObjectSetInteger(g_chart, TVN_BTN_NAME, OBJPROP_BGCOLOR,      autoOn ? clrDodgerBlue : clrDimGray);
   ObjectSetInteger(g_chart, TVN_BTN_NAME, OBJPROP_COLOR,        clrWhite);
   ObjectSetInteger(g_chart, TVN_BTN_NAME, OBJPROP_BORDER_COLOR, autoOn ? clrDodgerBlue : clrGray);
  }

//+------------------------------------------------------------------+
//| ON/OFF button, just left of the AUTO button                      |
//| It stays on the chart while OFF so the tool can be switched back |
//+------------------------------------------------------------------+
void CreateToggleButton()
  {
   if(ObjectFind(g_chart, TVN_TGL_NAME) >= 0)
      ObjectDelete(g_chart, TVN_TGL_NAME);

   int xd = TVN_TGL_W + TVN_BTN_MARGIN;
   if(InpShowAutoButton)
      xd += TVN_BTN_W + TVN_BTN_MARGIN;   // leave room for AUTO on its right

   ObjectCreate(g_chart, TVN_TGL_NAME, OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(g_chart, TVN_TGL_NAME, OBJPROP_CORNER,     CORNER_RIGHT_LOWER);
   ObjectSetInteger(g_chart, TVN_TGL_NAME, OBJPROP_XDISTANCE,  xd);
   ObjectSetInteger(g_chart, TVN_TGL_NAME, OBJPROP_YDISTANCE,  TVN_BTN_H + TVN_BTN_MARGIN);
   ObjectSetInteger(g_chart, TVN_TGL_NAME, OBJPROP_XSIZE,      TVN_TGL_W);
   ObjectSetInteger(g_chart, TVN_TGL_NAME, OBJPROP_YSIZE,      TVN_BTN_H);
   ObjectSetString(g_chart,  TVN_TGL_NAME, OBJPROP_FONT,       "Arial");
   ObjectSetInteger(g_chart, TVN_TGL_NAME, OBJPROP_FONTSIZE,   8);
   ObjectSetInteger(g_chart, TVN_TGL_NAME, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(g_chart, TVN_TGL_NAME, OBJPROP_HIDDEN,     true);
   ObjectSetInteger(g_chart, TVN_TGL_NAME, OBJPROP_ZORDER,     100);
   ObjectSetString(g_chart,  TVN_TGL_NAME, OBJPROP_TOOLTIP,
                   "Turn TradingView-style navigation on / off");
  }

//--- green "ON" = tool active, grey "OFF" = normal MetaTrader chart
void UpdateToggleButton()
  {
   if(ObjectFind(g_chart, TVN_TGL_NAME) < 0)
      return;

   ObjectSetString(g_chart,  TVN_TGL_NAME, OBJPROP_TEXT,         g_enabled ? "ON" : "OFF");
   ObjectSetInteger(g_chart, TVN_TGL_NAME, OBJPROP_STATE,        g_enabled);
   ObjectSetInteger(g_chart, TVN_TGL_NAME, OBJPROP_BGCOLOR,      g_enabled ? clrSeaGreen : clrDimGray);
   ObjectSetInteger(g_chart, TVN_TGL_NAME, OBJPROP_COLOR,        clrWhite);
   ObjectSetInteger(g_chart, TVN_TGL_NAME, OBJPROP_BORDER_COLOR, g_enabled ? clrSeaGreen : clrGray);
  }

//--- is (x,y) over one of our corner buttons?
bool IsOverButton(const string name, const int x, const int y)
  {
   if(ObjectFind(g_chart, name) < 0)
      return false;

   int w  = (int)ChartGetInteger(g_chart, CHART_WIDTH_IN_PIXELS);
   int h  = (int)ChartGetInteger(g_chart, CHART_HEIGHT_IN_PIXELS, 0);
   int xd = (int)ObjectGetInteger(g_chart, name, OBJPROP_XDISTANCE);
   int yd = (int)ObjectGetInteger(g_chart, name, OBJPROP_YDISTANCE);
   int xs = (int)ObjectGetInteger(g_chart, name, OBJPROP_XSIZE);
   int ys = (int)ObjectGetInteger(g_chart, name, OBJPROP_YSIZE);

   int left = w - xd;
   int top  = h - yd;
   return (x >= left - 2 && x <= left + xs + 2 && y >= top - 2 && y <= top + ys + 2);
  }
//+------------------------------------------------------------------+