Загрузка данных


diff --git a/src/components/Menu/TimeframesMenu/constants.ts b/src/components/Menu/TimeframesMenu/constants.ts
index 062b13e..47ec3f1 100644
--- a/src/components/Menu/TimeframesMenu/constants.ts
+++ b/src/components/Menu/TimeframesMenu/constants.ts
@@ -1,96 +1,54 @@
-import { t } from '@src/translations';
 import { Timeframes } from '@src/types/timeframes';
+import { formatTimeframeLabel, TimeframeUnit } from '@src/utils';
+
+const timeframeOption = (value: Timeframes, amount: number, unit: TimeframeUnit) => ({
+  value,
+  label: formatTimeframeLabel(amount, unit),
+});
 
 export const timeframes = () => {
   return {
     ticks: [
-      { value: Timeframes['1t'], label: `1 ${t('tick')}` },
-      { value: Timeframes['10t'], label: `10 ${t('ticks')}` },
-      { value: Timeframes['100t'], label: `100 ${t('ticks')}` },
-      { value: Timeframes['1000t'], label: `1000 ${t('ticks')}` },
+      timeframeOption(Timeframes['1t'], 1, 'tick'),
+      timeframeOption(Timeframes['10t'], 10, 'tick'),
+      timeframeOption(Timeframes['100t'], 100, 'tick'),
+      timeframeOption(Timeframes['1000t'], 1000, 'tick'),
     ],
     seconds: [
-      { value: Timeframes['1s'], label: `1 ${t('second')}` },
-      { value: Timeframes['5s'], label: `5 ${t('seconds')}` },
-      { value: Timeframes['10s'], label: `10 ${t('seconds')}` },
-      { value: Timeframes['15s'], label: `15 ${t('seconds')}` },
-      { value: Timeframes['30s'], label: `30 ${t('seconds')}` },
-      { value: Timeframes['45s'], label: `45 ${t('seconds')}` },
+      timeframeOption(Timeframes['1s'], 1, 'second'),
+      timeframeOption(Timeframes['5s'], 5, 'second'),
+      timeframeOption(Timeframes['10s'], 10, 'second'),
+      timeframeOption(Timeframes['15s'], 15, 'second'),
+      timeframeOption(Timeframes['30s'], 30, 'second'),
+      timeframeOption(Timeframes['45s'], 45, 'second'),
     ],
     minutes: [
-      { value: Timeframes['1m'], label: `1 ${t('minute')}` },
-      { value: Timeframes['2m'], label: `2 ${t('minutes')}` },
-      { value: Timeframes['3m'], label: `3 ${t('minutes')}` },
-      { value: Timeframes['5m'], label: `5 ${t('minutes')}` },
-      { value: Timeframes['10m'], label: `10 ${t('minutes')}` },
-      { value: Timeframes['15m'], label: `15 ${t('minutes')}` },
-      { value: Timeframes['30m'], label: `30 ${t('minutes')}` },
-      { value: Timeframes['45m'], label: `45 ${t('minutes')}` },
+      timeframeOption(Timeframes['1m'], 1, 'minute'),
+      timeframeOption(Timeframes['2m'], 2, 'minute'),
+      timeframeOption(Timeframes['3m'], 3, 'minute'),
+      timeframeOption(Timeframes['5m'], 5, 'minute'),
+      timeframeOption(Timeframes['10m'], 10, 'minute'),
+      timeframeOption(Timeframes['15m'], 15, 'minute'),
+      timeframeOption(Timeframes['30m'], 30, 'minute'),
+      timeframeOption(Timeframes['45m'], 45, 'minute'),
     ],
     hours: [
-      { value: Timeframes['1h'], label: `1 ${t('hour')}` },
-      { value: Timeframes['2h'], label: `2 ${t('hours')}` },
-      { value: Timeframes['3h'], label: `3 ${t('hours')}` },
-      { value: Timeframes['4h'], label: `4 ${t('hours')}` },
-      { value: Timeframes['8h'], label: `8 ${t('hours')}` },
-      { value: Timeframes['12h'], label: `12 ${t('hours')}` },
+      timeframeOption(Timeframes['1h'], 1, 'hour'),
+      timeframeOption(Timeframes['2h'], 2, 'hour'),
+      timeframeOption(Timeframes['3h'], 3, 'hour'),
+      timeframeOption(Timeframes['4h'], 4, 'hour'),
+      timeframeOption(Timeframes['8h'], 8, 'hour'),
+      timeframeOption(Timeframes['12h'], 12, 'hour'),
     ],
     days: [
-      { value: Timeframes['1d'], label: `1 ${t('day')}` },
-      { value: Timeframes['3d'], label: `3 ${t('days')}` },
-      { value: Timeframes['5d'], label: `5 ${t('days')}` },
-    ],
-    weeks: [{ value: Timeframes['1w'], label: `1 ${t('week')}` }],
-    months: [
-      { value: Timeframes['1М'], label: `1 ${t('month')}` },
-      { value: Timeframes['3М'], label: `3 ${t('months')}` },
+      timeframeOption(Timeframes['1d'], 1, 'day'),
+      timeframeOption(Timeframes['3d'], 3, 'day'),
+      timeframeOption(Timeframes['5d'], 5, 'day'),
     ],
+    weeks: [timeframeOption(Timeframes['1w'], 1, 'week')],
+    months: [timeframeOption(Timeframes['1М'], 1, 'month'), timeframeOption(Timeframes['3М'], 3, 'month')],
     Range: [],
   };
 };
 
-export const TIMEFRAMES = {
-  ticks: [
-    { value: Timeframes['1t'], label: `1 ${t('tick')}` },
-    { value: Timeframes['10t'], label: `10 ${t('ticks')}` },
-    { value: Timeframes['100t'], label: `100 ${t('ticks')}` },
-    { value: Timeframes['1000t'], label: `1000 ${t('ticks')}` },
-  ],
-  seconds: [
-    { value: Timeframes['1s'], label: `1 ${t('second')}` },
-    { value: Timeframes['5s'], label: `5 ${t('seconds')}` },
-    { value: Timeframes['10s'], label: `10 ${t('seconds')}` },
-    { value: Timeframes['15s'], label: `15 ${t('seconds')}` },
-    { value: Timeframes['30s'], label: `30 ${t('seconds')}` },
-    { value: Timeframes['45s'], label: `45 ${t('seconds')}` },
-  ],
-  minutes: [
-    { value: Timeframes['1m'], label: `1 ${t('minute')}` },
-    { value: Timeframes['2m'], label: `2 ${t('minutes')}` },
-    { value: Timeframes['3m'], label: `3 ${t('minutes')}` },
-    { value: Timeframes['5m'], label: `5 ${t('minutes')}` },
-    { value: Timeframes['10m'], label: `10 ${t('minutes')}` },
-    { value: Timeframes['15m'], label: `15 ${t('minutes')}` },
-    { value: Timeframes['30m'], label: `30 ${t('minutes')}` },
-    { value: Timeframes['45m'], label: `45 ${t('minutes')}` },
-  ],
-  hours: [
-    { value: Timeframes['1h'], label: `1 ${t('hour')}` },
-    { value: Timeframes['2h'], label: `2 ${t('hours')}` },
-    { value: Timeframes['3h'], label: `3 ${t('hours')}` },
-    { value: Timeframes['4h'], label: `4 ${t('hours')}` },
-    { value: Timeframes['8h'], label: `8 ${t('hours')}` },
-    { value: Timeframes['12h'], label: `12 ${t('hours')}` },
-  ],
-  days: [
-    { value: Timeframes['1d'], label: `1 ${t('day')}` },
-    { value: Timeframes['3d'], label: `3 ${t('days')}` },
-    { value: Timeframes['5d'], label: `5 ${t('days')}` },
-  ],
-  weeks: [{ value: Timeframes['1w'], label: `1 ${t('week')}` }],
-  months: [
-    { value: Timeframes['1М'], label: `1 ${t('month')}` },
-    { value: Timeframes['3М'], label: `3 ${t('months')}` },
-  ],
-  Range: [],
-} as const;
+export const TIMEFRAMES = timeframes();
diff --git a/src/components/PriceScaleControls/index.module.scss b/src/components/PriceScaleControls/index.module.scss
new file mode 100644
index 0000000..7afd802
--- /dev/null
+++ b/src/components/PriceScaleControls/index.module.scss
@@ -0,0 +1,24 @@
+@use '../../theme/mixins' as m;
+
+.tooltip {
+  @include m.tooltipHint;
+}
+
+.root {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: var(--space-0250);
+  padding: var(--space-0250);
+  background-color: var(--neutral-0);
+  pointer-events: none;
+
+  .button {
+    @include m.buttonBase;
+
+    min-width: var(--space-1250);
+    height: var(--space-1500);
+    flex-shrink: 0;
+    pointer-events: auto;
+  }
+}
diff --git a/src/components/PriceScaleControls/index.tsx b/src/components/PriceScaleControls/index.tsx
new file mode 100644
index 0000000..7f342dc
--- /dev/null
+++ b/src/components/PriceScaleControls/index.tsx
@@ -0,0 +1,63 @@
+import classNames from 'classnames';
+import { Button, Tooltip } from 'exchange-elements/v2';
+
+import { t } from '@src/translations';
+
+import styles from './index.module.scss';
+
+import type { SyntheticEvent } from 'react';
+
+interface PriceScaleControlsProps {
+  isAutoScale: boolean;
+  isLogarithmic: boolean;
+  onToggleAutoScale: () => void;
+  onToggleLogarithmic: () => void;
+}
+
+export function PriceScaleControls({
+  isAutoScale,
+  isLogarithmic,
+  onToggleAutoScale,
+  onToggleLogarithmic,
+}: PriceScaleControlsProps) {
+  const stopPropagation = (event: SyntheticEvent) => {
+    event.stopPropagation();
+  };
+
+  return (
+    <section
+      className={classNames(styles.root, 'moex-price-scale-controls-content')}
+      onPointerDown={stopPropagation}
+      onMouseDown={stopPropagation}
+      onClick={stopPropagation}
+    >
+      <Tooltip
+        tooltipClassName={styles.tooltip}
+        label={t('AutoScale')}
+      >
+        <Button
+          size="sm"
+          className={classNames(styles.button, {
+            [styles.pressed]: isAutoScale,
+          })}
+          onClick={onToggleAutoScale}
+          label="A"
+        />
+      </Tooltip>
+
+      <Tooltip
+        tooltipClassName={styles.tooltip}
+        label={t('Logarithmic')}
+      >
+        <Button
+          size="sm"
+          className={classNames(styles.button, {
+            [styles.pressed]: isLogarithmic,
+          })}
+          onClick={onToggleLogarithmic}
+          label="L"
+        />
+      </Tooltip>
+    </section>
+  );
+}
diff --git a/src/core/Chart.ts b/src/core/Chart.ts
index 63309e4..3622e5e 100644
--- a/src/core/Chart.ts
+++ b/src/core/Chart.ts
@@ -26,7 +26,6 @@ import { EventManager } from '@core/EventManager';
 import { IndicatorManager } from '@core/IndicatorManager';
 import { ModalRenderer } from '@core/ModalRenderer';
 import { PaneManager } from '@core/PaneManager';
-import { PriceAxisLabelsController } from '@core/PriceAxisLabels';
 import { CompareManager } from '@src/core/CompareManager';
 import { SeriesStrategies } from '@src/modules/series-strategies/SeriesFactory';
 import { getThemeStore } from '@src/theme/store';
@@ -43,7 +42,6 @@ import {
 } from '@src/types';
 import { Defaults } from '@src/types/defaults';
 import { DayjsOffset, Intervals, intervalsToDayjs } from '@src/types/intervals';
-
 import { ChartSnapshot, ISerializable, PaneSnapshot } from '@src/types/snapshot';
 import { formatCompactNumber } from '@src/utils';
 import { createTickMarkFormatter, formatDate } from '@src/utils/formatter';
@@ -87,7 +85,6 @@ export class Chart implements ISerializable<ChartSnapshot> {
   private compareManager: CompareManager;
   private mouseEvents: ChartMouseEvents;
   private indicatorManager: IndicatorManager;
-  private priceAxisLabelsController: PriceAxisLabelsController;
   private optionsSubscription: Subscription;
   private dataSource: DataSource;
   private chartConfig: Omit<ChartConfig, 'theme' | 'mode'>;
@@ -118,7 +115,12 @@ export class Chart implements ISerializable<ChartSnapshot> {
     this.optionsSubscription = this.eventManager
       .getChartOptionsModel()
       .subscribe(({ dateFormat, timeFormat, showTime }) => {
-        const configToApply = { ...lwcChartConfig, dateFormat, timeFormat, showTime };
+        const configToApply = {
+          ...lwcChartConfig,
+          dateFormat,
+          timeFormat,
+          showTime,
+        };
 
         this.lwcChart.applyOptions({
           ...getOptions(configToApply),
@@ -130,7 +132,10 @@ export class Chart implements ISerializable<ChartSnapshot> {
 
     this.subscriptions.add(this.optionsSubscription);
 
-    this.mouseEvents = new ChartMouseEvents({ lwcChart: this.lwcChart, container: this.container });
+    this.mouseEvents = new ChartMouseEvents({
+      lwcChart: this.lwcChart,
+      container: this.container,
+    });
 
     this.mouseEvents.subscribe('wheel', this.onWheel);
     this.mouseEvents.subscribe('pointerDown', this.onPointerDown);
@@ -138,7 +143,9 @@ export class Chart implements ISerializable<ChartSnapshot> {
     this.mouseEvents.subscribe('pointerUp', this.onPointerUp);
     this.mouseEvents.subscribe('pointerCancel', this.onPointerUp);
 
-    this.DOM = new DOMModel({ modalRenderer });
+    this.DOM = new DOMModel({
+      modalRenderer,
+    });
 
     this.paneManager = new PaneManager({
       eventManager: this.eventManager,
@@ -158,7 +165,12 @@ export class Chart implements ISerializable<ChartSnapshot> {
     this.indicatorManager = new IndicatorManager({
       eventManager,
       initialIndicators: flatten(
-        panesSnapshot.map((pane) => pane.indicators.map((ind) => ({ ...ind, paneId: pane.id }))),
+        panesSnapshot.map((pane) =>
+          pane.indicators.map((indicator) => ({
+            ...indicator,
+            paneId: pane.id,
+          })),
+        ),
       ),
       DOM: this.DOM,
       dataSource: this.dataSource,
@@ -170,7 +182,12 @@ export class Chart implements ISerializable<ChartSnapshot> {
     this.compareManager = new CompareManager({
       chart: this.lwcChart,
       initialIndicators: flatten(
-        panesSnapshot.map((pane) => pane.indicators.map((ind) => ({ ...ind, paneId: pane.id }))),
+        panesSnapshot.map((pane) =>
+          pane.indicators.map((indicator) => ({
+            ...indicator,
+            paneId: pane.id,
+          })),
+        ),
       ),
       eventManager: this.eventManager,
       dataSource: this.dataSource,
@@ -178,14 +195,13 @@ export class Chart implements ISerializable<ChartSnapshot> {
       paneManager: this.paneManager,
     });
 
-    this.priceAxisLabelsController = new PriceAxisLabelsController({
-      mainSeries$: this.paneManager.getMainPane().getMainSerie().asObservable(),
-      mainSymbol$: this.eventManager.symbol(),
+    this.paneManager.initializePriceAxisLabels({
       compareEntities$: this.compareManager.entities(),
       indicatorEntities$: this.indicatorManager.entities(),
     });
 
-    this.priceAxisLabelsController.setVisibleLogicalRange(this.lwcChart.timeScale().getVisibleLogicalRange());
+    this.paneManager.setVisibleLogicalRange(this.lwcChart.timeScale().getVisibleLogicalRange());
+    this.paneManager.refreshPriceScaleControls();
 
     this.setupDataSourceSubs();
     this.setupHistoricalDataLoading();
@@ -210,7 +226,9 @@ export class Chart implements ISerializable<ChartSnapshot> {
   };
 
   private onWheel = () => {
-    this.eventManager.resetInterval({ history: false });
+    this.eventManager.resetInterval({
+      history: false,
+    });
   };
 
   private onPointerDown = () => {
@@ -223,7 +241,10 @@ export class Chart implements ISerializable<ChartSnapshot> {
     if (this.didResetOnDrag) return;
 
     this.didResetOnDrag = true;
-    this.eventManager.resetInterval({ history: false });
+
+    this.eventManager.resetInterval({
+      history: false,
+    });
   };
 
   private onPointerUp = () => {
@@ -243,15 +264,22 @@ export class Chart implements ISerializable<ChartSnapshot> {
   }
 
   public updateTheme(theme: ThemeKey, mode: ThemeMode) {
-    this.lwcChart.applyOptions(getOptions({ ...this.chartConfig, theme, mode }));
-    this.priceAxisLabelsController.invalidate();
+    this.lwcChart.applyOptions(
+      getOptions({
+        ...this.chartConfig,
+        theme,
+        mode,
+      }),
+    );
+
+    this.paneManager.invalidatePriceAxisLabels();
   }
 
   public destroy(): void {
-    this.priceAxisLabelsController.destroy();
     this.mouseEvents.destroy();
-    this.compareManager.clear();
     this.subscriptions.unsubscribe();
+    this.compareManager.destroy();
+    this.paneManager.destroy();
     this.lwcChart.remove();
   }
 
@@ -264,36 +292,50 @@ export class Chart implements ISerializable<ChartSnapshot> {
 
   // todo: add/move to undo/redo model(eventManager)
   public scrollTimeScale = (direction: Direction) => {
-    this.eventManager.resetInterval({ history: false });
+    this.eventManager.resetInterval({
+      history: false,
+    });
+
     const diff = direction === Direction.Left ? -2 : 2;
+
     const currentPosition = this.lwcChart.timeScale().scrollPosition();
+
     this.lwcChart.timeScale().scrollToPosition(currentPosition + diff, false);
   };
 
   // todo: add/move to undo/redo model(eventManager)
   public zoomTimeScale = (resize: Resize) => {
-    this.eventManager.resetInterval({ history: false });
+    this.eventManager.resetInterval({
+      history: false,
+    });
+
     const diff = resize === Resize.Shrink ? -1 : 1;
 
     const currentRange = this.lwcChart.timeScale().getVisibleRange();
+
     if (!currentRange) return;
 
     const { from, to } = currentRange as IRange<number>;
+
     if (!from || !to) return;
 
     const next: IRange<Time> = {
       from: (from + (to - from) * 0.1 * diff) as Time,
       to: to as Time,
     };
+
     this.lwcChart.timeScale().setVisibleRange(next);
   };
 
   // todo: add to undo/redo model(eventManager)
   public resetZoom = () => {
-    this.eventManager.resetInterval({ history: false });
+    this.eventManager.resetInterval({
+      history: false,
+    });
+
     this.lwcChart.timeScale().resetTimeScale();
-    this.lwcChart.priceScale(Direction.Right).setAutoScale(true);
-    this.lwcChart.priceScale(Direction.Left).setAutoScale(true);
+
+    this.paneManager.resetPriceScalesAutoScale();
   };
 
   public getRealtimeApi() {
@@ -323,10 +365,11 @@ export class Chart implements ISerializable<ChartSnapshot> {
     requestAnimationFrame(() => {
       const symbols = this.activeSymbols.slice();
 
-      Promise.all(symbols.map((s) => this.dataSource.loadMoreHistory(s))).finally(() => {
+      Promise.all(symbols.map((symbol) => this.dataSource.loadMoreHistory(symbol))).finally(() => {
         this.historyBatchRunning = false;
 
         const range = this.lwcChart.timeScale().getVisibleLogicalRange();
+
         if (range && range.from < HISTORY_LOAD_THRESHOLD) {
           this.scheduleHistoryBatch();
         }
@@ -341,9 +384,11 @@ export class Chart implements ISerializable<ChartSnapshot> {
       }
 
       const range = this.lwcChart.timeScale().getVisibleRange();
+
       if (!range) return 0;
 
       const { from } = range as IRange<number>;
+
       return from as number;
     };
 
@@ -351,14 +396,16 @@ export class Chart implements ISerializable<ChartSnapshot> {
       if (!symbols.length) return;
 
       const from = getWarmupFrom();
+
       if (!from) return;
 
       Promise.all(symbols.map((symbol) => this.dataSource.loadTill(symbol, from))).catch((error) => {
         console.error('[Chart] Ошибка при прогреве символов:', error);
       });
     };
+
     const symbols$ = combineLatest([this.eventManager.symbol(), this.compareManager.itemsObs()]).pipe(
-      map(([main, items]) => Array.from(new Set([main, ...items.map((i) => i.symbol)]))),
+      map(([main, items]) => Array.from(new Set([main, ...items.map((item) => item.symbol)]))),
     );
 
     this.subscriptions.add(
@@ -371,7 +418,7 @@ export class Chart implements ISerializable<ChartSnapshot> {
           if (!interval) return;
 
           if (interval === Intervals.All) {
-            Promise.all(symbols.map((s) => this.dataSource.loadAllHistory(s)))
+            Promise.all(symbols.map((symbol) => this.dataSource.loadAllHistory(symbol)))
               .then(() => {
                 requestAnimationFrame(() => this.lwcChart.timeScale().fitContent());
               })
@@ -382,9 +429,12 @@ export class Chart implements ISerializable<ChartSnapshot> {
 
           const { from, to } = getIntervalRange(interval);
 
-          Promise.all(symbols.map((s) => this.dataSource.loadTill(s, from)))
+          Promise.all(symbols.map((symbol) => this.dataSource.loadTill(symbol, from)))
             .then(() => {
-              this.lwcChart.timeScale().setVisibleRange({ from: from as Time, to: to as Time });
+              this.lwcChart.timeScale().setVisibleRange({
+                from: from as Time,
+                to: to as Time,
+              });
             })
             .catch((error) => {
               console.error('[Chart] Ошибка при применении интервала:', error);
@@ -394,23 +444,28 @@ export class Chart implements ISerializable<ChartSnapshot> {
 
     this.subscriptions.add(
       symbols$.subscribe((symbols) => {
-        const prevSymbols = this.activeSymbols;
+        const previousSymbols = this.activeSymbols;
+
         this.activeSymbols = symbols;
 
         this.dataSource.setSymbols(symbols);
 
-        const prevSet = new Set(prevSymbols);
-        const added: string[] = [];
+        const previousSymbolsSet = new Set(previousSymbols);
+        const addedSymbols: string[] = [];
+
+        for (let index = 0; index < symbols.length; index += 1) {
+          const symbol = symbols[index];
+
+          if (!symbol) continue;
+          if (previousSymbolsSet.has(symbol)) {
+            continue;
+          }
 
-        for (let i = 0; i < symbols.length; i += 1) {
-          const s = symbols[i];
-          if (!s) continue;
-          if (prevSet.has(s)) continue;
-          added.push(s);
+          addedSymbols.push(symbol);
         }
 
-        if (added.length) {
-          warmupSymbols(added);
+        if (addedSymbols.length) {
+          warmupSymbols(addedSymbols);
         }
       }),
     );
@@ -419,12 +474,16 @@ export class Chart implements ISerializable<ChartSnapshot> {
   private setupHistoricalDataLoading(): void {
     // todo (не)вызвать loadMoreHistory после проверки на необходимость дозагрузки после смены таймфрейма
     this.mouseEvents.subscribe('visibleLogicalRangeChange', (logicalRange: LogicalRange | null) => {
-      this.priceAxisLabelsController.setVisibleLogicalRange(logicalRange);
+      this.paneManager.setVisibleLogicalRange(logicalRange);
 
       if (!logicalRange) return;
-      if (this.currentInterval === Intervals.All) return;
+
+      if (this.currentInterval === Intervals.All) {
+        return;
+      }
 
       const needsMoreData = logicalRange.from < HISTORY_LOAD_THRESHOLD;
+
       if (!needsMoreData) return;
 
       this.scheduleHistoryBatch();
@@ -432,13 +491,19 @@ export class Chart implements ISerializable<ChartSnapshot> {
   }
 }
 
-function getIntervalRange(interval: Intervals): { from: number; to: number } {
+function getIntervalRange(interval: Intervals): {
+  from: number;
+  to: number;
+} {
   const { value, unit } = intervalsToDayjs[interval] as DayjsOffset;
 
   const from = Math.floor(dayjs().subtract(value, unit).valueOf() / 1000);
   const to = Math.floor(dayjs().valueOf() / 1000);
 
-  return { from, to };
+  return {
+    from,
+    to,
+  };
 }
 
 function getOptions(config: ChartConfig): DeepPartial<ChartOptions> {
@@ -462,17 +527,31 @@ function getOptions(config: ChartConfig): DeepPartial<ChartOptions> {
     height: config.container.clientHeight,
     autoSize: true,
     layout: {
-      background: { color: colors.chartBackground },
+      background: {
+        color: colors.chartBackground,
+      },
       textColor: colors.chartTextPrimary,
     },
     grid: {
-      vertLines: { color: colors.chartGridLine },
-      horzLines: { color: colors.chartGridLine },
+      vertLines: {
+        color: colors.chartGridLine,
+      },
+      horzLines: {
+        color: colors.chartGridLine,
+      },
     },
     crosshair: {
       mode: CrosshairMode.Normal,
-      vertLine: { color: colors.chartCrosshairLine, labelBackgroundColor: colors.chartCrosshairLabel, style: 0 },
-      horzLine: { color: colors.chartCrosshairLine, labelBackgroundColor: colors.chartCrosshairLabel, style: 2 },
+      vertLine: {
+        color: colors.chartCrosshairLine,
+        labelBackgroundColor: colors.chartCrosshairLabel,
+        style: 0,
+      },
+      horzLine: {
+        color: colors.chartCrosshairLine,
+        labelBackgroundColor: colors.chartCrosshairLabel,
+        style: 2,
+      },
     },
     timeScale: {
       timeVisible: showTime,
diff --git a/src/core/CompareManager.ts b/src/core/CompareManager.ts
index e289018..9069066 100644
--- a/src/core/CompareManager.ts
+++ b/src/core/CompareManager.ts
@@ -1,14 +1,14 @@
 import { IChartApi, PriceScaleMode, SeriesType } from 'lightweight-charts';
+
 import { flatten } from 'lodash-es';
-import { BehaviorSubject, distinctUntilChanged, map, Observable } from 'rxjs';
+import { BehaviorSubject, distinctUntilChanged, map, Observable, Subscription } from 'rxjs';
 
 import { DataSource } from '@core/DataSource';
 import { EventManager } from '@core/EventManager';
 import { Indicator } from '@core/Indicator';
 import { IndicatorManager } from '@core/IndicatorManager';
 import { PaneManager } from '@core/PaneManager';
-
-import { MAIN_PANE_INDEX } from '@src/constants';
+import { PriceScale } from '@core/PriceScale';
 import { COMPARE_COLOR_PALETTE } from '@src/theme';
 import { CompareItem, CompareMode, Direction, IndicatorConfig } from '@src/types';
 import { IndicatorSnapshot } from '@src/types/snapshot';
@@ -18,7 +18,6 @@ interface CompareEntry {
   key: string;
   symbol: string;
   mode: CompareMode;
-  paneIndex: number;
   symbol$: BehaviorSubject<string>;
   entity: Indicator;
 }
@@ -38,10 +37,13 @@ export class CompareManager {
   private readonly dataSource: DataSource;
   private readonly indicatorManager: IndicatorManager;
   private readonly paneManager: PaneManager;
-
   private readonly entries = new Map<string, CompareEntry>();
   private readonly itemsSubject = new BehaviorSubject<CompareItem[]>([]);
   private readonly entitiesSubject = new BehaviorSubject<Indicator[]>([]);
+  private readonly subscriptions = new Subscription();
+
+  private hasPercentageComparison = false;
+  private restoringInitialIndicators = false;
 
   constructor({
     chart,
@@ -57,36 +59,15 @@ export class CompareManager {
     this.indicatorManager = indicatorManager;
     this.paneManager = paneManager;
 
-    this.eventManager.timeframe().subscribe(() => {
-      this.applyPolicy();
-    });
-
-    if (!initialIndicators) {
-      return;
-    }
+    this.subscriptions.add(
+      this.eventManager.timeframe().subscribe(() => {
+        this.applyPolicy();
+      }),
+    );
 
     this.setup(initialIndicators);
   }
 
-  private async setup(initialIndicators: IndicatorSnapshot[]) {
-    for (const indicator of initialIndicators) {
-      if (indicator.config && indicator.config.label) {
-        // условие проверки compare ли это
-        const serie = indicator.config.series[0];
-
-        const compareMode =
-          serie.seriesOptions?.priceScaleId === Direction.Left
-            ? CompareMode.NewScale
-            : indicator.config.newPane
-              ? CompareMode.NewPane
-              : CompareMode.Percentage;
-
-        // eslint-disable-next-line no-await-in-loop
-        await this.setSymbolMode(serie.name, indicator.config.label, compareMode, indicator.paneId);
-      }
-    }
-  }
-
   public itemsObs(): Observable<CompareItem[]> {
     return this.itemsSubject.asObservable();
   }
@@ -97,11 +78,12 @@ export class CompareManager {
 
   public clear(): void {
     const keys = Array.from(this.entries.keys());
-    for (let i = 0; i < keys.length; i += 1) {
-      this.removeByKey(keys[i]);
+
+    for (let index = 0; index < keys.length; index += 1) {
+      this.removeEntry(keys[index]);
     }
-    this.applyPolicy();
-    this.publish();
+
+    this.commitEntriesChange();
   }
 
   public async setSymbolMode(
@@ -111,14 +93,20 @@ export class CompareManager {
     paneId?: number,
   ): Promise<void> {
     const symbol = normalizeSymbol(symbolRaw);
-    if (!symbol) return;
 
-    if (mode === CompareMode.NewScale && this.isNewScaleDisabled()) {
+    if (!symbol) {
+      return;
+    }
+
+    if (mode === CompareMode.NewScale && this.isNewScaleDisabled() && !this.restoringInitialIndicators) {
       return;
     }
 
     const key = makeKey(symbol, mode);
-    if (this.entries.has(key)) return;
+
+    if (this.entries.has(key)) {
+      return;
+    }
 
     const symbol$ = new BehaviorSubject(symbol);
 
@@ -126,17 +114,19 @@ export class CompareManager {
       const usedColorsByCompare = this.entitiesSubject.value.map(
         // eslint-disable-next-line @typescript-eslint/ban-ts-comment
         // @ts-ignore
-        (ind) => ind.getConfig().series?.[0]?.seriesOptions?.color,
+        (indicator) => indicator.getConfig().series?.[0]?.seriesOptions?.color,
       );
-      const existIndicators = Array.from(this.indicatorManager.getIndicators().value.values());
-      const usedColorsByIndicatorsRaw = existIndicators.map((ind) =>
+
+      const existingIndicators = Array.from(this.indicatorManager.getIndicators().value.values());
+
+      const usedColorsByIndicatorsRaw = existingIndicators.map((indicator) =>
         // eslint-disable-next-line @typescript-eslint/ban-ts-comment
         // @ts-ignore
-        ind.config?.series?.map((serie) => serie.seriesOptions?.color),
+        indicator.config?.series?.map((series) => series.seriesOptions?.color),
       );
+
       const usedColorsByIndicators = flatten(usedColorsByIndicatorsRaw).filter((color) => color !== undefined);
       const usedColors = usedColorsByCompare.concat(usedColorsByIndicators);
-
       const config = getDefaultCompareIndicatorConfig(symbol, usedColors);
 
       const associatedPane =
@@ -166,42 +156,65 @@ export class CompareManager {
           newPane: mode === CompareMode.NewPane,
         },
         zIndex,
-        onDelete: () => this.removeByKey(key),
+        onDelete: () => {
+          if (this.removeEntry(key)) {
+            this.commitEntriesChange();
+          }
+        },
         moveUp,
         moveDown,
         paneId: associatedPane.getId(),
       });
     });
 
-    this.entries.set(key, { key, symbol, mode, paneIndex: entity.getPane().getId(), symbol$, entity });
+    this.entries.set(key, {
+      key,
+      symbol,
+      mode,
+      symbol$,
+      entity,
+    });
 
-    this.applyPolicy();
-    this.publish();
+    this.commitEntriesChange();
 
     await this.dataSource.isReady(symbol);
   }
 
   public removeSymbolMode(symbolRaw: string, mode: CompareMode): void {
     const symbol = normalizeSymbol(symbolRaw);
-    if (!symbol) return;
 
-    this.removeByKey(makeKey(symbol, mode));
-    this.applyPolicy();
-    this.publish();
+    if (!symbol) {
+      return;
+    }
+
+    if (this.removeEntry(makeKey(symbol, mode))) {
+      this.commitEntriesChange();
+    }
   }
 
   public removeSymbol(symbolRaw: string): void {
     const symbol = normalizeSymbol(symbolRaw);
-    if (!symbol) return;
 
-    const all = Array.from(this.entries.entries());
-    for (let i = 0; i < all.length; i += 1) {
-      const [key, entry] = all[i];
-      if (entry.symbol === symbol) this.removeByKey(key);
+    if (!symbol) {
+      return;
     }
 
-    this.applyPolicy();
-    this.publish();
+    const entries = Array.from(this.entries.entries());
+    let removed = false;
+
+    for (let index = 0; index < entries.length; index += 1) {
+      const [key, entry] = entries[index];
+
+      if (entry.symbol !== symbol) {
+        continue;
+      }
+
+      removed = this.removeEntry(key) || removed;
+    }
+
+    if (removed) {
+      this.commitEntriesChange();
+    }
   }
 
   public isNewScaleDisabled(): boolean {
@@ -224,117 +237,145 @@ export class CompareManager {
   }
 
   public destroy(): void {
+    this.subscriptions.unsubscribe();
     this.clear();
     this.itemsSubject.complete();
     this.entitiesSubject.complete();
   }
 
-  private removeByKey(key: string): void {
+  private async setup(initialIndicators: IndicatorSnapshot[]): Promise<void> {
+    this.restoringInitialIndicators = true;
+
+    try {
+      for (const indicator of initialIndicators) {
+        if (indicator.indicatorType !== undefined) {
+          continue;
+        }
+
+        if (!indicator.config?.label) {
+          continue;
+        }
+
+        const series = indicator.config.series[0];
+
+        const compareMode =
+          series.seriesOptions?.priceScaleId === Direction.Left
+            ? CompareMode.NewScale
+            : indicator.config.newPane
+              ? CompareMode.NewPane
+              : CompareMode.Percentage;
+
+        // eslint-disable-next-line no-await-in-loop
+        await this.setSymbolMode(series.name, indicator.config.label, compareMode, indicator.paneId);
+      }
+    } finally {
+      this.restoringInitialIndicators = false;
+    }
+  }
+
+  private removeEntry(key: string): boolean {
     const entry = this.entries.get(key);
-    if (!entry) return;
+
+    if (!entry) {
+      return false;
+    }
 
     this.entries.delete(key);
     this.indicatorManager.removeEntity(entry.entity);
     entry.entity.destroy();
     entry.symbol$.complete();
 
+    return true;
+  }
+
+  private commitEntriesChange(): void {
     this.applyPolicy();
     this.publish();
   }
 
   private publish(): void {
     const values = Array.from(this.entries.values());
-
     const items: CompareItem[] = [];
     const entities: Indicator[] = [];
 
-    for (let i = 0; i < values.length; i += 1) {
-      items.push({ symbol: values[i].symbol, mode: values[i].mode });
-      entities.push(values[i].entity);
+    for (let index = 0; index < values.length; index += 1) {
+      items.push({
+        symbol: values[index].symbol,
+        mode: values[index].mode,
+      });
+
+      entities.push(values[index].entity);
     }
 
     this.itemsSubject.next(items);
     this.entitiesSubject.next(entities);
   }
 
-  private applyPolicy(): void {
-    const list = Array.from(this.entries.values());
-
-    let percentEnabled = false;
-    for (let i = 0; i < list.length; i += 1) {
-      if (list[i].mode === CompareMode.Percentage) {
-        percentEnabled = true;
-        break;
-      }
+  private syncPercentageMode(priceScale: PriceScale, hasPercentageComparison: boolean): void {
+    if (this.restoringInitialIndicators) {
+      this.hasPercentageComparison = hasPercentageComparison;
+      return;
     }
 
-    let hasMainNewScale = false;
-    for (let i = 0; i < list.length; i += 1) {
-      // [0 - в индикаторах compare сущности может быть только одна серия] [1 - entry]
-      const paneIndex = Array.from(list[i].entity.getSeriesMap())[0][1].getPane().paneIndex();
-      if (paneIndex === MAIN_PANE_INDEX && list[i].mode === CompareMode.NewScale) {
-        hasMainNewScale = true;
-        break;
-      }
+    if (this.hasPercentageComparison === hasPercentageComparison) {
+      return;
     }
 
-    const paneSet = new Set<number>();
-    for (let i = 0; i < list.length; i += 1) {
-      // [0 - в индикаторах compare сущности может быть только одна серия] [1 - entry]
-      const paneIndex = Array.from(list[i].entity.getSeriesMap())[0][1].getPane().paneIndex();
+    const hadPercentageComparison = this.hasPercentageComparison;
 
-      if (paneIndex !== MAIN_PANE_INDEX) paneSet.add(paneIndex);
-    }
+    this.hasPercentageComparison = hasPercentageComparison;
 
-    this.chart.applyOptions({
-      leftPriceScale: { visible: hasMainNewScale, borderVisible: false },
-    });
+    if (hasPercentageComparison && !hadPercentageComparison) {
+      priceScale.setMode(PriceScaleMode.Percentage);
+      return;
+    }
 
-    this.chart.priceScale(Direction.Right, MAIN_PANE_INDEX).applyOptions({
-      visible: true,
-      mode: percentEnabled ? PriceScaleMode.Percentage : PriceScaleMode.Normal,
-    });
+    if (!hasPercentageComparison && hadPercentageComparison && priceScale.getMode() === PriceScaleMode.Percentage) {
+      priceScale.setMode(PriceScaleMode.Normal);
+    }
+  }
 
-    this.chart.priceScale(Direction.Left, MAIN_PANE_INDEX).applyOptions({
-      visible: hasMainNewScale,
-      mode: PriceScaleMode.Normal,
-      borderVisible: false,
-    });
+  private applyPolicy(): void {
+    const entries = Array.from(this.entries.values());
 
-    const panes = Array.from(paneSet.values());
-    for (let i = 0; i < panes.length; i += 1) {
-      const paneIndex = panes[i];
+    let hasPercentageComparison = false;
+    let hasNewScaleComparison = false;
 
-      this.chart.priceScale(Direction.Right, paneIndex).applyOptions({
-        visible: true,
-        mode: PriceScaleMode.Normal,
-      });
+    for (let index = 0; index < entries.length; index += 1) {
+      if (entries[index].mode === CompareMode.Percentage) {
+        hasPercentageComparison = true;
+      }
 
-      if (hasMainNewScale) {
-        this.chart.priceScale(Direction.Left, paneIndex).applyOptions({
-          visible: true,
-          mode: PriceScaleMode.Normal,
-          borderVisible: false,
-        });
+      if (entries[index].mode === CompareMode.NewScale) {
+        hasNewScaleComparison = true;
       }
     }
 
-    for (let i = 0; i < list.length; i += 1) {
-      const entry = list[i];
+    for (let index = 0; index < entries.length; index += 1) {
+      const entry = entries[index];
+      const pane = entry.entity.getPane();
 
       // [0 - в индикаторах compare сущности может быть только одна серия] [1 - entry]
-      const serie = Array.from(entry.entity.getSeriesMap())[0][1];
-      const paneIndex = serie.getPane().paneIndex();
+      const series = Array.from(entry.entity.getSeriesMap().values())[0];
 
-      if (paneIndex !== MAIN_PANE_INDEX) {
-        serie.applyOptions({ priceScaleId: Direction.Right });
+      if (!series) {
         continue;
       }
 
-      serie.applyOptions({
-        priceScaleId: entry.mode === CompareMode.NewScale ? Direction.Left : Direction.Right,
+      series.applyOptions({
+        priceScaleId: pane.isMainPane() && entry.mode === CompareMode.NewScale ? Direction.Left : Direction.Right,
       });
     }
+
+    this.paneManager.setPriceScaleSideVisible(Direction.Left, hasNewScaleComparison);
+    this.paneManager.setPriceScaleSideVisible(Direction.Right, true);
+
+    const mainRightPriceScale = this.paneManager.getMainPane().getPriceScale(Direction.Right);
+
+    this.syncPercentageMode(mainRightPriceScale, hasPercentageComparison);
+
+    this.paneManager.refreshPriceScaleControls();
+    this.paneManager.invalidatePriceAxisLabels();
   }
 }
 
diff --git a/src/core/Drawings/diapson/diapson.ts b/src/core/Drawings/diapson/diapson.ts
index 0748bef..bfbef38 100644
--- a/src/core/Drawings/diapson/diapson.ts
+++ b/src/core/Drawings/diapson/diapson.ts
@@ -1001,7 +1001,7 @@ export class Diapson implements ISeriesDrawing {
     let volume = 0;
 
     for (let index = from; index <= to; index += 1) {
-      const item = data.at(index) as Record<string, unknown> | undefined;
+      const item = data[index] as unknown as Record<string, unknown> | undefined;
 
       if (!item) {
         continue;
diff --git a/src/core/Drawings/ruler/ruler.ts b/src/core/Drawings/ruler/ruler.ts
index 1ce3fd5..5850c13 100644
--- a/src/core/Drawings/ruler/ruler.ts
+++ b/src/core/Drawings/ruler/ruler.ts
@@ -672,7 +672,7 @@ export class Ruler implements ISeriesDrawing {
     let volume = 0;
 
     for (let index = from; index <= to; index += 1) {
-      const item = data.at(index) as Record<string, unknown> | undefined;
+      const item = data[index] as unknown as Record<string, unknown> | undefined;
 
       if (!item) {
         continue;
diff --git a/src/core/Indicator.ts b/src/core/Indicator.ts
index ebb7662..b0d9ee7 100644
--- a/src/core/Indicator.ts
+++ b/src/core/Indicator.ts
@@ -75,10 +75,6 @@ export class Indicator extends DOMObject implements ISerializable<IndicatorSnaps
     });
   }
 
-  public deletable = (): boolean => {
-    return this.associatedPane.isMainPane() || this.associatedPane.isLast();
-  };
-
   public getLabel = () => {
     if (this.config.label) {
       return this.config.label;
@@ -207,7 +203,7 @@ export class Indicator extends DOMObject implements ISerializable<IndicatorSnaps
         mainSymbol$: this.mainSymbol$,
         mainSerie$: this.associatedPane.getMainSerie(),
         showSymbolLabel: false,
-        paneIndex: this.associatedPane.getId(),
+        paneIndex: this.associatedPane.paneIndex(),
         indicatorReference: this,
       });
 
diff --git a/src/core/Legend.ts b/src/core/Legend.ts
index 68e535f..b57a6b1 100644
--- a/src/core/Legend.ts
+++ b/src/core/Legend.ts
@@ -17,6 +17,7 @@ export interface LegendParams {
   subscribeChartEvent: ChartMouseEvents['subscribe'];
   mainSeries: BehaviorSubject<SeriesStrategies | null> | null;
   paneId: number;
+  paneIndex: () => number;
   openIndicatorSettings: (id: IndicatorsIds, indicator: Indicator) => void;
 }
 
@@ -85,6 +86,7 @@ export class Legend {
   private tooltipVisability = new BehaviorSubject<boolean>(false);
   private tooltipPos = new BehaviorSubject<null | Point>(null);
   private paneId: number;
+  private paneIndex: () => number;
 
   private openIndicatorSettings: (id: IndicatorsIds, indicator: Indicator) => void;
   private subscriptions = new Subscription();
@@ -98,16 +100,20 @@ export class Legend {
     subscribeChartEvent,
     mainSeries,
     paneId,
+    paneIndex,
     openIndicatorSettings,
   }: LegendParams) {
     this.config = config;
     this.eventManager = eventManager;
     this.paneId = paneId;
+    this.paneIndex = paneIndex;
     this.openIndicatorSettings = openIndicatorSettings;
 
     this.subscriptions.add(
       this.eventManager.symbol().subscribe((symbol) => {
-        this.mainSymbol = symbol.split(':').at(-1) || symbol;
+        const symbolParts = symbol.split(':');
+
+        this.mainSymbol = symbolParts[symbolParts.length - 1] || symbol;
         this.updateWithLastCandle();
       }),
     );
@@ -210,8 +216,6 @@ export class Legend {
         indicatorSeries.set(serieName, { ...value, name: indicator.getSeriesLabel(serieName) });
       }
 
-      const remove = indicator.deletable() ? () => indicator.delete() : undefined;
-
       model.push({
         id: indicator.getId(),
         name: indicator.getLabel(),
@@ -219,7 +223,7 @@ export class Legend {
           Record<keyof Ohlc, { value: number | string | Time; color: string; name?: string }>
         >,
         isIndicator: true,
-        remove,
+        remove: () => indicator.delete(),
         settings:
           indicatorType && indicator.hasSettings()
             ? () => this.openIndicatorSettings(indicatorType, indicator)
@@ -240,7 +244,7 @@ export class Legend {
       return;
     }
 
-    if (this.paneId === param.paneIndex) {
+    if (this.paneIndex() === param.paneIndex) {
       this.tooltipVisability.next(true);
     } else {
       this.tooltipVisability.next(false);
@@ -289,8 +293,6 @@ export class Legend {
         indicatorSeries.set(serieName, { ...value, name: indicator.getSeriesLabel(serieName) });
       }
 
-      const remove = indicator.deletable() ? () => indicator.delete() : undefined;
-
       model.push({
         id: indicator.getId(),
         name: indicator.getLabel(),
@@ -298,7 +300,7 @@ export class Legend {
           Record<keyof Ohlc, { value: number | string | Time; color: string; name?: string }>
         >,
         isIndicator: true,
-        remove,
+        remove: () => indicator.delete(),
         settings:
           indicatorType && indicator.hasSettings()
             ? () => this.openIndicatorSettings(indicatorType, indicator)
diff --git a/src/core/Pane.tsx b/src/core/Pane.tsx
index 11749e0..1692edb 100644
--- a/src/core/Pane.tsx
+++ b/src/core/Pane.tsx
@@ -1,4 +1,4 @@
-import { IChartApi, IPaneApi, Time } from 'lightweight-charts';
+import { IChartApi, IPaneApi, PriceScaleMode, Time } from 'lightweight-charts';
 
 import { BehaviorSubject, Subscription } from 'rxjs';
 
@@ -12,16 +12,24 @@ import { DrawingsManager, DrawingsManagerSnapshot } from '@core/DrawingsManager'
 import { EventManager } from '@core/EventManager';
 import { Indicator } from '@core/Indicator';
 import { Legend } from '@core/Legend';
+import { PriceScale, PriceScaleControls } from '@core/PriceScale';
 import { ReactRenderer } from '@core/ReactRenderer';
 import { TooltipService } from '@core/Tooltip';
 import { UIRenderer } from '@core/UIRenderer';
 import { EntitySettingsModal } from '@src/components/EntitySettingsModal';
-import { DrawingsNames, indicatorLabelById } from '@src/constants';
+import { DrawingsNames, indicatorLabelById, MAIN_PANE_INDEX } from '@src/constants';
 import { ModalRenderer } from '@src/core/ModalRenderer';
 import { SeriesFactory, SeriesStrategies } from '@src/modules/series-strategies/SeriesFactory';
 import { t } from '@src/translations';
-import { OHLCConfig, TooltipConfig } from '@src/types';
-import { DOMObjectSnapshot, IndicatorSnapshot, ISerializable, PaneSnapshot } from '@src/types/snapshot';
+import { Direction, OHLCConfig, TooltipConfig } from '@src/types';
+import {
+  DOMObjectSnapshot,
+  IndicatorSnapshot,
+  ISerializable,
+  PaneSnapshot,
+  PriceScaleSide,
+  PriceScaleSnapshot,
+} from '@src/types/snapshot';
 import { ensureDefined } from '@src/utils';
 
 export interface PaneParams {
@@ -38,6 +46,10 @@ export interface PaneParams {
   onDelete: () => void;
   chartContainer: HTMLElement;
   modalRenderer: ModalRenderer;
+  initialPriceScales?: PriceScaleSnapshot[];
+  onPriceScaleStateChange: () => void;
+  leftPriceScaleVisible: boolean;
+  rightPriceScaleVisible: boolean;
 }
 
 // todo: Pane, ему должна принадлежать mainSerie, а также IndicatorManager и drawingsManager, mouseEvents. Также перекинуть соответствующие/необходимые свойства из чарта, и из чарта удалить
@@ -48,33 +60,29 @@ export interface PaneParams {
 
 export class Pane implements ISerializable<PaneSnapshot> {
   private readonly id: number;
-  private isMain: boolean;
-  private mainSeries: BehaviorSubject<SeriesStrategies | null> = new BehaviorSubject<SeriesStrategies | null>(null); // Main Series. Exists in a single copy
+  private readonly isMain: boolean;
+  private mainSeries = new BehaviorSubject<SeriesStrategies | null>(null); // Main Series. Exists in a single copy
   private legend!: Legend;
   private tooltip: TooltipService | undefined;
-
-  private indicatorsMap: BehaviorSubject<Map<string, Indicator>> = new BehaviorSubject<Map<string, Indicator>>(
-    new Map(),
-  );
-
-  private lwcPane: IPaneApi<Time>;
-  private lwcChart: IChartApi;
-
-  private eventManager: EventManager;
-  private drawingsManager: DrawingsManager;
-
+  private readonly indicatorsMap = new BehaviorSubject<Map<string, Indicator>>(new Map());
+  private readonly lwcPane: IPaneApi<Time>;
+  private readonly lwcChart: IChartApi;
+  private readonly eventManager: EventManager;
+  private readonly drawingsManager: DrawingsManager;
   private legendContainer!: HTMLElement;
   private paneOverlayContainer!: HTMLElement;
   private legendRenderer!: UIRenderer;
   private tooltipRenderer: UIRenderer | undefined;
-  private modalRenderer: ModalRenderer;
-
-  private mainSerieSub!: Subscription;
-  private subscribeChartEvent: ChartMouseEvents['subscribe'];
-  private onDelete: () => void;
-  private subscriptions = new Subscription();
-
-  private last = false; // временное решение чтобы блочить удаление не последнего пейна
+  private readonly modalRenderer: ModalRenderer;
+  private readonly leftPriceScale: PriceScale;
+  private readonly rightPriceScale: PriceScale;
+  private readonly priceScaleControls: PriceScaleControls;
+  private mainSerieSub?: Subscription;
+  private readonly subscribeChartEvent: ChartMouseEvents['subscribe'];
+  private readonly onDelete: () => void;
+  private readonly onPriceScaleStateChange: () => void;
+  private readonly subscriptions = new Subscription();
+  private paneContainerSyncFrameId: number | null = null;
 
   constructor({
     lwcChart,
@@ -90,23 +98,37 @@ export class Pane implements ISerializable<PaneSnapshot> {
     onDelete,
     chartContainer,
     modalRenderer,
+    initialPriceScales = [],
+    onPriceScaleStateChange,
+    leftPriceScaleVisible,
+    rightPriceScaleVisible,
   }: PaneParams) {
     this.onDelete = onDelete;
+    this.onPriceScaleStateChange = onPriceScaleStateChange;
     this.eventManager = eventManager;
     this.lwcChart = lwcChart;
     this.modalRenderer = modalRenderer;
     this.subscribeChartEvent = subscribeChartEvent;
-    this.isMain = isMainPane ?? false;
+    this.isMain = isMainPane;
     this.id = id;
 
-    this.initializeLegend({ ohlcConfig });
-
     if (isMainPane) {
-      this.lwcPane = this.lwcChart.panes()[this.id];
+      this.lwcPane = this.lwcChart.panes()[MAIN_PANE_INDEX];
     } else {
       this.lwcPane = this.lwcChart.addPane(true);
     }
 
+    this.leftPriceScale = this.createPriceScale(Direction.Left, initialPriceScales, leftPriceScaleVisible);
+    this.rightPriceScale = this.createPriceScale(Direction.Right, initialPriceScales, rightPriceScaleVisible);
+
+    this.priceScaleControls = new PriceScaleControls({
+      leftPriceScale: this.leftPriceScale,
+      rightPriceScale: this.rightPriceScale,
+      onPriceScaleChange: this.handlePriceScaleStateChange,
+    });
+
+    this.initializeLegend({ ohlcConfig });
+
     this.tooltip = new TooltipService({
       config: tooltipConfig,
       legend: this.legend,
@@ -129,7 +151,7 @@ export class Pane implements ISerializable<PaneSnapshot> {
     if (dataSource) {
       this.initializeMainSerie({ lwcChart, dataSource });
     } else if (basedOn) {
-      this.mainSeries = basedOn?.getMainSerie();
+      this.mainSeries = basedOn.getMainSerie();
     } else {
       console.error('[Pane]: There is no any mainSerie for new pane');
     }
@@ -148,23 +170,16 @@ export class Pane implements ISerializable<PaneSnapshot> {
     this.subscriptions.add(
       this.drawingsManager.entities().subscribe((drawings) => {
         const hasRuler = drawings.some((drawing) => drawing.getDrawingName() === DrawingsNames.ruler);
+
         this.legendContainer.style.display = hasRuler ? 'none' : '';
       }),
     );
   }
 
-  public setIsLast(isLast: boolean): void {
-    this.last = isLast;
-  }
-
   public isMainPane = () => {
     return this.isMain;
   };
 
-  public isLast = () => {
-    return this.last;
-  };
-
   public getDrawingsSnapshot(): DrawingsManagerSnapshot {
     return this.drawingsManager.getSnapshot();
   }
@@ -181,23 +196,31 @@ export class Pane implements ISerializable<PaneSnapshot> {
     return this.id;
   };
 
+  public paneIndex = () => {
+    return this.lwcPane.paneIndex();
+  };
+
+  public getPriceScale(side: PriceScaleSide): PriceScale {
+    return side === Direction.Left ? this.leftPriceScale : this.rightPriceScale;
+  }
+
   public setIndicator(indicatorId: string, indicator: Indicator): void {
     const map = this.indicatorsMap.value;
 
     map.set(indicatorId, indicator);
-
     this.indicatorsMap.next(map);
+    this.priceScaleControls.refresh();
   }
 
   public removeIndicator(indicatorId: string): void {
     const map = this.indicatorsMap.value;
 
     map.delete(indicatorId);
-
     this.indicatorsMap.next(map);
+    this.priceScaleControls.refresh();
 
     if (map.size === 0 && !this.isMain) {
-      this.destroy();
+      this.onDelete();
     }
   }
 
@@ -205,21 +228,123 @@ export class Pane implements ISerializable<PaneSnapshot> {
     return this.drawingsManager;
   }
 
-  private initializeLegend({ ohlcConfig }: { ohlcConfig: OHLCConfig }) {
+  public schedulePaneContainerSync(): void {
+    if (this.paneContainerSyncFrameId !== null) {
+      return;
+    }
+
+    this.paneContainerSyncFrameId = requestAnimationFrame(() => {
+      this.paneContainerSyncFrameId = null;
+      this.syncPaneContainers();
+    });
+  }
+
+  public refreshPriceScaleControls(): void {
+    this.priceScaleControls.refresh();
+  }
+
+  public resetPriceScalesAutoScale(): void {
+    this.leftPriceScale.enableAutoScale();
+    this.rightPriceScale.enableAutoScale();
+    this.handlePriceScaleStateChange();
+  }
+
+  public getSnapshot(): PaneSnapshot {
+    const indicators: (DOMObjectSnapshot & IndicatorSnapshot)[] = [];
+
+    this.indicatorsMap.value.forEach((indicator) => {
+      indicators.push(indicator.getSnapshot());
+    });
+
+    return {
+      isMain: this.isMain,
+      id: this.id,
+      indicators,
+      drawings: this.getDrawingsSnapshot(),
+      priceScales: [this.leftPriceScale.getSnapshot(), this.rightPriceScale.getSnapshot()],
+    };
+  }
+
+  public destroy(): void {
+    if (this.paneContainerSyncFrameId !== null) {
+      cancelAnimationFrame(this.paneContainerSyncFrameId);
+      this.paneContainerSyncFrameId = null;
+    }
+
+    this.subscriptions.unsubscribe();
+    this.tooltip?.destroy();
+    this.legend?.destroy();
+    this.legendRenderer.destroy();
+    this.tooltipRenderer?.destroy();
+    this.priceScaleControls.destroy();
+    this.legendContainer.remove();
+    this.paneOverlayContainer.remove();
+    this.indicatorsMap.complete();
+    this.mainSerieSub?.unsubscribe();
+
+    if (this.isMain) {
+      this.mainSeries.value?.destroy();
+      this.mainSeries.complete();
+    }
+  }
+
+  private createPriceScale(
+    side: PriceScaleSide,
+    initialPriceScales: PriceScaleSnapshot[],
+    initialVisible: boolean,
+  ): PriceScale {
+    const initialMode =
+      initialPriceScales.find((priceScaleSnapshot) => priceScaleSnapshot.side === side)?.mode ?? PriceScaleMode.Normal;
+
+    return new PriceScale({
+      paneId: this.id,
+      side,
+      pane: this.lwcPane,
+      initialMode,
+      initialVisible,
+      hasVisibleSeriesData: () => this.hasVisibleSeriesData(side),
+    });
+  }
+
+  private hasVisibleSeriesData(side: PriceScaleSide): boolean {
+    const mainSeries = this.mainSeries.value;
+
+    if (this.isMain && side === Direction.Right && mainSeries?.isVisible() && mainSeries.data().length > 0) {
+      return true;
+    }
+
+    const indicators = Array.from(this.indicatorsMap.value.values());
+
+    for (let indicatorIndex = 0; indicatorIndex < indicators.length; indicatorIndex += 1) {
+      const series = Array.from(indicators[indicatorIndex].getSeriesMap().values());
+
+      for (let seriesIndex = 0; seriesIndex < series.length; seriesIndex += 1) {
+        const currentSeries = series[seriesIndex];
+        const options = currentSeries.options();
+        const seriesPriceScaleSide = options.priceScaleId ?? Direction.Right;
+
+        if (currentSeries.isVisible() && currentSeries.data().length > 0 && seriesPriceScaleSide === side) {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+
+  private handlePriceScaleStateChange = (): void => {
+    this.priceScaleControls.refresh();
+    this.onPriceScaleStateChange();
+  };
+
+  private initializeLegend({ ohlcConfig }: { ohlcConfig: OHLCConfig }): void {
     const { legendContainer, paneOverlayContainer } = ContainerManager.createPaneContainers();
+
     this.legendContainer = legendContainer;
     this.paneOverlayContainer = paneOverlayContainer;
     this.legendRenderer = new ReactRenderer(legendContainer);
 
-    requestAnimationFrame(() => {
-      setTimeout(() => {
-        const lwcPaneElement = this.lwcPane.getHTMLElement();
-        if (!lwcPaneElement) return;
-        lwcPaneElement.style.position = 'relative';
-        lwcPaneElement.appendChild(legendContainer);
-        lwcPaneElement.appendChild(paneOverlayContainer);
-      }, 0);
-    });
+    this.schedulePaneContainerSync();
 
     // todo: переписать код ниже под логику пейнов
     // /*
@@ -265,12 +390,19 @@ export class Pane implements ISerializable<PaneSnapshot> {
       subscribeChartEvent: this.subscribeChartEvent,
       mainSeries: this.isMain ? this.mainSeries : null,
       paneId: this.id,
+      paneIndex: this.paneIndex,
       openIndicatorSettings: (indicatorId, indicator) => {
         let settings = indicator.getSettings();
 
         this.modalRenderer.renderComponent(
           <EntitySettingsModal
-            tabs={[{ key: 'arguments', label: t('Arguments'), fields: indicator.getSettingsConfig() }]}
+            tabs={[
+              {
+                key: 'arguments',
+                label: t('Arguments'),
+                fields: indicator.getSettingsConfig(),
+              },
+            ]}
             values={settings}
             onChange={(nextSettings) => {
               settings = nextSettings;
@@ -295,7 +427,7 @@ export class Pane implements ISerializable<PaneSnapshot> {
     );
   }
 
-  private initializeMainSerie({ lwcChart, dataSource }: { lwcChart: IChartApi; dataSource: DataSource }) {
+  private initializeMainSerie({ lwcChart, dataSource }: { lwcChart: IChartApi; dataSource: DataSource }): void {
     this.mainSerieSub = this.eventManager.subscribeSeriesSelected((nextSeries) => {
       this.mainSeries.value?.destroy();
 
@@ -307,48 +439,30 @@ export class Pane implements ISerializable<PaneSnapshot> {
       });
 
       this.mainSeries.next(next);
+      this.priceScaleControls.refresh();
     });
   }
 
-  public getSnapshot(): PaneSnapshot {
-    const indicators: (DOMObjectSnapshot & IndicatorSnapshot)[] = [];
-
-    this.indicatorsMap.value.forEach((ind) => {
-      indicators.push(ind.getSnapshot());
-    });
-
-    const snap = {
-      isMain: this.isMain,
-      id: this.id,
-      indicators,
-      drawings: this.getDrawingsSnapshot(),
-    };
-
-    return snap;
-  }
+  private syncPaneContainers(): void {
+    const lwcPaneElement = this.lwcPane.getHTMLElement();
 
-  public destroy() {
-    this.subscriptions.unsubscribe();
-    this.tooltip?.destroy();
-    this.legend?.destroy();
-    this.legendRenderer.destroy();
-
-    this.tooltipRenderer?.destroy();
-    this.indicatorsMap.complete();
+    if (!lwcPaneElement) {
+      this.schedulePaneContainerSync();
+      return;
+    }
 
-    this.mainSerieSub?.unsubscribe();
+    const cells = lwcPaneElement.querySelectorAll<HTMLTableCellElement>(':scope > td');
+    const chartCell = cells.item(1);
 
-    if (this.isMain) {
-      this.mainSeries.value?.destroy();
-      this.mainSeries?.complete();
+    if (!chartCell) {
+      this.schedulePaneContainerSync();
+      return;
     }
 
-    try {
-      this.lwcChart.removePane(this.id);
-    } catch (e) {
-      console.log(e);
-    }
+    chartCell.style.position = 'relative';
+    chartCell.appendChild(this.legendContainer);
+    chartCell.appendChild(this.paneOverlayContainer);
 
-    this.onDelete();
+    this.priceScaleControls.mount(lwcPaneElement);
   }
 }
diff --git a/src/core/PaneManager.ts b/src/core/PaneManager.ts
index 158738c..b1bc243 100644
--- a/src/core/PaneManager.ts
+++ b/src/core/PaneManager.ts
@@ -1,12 +1,36 @@
 import { DataSource } from '@core/DataSource';
 import { DrawingsManager, DrawingsManagerSnapshot } from '@core/DrawingsManager';
 import { Pane, PaneParams } from '@core/Pane';
-import { ISerializable, PaneSnapshot } from '@src/types/snapshot';
-
-interface PaneManagerParams extends Omit<PaneParams, 'isMainPane' | 'id' | 'basedOn' | 'onDelete'> {
+import { PriceAxisLabels } from '@core/PriceAxisLabels';
+import { Direction } from '@src/types';
+import { ISerializable, PaneSnapshot, PriceScaleSide, PriceScaleSnapshot } from '@src/types/snapshot';
+
+import type { Indicator } from '@core/Indicator';
+import type { LogicalRange } from 'lightweight-charts';
+import type { Observable } from 'rxjs';
+
+interface PaneManagerParams
+  extends Omit<
+    PaneParams,
+    | 'id'
+    | 'isMainPane'
+    | 'basedOn'
+    | 'onDelete'
+    | 'initialPriceScales'
+    | 'onPriceScaleStateChange'
+    | 'leftPriceScaleVisible'
+    | 'rightPriceScaleVisible'
+  > {
   panesSnapshot: PaneSnapshot[];
 }
 
+interface PriceAxisLabelsSources {
+  compareEntities$: Observable<Indicator[]>;
+  indicatorEntities$: Observable<Indicator[]>;
+}
+
+type SharedPaneParams = Omit<PaneManagerParams, 'panesSnapshot'>;
+
 // todo: PaneManager, регулирует порядок пейнов. Знает про MainPane.
 // todo: Также перекинуть соответствующие/необходимые свойства из чарта, и из чарта удалить
 // todo: в CompareManage, при создании нового пейна для сравнения - инициализируем новый dataSource, принадлежащий только конкретному пейну. Убираем возможность добавлять индикаторы на такие пейны
@@ -14,39 +38,86 @@ interface PaneManagerParams extends Omit<PaneParams, 'isMainPane' | 'id' | 'base
 // todo: сделать два разных представления для compare, в зависимости от отображения на главном пейне или на второстепенном
 
 export class PaneManager implements ISerializable<PaneSnapshot[]> {
+  private readonly sharedPaneParams: SharedPaneParams;
+  private readonly panesMap = new Map<number, Pane>();
+
   private mainPane: Pane;
-  private paneChartInheritedParams: PaneManagerParams & { isMainPane: boolean };
-  private panesMap: Map<number, Pane> = new Map<number, Pane>();
-  private panesIdIterator = 0;
+  private nextPaneId: number;
+  private priceAxisLabels: PriceAxisLabels | null = null;
+  private leftPriceScaleVisible = false;
+  private rightPriceScaleVisible = true;
+
+  constructor({ panesSnapshot, ...sharedPaneParams }: PaneManagerParams) {
+    this.sharedPaneParams = sharedPaneParams;
+
+    const mainPaneSnapshot = panesSnapshot.find((paneSnapshot) => paneSnapshot.isMain);
+    const mainPaneId = mainPaneSnapshot?.id ?? 0;
+
+    this.mainPane = new Pane({
+      ...this.sharedPaneParams,
+      id: mainPaneId,
+      isMainPane: true,
+      onDelete: () => {},
+      initialPriceScales: mainPaneSnapshot?.priceScales,
+      onPriceScaleStateChange: this.handlePriceScaleStateChange,
+      leftPriceScaleVisible: this.leftPriceScaleVisible,
+      rightPriceScaleVisible: this.rightPriceScaleVisible,
+    });
+
+    this.panesMap.set(mainPaneId, this.mainPane);
+
+    if (mainPaneSnapshot) {
+      this.mainPane.setDrawingsSnapshot(mainPaneSnapshot.drawings);
+    }
+
+    const greatestPaneId = panesSnapshot.reduce(
+      (greatestId, paneSnapshot) => Math.max(greatestId, paneSnapshot.id),
+      mainPaneId,
+    );
+
+    this.nextPaneId = greatestPaneId + 1;
+
+    panesSnapshot.forEach((paneSnapshot) => {
+      if (paneSnapshot.isMain) {
+        return;
+      }
 
-  constructor(params: PaneManagerParams) {
-    this.paneChartInheritedParams = { ...params, isMainPane: false };
+      const pane = this.addPane(undefined, paneSnapshot.id, paneSnapshot.priceScales);
 
-    this.mainPane = new Pane({ ...params, isMainPane: true, id: 0, onDelete: () => {} });
+      pane.setDrawingsSnapshot(paneSnapshot.drawings);
+    });
 
-    this.panesMap.set(this.panesIdIterator++, this.mainPane);
-    this.setup(params.panesSnapshot);
+    this.syncPaneContainers();
   }
 
-  private setup(panesSnapshot: PaneSnapshot[]) {
-    panesSnapshot.forEach((paneSnap: PaneSnapshot) => {
-      const { isMain, id, indicators, drawings } = paneSnap;
+  public initializePriceAxisLabels({ compareEntities$, indicatorEntities$ }: PriceAxisLabelsSources): void {
+    this.priceAxisLabels?.destroy();
 
-      this.panesMap.get(id)?.destroy();
+    this.priceAxisLabels = new PriceAxisLabels({
+      mainSeries$: this.mainPane.getMainSerie().asObservable(),
+      mainSymbol$: this.sharedPaneParams.eventManager.symbol(),
+      compareEntities$,
+      indicatorEntities$,
+    });
+  }
 
-      if (isMain) {
-        this.mainPane = new Pane({ ...this.paneChartInheritedParams, isMainPane: true, id: 0, onDelete: () => {} });
+  public setVisibleLogicalRange(logicalRange: LogicalRange | null): void {
+    this.priceAxisLabels?.setVisibleLogicalRange(logicalRange);
+  }
 
-        this.panesMap.set(id, this.mainPane);
+  public invalidatePriceAxisLabels(): void {
+    this.priceAxisLabels?.invalidate();
+  }
 
-        this.mainPane.setDrawingsSnapshot(drawings);
-      } else {
-        const pane = this.addPane();
-        pane.setDrawingsSnapshot(drawings);
-      }
-      const lastPane = Array.from(this.panesMap.values()).at(-1);
+  public setPriceScaleSideVisible(side: PriceScaleSide, visible: boolean): void {
+    if (side === Direction.Left) {
+      this.leftPriceScaleVisible = visible;
+    } else {
+      this.rightPriceScaleVisible = visible;
+    }
 
-      lastPane?.setIsLast(true);
+    this.panesMap.forEach((pane) => {
+      pane.getPriceScale(side).setVisible(visible);
     });
   }
 
@@ -62,39 +133,49 @@ export class PaneManager implements ISerializable<PaneSnapshot[]> {
     this.mainPane.setDrawingsSnapshot(snapshot);
   }
 
-  public getPanes() {
+  public getPanes(): Map<number, Pane> {
     return this.panesMap;
   }
 
-  public getMainPane: () => Pane = () => {
+  public getMainPane = (): Pane => {
     return this.mainPane;
   };
 
-  public addPane(dataSource?: DataSource): Pane {
-    const id = this.panesIdIterator++;
+  public addPane(dataSource?: DataSource, paneId?: number, initialPriceScales?: PriceScaleSnapshot[]): Pane {
+    const id = paneId ?? this.nextPaneId++;
+
+    this.nextPaneId = Math.max(this.nextPaneId, id + 1);
 
-    const newPane = new Pane({
-      ...this.paneChartInheritedParams,
+    const pane = new Pane({
+      ...this.sharedPaneParams,
       id,
+      isMainPane: false,
       dataSource: dataSource ?? null,
       basedOn: dataSource ? undefined : this.mainPane,
-      onDelete: () => {
-        this.panesIdIterator--;
-        this.panesMap.delete(id);
-
-        const prevPane = Array.from(this.panesMap.values()).at(-1);
-        prevPane?.setIsLast(true);
-      },
+      onDelete: () => this.destroyPane(id),
+      initialPriceScales,
+      onPriceScaleStateChange: this.handlePriceScaleStateChange,
+      leftPriceScaleVisible: this.leftPriceScaleVisible,
+      rightPriceScaleVisible: this.rightPriceScaleVisible,
     });
 
-    const prevPane = Array.from(this.panesMap.values()).at(-1);
+    this.panesMap.set(id, pane);
+    this.syncPaneContainers();
+    this.priceAxisLabels?.invalidate();
 
-    prevPane?.setIsLast(false);
-    newPane.setIsLast(true);
+    return pane;
+  }
 
-    this.panesMap.set(id, newPane);
+  public refreshPriceScaleControls(): void {
+    this.panesMap.forEach((pane) => {
+      pane.refreshPriceScaleControls();
+    });
+  }
 
-    return newPane;
+  public resetPriceScalesAutoScale(): void {
+    this.panesMap.forEach((pane) => {
+      pane.resetPriceScalesAutoScale();
+    });
   }
 
   public getDrawingsManager(): DrawingsManager {
@@ -103,11 +184,53 @@ export class PaneManager implements ISerializable<PaneSnapshot[]> {
   }
 
   public getSnapshot(): PaneSnapshot[] {
-    const res: PaneSnapshot[] = [];
+    const snapshot: PaneSnapshot[] = [];
+
+    this.panesMap.forEach((pane) => {
+      snapshot.push(pane.getSnapshot());
+    });
+
+    return snapshot;
+  }
+
+  public destroy(): void {
+    this.priceAxisLabels?.destroy();
+    this.priceAxisLabels = null;
+
     this.panesMap.forEach((pane) => {
-      res.push(pane.getSnapshot());
+      pane.destroy();
     });
 
-    return res;
+    this.panesMap.clear();
   }
+
+  private destroyPane(id: number): void {
+    const pane = this.panesMap.get(id);
+
+    if (!pane) {
+      return;
+    }
+
+    const paneIndex = pane.paneIndex();
+
+    this.panesMap.delete(id);
+    pane.destroy();
+
+    if (paneIndex >= 0) {
+      this.sharedPaneParams.lwcChart.removePane(paneIndex);
+    }
+
+    this.syncPaneContainers();
+    this.priceAxisLabels?.invalidate();
+  }
+
+  private syncPaneContainers(): void {
+    this.panesMap.forEach((pane) => {
+      pane.schedulePaneContainerSync();
+    });
+  }
+
+  private handlePriceScaleStateChange = (): void => {
+    this.priceAxisLabels?.invalidate();
+  };
 }
diff --git a/src/core/PriceAxisLabels/controller.ts b/src/core/PriceAxisLabels/PriceAxisLabels.ts
similarity index 95%
rename from src/core/PriceAxisLabels/controller.ts
rename to src/core/PriceAxisLabels/PriceAxisLabels.ts
index 46012f7..6a7fdcb 100644
--- a/src/core/PriceAxisLabels/controller.ts
+++ b/src/core/PriceAxisLabels/PriceAxisLabels.ts
@@ -1,16 +1,16 @@
 import { MismatchDirection, PriceScaleMode } from 'lightweight-charts';
+
 import { Subscription } from 'rxjs';
 
 import { Indicator } from '@core/Indicator';
 import { IndicatorsIds, MAIN_PANE_INDEX } from '@src/constants';
-
 import { SeriesStrategies } from '@src/modules/series-strategies/SeriesFactory';
 import { getThemeStore } from '@src/theme';
 import { Direction } from '@src/types';
 import { formatCompactNumber, formatPercent, formatPrice } from '@src/utils';
 import { removeAlphaFromHex } from '@src/utils/removeAlphaFromHex';
 
-import { PriceAxisLabelsPrimitive } from './primitive';
+import { PriceAxisLabelsPrimitive } from './PriceAxisLabelsPrimitive';
 import { getAxisSideBySeries, getContrastTextColor } from './utils';
 
 import type { PriceAxisLabel } from './types';
@@ -22,7 +22,7 @@ type EntityCollection = 'compare' | 'indicator';
 type SourceRole = 'main' | 'compare' | 'indicator' | 'volume';
 type PriceAxisSide = Direction.Left | Direction.Right;
 
-interface PriceAxisLabelsControllerOptions {
+interface PriceAxisLabelsParams {
   mainSeries$: Observable<SeriesStrategies | null>;
   mainSymbol$: Observable<string>;
   compareEntities$: Observable<Indicator[]>;
@@ -133,11 +133,12 @@ function getAxisSide(source: PriceLabelSource): PriceAxisSide {
   return getAxisSideBySeries(source.series);
 }
 
-export class PriceAxisLabelsController {
+export class PriceAxisLabels {
   private subscriptions = new Subscription();
   private entitySubscriptions = new Map<string, EntitySubscription>();
   private sourceDefaults = new WeakMap<SeriesStrategies, SourceDefaults>();
   private layers = new Map<string, AxisLayer>();
+
   private mainSeries: SeriesStrategies | null = null;
   private mainSeriesDataHandler: (() => void) | null = null;
   private compareEntities: Indicator[] = [];
@@ -149,10 +150,13 @@ export class PriceAxisLabelsController {
   private isHistoryMode = false;
   private updateFrame: number | null = null;
 
-  constructor({ mainSeries$, mainSymbol$, compareEntities$, indicatorEntities$ }: PriceAxisLabelsControllerOptions) {
+  constructor({ mainSeries$, mainSymbol$, compareEntities$, indicatorEntities$ }: PriceAxisLabelsParams) {
     this.subscriptions.add(
       mainSymbol$.subscribe((symbol) => {
-        this.mainSymbol = symbol.split(':').at(-1) || symbol;
+        const symbolParts = symbol.split(':');
+
+        this.mainSymbol = symbolParts[symbolParts.length - 1] || symbol;
+
         this.applyDisplayMode();
         this.scheduleUpdate();
       }),
@@ -179,6 +183,7 @@ export class PriceAxisLabelsController {
 
   public setVisibleLogicalRange(logicalRange: LogicalRange | null): void {
     this.visibleLogicalRange = logicalRange;
+
     this.refreshHistoryMode();
     this.scheduleUpdate();
   }
@@ -208,7 +213,7 @@ export class PriceAxisLabelsController {
       try {
         host.detachPrimitive(primitive);
       } catch {
-        // Серия могла быть удалена раньше контроллера.
+        // Серия могла быть удалена раньше объекта PriceAxisLabels.
       }
     });
 
@@ -261,7 +266,7 @@ export class PriceAxisLabelsController {
     try {
       this.mainSeries.unsubscribeDataChanged(this.mainSeriesDataHandler);
     } catch {
-      // Серия могла быть удалена раньше контроллера.
+      // Серия могла быть удалена раньше объекта PriceAxisLabels.
     }
 
     this.mainSeriesDataHandler = null;
@@ -307,6 +312,7 @@ export class PriceAxisLabelsController {
 
       const subscription = entity.subscribeDataChange(() => {
         this.applyDisplayModeToSources(this.getEntitySources(collection, entity));
+
         this.scheduleUpdate();
       });
 
@@ -370,6 +376,7 @@ export class PriceAxisLabelsController {
     }
 
     const options = series.options();
+
     const defaults = {
       lastValueVisible: options.lastValueVisible,
       priceLineVisible: options.priceLineVisible,
@@ -391,7 +398,7 @@ export class PriceAxisLabelsController {
     try {
       series.applyOptions(defaults);
     } catch {
-      // Серия могла быть удалена раньше контроллера.
+      // Серия могла быть удалена раньше объекта PriceAxisLabels.
     }
   }
 
@@ -413,6 +420,7 @@ export class PriceAxisLabelsController {
             lastValueVisible: false,
             priceLineVisible: false,
           });
+
           return;
         }
 
@@ -421,6 +429,7 @@ export class PriceAxisLabelsController {
             lastValueVisible: this.isHistoryMode ? false : defaults.lastValueVisible,
             title: this.isHistoryMode ? '' : this.mainSymbol || defaults.title,
           });
+
           return;
         }
 
@@ -428,7 +437,7 @@ export class PriceAxisLabelsController {
           lastValueVisible: this.isHistoryMode ? false : defaults.lastValueVisible,
         });
       } catch {
-        // Серия могла быть удалена раньше контроллера.
+        // Серия могла быть удалена раньше объекта PriceAxisLabels.
       }
     });
   }
@@ -480,6 +489,7 @@ export class PriceAxisLabelsController {
       }
 
       const paneIndex = source.series.getPane().paneIndex();
+
       const side = getAxisSide(source);
       const key = `${paneIndex}:${side}`;
       const group = groups.get(key);
@@ -516,6 +526,7 @@ export class PriceAxisLabelsController {
     }
 
     const paneIndex = mainSource.series.getPane().paneIndex();
+
     const side = getAxisSide(mainSource);
     const group = groups.get(`${paneIndex}:${side}`);
 
@@ -555,7 +566,9 @@ export class PriceAxisLabelsController {
       return source.series.dataByIndex(Math.floor(this.visibleLogicalRange.to), MismatchDirection.NearestLeft);
     }
 
-    return source.series.data().at(-1) ?? null;
+    const data = source.series.data();
+
+    return data[data.length - 1] ?? null;
   }
 
   private updateAxisLayers(groups: Map<string, AxisLabelsGroup>, sources: PriceLabelSource[]): void {
@@ -569,6 +582,7 @@ export class PriceAxisLabelsController {
       }
 
       activeKeys.add(key);
+
       this.getOrCreateLayer(key, host).primitive.setLabels(group.labels, group.reservedCoordinate);
     });
 
@@ -580,7 +594,7 @@ export class PriceAxisLabelsController {
       try {
         layer.host.detachPrimitive(layer.primitive);
       } catch {
-        // Серия могла быть удалена раньше контроллера.
+        // Серия могла быть удалена раньше объекта PriceAxisLabels.
       }
 
       this.layers.delete(key);
@@ -618,7 +632,7 @@ export class PriceAxisLabelsController {
       try {
         currentLayer.host.detachPrimitive(currentLayer.primitive);
       } catch {
-        // Серия могла быть удалена раньше контроллера.
+        // Серия могла быть удалена раньше объекта PriceAxisLabels.
       }
     }
 
@@ -641,7 +655,8 @@ export class PriceAxisLabelsController {
       return;
     }
 
-    const lastData = mainSource.series.data().at(-1);
+    const data = mainSource.series.data();
+    const lastData = data[data.length - 1];
     const price = getDataPrice(lastData);
 
     if (price === null) {
@@ -667,7 +682,8 @@ export class PriceAxisLabelsController {
       return null;
     }
 
-    const price = getDataPrice(this.mainSeries.data().at(-1));
+    const data = this.mainSeries.data();
+    const price = getDataPrice(data[data.length - 1]);
 
     return price === null ? null : this.mainSeries.priceToCoordinate(price);
   }
@@ -705,7 +721,7 @@ export class PriceAxisLabelsController {
       try {
         this.currentPriceLineHost.removePriceLine(this.currentPriceLine);
       } catch {
-        // Серия могла быть удалена раньше контроллера.
+        // Серия могла быть удалена раньше объекта PriceAxisLabels.
       }
     }
 
@@ -715,6 +731,7 @@ export class PriceAxisLabelsController {
 
   private formatValue(series: SeriesStrategies, price: number): string {
     const { mode } = series.priceScale().options();
+
     const formattedPrice = formatPrice(price) ?? series.priceFormatter().format(price);
 
     if (mode !== PriceScaleMode.Percentage && mode !== PriceScaleMode.IndexedTo100) {
diff --git a/src/core/PriceAxisLabels/layout.ts b/src/core/PriceAxisLabels/PriceAxisLabelsLayout.ts
similarity index 100%
rename from src/core/PriceAxisLabels/layout.ts
rename to src/core/PriceAxisLabels/PriceAxisLabelsLayout.ts
diff --git a/src/core/PriceAxisLabels/primitive.ts b/src/core/PriceAxisLabels/PriceAxisLabelsPrimitive.ts
similarity index 74%
rename from src/core/PriceAxisLabels/primitive.ts
rename to src/core/PriceAxisLabels/PriceAxisLabelsPrimitive.ts
index dad4dbe..ec43639 100644
--- a/src/core/PriceAxisLabels/primitive.ts
+++ b/src/core/PriceAxisLabels/PriceAxisLabelsPrimitive.ts
@@ -1,8 +1,7 @@
 import { getThemeStore } from '@src/theme';
-
 import { Direction } from '@src/types';
 
-import { layoutPriceAxisLabels } from './layout';
+import { layoutPriceAxisLabels } from './PriceAxisLabelsLayout';
 import { getAxisSideBySeries, getContrastTextColor } from './utils';
 
 import type { LaidOutPriceAxisLabel, MeasuredPriceAxisLabel, PriceAxisLabel, PriceAxisSide } from './types';
@@ -26,6 +25,7 @@ interface PriceAxisLabelsRenderState {
   chart: SeriesAttachedParameter<Time>['chart'] | null;
   series: SeriesAttachedParameter<Time>['series'] | null;
 }
+
 interface LabelGeometry {
   label: LaidOutPriceAxisLabel;
   left: number;
@@ -180,10 +180,7 @@ function drawLabelTexts(
 }
 
 class PriceAxisLabelsRenderer implements IPrimitivePaneRenderer {
-  constructor(
-    private state: PriceAxisLabelsRenderState,
-    private setMinimumWidth: (width: number) => void,
-  ) {}
+  constructor(private readonly state: PriceAxisLabelsRenderState) {}
 
   public draw(target: CanvasRenderingTarget2D): void {
     const { labels, chart, series, reservedCoordinate } = this.state;
@@ -204,12 +201,6 @@ class PriceAxisLabelsRenderer implements IPrimitivePaneRenderer {
       context.font = getFont(layout);
 
       const measuredLabels = measureLabels(context, labels, layout);
-      const minimumWidth = measuredLabels.reduce(
-        (maximumWidth, label) => Math.max(maximumWidth, label.width + LABEL_EDGE_GAP * 2),
-        0,
-      );
-
-      this.setMinimumWidth(minimumWidth);
 
       geometries = createLabelGeometries(
         measuredLabels,
@@ -233,13 +224,10 @@ class PriceAxisLabelsRenderer implements IPrimitivePaneRenderer {
 }
 
 class PriceAxisLabelsPaneView implements IPrimitivePaneView {
-  private rendererInstance: PriceAxisLabelsRenderer;
+  private readonly rendererInstance: PriceAxisLabelsRenderer;
 
-  constructor(
-    private state: PriceAxisLabelsRenderState,
-    setMinimumWidth: (width: number) => void,
-  ) {
-    this.rendererInstance = new PriceAxisLabelsRenderer(state, setMinimumWidth);
+  constructor(private readonly state: PriceAxisLabelsRenderState) {
+    this.rendererInstance = new PriceAxisLabelsRenderer(state);
   }
 
   public renderer(): IPrimitivePaneRenderer | null {
@@ -252,49 +240,30 @@ class PriceAxisLabelsPaneView implements IPrimitivePaneView {
 }
 
 export class PriceAxisLabelsPrimitive implements ISeriesPrimitive<Time> {
-  private state: PriceAxisLabelsRenderState = {
+  private readonly state: PriceAxisLabelsRenderState = {
     labels: [],
     reservedCoordinate: null,
     chart: null,
     series: null,
   };
 
-  private priceAxisPaneView: PriceAxisLabelsPaneView;
-  private priceAxisPaneViewList: IPrimitivePaneView[];
+  private readonly priceAxisPaneView = new PriceAxisLabelsPaneView(this.state);
+  private readonly priceAxisPaneViewList: IPrimitivePaneView[] = [this.priceAxisPaneView];
   private requestUpdate: (() => void) | null = null;
-  private initialMinimumWidth = 0;
-  private minimumWidth = 0;
-  private pendingMinimumWidth: number | null = null;
-  private minimumWidthFrame: number | null = null;
-
-  constructor() {
-    this.priceAxisPaneView = new PriceAxisLabelsPaneView(this.state, (width) => {
-      this.scheduleMinimumWidth(width);
-    });
-
-    this.priceAxisPaneViewList = [this.priceAxisPaneView];
-  }
 
   public attached({ chart, series, requestUpdate }: SeriesAttachedParameter<Time>): void {
     this.state.chart = chart;
     this.state.series = series;
     this.requestUpdate = requestUpdate;
-    this.initialMinimumWidth = series.priceScale().options().minimumWidth ?? 0;
-    this.minimumWidth = this.initialMinimumWidth;
     this.requestUpdate();
   }
 
   public detached(): void {
-    this.cancelMinimumWidthUpdate();
-    this.applyMinimumWidth(this.initialMinimumWidth);
-
     this.state.chart = null;
     this.state.series = null;
-    this.requestUpdate = null;
     this.state.labels = [];
     this.state.reservedCoordinate = null;
-    this.initialMinimumWidth = 0;
-    this.minimumWidth = 0;
+    this.requestUpdate = null;
   }
 
   public priceAxisPaneViews(): IPrimitivePaneView[] {
@@ -310,11 +279,6 @@ export class PriceAxisLabelsPrimitive implements ISeriesPrimitive<Time> {
 
     this.state.labels = labels;
     this.state.reservedCoordinate = reservedCoordinate;
-
-    if (labels.length === 0) {
-      this.scheduleMinimumWidth(0);
-    }
-
     this.requestUpdate?.();
   }
 
@@ -325,54 +289,6 @@ export class PriceAxisLabelsPrimitive implements ISeriesPrimitive<Time> {
 
     this.state.labels = [];
     this.state.reservedCoordinate = null;
-    this.scheduleMinimumWidth(0);
     this.requestUpdate?.();
   }
-
-  private scheduleMinimumWidth(width: number): void {
-    const minimumWidth = Math.max(this.initialMinimumWidth, Math.ceil(width));
-
-    if (minimumWidth === this.minimumWidth || minimumWidth === this.pendingMinimumWidth) {
-      return;
-    }
-
-    this.pendingMinimumWidth = minimumWidth;
-
-    if (this.minimumWidthFrame !== null) {
-      return;
-    }
-
-    this.minimumWidthFrame = requestAnimationFrame(() => {
-      const { pendingMinimumWidth } = this;
-
-      this.minimumWidthFrame = null;
-      this.pendingMinimumWidth = null;
-
-      if (pendingMinimumWidth !== null) {
-        this.applyMinimumWidth(pendingMinimumWidth);
-      }
-    });
-  }
-
-  private applyMinimumWidth(minimumWidth: number): void {
-    if (!this.state.series || minimumWidth === this.minimumWidth) {
-      return;
-    }
-
-    this.state.series.priceScale().applyOptions({
-      minimumWidth,
-    });
-
-    this.minimumWidth = minimumWidth;
-  }
-
-  private cancelMinimumWidthUpdate(): void {
-    if (this.minimumWidthFrame === null) {
-      return;
-    }
-
-    cancelAnimationFrame(this.minimumWidthFrame);
-    this.minimumWidthFrame = null;
-    this.pendingMinimumWidth = null;
-  }
 }
diff --git a/src/core/PriceAxisLabels/index.ts b/src/core/PriceAxisLabels/index.ts
index 4beada3..a14ccbd 100644
--- a/src/core/PriceAxisLabels/index.ts
+++ b/src/core/PriceAxisLabels/index.ts
@@ -1,6 +1,8 @@
-export { PriceAxisLabelsController } from './controller';
-export { layoutPriceAxisLabels } from './layout';
-export { PriceAxisLabelsPrimitive } from './primitive';
+export { PriceAxisLabels } from './PriceAxisLabels';
+
+export { layoutPriceAxisLabels } from './PriceAxisLabelsLayout';
+export { PriceAxisLabelsPrimitive } from './PriceAxisLabelsPrimitive';
+
 export type {
   LaidOutPriceAxisLabel,
   MeasuredPriceAxisLabel,
diff --git a/src/core/PriceScale/PriceScale.ts b/src/core/PriceScale/PriceScale.ts
new file mode 100644
index 0000000..77ece5d
--- /dev/null
+++ b/src/core/PriceScale/PriceScale.ts
@@ -0,0 +1,125 @@
+import { IPaneApi, IPriceScaleApi, PriceScaleMode, Time } from 'lightweight-charts';
+
+import type { PriceScaleSide, PriceScaleSnapshot } from '@src/types/snapshot';
+
+interface PriceScaleParams {
+  paneId: number;
+  side: PriceScaleSide;
+  pane: IPaneApi<Time>;
+  initialMode?: PriceScaleMode;
+  initialVisible: boolean;
+  hasVisibleSeriesData: () => boolean;
+}
+
+export class PriceScale {
+  public readonly paneId: number;
+  public readonly side: PriceScaleSide;
+
+  private readonly pane: IPaneApi<Time>;
+  private readonly hasVisibleSeriesDataCallback: () => boolean;
+
+  private mode: PriceScaleMode;
+  private autoScaleEnabled: boolean;
+  private visible: boolean;
+
+  constructor({
+    paneId,
+    side,
+    pane,
+    initialMode = PriceScaleMode.Normal,
+    initialVisible,
+    hasVisibleSeriesData,
+  }: PriceScaleParams) {
+    this.paneId = paneId;
+    this.side = side;
+    this.pane = pane;
+    this.mode = initialMode;
+    this.visible = initialVisible;
+    this.hasVisibleSeriesDataCallback = hasVisibleSeriesData;
+
+    const priceScale = this.getPriceScaleApi();
+
+    this.autoScaleEnabled = priceScale.options().autoScale ?? true;
+
+    priceScale.applyOptions({
+      mode: this.mode,
+      autoScale: this.autoScaleEnabled,
+      visible: this.visible,
+      borderVisible: false,
+    });
+  }
+
+  public getMode(): PriceScaleMode {
+    return this.mode;
+  }
+
+  public setMode(mode: PriceScaleMode): void {
+    if (this.mode === mode) {
+      return;
+    }
+
+    this.mode = mode;
+
+    this.getPriceScaleApi().applyOptions({
+      mode,
+      autoScale: this.autoScaleEnabled,
+    });
+  }
+
+  public toggleLogarithmic(): void {
+    const nextMode = this.mode === PriceScaleMode.Logarithmic ? PriceScaleMode.Normal : PriceScaleMode.Logarithmic;
+
+    this.setMode(nextMode);
+  }
+
+  public isAutoScaleEnabled(): boolean {
+    return this.autoScaleEnabled;
+  }
+
+  public toggleAutoScale(): void {
+    this.autoScaleEnabled = !this.autoScaleEnabled;
+
+    this.getPriceScaleApi().setAutoScale(this.autoScaleEnabled);
+  }
+
+  public enableAutoScale(): void {
+    if (this.autoScaleEnabled) {
+      return;
+    }
+
+    this.autoScaleEnabled = true;
+    this.getPriceScaleApi().setAutoScale(true);
+  }
+
+  public isVisible(): boolean {
+    return this.visible;
+  }
+
+  public setVisible(visible: boolean): void {
+    if (this.visible === visible) {
+      return;
+    }
+
+    this.visible = visible;
+
+    this.getPriceScaleApi().applyOptions({
+      visible,
+      borderVisible: false,
+    });
+  }
+
+  public hasVisibleSeriesData(): boolean {
+    return this.hasVisibleSeriesDataCallback();
+  }
+
+  public getSnapshot(): PriceScaleSnapshot {
+    return {
+      side: this.side,
+      mode: this.mode,
+    };
+  }
+
+  private getPriceScaleApi(): IPriceScaleApi {
+    return this.pane.priceScale(this.side);
+  }
+}
diff --git a/src/core/PriceScale/PriceScaleControls.tsx b/src/core/PriceScale/PriceScaleControls.tsx
new file mode 100644
index 0000000..3162aa2
--- /dev/null
+++ b/src/core/PriceScale/PriceScaleControls.tsx
@@ -0,0 +1,181 @@
+import { PriceScaleMode } from 'lightweight-charts';
+
+import { PriceScaleControls as PriceScaleControlsView } from '@components/PriceScaleControls';
+import { ReactRenderer } from '@core/ReactRenderer';
+import { Direction } from '@src/types';
+
+import { PriceScale } from './PriceScale';
+
+interface PriceScaleControlsParams {
+  leftPriceScale: PriceScale;
+  rightPriceScale: PriceScale;
+  onPriceScaleChange: () => void;
+}
+
+export class PriceScaleControls {
+  private readonly leftPriceScale: PriceScale;
+  private readonly rightPriceScale: PriceScale;
+  private readonly onPriceScaleChange: () => void;
+  private readonly container: HTMLElement;
+  private readonly renderer: ReactRenderer;
+
+  private paneElement: HTMLElement | null = null;
+  private hoveredPriceScale: PriceScale | null = null;
+
+  constructor({ leftPriceScale, rightPriceScale, onPriceScaleChange }: PriceScaleControlsParams) {
+    this.leftPriceScale = leftPriceScale;
+    this.rightPriceScale = rightPriceScale;
+    this.onPriceScaleChange = onPriceScaleChange;
+
+    this.container = document.createElement('div');
+    this.container.className = 'moex-price-scale-controls';
+    this.container.style.position = 'absolute';
+    this.container.style.inset = '0';
+    this.container.style.display = 'flex';
+    this.container.style.alignItems = 'flex-end';
+    this.container.style.justifyContent = 'center';
+    this.container.style.zIndex = '10';
+    this.container.style.pointerEvents = 'none';
+
+    this.renderer = new ReactRenderer(this.container);
+  }
+
+  public mount(paneElement: HTMLElement): void {
+    if (this.paneElement === paneElement) {
+      this.refresh();
+      return;
+    }
+
+    this.unmount();
+
+    this.paneElement = paneElement;
+    this.paneElement.addEventListener('pointermove', this.handlePointerMove);
+    this.paneElement.addEventListener('pointerleave', this.handlePointerLeave);
+
+    this.refresh();
+  }
+
+  public refresh(): void {
+    if (!this.hoveredPriceScale) {
+      this.hide();
+      return;
+    }
+
+    this.render(this.hoveredPriceScale);
+  }
+
+  public destroy(): void {
+    this.unmount();
+    this.renderer.destroy();
+  }
+
+  private handlePointerMove = (event: PointerEvent): void => {
+    const nextPriceScale = this.getHoveredPriceScale(event.clientX);
+
+    if (this.hoveredPriceScale === nextPriceScale) {
+      return;
+    }
+
+    this.hoveredPriceScale = nextPriceScale;
+    this.refresh();
+  };
+
+  private handlePointerLeave = (): void => {
+    this.hoveredPriceScale = null;
+    this.refresh();
+  };
+
+  private render(priceScale: PriceScale): void {
+    const priceScaleElement = this.getPriceScaleElement(priceScale);
+
+    if (!priceScaleElement || !this.canShowControls(priceScale, priceScaleElement)) {
+      this.hide();
+      return;
+    }
+
+    priceScaleElement.style.position = 'relative';
+
+    if (this.container.parentElement !== priceScaleElement) {
+      priceScaleElement.appendChild(this.container);
+    }
+
+    this.renderer.renderComponent(
+      <PriceScaleControlsView
+        isAutoScale={priceScale.isAutoScaleEnabled()}
+        isLogarithmic={priceScale.getMode() === PriceScaleMode.Logarithmic}
+        onToggleAutoScale={() => {
+          priceScale.toggleAutoScale();
+          this.refresh();
+          this.onPriceScaleChange();
+        }}
+        onToggleLogarithmic={() => {
+          priceScale.toggleLogarithmic();
+          this.refresh();
+          this.onPriceScaleChange();
+        }}
+      />,
+    );
+
+    this.container.classList.add('moex-price-scale-controls_visible');
+  }
+
+  private hide(): void {
+    this.container.classList.remove('moex-price-scale-controls_visible');
+  }
+
+  private getHoveredPriceScale(clientX: number): PriceScale | null {
+    if (this.isPointerInsidePriceScale(this.leftPriceScale, clientX)) {
+      return this.leftPriceScale;
+    }
+
+    if (this.isPointerInsidePriceScale(this.rightPriceScale, clientX)) {
+      return this.rightPriceScale;
+    }
+
+    return null;
+  }
+
+  private isPointerInsidePriceScale(priceScale: PriceScale, clientX: number): boolean {
+    const priceScaleElement = this.getPriceScaleElement(priceScale);
+
+    if (!priceScaleElement || !this.canShowControls(priceScale, priceScaleElement)) {
+      return false;
+    }
+
+    const rect = priceScaleElement.getBoundingClientRect();
+
+    return clientX >= rect.left && clientX <= rect.right;
+  }
+
+  private canShowControls(priceScale: PriceScale, priceScaleElement: HTMLElement): boolean {
+    return (
+      priceScale.isVisible() && priceScale.hasVisibleSeriesData() && priceScaleElement.getBoundingClientRect().width > 0
+    );
+  }
+
+  private getPriceScaleElement(priceScale: PriceScale): HTMLTableCellElement | null {
+    if (!this.paneElement) {
+      return null;
+    }
+
+    const cells = this.paneElement.querySelectorAll<HTMLTableCellElement>(':scope > td');
+
+    if (cells.length < 3) {
+      return null;
+    }
+
+    return priceScale.side === Direction.Left ? cells.item(0) : cells.item(cells.length - 1);
+  }
+
+  private unmount(): void {
+    if (this.paneElement) {
+      this.paneElement.removeEventListener('pointermove', this.handlePointerMove);
+      this.paneElement.removeEventListener('pointerleave', this.handlePointerLeave);
+    }
+
+    this.paneElement = null;
+    this.hoveredPriceScale = null;
+    this.container.remove();
+    this.hide();
+  }
+}
diff --git a/src/core/PriceScale/index.ts b/src/core/PriceScale/index.ts
new file mode 100644
index 0000000..872bcc3
--- /dev/null
+++ b/src/core/PriceScale/index.ts
@@ -0,0 +1,2 @@
+export { PriceScale } from './PriceScale';
+export { PriceScaleControls } from './PriceScaleControls';
diff --git a/src/core/Series/BaseSeries.ts b/src/core/Series/BaseSeries.ts
index 15c2f00..74e19e0 100644
--- a/src/core/Series/BaseSeries.ts
+++ b/src/core/Series/BaseSeries.ts
@@ -152,13 +152,13 @@ export abstract class BaseSeries<TSeries extends SeriesType> implements IBaseSer
   > => {
     if (!param) {
       const seriesData = this.data();
-      const currentBar = seriesData.at(-1);
+      const currentBar = seriesData[seriesData.length - 1];
 
       if (!currentBar) {
         return {};
       }
 
-      return this.formatLegendValues(currentBar, seriesData.at(-2) ?? null);
+      return this.formatLegendValues(currentBar, seriesData[seriesData.length - 2] ?? null);
     }
 
     const currentBar = param.seriesData.get(this.lwcSeries) ?? null;
@@ -286,7 +286,8 @@ export abstract class BaseSeries<TSeries extends SeriesType> implements IBaseSer
   }
 
   public update(bar: SeriesDataItemTypeMap<Time>[TSeries], historicalUpdate?: boolean): void {
-    const lastBar = this.lwcSeries.data().at(-1);
+    const data = this.lwcSeries.data();
+    const lastBar = data[data.length - 1];
 
     if (!lastBar) {
       this.lwcSeries.update(bar, false);
diff --git a/src/styles/global.scss b/src/styles/global.scss
index 9366d3e..9c43790 100644
--- a/src/styles/global.scss
+++ b/src/styles/global.scss
@@ -18,3 +18,17 @@
 .moex-chart-drawing-tooltip .tooltip-arrow-apca {
   display: none;
 }
+
+.moex-price-scale-controls {
+  opacity: 0;
+  pointer-events: none;
+  transition: opacity 120ms ease;
+}
+
+.moex-price-scale-controls_visible {
+  opacity: 1;
+}
+
+.moex-price-scale-controls_visible .moex-price-scale-controls-content {
+  pointer-events: auto;
+}
diff --git a/src/translations/russianDict.ts b/src/translations/russianDict.ts
index 97e706c..97958be 100644
--- a/src/translations/russianDict.ts
+++ b/src/translations/russianDict.ts
@@ -4,18 +4,32 @@ export const russian = {
   'Add volume indicator': 'Добавить индикатор объёма',
   tick: 'тик',
   ticks: 'тики',
+  tick_few: 'тика',
+  tick_many: 'тиков',
   second: 'секунда',
   seconds: 'секунды',
+  second_few: 'секунды',
+  second_many: 'секунд',
   minute: 'минута',
   minutes: 'минуты',
+  minute_few: 'минуты',
+  minute_many: 'минут',
   hour: 'час',
   hours: 'часы',
+  hour_few: 'часа',
+  hour_many: 'часов',
   day: 'день',
   days: 'дни',
+  day_few: 'дня',
+  day_many: 'дней',
   week: 'неделя',
   weeks: 'недели',
-  months: 'месяца',
+  week_few: 'недели',
+  week_many: 'недель',
   month: 'месяц',
+  months: 'месяца',
+  month_few: 'месяца',
+  month_many: 'месяцев',
   Bars: 'Бары',
   Line: 'Линия',
   Candles: 'Свечи',
@@ -139,6 +153,8 @@ export const russian = {
   Stop: 'Стоп',
   Vol: 'Объем',
   bars: 'бары',
+  AutoScale: 'Автомасштаб',
+  Logarithmic: 'Логарифмическая',
 
   '1t': '1т',
   '10t': '10т',
diff --git a/src/types/snapshot.ts b/src/types/snapshot.ts
index e476039..bcd0a80 100644
--- a/src/types/snapshot.ts
+++ b/src/types/snapshot.ts
@@ -1,50 +1,60 @@
-import { ChartSettings as ChartSettingsSnapshot, ChartSettingsSource } from '@core/ChartSettings';
 import { DataSource } from '@core/DataSource';
 import { DrawingsManagerSnapshot } from '@core/DrawingsManager';
 import { ChartSeriesType, IndicatorsIds, Timeframes } from '@lib';
+import { Direction } from '@src/types/chart';
 import { IndicatorConfig } from '@src/types/indicator';
 
+import type { PriceScaleMode } from 'lightweight-charts';
+
+export type PriceScaleSide = Direction.Left | Direction.Right;
+
 export interface ISerializable<T extends object> {
   getSnapshot: () => T;
 }
 
-export type InitialSnapshot = {
+export interface InitialSnapshot {
   timeframe: Timeframes; // todo: move to snap
   chartSeriesType: ChartSeriesType; // todo: move to snap
   symbol: string; // todo: move to snap
-};
+}
 
-export type MoexChartSnapshot = {
+export interface MoexChartSnapshot {
   // settings: ChartSettingsSnapshot;
   charts: ChartSnapshot[];
-};
+}
 
-export type ChartSnapshot = {
+export interface ChartSnapshot {
   timeframe: Timeframes;
   chartSeriesType: ChartSeriesType;
   symbol: string;
   panes: PaneSnapshot[];
-};
+}
 
-export type PaneSnapshot = {
+export interface PriceScaleSnapshot {
+  side: PriceScaleSide;
+  mode: PriceScaleMode;
+}
+
+export interface PaneSnapshot {
   isMain: boolean;
   id: number;
   indicators: IndicatorSnapshot[];
   drawings: DrawingsManagerSnapshot;
-};
+  priceScales?: PriceScaleSnapshot[];
+}
 
-export type IndicatorSnapshot = Partial<DOMObjectSnapshot> & {
+export interface IndicatorSnapshot extends Partial<DOMObjectSnapshot> {
   dataSource?: DataSource;
   indicatorType: IndicatorsIds | undefined; // if indicatorType is undefined, then its compareIndicator
   config?: IndicatorConfig;
-};
+}
 
 // todo: move DrawingsManagerSnapshot here
 
-export type DOMObjectSnapshot = {
+export interface DOMObjectSnapshot {
   id: string;
   name: string;
   zIndex: number;
   hidden: boolean;
   paneId: number;
-};
+}
diff --git a/src/utils/formatTimeframeLabel.ts b/src/utils/formatTimeframeLabel.ts
new file mode 100644
index 0000000..9a5b99f
--- /dev/null
+++ b/src/utils/formatTimeframeLabel.ts
@@ -0,0 +1,83 @@
+import { getLocale, t } from '@src/translations';
+
+export type TimeframeUnit = 'tick' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month';
+
+interface UnitForms {
+  one: string;
+  few?: string;
+  many?: string;
+  other: string;
+}
+
+const pluralRulesCache = new Map<string, Intl.PluralRules>();
+
+const unitForms: Record<TimeframeUnit, UnitForms> = {
+  tick: {
+    one: 'tick',
+    few: 'tick_few',
+    many: 'tick_many',
+    other: 'ticks',
+  },
+  second: {
+    one: 'second',
+    few: 'second_few',
+    many: 'second_many',
+    other: 'seconds',
+  },
+  minute: {
+    one: 'minute',
+    few: 'minute_few',
+    many: 'minute_many',
+    other: 'minutes',
+  },
+  hour: {
+    one: 'hour',
+    few: 'hour_few',
+    many: 'hour_many',
+    other: 'hours',
+  },
+  day: {
+    one: 'day',
+    few: 'day_few',
+    many: 'day_many',
+    other: 'days',
+  },
+  week: {
+    one: 'week',
+    few: 'week_few',
+    many: 'week_many',
+    other: 'weeks',
+  },
+  month: {
+    one: 'month',
+    few: 'month_few',
+    many: 'month_many',
+    other: 'months',
+  },
+};
+
+const getPluralRules = (): Intl.PluralRules => {
+  const locale = getLocale();
+  const cachedRules = pluralRulesCache.get(locale);
+
+  if (cachedRules) {
+    return cachedRules;
+  }
+
+  const rules = new Intl.PluralRules(locale);
+
+  pluralRulesCache.set(locale, rules);
+
+  return rules;
+};
+
+const getUnitTranslationKey = (value: number, unit: TimeframeUnit): string => {
+  const form = getPluralRules().select(value);
+  const forms = unitForms[unit];
+
+  return forms[form as keyof UnitForms] ?? forms.other;
+};
+
+export const formatTimeframeLabel = (value: number, unit: TimeframeUnit): string => {
+  return `${value} ${t(getUnitTranslationKey(value, unit))}`;
+};
diff --git a/src/utils/index.ts b/src/utils/index.ts
index ab3614b..e589bb1 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -2,6 +2,7 @@ export * from './calcTooltipPosition';
 export * from './delay';
 export * from './enshureDefind';
 export * from './formatter';
+export * from './formatTimeframeLabel';
 export * from './formatValues';
 export * from './getPortalHost';
 export * from './getTimeframeByInterval';