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


diff --git a/CHANGELOG.md b/CHANGELOG.md
index 36bac5f..84d127a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # next
 
+- Добавлена поддержка хоткеев на элементы рисования (alt+t, alt+v, alt+h, alt+f, del, esc)
+
+# 0.1.10
+
 - Добавление логарифмической шкалы
 
 # 0.1.9
@@ -9,6 +13,8 @@
 # 0.1.8
 
 - Фикс перевода в uppercase всех символов
+- Фикс отображения лейбла на прайс-шкале
+- Удаление пейнов теперь доступно в произвольном порядке
 
 # 0.1.7
 
diff --git a/src/components/Toolbar/index.tsx b/src/components/Toolbar/index.tsx
index 6f0e5bb..fded0c0 100644
--- a/src/components/Toolbar/index.tsx
+++ b/src/components/Toolbar/index.tsx
@@ -1,10 +1,13 @@
 import classNames from 'classnames';
+
 import { Button, Divider, Tooltip } from 'exchange-elements/v2';
 
-import { Dispatch, SetStateAction, useRef, useState } from 'react';
+import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react';
 
 import { Observable } from 'rxjs';
 
+import { Hotkeys, Keys } from '@core/Hotkeys';
+
 import { MenuList } from '@src/components/Menu';
 import SplitDropdown from '@src/components/SplitDropdown';
 
@@ -39,11 +42,12 @@ import styles from './index.module.scss';
 
 interface ToolbarProps {
   toggleDOM: () => void;
-  addDrawing: (name: DrawingsNames) => void;
+  addDrawing: (name: DrawingsNames) => Promise<void>;
   setEndlessDrawingsMode: (value: boolean) => void;
   isEndlessDrawingsMode$: Observable<boolean>;
   activateCrosshair: () => void;
   activeTool$: Observable<ActiveDrawingTool>;
+  hotkeys: Hotkeys;
 }
 
 const implemented = {
@@ -66,6 +70,7 @@ export default function Toolbar({
   isEndlessDrawingsMode$,
   activateCrosshair,
   activeTool$,
+  hotkeys,
 }: ToolbarProps) {
   const [selectedLineType, setSelectedLineType] = useState<DrawingsNames>(DrawingsNames.trendLine);
   const [selectedMeasurementTool, setSelectedMeasurementTool] = useState(DrawingsNames.fixedRangeProfile);
@@ -78,9 +83,9 @@ export default function Toolbar({
 
   const toolbarRef = useRef<HTMLDivElement | null>(null);
 
-  const createDrawingHandler = (setter: Dispatch<SetStateAction<DrawingsNames>>) => (value: DrawingsNames) => {
+  const createDrawingHandler = (setter: Dispatch<SetStateAction<DrawingsNames>>) => async (value: DrawingsNames) => {
     setter(value);
-    addDrawing(value);
+    await addDrawing(value);
   };
 
   const findOptionByValue = <T extends { value: string }>(options: T[], selectedValue: T['value']): T | undefined =>
@@ -98,6 +103,39 @@ export default function Toolbar({
 
   const getTooltipClassName = () => classNames(styles.tooltipHint, TOOLTIP_CLASSNAME);
 
+  useEffect(() => {
+    hotkeys.register({
+      keys: [Keys.alt, Keys.t],
+      callback: async () => {
+        await createDrawingHandler(setSelectedLineType)(DrawingsNames.trendLine);
+      },
+    });
+    hotkeys.register({
+      keys: [Keys.alt, Keys.h],
+      callback: async () => {
+        await createDrawingHandler(setSelectedLineType)(DrawingsNames.horizontalLine);
+      },
+    });
+    hotkeys.register({
+      keys: [Keys.alt, Keys.v],
+      callback: async () => {
+        await createDrawingHandler(setSelectedLineType)(DrawingsNames.verticalLine);
+      },
+    });
+    hotkeys.register({
+      keys: [Keys.alt, Keys.f],
+      callback: async () => {
+        await createDrawingHandler(setSelectedGannAndFibonacci)(DrawingsNames.fibonacciRetracement);
+      },
+    });
+    hotkeys.register({
+      keys: [Keys.shift],
+      longPress: true,
+      callback: async () => {
+        await addDrawing(DrawingsNames.ruler);
+      },
+    });
+  }, []);
   return (
     <div
       ref={toolbarRef}
diff --git a/src/constants/drawing.ts b/src/constants/drawing.ts
index 34607e5..741bce8 100644
--- a/src/constants/drawing.ts
+++ b/src/constants/drawing.ts
@@ -196,9 +196,10 @@ export const drawingsMap: Record<DrawingsNames, DrawingConfig> = {
   },
   [DrawingsNames.ruler]: {
     singleInstance: true,
-    construct: ({ chart, series, eventManager, removeSelf }) => {
+    construct: ({ chart, series, eventManager, container, removeSelf }) => {
       return new Ruler(chart, series, {
         formatObservable: eventManager.getChartOptionsModel(),
+        container,
         resetTriggers: [eventManager.getTimeframeObs(), eventManager.getInterval()],
         removeSelf,
       });
diff --git a/src/core/Chart.ts b/src/core/Chart.ts
index 5b8fb3f..5382a98 100644
--- a/src/core/Chart.ts
+++ b/src/core/Chart.ts
@@ -22,6 +22,7 @@ import { DataSource } from '@core/DataSource';
 import { DOMModel } from '@core/DOMModel';
 import { DrawingsManager } from '@core/DrawingsManager';
 import { EventManager } from '@core/EventManager';
+import { Hotkeys } from '@core/Hotkeys';
 import { IndicatorManager } from '@core/IndicatorManager';
 import { ModalRenderer } from '@core/ModalRenderer';
 import { PaneManager } from '@core/PaneManager';
@@ -69,6 +70,7 @@ interface ChartParams {
     ohlcConfig: OHLCConfig;
     tooltipConfig: TooltipConfig;
     panes: PaneSnapshot[];
+    hotkeys: Hotkeys;
   };
   lwcChartConfig: ChartConfig;
 }
@@ -171,6 +173,7 @@ export class Chart implements ISerializable<ChartSnapshot> {
       chartContainer: this.container,
       tooltipConfig,
       modalRenderer,
+      hotkeys: params.hotkeys,
     });
 
     this.mainSeries = this.paneManager.getMainPane().getMainSerie();
diff --git a/src/core/Drawings.ts b/src/core/Drawings.ts
index 3bf77b9..bbceca5 100644
--- a/src/core/Drawings.ts
+++ b/src/core/Drawings.ts
@@ -2,6 +2,8 @@ import { IChartApi, ISeriesApi, SeriesType } from 'lightweight-charts';
 
 import { DOMObject, DOMObjectParams } from '@core/DOMObject';
 import { ISeriesDrawing } from '@core/Drawings/common';
+import { DrawingSnapshotItem } from '@core/DrawingsManager';
+import { Hotkeys, Keys } from '@core/Hotkeys';
 import { DrawingsNames } from '@src/constants';
 import { SeriesStrategies } from '@src/modules/series-strategies/SeriesFactory';
 import { SettingsTab, SettingsValues } from '@src/types/settings';
@@ -14,12 +16,20 @@ interface DrawingParams extends DOMObjectParams {
   mainSeries: SeriesStrategies;
   onDelete: (id: string) => void;
   construct: (chart: IChartApi, series: ISeriesApi<SeriesType>) => ISeriesDrawing;
+  hotkeys: Hotkeys;
+  setCopyPasteBuffer: (copiedObject: DrawingSnapshotItem) => void;
+  resetActiveTool: () => void;
 }
 
 export class Drawing extends DOMObject implements IDrawing {
   private lwcDrawing: ISeriesDrawing;
   private mainSeries: SeriesStrategies;
   private drawingName: DrawingsNames;
+  private hotkeys: Hotkeys;
+
+  private escapeUnregisterHash: string | null = null;
+  private deleteUnregisterHash: string | null = null;
+  private copyUnregisterHash: string | null = null;
 
   constructor({
     lwcChart,
@@ -33,13 +43,66 @@ export class Drawing extends DOMObject implements IDrawing {
     moveDown,
     construct,
     paneId,
+    hotkeys,
+    setCopyPasteBuffer,
+    resetActiveTool,
   }: DrawingParams) {
     super({ id, name, zIndex, onDelete, moveUp, moveDown, paneId });
-
+    this.hotkeys = hotkeys;
     this.lwcDrawing = construct(lwcChart, mainSeries);
     this.onDelete = onDelete;
+    this.escapeUnregisterHash = hotkeys.register({
+      keys: [Keys.escape],
+      callback: () => {
+        this.delete();
+        resetActiveTool();
+      },
+    });
+
+    this.deleteUnregisterHash = this.hotkeys.register({
+      keys: [Keys.delete],
+      callback: () => {
+        this.delete();
+      },
+    });
+
+    this.lwcDrawing.subscribeIsSelected((isSelected) => {
+      if (isSelected) {
+        this.deleteUnregisterHash = hotkeys.register({
+          keys: [Keys.delete],
+          callback: () => {
+            this.delete();
+          },
+        });
+        this.copyUnregisterHash = hotkeys.register({
+          keys: [Keys.control, Keys.c],
+          callback: () => {
+            if (!this.isCreationPending()) {
+              const copiedObject = {
+                id: this.id,
+                drawingName: this.getDrawingName(),
+                state: this.getState(),
+              };
+              setCopyPasteBuffer(copiedObject);
+            }
+          },
+        });
+      } else {
+        hotkeys.unregister({
+          keys: [Keys.delete],
+          hash: this.deleteUnregisterHash,
+        });
+      }
+    });
     this.mainSeries = mainSeries;
     this.drawingName = drawingName;
+
+    this.afterCreation(() => {
+      hotkeys.unregister({
+        keys: [Keys.escape],
+        hash: this.escapeUnregisterHash,
+      });
+    });
   }
 
   public delete() {
@@ -74,6 +137,10 @@ export class Drawing extends DOMObject implements IDrawing {
     return this.lwcDrawing.isCreationPending();
   }
 
+  public async waitForCreation(): Promise<void> {
+    return this.lwcDrawing.waitTillReady();
+  }
+
   public shouldShowInObjectTree(): boolean {
     return this.lwcDrawing.shouldShowInObjectTree();
   }
@@ -103,7 +170,24 @@ export class Drawing extends DOMObject implements IDrawing {
   }
 
   public destroy() {
+    this.hotkeys.unregister({
+      keys: [Keys.delete],
+      hash: this.deleteUnregisterHash,
+    });
+    this.hotkeys.unregister({
+      keys: [Keys.escape],
+      hash: this.escapeUnregisterHash,
+    });
+    this.hotkeys.unregister({
+      keys: [Keys.control, Keys.c],
+      hash: this.copyUnregisterHash,
+    });
     this.mainSeries.detachPrimitive(this.lwcDrawing);
     this.lwcDrawing.destroy();
   }
+
+  private async afterCreation(cb: () => void) {
+    await this.lwcDrawing.waitTillReady();
+    cb();
+  }
 }
diff --git a/src/core/Drawings/axisLine/axisLine.ts b/src/core/Drawings/axisLine/axisLine.ts
index 0a793df..74faefd 100644
--- a/src/core/Drawings/axisLine/axisLine.ts
+++ b/src/core/Drawings/axisLine/axisLine.ts
@@ -1,26 +1,16 @@
-import {
-  AutoscaleInfo,
-  CrosshairMode,
-  IChartApi,
-  IPrimitivePaneView,
-  Logical,
-  PrimitiveHoveredItem,
-  SeriesAttachedParameter,
-  SeriesOptionsMap,
-  Time,
-  UTCTimestamp,
-} from 'lightweight-charts';
-import { Observable, Subscription } from 'rxjs';
+import { IChartApi, IPrimitivePaneView, PrimitiveHoveredItem, Time, UTCTimestamp } from 'lightweight-charts';
+import { Observable } from 'rxjs';
 
 import { CustomPriceAxisView, CustomTimeAxisView } from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
 import {
-  getPointerPoint as getPointerPointFromEvent,
   getPriceFromYCoordinate,
   getTimeFromXCoordinate,
   getXCoordinateFromTime,
   getYCoordinateFromPrice,
   isNearPoint,
 } from '@core/Drawings/helpers';
+import { AxisSegment } from '@core/Drawings/types';
 import { updateViews } from '@core/Drawings/utils';
 
 import { getThemeStore } from '@src/theme';
@@ -40,7 +30,7 @@ import {
 
 import type { ISeriesDrawing } from '@core/Drawings/common';
 import type { AxisLabel, Point, SeriesApi } from '@core/Drawings/types';
-import type { ChartOptionsModel, SettingsTab, SettingsValues } from '@src/types';
+import type { ChartOptionsModel, SettingsTab } from '@src/types';
 
 export type AxisLineDirection = 'vertical' | 'horizontal';
 
@@ -73,22 +63,15 @@ export interface AxisLineRenderData extends AxisLineStyle, AxisLineTextStyle {
 const HANDLE_HIT_TOLERANCE = 8;
 const LINE_HIT_TOLERANCE = 6;
 
-export class AxisLine implements ISeriesDrawing {
-  private chart: IChartApi;
-  private series: SeriesApi;
-  private container: HTMLElement;
+export class AxisLine extends SeriesDrawingBase<AxisLineSettings> implements ISeriesDrawing {
   private removeSelf?: () => void;
   private openSettings?: () => void;
 
-  private settings: AxisLineSettings = createDefaultSettings();
+  protected settings: AxisLineSettings = createDefaultSettings();
 
-  private requestUpdate: (() => void) | null = null;
-  private subscriptions = new Subscription();
   private isBound = false;
 
-  private hidden = false;
-  private isActive = false;
-  private mode: AxisLineMode = 'idle';
+  protected mode: AxisLineMode = 'idle';
 
   private direction: AxisLineDirection;
   private time: Time | null = null;
@@ -110,10 +93,8 @@ export class AxisLine implements ISeriesDrawing {
     series: SeriesApi,
     { direction, container, formatObservable, removeSelf, openSettings }: AxisLineParams,
   ) {
-    this.chart = chart;
-    this.series = series;
+    super({ chart, series, container });
     this.direction = direction;
-    this.container = container;
     this.removeSelf = removeSelf;
     this.openSettings = openSettings;
 
@@ -141,53 +122,14 @@ export class AxisLine implements ISeriesDrawing {
     this.series.attachPrimitive(this);
   }
 
-  public show(): void {
-    this.hidden = false;
-    this.render();
-  }
-
-  public hide(): void {
-    this.hidden = true;
-    this.showCrosshair();
-    this.render();
-  }
-
-  public destroy(): void {
-    this.showCrosshair();
-    this.unbindEvents();
-    this.subscriptions.unsubscribe();
-    this.series.detachPrimitive(this);
-    this.requestUpdate = null;
-  }
-
-  public rebind(series: SeriesApi): void {
-    if (this.series === series) {
-      return;
-    }
-
-    this.showCrosshair();
-    this.unbindEvents();
-    this.series.detachPrimitive(this);
-
-    this.series = series;
-    this.requestUpdate = null;
-
-    this.series.attachPrimitive(this);
-    this.render();
-  }
-
   public isCreationPending(): boolean {
     return this.mode === 'idle';
   }
 
-  public shouldShowInObjectTree(): boolean {
-    return this.mode !== 'idle';
-  }
-
   public getState(): AxisLineState {
     return {
       hidden: this.hidden,
-      isActive: this.isActive,
+      isActive: this.isActive.value,
       mode: this.mode,
       time: this.time,
       price: this.price,
@@ -195,6 +137,14 @@ export class AxisLine implements ISeriesDrawing {
     };
   }
 
+  public timeAxisPaneViews(): readonly IPrimitivePaneView[] {
+    return [];
+  }
+
+  public priceAxisPaneViews(): readonly IPrimitivePaneView[] {
+    return [];
+  }
+
   public setState(state: unknown): void {
     const nextState = state as Partial<AxisLineState>;
 
@@ -203,7 +153,7 @@ export class AxisLine implements ISeriesDrawing {
     }
 
     if ('isActive' in nextState && typeof nextState.isActive === 'boolean') {
-      this.isActive = nextState.isActive;
+      this.isActive.next(nextState.isActive);
     }
 
     if ('mode' in nextState && nextState.mode) {
@@ -228,34 +178,10 @@ export class AxisLine implements ISeriesDrawing {
     this.render();
   }
 
-  public getSettings(): SettingsValues {
-    return { ...this.settings };
-  }
-
   public getSettingsTabs(): SettingsTab[] {
     return getAxisLineSettingsTabs(this.settings);
   }
 
-  public updateSettings(settings: SettingsValues): void {
-    this.settings = {
-      ...this.settings,
-      ...settings,
-    };
-
-    this.render();
-  }
-
-  public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
-    this.requestUpdate = param.requestUpdate;
-    this.bindEvents();
-  }
-
-  public detached(): void {
-    this.showCrosshair();
-    this.unbindEvents();
-    this.requestUpdate = null;
-  }
-
   public updateAllViews(): void {
     updateViews([this.paneView, this.timeAxisView, this.priceAxisView]);
   }
@@ -272,10 +198,6 @@ export class AxisLine implements ISeriesDrawing {
     return this.direction === 'horizontal' ? [this.priceAxisView] : [];
   }
 
-  public autoscaleInfo(_start: Logical, _end: Logical): AutoscaleInfo | null {
-    return null;
-  }
-
   public getRenderData(): AxisLineRenderData | null {
     if (this.hidden) {
       return null;
@@ -293,7 +215,7 @@ export class AxisLine implements ISeriesDrawing {
       direction: this.direction,
       coordinate,
       handle: this.direction === 'vertical' ? { x: coordinate, y: height / 2 } : { x: width / 2, y: coordinate },
-      showHandle: this.isActive,
+      showHandle: this.isActive.value,
       ...this.settings,
     };
   }
@@ -310,7 +232,7 @@ export class AxisLine implements ISeriesDrawing {
       return null;
     }
 
-    if (this.isActive && isNearPoint(point, data.handle.x, data.handle.y, HANDLE_HIT_TOLERANCE)) {
+    if (this.isActive.value && isNearPoint(point, data.handle.x, data.handle.y, HANDLE_HIT_TOLERANCE)) {
       return {
         cursorStyle: this.getCursorStyle(),
         externalId: 'axis-line',
@@ -330,7 +252,7 @@ export class AxisLine implements ISeriesDrawing {
   }
 
   public getTimeAxisLabel(kind: string): AxisLabel | null {
-    if (kind !== 'main' || this.direction !== 'vertical' || !this.isActive || this.time === null) {
+    if (kind !== 'main' || this.direction !== 'vertical' || !this.isActive.value || this.time === null) {
       return null;
     }
 
@@ -356,7 +278,7 @@ export class AxisLine implements ISeriesDrawing {
   }
 
   public getPriceAxisLabel(kind: string): AxisLabel | null {
-    if (kind !== 'main' || this.direction !== 'horizontal' || !this.isActive || this.price === null) {
+    if (kind !== 'main' || this.direction !== 'horizontal' || !this.isActive.value || this.price === null) {
       return null;
     }
 
@@ -376,7 +298,19 @@ export class AxisLine implements ISeriesDrawing {
     };
   }
 
-  private bindEvents(): void {
+  public getTimeAxisSegments(): AxisSegment[] {
+    return [];
+  }
+
+  public getPriceAxisSegments(): AxisSegment[] {
+    return [];
+  }
+
+  protected getGeometry(): void {
+    console.log('stub');
+  }
+
+  protected bindEvents(): void {
     if (this.isBound) {
       return;
     }
@@ -390,7 +324,7 @@ export class AxisLine implements ISeriesDrawing {
     window.addEventListener('pointercancel', this.handlePointerUp);
   }
 
-  private unbindEvents(): void {
+  protected unbindEvents(): void {
     if (!this.isBound) {
       return;
     }
@@ -421,7 +355,7 @@ export class AxisLine implements ISeriesDrawing {
       y: event.clientY - rect.top,
     };
 
-    const isNearHandle = this.isActive && isNearPoint(point, data.handle.x, data.handle.y, HANDLE_HIT_TOLERANCE);
+    const isNearHandle = this.isActive.value && isNearPoint(point, data.handle.x, data.handle.y, HANDLE_HIT_TOLERANCE);
     const isNearLine = this.isPointNearLine(point, data.coordinate);
 
     if (!isNearHandle && !isNearLine) {
@@ -446,8 +380,10 @@ export class AxisLine implements ISeriesDrawing {
       event.stopPropagation();
 
       this.updateLine(point);
-      this.isActive = true;
+      this.isActive.next(true);
       this.mode = 'ready';
+      this.resolveReady?.();
+
       this.render();
       return;
     }
@@ -462,10 +398,10 @@ export class AxisLine implements ISeriesDrawing {
       return;
     }
 
-    const isNearHandle = this.isActive && isNearPoint(point, data.handle.x, data.handle.y, HANDLE_HIT_TOLERANCE);
+    const isNearHandle = this.isActive.value && isNearPoint(point, data.handle.x, data.handle.y, HANDLE_HIT_TOLERANCE);
     const isNearLine = this.isPointNearLine(point, data.coordinate);
 
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       if (!isNearLine) {
         return;
       }
@@ -473,13 +409,13 @@ export class AxisLine implements ISeriesDrawing {
       event.preventDefault();
       event.stopPropagation();
 
-      this.isActive = true;
+      this.isActive.next(true);
       this.render();
       return;
     }
 
     if (!isNearHandle && !isNearLine) {
-      this.isActive = false;
+      this.isActive.next(false);
       this.render();
       return;
     }
@@ -511,6 +447,8 @@ export class AxisLine implements ISeriesDrawing {
     }
 
     this.mode = 'ready';
+    this.resolveReady?.();
+
     this.dragPointerId = null;
     this.showCrosshair();
     this.render();
@@ -552,29 +490,4 @@ export class AxisLine implements ISeriesDrawing {
   private getCursorStyle(): PrimitiveHoveredItem['cursorStyle'] {
     return this.direction === 'vertical' ? 'ew-resize' : 'ns-resize';
   }
-
-  private hideCrosshair(): void {
-    this.chart.applyOptions({
-      crosshair: {
-        mode: CrosshairMode.Hidden,
-      },
-    });
-  }
-
-  private showCrosshair(): void {
-    this.chart.applyOptions({
-      crosshair: {
-        mode: CrosshairMode.Normal,
-      },
-    });
-  }
-
-  private getEventPoint(event: PointerEvent): Point {
-    return getPointerPointFromEvent(this.container, event);
-  }
-
-  private render(): void {
-    this.updateAllViews();
-    this.requestUpdate?.();
-  }
 }
diff --git a/src/core/Drawings/common.ts b/src/core/Drawings/common.ts
index 2b3bed2..37094b0 100644
--- a/src/core/Drawings/common.ts
+++ b/src/core/Drawings/common.ts
@@ -1,31 +1,34 @@
-import { Coordinate, ISeriesApi, ISeriesPrimitive, SeriesType, Time } from 'lightweight-charts';
+import {
+  AutoscaleInfo,
+  CrosshairMode,
+  IChartApi,
+  IPrimitivePaneView,
+  ISeriesApi,
+  ISeriesPrimitive,
+  ISeriesPrimitiveAxisView,
+  Logical,
+  PrimitiveHoveredItem,
+  SeriesAttachedParameter,
+  SeriesOptionsMap,
+  SeriesType,
+  Time,
+} from 'lightweight-charts';
 
-import { SettingsTab, SettingsValues } from '@src/types';
+import { BehaviorSubject, distinctUntilChanged, Observable, Subscription } from 'rxjs';
 
-export interface BitmapPositionLength {
-  /** coordinate for use with a bitmap rendering scope */
-  position: number;
-  /** length for use with a bitmap rendering scope */
-  length: number;
-}
+import { getPointerPoint as getPointerPointFromEvent } from '@core/Drawings/helpers';
+import { AxisLabel, AxisSegment, Point, SeriesApi } from '@core/Drawings/types';
 
-export interface ViewPoint {
-  x: Coordinate | null;
-  y: Coordinate | null;
-}
+import { ChartOptionsModel, SettingsTab, SettingsValues } from '@src/types';
 
-export interface Point {
-  time: Time;
-  price: number;
-}
-
-// todo: make base abstract class and extend all drawings by this
 export interface ISeriesDrawing extends ISeriesPrimitive<Time> {
   show(): void;
   hide(): void;
   rebind(series: ISeriesApi<SeriesType>): void;
   destroy(): void;
   isCreationPending(): boolean;
+  waitTillReady(): Promise<void>;
+
   shouldShowInObjectTree(): boolean;
 
   getState(): unknown;
@@ -34,14 +37,165 @@ export interface ISeriesDrawing extends ISeriesPrimitive<Time> {
   getSettings(): SettingsValues;
   updateSettings(settings: SettingsValues): void;
   getSettingsTabs(): SettingsTab[];
+
+  subscribeIsSelected(cb: (isSelected: boolean) => void): void;
+}
+
+interface SeriesDrawingBaseParams {
+  container: HTMLElement;
+  chart: IChartApi;
+  series: SeriesApi;
 }
 
-export function positionsBox(position1Media: number, position2Media: number, pixelRatio: number): BitmapPositionLength {
-  const scaledPosition1 = Math.round(pixelRatio * position1Media);
-  const scaledPosition2 = Math.round(pixelRatio * position2Media);
+export abstract class SeriesDrawingBase<TSettings extends SettingsValues = SettingsValues> implements ISeriesDrawing {
+  protected hidden = false;
+  protected chart: IChartApi;
+  protected series: SeriesApi;
+  protected subscriptions = new Subscription();
+  protected abstract mode: unknown;
+  protected abstract settings: TSettings;
+  protected readonly container: HTMLElement;
+  protected isActive: BehaviorSubject<boolean> = new BehaviorSubject(false);
+
+  protected readyPromise: null | Promise<void> = null;
+  protected resolveReady: null | (() => void) = null;
+
+  protected requestUpdate: (() => void) | null = null;
+
+  constructor({ chart, series, container }: SeriesDrawingBaseParams) {
+    this.chart = chart;
+    this.series = series;
+    this.container = container;
+  }
+
+  public subscribeIsSelected(cb: (isSelected: boolean) => void) {
+    return this.isActive.pipe(distinctUntilChanged()).subscribe(cb);
+  }
+
+  public show(): void {
+    this.hidden = false;
+    this.render();
+  }
+
+  public hide(): void {
+    this.hidden = true;
+    this.showCrosshair();
+    this.render();
+  }
+
+  public rebind(series: SeriesApi): void {
+    if (this.series === series) {
+      return;
+    }
+
+    this.showCrosshair();
+    this.unbindEvents();
+    this.series.detachPrimitive(this as unknown as ISeriesPrimitive<Time>);
+
+    this.series = series;
+    this.requestUpdate = null;
+
+    this.series.attachPrimitive(this as unknown as ISeriesPrimitive<Time>);
+    this.render();
+  }
+
+  public destroy(): void {
+    this.showCrosshair();
+    this.unbindEvents();
+    this.subscriptions.unsubscribe();
+    this.series.detachPrimitive(this as unknown as ISeriesPrimitive<Time>);
+    this.requestUpdate = null;
+  }
+
+  public waitTillReady(): Promise<void> {
+    if (this.mode === 'ready') {
+      return Promise.resolve();
+    }
+    if (!this.readyPromise) {
+      this.readyPromise = new Promise((resolve) => {
+        this.resolveReady = resolve as () => void;
+      });
+    }
+
+    return this.readyPromise;
+  }
+
+  public shouldShowInObjectTree(): boolean {
+    return this.mode !== 'idle';
+  }
+
+  public getSettings(): SettingsValues {
+    return { ...this.settings };
+  }
+
+  public updateSettings(settings: SettingsValues): void {
+    this.settings = {
+      ...this.settings,
+      ...settings,
+    };
+
+    this.render();
+  }
+
+  public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
+    this.requestUpdate = param.requestUpdate;
+    this.bindEvents();
+  }
+
+  public detached(): void {
+    this.showCrosshair();
+    this.unbindEvents();
+    this.requestUpdate = null;
+  }
+
+  public autoscaleInfo(_start: Logical, _end: Logical): AutoscaleInfo | null {
+    return null;
+  }
+
+  protected render(): void {
+    this.updateAllViews();
+    this.requestUpdate?.();
+  }
+
+  protected hideCrosshair(): void {
+    this.chart.applyOptions({
+      crosshair: {
+        mode: CrosshairMode.Hidden,
+      },
+    });
+  }
+
+  protected showCrosshair(): void {
+    this.chart.applyOptions({
+      crosshair: {
+        mode: CrosshairMode.Normal,
+      },
+    });
+  }
+
+  protected getEventPoint(event: PointerEvent): Point {
+    return getPointerPointFromEvent(this.container, event);
+  }
+
+  public abstract getRenderData(): any; // todo: make proper type  // todo: добавить в интерфейс\точно ли паблик
+  public abstract hitTest(x: number, y: number): PrimitiveHoveredItem | null; // todo: добавить в интерфейс\точно ли паблик
+  public abstract getTimeAxisSegments(): AxisSegment[]; // todo: добавить в интерфейс\точно ли паблик
+  public abstract getPriceAxisSegments(): AxisSegment[]; // todo: добавить в интерфейс\точно ли паблик
+  public abstract getTimeAxisLabel(kind: string): AxisLabel | null; // todo: добавить в интерфейс\точно ли паблик
+  public abstract getPriceAxisLabel(kind: string): AxisLabel | null; // todo: добавить в интерфейс\точно ли паблик
+  protected abstract bindEvents(): void;
+  public abstract updateAllViews(): void;
+  protected abstract unbindEvents(): void;
+  protected abstract getGeometry(): any; // todo: make proper type // todo: добавить в интерфейс\точно ли паблик
+
+  public abstract getSettingsTabs(): SettingsTab[];
+  public abstract getState(): unknown;
+  public abstract isCreationPending(): boolean;
 
-  return {
-    position: Math.min(scaledPosition1, scaledPosition2),
-    length: Math.abs(scaledPosition1 - scaledPosition2) + 1,
-  };
+  public abstract paneViews(): readonly IPrimitivePaneView[];
+  public abstract priceAxisPaneViews(): readonly IPrimitivePaneView[];
+  public abstract priceAxisViews(): readonly ISeriesPrimitiveAxisView[];
+  public abstract setState(state: unknown): void;
+  public abstract timeAxisPaneViews(): readonly IPrimitivePaneView[];
+  public abstract timeAxisViews(): readonly ISeriesPrimitiveAxisView[];
 }
diff --git a/src/core/Drawings/diapson/diapson.ts b/src/core/Drawings/diapson/diapson.ts
index bfbef38..b6f80a2 100644
--- a/src/core/Drawings/diapson/diapson.ts
+++ b/src/core/Drawings/diapson/diapson.ts
@@ -1,4 +1,4 @@
-import { Observable, Subscription } from 'rxjs';
+import { Observable } from 'rxjs';
 
 import {
   CustomPriceAxisPaneView,
@@ -6,12 +6,12 @@ import {
   CustomTimeAxisPaneView,
   CustomTimeAxisView,
 } from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
 import {
   clamp,
   clampPointToContainer as clampPointToContainerInElement,
   getAnchorFromPoint,
   getContainerSize as getElementContainerSize,
-  getPointerPoint as getPointerPointFromEvent,
   getXCoordinateFromTime,
   getYCoordinateFromPrice,
   isNearPoint,
@@ -37,18 +37,8 @@ import {
 
 import type { ISeriesDrawing } from '@core/Drawings/common';
 import type { Anchor, AxisLabel, AxisSegment, Point, SeriesApi } from '@core/Drawings/types';
-import type { ChartOptionsModel, SettingsTab, SettingsValues } from '@src/types';
-import type {
-  AutoscaleInfo,
-  IChartApi,
-  IPrimitivePaneView,
-  Logical,
-  PrimitiveHoveredItem,
-  SeriesAttachedParameter,
-  SeriesOptionsMap,
-  Time,
-  UTCTimestamp,
-} from 'lightweight-charts';
+import type { ChartOptionsModel, SettingsTab } from '@src/types';
+import type { IChartApi, IPrimitivePaneView, PrimitiveHoveredItem, Time, UTCTimestamp } from 'lightweight-charts';
 
 export type DiapsonRangeMode = 'date' | 'price';
 
@@ -114,22 +104,15 @@ const BODY_HIT_TOLERANCE = 6;
 const MIN_RECTANGLE_WIDTH = 6;
 const MIN_RECTANGLE_HEIGHT = 6;
 
-export class Diapson implements ISeriesDrawing {
-  private chart: IChartApi;
-  private series: SeriesApi;
-  private container: HTMLElement;
+export class Diapson extends SeriesDrawingBase<DiapsonSettings> implements ISeriesDrawing {
   private removeSelf?: () => void;
   private openSettings?: () => void;
 
-  private settings: DiapsonSettings = createDefaultSettings();
+  protected settings: DiapsonSettings = createDefaultSettings();
 
-  private requestUpdate: (() => void) | null = null;
   private isBound = false;
-  private subscriptions = new Subscription();
 
-  private hidden = false;
-  private isActive = false;
-  private mode: DiapsonMode = 'idle';
+  protected mode: DiapsonMode = 'idle';
   private rangeMode: DiapsonRangeMode;
 
   private startTime: Time | null = null;
@@ -160,11 +143,10 @@ export class Diapson implements ISeriesDrawing {
   private readonly bottomPriceAxisView: CustomPriceAxisView;
 
   constructor(chart: IChartApi, series: SeriesApi, params: DiapsonParams) {
-    const { container, rangeMode, formatObservable, removeSelf, openSettings, stepSize = 1, stepLabel = '' } = params;
+    super({ chart, series, container: params.container });
+
+    const { rangeMode, formatObservable, removeSelf, openSettings, stepSize = 1, stepLabel = '' } = params;
 
-    this.chart = chart;
-    this.series = series;
-    this.container = container;
     this.rangeMode = rangeMode;
     this.removeSelf = removeSelf;
     this.openSettings = openSettings;
@@ -213,46 +195,10 @@ export class Diapson implements ISeriesDrawing {
     this.series.attachPrimitive(this);
   }
 
-  public show(): void {
-    this.hidden = false;
-    this.render();
-  }
-
-  public hide(): void {
-    this.hidden = true;
-    this.render();
-  }
-
-  public destroy(): void {
-    this.unbindEvents();
-    this.subscriptions.unsubscribe();
-    this.series.detachPrimitive(this);
-    this.requestUpdate = null;
-  }
-
-  public rebind(series: SeriesApi): void {
-    if (this.series === series) {
-      return;
-    }
-
-    this.unbindEvents();
-    this.series.detachPrimitive(this);
-
-    this.series = series;
-    this.requestUpdate = null;
-
-    this.series.attachPrimitive(this);
-    this.render();
-  }
-
   public isCreationPending(): boolean {
     return this.mode === 'idle' || this.mode === 'drawing';
   }
 
-  public shouldShowInObjectTree(): boolean {
-    return this.mode !== 'idle';
-  }
-
   public setRangeMode(nextMode: DiapsonRangeMode): void {
     if (this.rangeMode === nextMode) {
       return;
@@ -265,7 +211,7 @@ export class Diapson implements ISeriesDrawing {
   public getState(): DiapsonState {
     return {
       hidden: this.hidden,
-      isActive: this.isActive,
+      isActive: this.isActive.value,
       mode: this.mode,
       rangeMode: this.rangeMode,
       startTime: this.startTime,
@@ -284,7 +230,9 @@ export class Diapson implements ISeriesDrawing {
     const nextState = state as Partial<DiapsonState>;
 
     this.hidden = typeof nextState.hidden === 'boolean' ? nextState.hidden : this.hidden;
-    this.isActive = typeof nextState.isActive === 'boolean' ? nextState.isActive : this.isActive;
+    if (typeof nextState.isActive === 'boolean') {
+      this.isActive.next(nextState.isActive);
+    }
     this.mode = nextState.mode ?? this.mode;
     this.rangeMode = nextState.rangeMode ?? this.rangeMode;
     this.startTime = 'startTime' in nextState ? (nextState.startTime ?? null) : this.startTime;
@@ -302,33 +250,10 @@ export class Diapson implements ISeriesDrawing {
     this.render();
   }
 
-  public getSettings(): SettingsValues {
-    return { ...this.settings };
-  }
-
   public getSettingsTabs(): SettingsTab[] {
     return getDiapsonSettingsTabs(this.settings);
   }
 
-  public updateSettings(settings: SettingsValues): void {
-    this.settings = {
-      ...this.settings,
-      ...settings,
-    };
-
-    this.render();
-  }
-
-  public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
-    this.requestUpdate = param.requestUpdate;
-    this.bindEvents();
-  }
-
-  public detached(): void {
-    this.unbindEvents();
-    this.requestUpdate = null;
-  }
-
   public updateAllViews(): void {
     updateViews([
       this.paneView,
@@ -361,10 +286,6 @@ export class Diapson implements ISeriesDrawing {
     return [this.topPriceAxisView, this.bottomPriceAxisView];
   }
 
-  public autoscaleInfo(_startTimePoint: Logical, _endTimePoint: Logical): AutoscaleInfo | null {
-    return null;
-  }
-
   public getRenderData(): DiapsonRenderData | null {
     if (this.hidden) {
       return null;
@@ -380,14 +301,14 @@ export class Diapson implements ISeriesDrawing {
       ...geometry,
       rangeMode: this.rangeMode,
       showFill: true,
-      showHandles: this.isActive,
+      showHandles: this.isActive.value,
       labelLines: this.getLabelLines(),
       ...this.settings,
     };
   }
 
   public getTimeAxisSegments(): AxisSegment[] {
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       return [];
     }
 
@@ -409,7 +330,7 @@ export class Diapson implements ISeriesDrawing {
   }
 
   public getPriceAxisSegments(): AxisSegment[] {
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       return [];
     }
 
@@ -431,7 +352,7 @@ export class Diapson implements ISeriesDrawing {
   }
 
   public getTimeAxisLabel(kind: string): AxisLabel | null {
-    if (!this.isActive || (kind !== 'left' && kind !== 'right')) {
+    if (!this.isActive.value || (kind !== 'left' && kind !== 'right')) {
       return null;
     }
 
@@ -454,7 +375,7 @@ export class Diapson implements ISeriesDrawing {
   }
 
   public getPriceAxisLabel(kind: string): AxisLabel | null {
-    if (!this.isActive || (kind !== 'top' && kind !== 'bottom')) {
+    if (!this.isActive.value || (kind !== 'top' && kind !== 'bottom')) {
       return null;
     }
 
@@ -483,7 +404,7 @@ export class Diapson implements ISeriesDrawing {
 
     const point = { x, y };
 
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       if (!this.containsPoint(point)) {
         return null;
       }
@@ -516,7 +437,7 @@ export class Diapson implements ISeriesDrawing {
     };
   }
 
-  private bindEvents(): void {
+  protected bindEvents(): void {
     if (this.isBound) {
       return;
     }
@@ -530,7 +451,7 @@ export class Diapson implements ISeriesDrawing {
     window.addEventListener('pointercancel', this.handlePointerUp);
   }
 
-  private unbindEvents(): void {
+  protected unbindEvents(): void {
     if (!this.isBound) {
       return;
     }
@@ -589,7 +510,7 @@ export class Diapson implements ISeriesDrawing {
       return;
     }
 
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       if (!this.containsPoint(point)) {
         return;
       }
@@ -597,7 +518,7 @@ export class Diapson implements ISeriesDrawing {
       event.preventDefault();
       event.stopPropagation();
 
-      this.isActive = true;
+      this.isActive.next(true);
       this.render();
       return;
     }
@@ -605,7 +526,7 @@ export class Diapson implements ISeriesDrawing {
     const dragTarget = this.getDragTarget(point);
 
     if (!dragTarget) {
-      this.isActive = false;
+      this.isActive.next(false);
       this.render();
       return;
     }
@@ -660,7 +581,7 @@ export class Diapson implements ISeriesDrawing {
     this.startPrice = anchor.price;
     this.endPrice = anchor.price;
 
-    this.isActive = true;
+    this.isActive.next(true);
     this.mode = 'drawing';
     this.render();
   }
@@ -697,6 +618,8 @@ export class Diapson implements ISeriesDrawing {
     }
 
     this.mode = 'ready';
+    this.resolveReady?.();
+
     this.render();
   }
 
@@ -712,6 +635,8 @@ export class Diapson implements ISeriesDrawing {
 
   private finishDragging(): void {
     this.mode = 'ready';
+    this.resolveReady?.();
+
     this.clearInteractionState();
     this.render();
   }
@@ -725,7 +650,7 @@ export class Diapson implements ISeriesDrawing {
 
   private resetToIdle(): void {
     this.hidden = false;
-    this.isActive = false;
+    this.isActive.next(false);
     this.mode = 'idle';
     this.startTime = null;
     this.endTime = null;
@@ -813,7 +738,7 @@ export class Diapson implements ISeriesDrawing {
     return getAnchorFromPoint(this.chart, this.series, point);
   }
 
-  private getGeometry(): DiapsonGeometry | null {
+  protected getGeometry(): DiapsonGeometry | null {
     if (this.startTime === null || this.endTime === null || this.startPrice === null || this.endPrice === null) {
       return null;
     }
@@ -1212,13 +1137,4 @@ export class Diapson implements ISeriesDrawing {
   private clampPointToContainer(point: Point): Point {
     return clampPointToContainerInElement(point, this.container);
   }
-
-  private getEventPoint(event: PointerEvent): Point {
-    return getPointerPointFromEvent(this.container, event);
-  }
-
-  private render(): void {
-    this.updateAllViews();
-    this.requestUpdate?.();
-  }
 }
diff --git a/src/core/Drawings/fibonacciRetracement/fibonacciRetracement.ts b/src/core/Drawings/fibonacciRetracement/fibonacciRetracement.ts
index e78ba00..3b2223f 100644
--- a/src/core/Drawings/fibonacciRetracement/fibonacciRetracement.ts
+++ b/src/core/Drawings/fibonacciRetracement/fibonacciRetracement.ts
@@ -1,4 +1,4 @@
-import { Observable, Subscription } from 'rxjs';
+import { Observable } from 'rxjs';
 
 import {
   CustomPriceAxisPaneView,
@@ -6,12 +6,12 @@ import {
   CustomTimeAxisPaneView,
   CustomTimeAxisView,
 } from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
 import {
   clamp,
   clampPointToContainer as clampPointToContainerInElement,
   getAnchorFromPoint,
   getContainerSize as getElementContainerSize,
-  getPointerPoint as getPointerPointFromEvent,
   getPriceDelta as getPriceDeltaFromCoordinates,
   getXCoordinateFromTime,
   getYCoordinateFromPrice,
@@ -41,16 +41,7 @@ import {
 import type { ISeriesDrawing } from '@core/Drawings/common';
 import type { AxisLabel, AxisSegment, Bounds, Point, SeriesApi } from '@core/Drawings/types';
 import type { ChartOptionsModel, SettingsTab, SettingsValues } from '@src/types';
-import type {
-  AutoscaleInfo,
-  IChartApi,
-  IPrimitivePaneView,
-  PrimitiveHoveredItem,
-  SeriesAttachedParameter,
-  SeriesOptionsMap,
-  Time,
-  UTCTimestamp,
-} from 'lightweight-charts';
+import type { IChartApi, IPrimitivePaneView, PrimitiveHoveredItem, Time, UTCTimestamp } from 'lightweight-charts';
 
 type FibonacciRetracementMode = 'idle' | 'drawing' | 'ready' | 'dragging';
 type FibonacciRetracementHandle = 'body' | 'start' | 'end' | null;
@@ -120,22 +111,15 @@ const LINE_HIT_TOLERANCE = 6;
 const MIN_DISTANCE = 6;
 const PERCENT_DIVIDER = 100;
 
-export class FibonacciRetracement implements ISeriesDrawing {
-  private chart: IChartApi;
-  private series: SeriesApi;
-  private container: HTMLElement;
+export class FibonacciRetracement extends SeriesDrawingBase<FibonacciRetracementSettings> implements ISeriesDrawing {
   private removeSelf?: () => void;
   private openSettings?: () => void;
 
-  private settings: FibonacciRetracementSettings = createDefaultSettings();
+  protected settings: FibonacciRetracementSettings = createDefaultSettings();
 
-  private requestUpdate: (() => void) | null = null;
   private isBound = false;
-  private subscriptions = new Subscription();
 
-  private hidden = false;
-  private isActive = false;
-  private mode: FibonacciRetracementMode = 'idle';
+  protected mode: FibonacciRetracementMode = 'idle';
 
   private startTime: Time | null = null;
   private endTime: Time | null = null;
@@ -167,9 +151,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
     series: SeriesApi,
     { container, formatObservable, removeSelf, openSettings }: FibonacciRetracementParams,
   ) {
-    this.chart = chart;
-    this.series = series;
-    this.container = container;
+    super({ chart, series, container });
     this.removeSelf = removeSelf;
     this.openSettings = openSettings;
 
@@ -215,50 +197,14 @@ export class FibonacciRetracement implements ISeriesDrawing {
     this.series.attachPrimitive(this);
   }
 
-  public show(): void {
-    this.hidden = false;
-    this.render();
-  }
-
-  public hide(): void {
-    this.hidden = true;
-    this.render();
-  }
-
-  public destroy(): void {
-    this.unbindEvents();
-    this.subscriptions.unsubscribe();
-    this.series.detachPrimitive(this);
-    this.requestUpdate = null;
-  }
-
-  public rebind(series: SeriesApi): void {
-    if (this.series === series) {
-      return;
-    }
-
-    this.unbindEvents();
-    this.series.detachPrimitive(this);
-
-    this.series = series;
-    this.requestUpdate = null;
-
-    this.series.attachPrimitive(this);
-    this.render();
-  }
-
   public isCreationPending(): boolean {
     return this.mode === 'idle' || this.mode === 'drawing';
   }
 
-  public shouldShowInObjectTree(): boolean {
-    return this.mode !== 'idle';
-  }
-
   public getState(): FibonacciRetracementState {
     return {
       hidden: this.hidden,
-      isActive: this.isActive,
+      isActive: this.isActive.value,
       mode: this.mode,
       startTime: this.startTime,
       endTime: this.endTime,
@@ -272,7 +218,9 @@ export class FibonacciRetracement implements ISeriesDrawing {
     const next = state as Partial<FibonacciRetracementState>;
 
     this.hidden = next.hidden ?? this.hidden;
-    this.isActive = next.isActive ?? this.isActive;
+    if (next.isActive !== undefined) {
+      this.isActive.next(next.isActive);
+    }
     this.mode = next.mode ?? this.mode;
 
     this.startTime = next.startTime ?? this.startTime;
@@ -300,16 +248,6 @@ export class FibonacciRetracement implements ISeriesDrawing {
     this.render();
   }
 
-  public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
-    this.requestUpdate = param.requestUpdate;
-    this.bindEvents();
-  }
-
-  public detached(): void {
-    this.unbindEvents();
-    this.requestUpdate = null;
-  }
-
   public updateAllViews(): void {
     updateViews([
       this.paneView,
@@ -342,10 +280,6 @@ export class FibonacciRetracement implements ISeriesDrawing {
     return [this.topPriceAxisView, this.bottomPriceAxisView];
   }
 
-  public autoscaleInfo(): AutoscaleInfo | null {
-    return null;
-  }
-
   public getRenderData(): FibonacciRetracementRenderData | null {
     const geometry = this.hidden ? null : this.getGeometry();
 
@@ -355,7 +289,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
 
     return {
       ...geometry,
-      showHandles: this.isActive,
+      showHandles: this.isActive.value,
 
       showBackground: this.settings.showBackground,
       backgroundOpacity: this.settings.backgroundOpacity / PERCENT_DIVIDER,
@@ -371,7 +305,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
   }
 
   public getTimeAxisSegments(): AxisSegment[] {
-    const bounds = this.isActive ? this.getTimeBounds() : null;
+    const bounds = this.isActive.value ? this.getTimeBounds() : null;
 
     if (!bounds) {
       return [];
@@ -389,7 +323,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
   }
 
   public getPriceAxisSegments(): AxisSegment[] {
-    const bounds = this.isActive ? this.getPriceBounds() : null;
+    const bounds = this.isActive.value ? this.getPriceBounds() : null;
 
     if (!bounds) {
       return [];
@@ -407,7 +341,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
   }
 
   public getTimeAxisLabel(kind: string): AxisLabel | null {
-    const coordinate = this.isActive ? this.getTimeCoordinate(kind as TimeLabelKind) : null;
+    const coordinate = this.isActive.value ? this.getTimeCoordinate(kind as TimeLabelKind) : null;
     const text = this.getTimeText(kind as TimeLabelKind);
 
     if (coordinate === null || !text) {
@@ -425,7 +359,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
   }
 
   public getPriceAxisLabel(kind: string): AxisLabel | null {
-    const coordinate = this.isActive ? this.getPriceCoordinate(kind as PriceLabelKind) : null;
+    const coordinate = this.isActive.value ? this.getPriceCoordinate(kind as PriceLabelKind) : null;
     const text = this.getPriceText(kind as PriceLabelKind);
 
     if (coordinate === null || !text) {
@@ -449,11 +383,11 @@ export class FibonacciRetracement implements ISeriesDrawing {
 
     const point = { x, y };
 
-    if (!this.isActive && !this.containsPoint(point)) {
+    if (!this.isActive.value && !this.containsPoint(point)) {
       return null;
     }
 
-    const handleTarget = this.isActive ? this.getHandleTarget(point) : null;
+    const handleTarget = this.isActive.value ? this.getHandleTarget(point) : null;
 
     if (handleTarget) {
       return {
@@ -468,13 +402,13 @@ export class FibonacciRetracement implements ISeriesDrawing {
     }
 
     return {
-      cursorStyle: this.isActive ? 'grab' : 'pointer',
+      cursorStyle: this.isActive.value ? 'grab' : 'pointer',
       externalId: 'fibonacci-retracement-position',
       zOrder: 'top',
     };
   }
 
-  private bindEvents(): void {
+  protected bindEvents(): void {
     if (this.isBound) {
       return;
     }
@@ -488,7 +422,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
     window.addEventListener('pointercancel', this.handlePointerUp);
   }
 
-  private unbindEvents(): void {
+  protected unbindEvents(): void {
     if (!this.isBound) {
       return;
     }
@@ -543,8 +477,8 @@ export class FibonacciRetracement implements ISeriesDrawing {
       return;
     }
 
-    if (!this.isActive) {
-      this.isActive = this.containsPoint(point);
+    if (!this.isActive.value) {
+      this.isActive.next(this.containsPoint(point));
       this.render();
       return;
     }
@@ -552,7 +486,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
     const dragTarget = this.getDragTarget(point);
 
     if (!dragTarget) {
-      this.isActive = false;
+      this.isActive.next(false);
       this.render();
       return;
     }
@@ -592,6 +526,8 @@ export class FibonacciRetracement implements ISeriesDrawing {
     }
 
     this.mode = 'ready';
+    this.resolveReady?.();
+
     this.clearInteractionState();
     this.render();
   };
@@ -608,7 +544,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
     this.startPrice = anchor.price;
     this.endPrice = anchor.price;
 
-    this.isActive = true;
+    this.isActive.next(true);
     this.mode = 'drawing';
 
     this.render();
@@ -645,6 +581,8 @@ export class FibonacciRetracement implements ISeriesDrawing {
     }
 
     this.mode = 'ready';
+    this.resolveReady?.();
+
     this.render();
   }
 
@@ -670,7 +608,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
 
   private resetToIdle(): void {
     this.hidden = false;
-    this.isActive = false;
+    this.isActive.next(false);
     this.mode = 'idle';
 
     this.startTime = null;
@@ -756,7 +694,7 @@ export class FibonacciRetracement implements ISeriesDrawing {
     return getAnchorFromPoint(this.chart, this.series, point);
   }
 
-  private getGeometry(): FibonacciRetracementGeometry | null {
+  protected getGeometry(): FibonacciRetracementGeometry | null {
     if (this.startTime === null || this.endTime === null || this.startPrice === null || this.endPrice === null) {
       return null;
     }
@@ -1001,10 +939,6 @@ export class FibonacciRetracement implements ISeriesDrawing {
     return clampPointToContainerInElement(point, this.container);
   }
 
-  private getEventPoint(event: PointerEvent): Point {
-    return getPointerPointFromEvent(this.container, event);
-  }
-
   private getMousePoint(event: MouseEvent): Point {
     const rect = this.container.getBoundingClientRect();
 
@@ -1013,9 +947,4 @@ export class FibonacciRetracement implements ISeriesDrawing {
       y: event.clientY - rect.top,
     });
   }
-
-  private render(): void {
-    this.updateAllViews();
-    this.requestUpdate?.();
-  }
 }
diff --git a/src/core/Drawings/ray/ray.ts b/src/core/Drawings/ray/ray.ts
index 9076149..6427435 100644
--- a/src/core/Drawings/ray/ray.ts
+++ b/src/core/Drawings/ray/ray.ts
@@ -1,16 +1,5 @@
-import {
-  AutoscaleInfo,
-  CrosshairMode,
-  IChartApi,
-  IPrimitivePaneView,
-  Logical,
-  PrimitiveHoveredItem,
-  SeriesAttachedParameter,
-  SeriesOptionsMap,
-  Time,
-  UTCTimestamp,
-} from 'lightweight-charts';
-import { Observable, Subscription } from 'rxjs';
+import { IChartApi, IPrimitivePaneView, PrimitiveHoveredItem, UTCTimestamp } from 'lightweight-charts';
+import { Observable } from 'rxjs';
 
 import {
   CustomPriceAxisPaneView,
@@ -18,9 +7,9 @@ import {
   CustomTimeAxisPaneView,
   CustomTimeAxisView,
 } from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
 import {
   getAnchorFromPoint,
-  getPointerPoint as getPointerPointFromEvent,
   getPriceDelta as getPriceDeltaFromCoordinates,
   getXCoordinateFromTime,
   getYCoordinateFromPrice,
@@ -40,7 +29,7 @@ import { createDefaultSettings, getRaySettingTabs, RaySettings, RayStyle, RayTex
 
 import type { ISeriesDrawing } from '@core/Drawings/common';
 import type { Anchor, AxisLabel, AxisSegment, Point, SeriesApi } from '@core/Drawings/types';
-import type { ChartOptionsModel, SettingsTab, SettingsValues } from '@src/types';
+import type { ChartOptionsModel, SettingsTab } from '@src/types';
 
 type RayMode = 'idle' | 'drawing' | 'ready' | 'dragging-start' | 'dragging-direction' | 'dragging-body';
 type TimeLabelKind = 'start' | 'direction';
@@ -80,22 +69,15 @@ const POINT_HIT_TOLERANCE = 8;
 const LINE_HIT_TOLERANCE = 6;
 const MIN_LINE_SIZE = 4;
 
-export class Ray implements ISeriesDrawing {
-  private chart: IChartApi;
-  private series: SeriesApi;
-  private container: HTMLElement;
+export class Ray extends SeriesDrawingBase<RaySettings> implements ISeriesDrawing {
   private removeSelf?: () => void;
   private openSettings?: () => void;
 
-  private settings: RaySettings = createDefaultSettings();
+  protected settings: RaySettings = createDefaultSettings();
 
-  private requestUpdate: (() => void) | null = null;
-  private subscriptions = new Subscription();
   private isBound = false;
 
-  private hidden = false;
-  private isActive = false;
-  private mode: RayMode = 'idle';
+  protected mode: RayMode = 'idle';
 
   private startAnchor: Anchor | null = null;
   private directionAnchor: Anchor | null = null;
@@ -123,9 +105,7 @@ export class Ray implements ISeriesDrawing {
     series: SeriesApi,
     { container, formatObservable, removeSelf, openSettings }: RayParams,
   ) {
-    this.chart = chart;
-    this.series = series;
-    this.container = container;
+    super({ chart, series, container });
     this.removeSelf = removeSelf;
     this.openSettings = openSettings;
 
@@ -171,53 +151,14 @@ export class Ray implements ISeriesDrawing {
     this.series.attachPrimitive(this);
   }
 
-  public show(): void {
-    this.hidden = false;
-    this.render();
-  }
-
-  public hide(): void {
-    this.hidden = true;
-    this.showCrosshair();
-    this.render();
-  }
-
-  public destroy(): void {
-    this.showCrosshair();
-    this.unbindEvents();
-    this.subscriptions.unsubscribe();
-    this.series.detachPrimitive(this);
-    this.requestUpdate = null;
-  }
-
-  public rebind(series: SeriesApi): void {
-    if (this.series === series) {
-      return;
-    }
-
-    this.showCrosshair();
-    this.unbindEvents();
-    this.series.detachPrimitive(this);
-
-    this.series = series;
-    this.requestUpdate = null;
-
-    this.series.attachPrimitive(this);
-    this.render();
-  }
-
   public isCreationPending(): boolean {
     return this.mode === 'idle' || this.mode === 'drawing';
   }
 
-  public shouldShowInObjectTree(): boolean {
-    return this.mode !== 'idle';
-  }
-
   public getState(): RayState {
     return {
       hidden: this.hidden,
-      isActive: this.isActive,
+      isActive: this.isActive.value,
       mode: this.mode,
       startAnchor: this.startAnchor,
       directionAnchor: this.directionAnchor,
@@ -233,7 +174,7 @@ export class Ray implements ISeriesDrawing {
     }
 
     if ('isActive' in nextState && typeof nextState.isActive === 'boolean') {
-      this.isActive = nextState.isActive;
+      this.isActive.next(nextState.isActive);
     }
 
     if ('mode' in nextState && nextState.mode) {
@@ -258,34 +199,10 @@ export class Ray implements ISeriesDrawing {
     this.render();
   }
 
-  public getSettings(): SettingsValues {
-    return { ...this.settings };
-  }
-
   public getSettingsTabs(): SettingsTab[] {
     return getRaySettingTabs(this.settings);
   }
 
-  public updateSettings(settings: SettingsValues): void {
-    this.settings = {
-      ...this.settings,
-      ...settings,
-    };
-
-    this.render();
-  }
-
-  public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
-    this.requestUpdate = param.requestUpdate;
-    this.bindEvents();
-  }
-
-  public detached(): void {
-    this.showCrosshair();
-    this.unbindEvents();
-    this.requestUpdate = null;
-  }
-
   public updateAllViews(): void {
     updateViews([
       this.paneView,
@@ -318,10 +235,6 @@ export class Ray implements ISeriesDrawing {
     return [this.startPriceAxisView, this.directionPriceAxisView];
   }
 
-  public autoscaleInfo(_start: Logical, _end: Logical): AutoscaleInfo | null {
-    return null;
-  }
-
   public getRenderData(): RayRenderData | null {
     if (this.hidden) {
       return null;
@@ -335,7 +248,7 @@ export class Ray implements ISeriesDrawing {
 
     return {
       ...geometry,
-      showHandles: this.isActive,
+      showHandles: this.isActive.value,
       ...this.settings,
     };
   }
@@ -367,7 +280,7 @@ export class Ray implements ISeriesDrawing {
   }
 
   public getTimeAxisSegments(): AxisSegment[] {
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       return [];
     }
 
@@ -389,7 +302,7 @@ export class Ray implements ISeriesDrawing {
   }
 
   public getPriceAxisSegments(): AxisSegment[] {
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       return [];
     }
 
@@ -411,7 +324,7 @@ export class Ray implements ISeriesDrawing {
   }
 
   public getTimeAxisLabel(kind: string): AxisLabel | null {
-    if (!this.isActive || (kind !== 'start' && kind !== 'direction')) {
+    if (!this.isActive.value || (kind !== 'start' && kind !== 'direction')) {
       return null;
     }
 
@@ -433,7 +346,7 @@ export class Ray implements ISeriesDrawing {
   }
 
   public getPriceAxisLabel(kind: string): AxisLabel | null {
-    if (!this.isActive || (kind !== 'start' && kind !== 'direction')) {
+    if (!this.isActive.value || (kind !== 'start' && kind !== 'direction')) {
       return null;
     }
 
@@ -454,7 +367,7 @@ export class Ray implements ISeriesDrawing {
     };
   }
 
-  private bindEvents(): void {
+  protected bindEvents(): void {
     if (this.isBound) {
       return;
     }
@@ -468,7 +381,7 @@ export class Ray implements ISeriesDrawing {
     window.addEventListener('pointercancel', this.handlePointerUp);
   }
 
-  private unbindEvents(): void {
+  protected unbindEvents(): void {
     if (!this.isBound) {
       return;
     }
@@ -533,7 +446,7 @@ export class Ray implements ISeriesDrawing {
 
     const pointTarget = this.getPointTarget(point);
 
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       if (!pointTarget && !this.isPointNearRay(point)) {
         return;
       }
@@ -541,7 +454,7 @@ export class Ray implements ISeriesDrawing {
       event.preventDefault();
       event.stopPropagation();
 
-      this.isActive = true;
+      this.isActive.next(true);
       this.render();
       return;
     }
@@ -570,7 +483,7 @@ export class Ray implements ISeriesDrawing {
       return;
     }
 
-    this.isActive = false;
+    this.isActive.next(false);
     this.render();
   };
 
@@ -623,7 +536,7 @@ export class Ray implements ISeriesDrawing {
 
     this.startAnchor = anchor;
     this.directionAnchor = anchor;
-    this.isActive = true;
+    this.isActive.next(true);
     this.mode = 'drawing';
 
     this.render();
@@ -658,6 +571,7 @@ export class Ray implements ISeriesDrawing {
     }
 
     this.mode = 'ready';
+    this.resolveReady?.();
 
     this.render();
   }
@@ -675,6 +589,8 @@ export class Ray implements ISeriesDrawing {
 
   private finishDragging(): void {
     this.mode = 'ready';
+    this.resolveReady?.();
+
     this.dragPointerId = null;
     this.dragStartPoint = null;
     this.dragStateSnapshot = null;
@@ -731,7 +647,7 @@ export class Ray implements ISeriesDrawing {
     return getAnchorFromPoint(this.chart, this.series, point);
   }
 
-  private getGeometry(): RayGeometry | null {
+  protected getGeometry(): RayGeometry | null {
     if (!this.startAnchor || !this.directionAnchor) {
       return null;
     }
@@ -913,29 +829,4 @@ export class Ray implements ISeriesDrawing {
 
     return formatPrice(anchor.price) ?? '';
   }
-
-  private hideCrosshair(): void {
-    this.chart.applyOptions({
-      crosshair: {
-        mode: CrosshairMode.Hidden,
-      },
-    });
-  }
-
-  private showCrosshair(): void {
-    this.chart.applyOptions({
-      crosshair: {
-        mode: CrosshairMode.Normal,
-      },
-    });
-  }
-
-  private getEventPoint(event: PointerEvent): Point {
-    return getPointerPointFromEvent(this.container, event);
-  }
-
-  private render(): void {
-    this.updateAllViews();
-    this.requestUpdate?.();
-  }
 }
diff --git a/src/core/Drawings/rectangle/rectangle.ts b/src/core/Drawings/rectangle/rectangle.ts
index 3d19a2b..bba8fe1 100644
--- a/src/core/Drawings/rectangle/rectangle.ts
+++ b/src/core/Drawings/rectangle/rectangle.ts
@@ -1,4 +1,4 @@
-import { Observable, Subscription } from 'rxjs';
+import { Observable } from 'rxjs';
 
 import {
   CustomPriceAxisPaneView,
@@ -6,6 +6,7 @@ import {
   CustomTimeAxisPaneView,
   CustomTimeAxisView,
 } from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
 import {
   clamp,
   clampPointToContainer as clampPointToContainerInElement,
@@ -96,22 +97,15 @@ const HANDLE_HIT_TOLERANCE = 8;
 const BODY_HIT_TOLERANCE = 6;
 const MIN_RECTANGLE_SIZE = 6;
 
-export class Rectangle implements ISeriesDrawing {
-  private chart: IChartApi;
-  private series: SeriesApi;
-  private container: HTMLElement;
+export class Rectangle extends SeriesDrawingBase<RectangleSettings> implements ISeriesDrawing {
   private removeSelf?: () => void;
   private openSettings?: () => void;
 
-  private settings: RectangleSettings = createDefaultSettings();
+  protected settings: RectangleSettings = createDefaultSettings();
 
-  private requestUpdate: (() => void) | null = null;
   private isBound = false;
-  private subscriptions = new Subscription();
 
-  private hidden = false;
-  private isActive = false;
-  private mode: RectangleMode = 'idle';
+  protected mode: RectangleMode = 'idle';
 
   private startTime: Time | null = null;
   private endTime: Time | null = null;
@@ -143,9 +137,7 @@ export class Rectangle implements ISeriesDrawing {
     series: SeriesApi,
     { container, formatObservable, removeSelf, openSettings }: RectangleParams,
   ) {
-    this.chart = chart;
-    this.series = series;
-    this.container = container;
+    super({ chart, series, container });
     this.removeSelf = removeSelf;
     this.openSettings = openSettings;
 
@@ -191,50 +183,14 @@ export class Rectangle implements ISeriesDrawing {
     this.series.attachPrimitive(this);
   }
 
-  public show(): void {
-    this.hidden = false;
-    this.render();
-  }
-
-  public hide(): void {
-    this.hidden = true;
-    this.render();
-  }
-
-  public destroy(): void {
-    this.unbindEvents();
-    this.subscriptions.unsubscribe();
-    this.series.detachPrimitive(this);
-    this.requestUpdate = null;
-  }
-
-  public rebind(series: SeriesApi): void {
-    if (this.series === series) {
-      return;
-    }
-
-    this.unbindEvents();
-    this.series.detachPrimitive(this);
-
-    this.series = series;
-    this.requestUpdate = null;
-
-    this.series.attachPrimitive(this);
-    this.render();
-  }
-
   public isCreationPending(): boolean {
     return this.mode === 'idle' || this.mode === 'drawing';
   }
 
-  public shouldShowInObjectTree(): boolean {
-    return this.mode !== 'idle';
-  }
-
   public getState(): RectangleState {
     return {
       hidden: this.hidden,
-      isActive: this.isActive,
+      isActive: this.isActive.value,
       mode: this.mode,
       startTime: this.startTime,
       endTime: this.endTime,
@@ -252,7 +208,7 @@ export class Rectangle implements ISeriesDrawing {
     }
 
     if ('isActive' in nextState && typeof nextState.isActive === 'boolean') {
-      this.isActive = nextState.isActive;
+      this.isActive.next(nextState.isActive);
     }
 
     if ('mode' in nextState && nextState.mode) {
@@ -285,33 +241,10 @@ export class Rectangle implements ISeriesDrawing {
     this.render();
   }
 
-  public getSettings(): SettingsValues {
-    return { ...this.settings };
-  }
-
   public getSettingsTabs(): SettingsTab[] {
     return getRectangleSettingsTabs(this.settings);
   }
 
-  public updateSettings(settings: SettingsValues): void {
-    this.settings = {
-      ...this.settings,
-      ...settings,
-    };
-
-    this.render();
-  }
-
-  public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
-    this.requestUpdate = param.requestUpdate;
-    this.bindEvents();
-  }
-
-  public detached(): void {
-    this.unbindEvents();
-    this.requestUpdate = null;
-  }
-
   public updateAllViews(): void {
     updateViews([
       this.paneView,
@@ -344,10 +277,6 @@ export class Rectangle implements ISeriesDrawing {
     return [this.topPriceAxisView, this.bottomPriceAxisView];
   }
 
-  public autoscaleInfo(_start: Logical, _end: Logical): AutoscaleInfo | null {
-    return null;
-  }
-
   public getRenderData(): RectangleRenderData | null {
     if (this.hidden) {
       return null;
@@ -362,13 +291,13 @@ export class Rectangle implements ISeriesDrawing {
     return {
       ...geometry,
       showFill: true,
-      showHandles: this.isActive,
+      showHandles: this.isActive.value,
       ...this.settings,
     };
   }
 
   public getTimeAxisSegments(): AxisSegment[] {
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       return [];
     }
 
@@ -390,7 +319,7 @@ export class Rectangle implements ISeriesDrawing {
   }
 
   public getPriceAxisSegments(): AxisSegment[] {
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       return [];
     }
 
@@ -412,7 +341,7 @@ export class Rectangle implements ISeriesDrawing {
   }
 
   public getTimeAxisLabel(kind: string): AxisLabel | null {
-    if (!this.isActive || (kind !== 'left' && kind !== 'right')) {
+    if (!this.isActive.value || (kind !== 'left' && kind !== 'right')) {
       return null;
     }
 
@@ -435,7 +364,7 @@ export class Rectangle implements ISeriesDrawing {
   }
 
   public getPriceAxisLabel(kind: string): AxisLabel | null {
-    if (!this.isActive || (kind !== 'top' && kind !== 'bottom')) {
+    if (!this.isActive.value || (kind !== 'top' && kind !== 'bottom')) {
       return null;
     }
 
@@ -464,7 +393,7 @@ export class Rectangle implements ISeriesDrawing {
 
     const point = { x, y };
 
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       if (!this.containsPoint(point)) {
         return null;
       }
@@ -497,7 +426,7 @@ export class Rectangle implements ISeriesDrawing {
     };
   }
 
-  private bindEvents(): void {
+  protected bindEvents(): void {
     if (this.isBound) {
       return;
     }
@@ -511,7 +440,7 @@ export class Rectangle implements ISeriesDrawing {
     window.addEventListener('pointercancel', this.handlePointerUp);
   }
 
-  private unbindEvents(): void {
+  protected unbindEvents(): void {
     if (!this.isBound) {
       return;
     }
@@ -574,7 +503,7 @@ export class Rectangle implements ISeriesDrawing {
       return;
     }
 
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       if (!this.containsPoint(point)) {
         return;
       }
@@ -582,7 +511,7 @@ export class Rectangle implements ISeriesDrawing {
       event.preventDefault();
       event.stopPropagation();
 
-      this.isActive = true;
+      this.isActive.next(true);
       this.render();
       return;
     }
@@ -590,7 +519,7 @@ export class Rectangle implements ISeriesDrawing {
     const dragTarget = this.getDragTarget(point);
 
     if (!dragTarget) {
-      this.isActive = false;
+      this.isActive.next(false);
       this.render();
       return;
     }
@@ -645,7 +574,7 @@ export class Rectangle implements ISeriesDrawing {
     this.startPrice = anchor.price;
     this.endPrice = anchor.price;
 
-    this.isActive = true;
+    this.isActive.next(true);
     this.mode = 'drawing';
 
     this.render();
@@ -679,6 +608,7 @@ export class Rectangle implements ISeriesDrawing {
     }
 
     this.mode = 'ready';
+    this.resolveReady?.();
 
     this.render();
   }
@@ -697,6 +627,7 @@ export class Rectangle implements ISeriesDrawing {
 
   private finishDragging(): void {
     this.mode = 'ready';
+    this.resolveReady?.();
 
     this.clearInteractionState();
     this.render();
@@ -712,7 +643,7 @@ export class Rectangle implements ISeriesDrawing {
 
   private resetToIdle(): void {
     this.hidden = false;
-    this.isActive = false;
+    this.isActive.next(false);
     this.mode = 'idle';
 
     this.startTime = null;
@@ -856,7 +787,7 @@ export class Rectangle implements ISeriesDrawing {
     return getAnchorFromPoint(this.chart, this.series, point);
   }
 
-  private getGeometry(): RectangleGeometry | null {
+  protected getGeometry(): RectangleGeometry | null {
     if (this.startTime === null || this.endTime === null || this.startPrice === null || this.endPrice === null) {
       return null;
     }
@@ -1077,13 +1008,4 @@ export class Rectangle implements ISeriesDrawing {
   private clampPointToContainer(point: Point): Point {
     return clampPointToContainerInElement(point, this.container);
   }
-
-  private getEventPoint(event: PointerEvent): Point {
-    return getPointerPointFromEvent(this.container, event);
-  }
-
-  private render(): void {
-    this.updateAllViews();
-    this.requestUpdate?.();
-  }
 }
diff --git a/src/core/Drawings/ruler/ruler.ts b/src/core/Drawings/ruler/ruler.ts
index 5850c13..29e1524 100644
--- a/src/core/Drawings/ruler/ruler.ts
+++ b/src/core/Drawings/ruler/ruler.ts
@@ -1,4 +1,5 @@
-import { Observable, skip, Subscription } from 'rxjs';
+import { PrimitiveHoveredItem } from 'lightweight-charts';
+import { Observable, skip } from 'rxjs';
 
 import {
   CustomPriceAxisPaneView,
@@ -6,6 +7,7 @@ import {
   CustomTimeAxisPaneView,
   CustomTimeAxisView,
 } from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
 import { getPriceFromYCoordinate, getXCoordinateFromTime, getYCoordinateFromPrice } from '@core/Drawings/helpers';
 import { updateViews } from '@core/Drawings/utils';
 
@@ -48,6 +50,7 @@ interface RulerState {
 }
 
 interface RulerParams {
+  container: HTMLElement;
   formatObservable?: Observable<ChartOptionsModel>;
   resetTriggers?: Observable<unknown>[];
   removeSelf?: () => void;
@@ -65,14 +68,9 @@ export interface RulerRenderData {
   verticalArrowSide: Direction.Top | Direction.Bottom | null;
 }
 
-export class Ruler implements ISeriesDrawing {
-  private chart: IChartApi;
-  private series: SeriesApi;
-
-  private requestUpdate: (() => void) | null = null;
+export class Ruler extends SeriesDrawingBase implements ISeriesDrawing {
   private removeSelf?: () => void;
-
-  private subscriptions = new Subscription();
+  protected settings: SettingsValues = {};
 
   private displayFormat: ChartOptionsModel = {
     dateFormat: Defaults.dateFormat,
@@ -80,8 +78,7 @@ export class Ruler implements ISeriesDrawing {
     showTime: Defaults.showTime,
   };
 
-  private hidden = false;
-  private mode: RulerMode = 'idle';
+  protected mode: RulerMode = 'idle';
 
   private startAnchor: Anchor | null = null;
   private endAnchor: Anchor | null = null;
@@ -99,9 +96,12 @@ export class Ruler implements ISeriesDrawing {
   private readonly startPriceAxisView: CustomPriceAxisView;
   private readonly endPriceAxisView: CustomPriceAxisView;
 
-  constructor(chart: IChartApi, series: SeriesApi, { resetTriggers = [], formatObservable, removeSelf }: RulerParams) {
-    this.chart = chart;
-    this.series = series;
+  constructor(
+    chart: IChartApi,
+    series: SeriesApi,
+    { resetTriggers = [], formatObservable, removeSelf, container }: RulerParams,
+  ) {
+    super({ chart, series, container });
     this.removeSelf = removeSelf;
 
     this.clickHandler = (params) => this.handleClick(params);
@@ -146,7 +146,7 @@ export class Ruler implements ISeriesDrawing {
       );
     }
 
-    resetTriggers.forEach((trigger) => {
+    (resetTriggers ?? []).forEach((trigger) => {
       this.subscriptions.add(
         trigger.pipe(skip(1)).subscribe(() => {
           this.removeSelf?.();
@@ -157,57 +157,14 @@ export class Ruler implements ISeriesDrawing {
     this.series.attachPrimitive(this);
   }
 
-  public show(): void {
-    this.hidden = false;
-    this.render();
-  }
-
-  public hide(): void {
-    this.hidden = true;
-    this.render();
-  }
-
-  public destroy(): void {
-    this.setCrosshairVisible(true);
-    this.unbindEvents();
-    this.subscriptions.unsubscribe();
-    this.series.detachPrimitive(this);
-    this.requestUpdate = null;
-  }
-
-  public rebind(series: SeriesApi): void {
-    if (this.series === series) {
-      return;
-    }
-
-    this.unbindEvents();
-    this.series.detachPrimitive(this);
-
-    this.series = series;
-    this.requestUpdate = null;
-
-    this.series.attachPrimitive(this);
-    this.render();
-  }
-
   public isCreationPending(): boolean {
     return this.mode === 'idle' || this.mode === 'placingEnd';
   }
 
-  public shouldShowInObjectTree(): boolean {
-    return this.mode !== 'idle';
-  }
-
-  public getSettings(): SettingsValues {
-    return {};
-  }
-
   public getSettingsTabs(): SettingsTab[] {
     return [];
   }
 
-  public updateSettings(_settings: SettingsValues): void {}
-
   public getState(): RulerState {
     return {
       hidden: this.hidden,
@@ -228,17 +185,6 @@ export class Ruler implements ISeriesDrawing {
     this.render();
   }
 
-  public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
-    this.requestUpdate = param.requestUpdate;
-    this.bindEvents();
-  }
-
-  public detached(): void {
-    this.setCrosshairVisible(true);
-    this.unbindEvents();
-    this.requestUpdate = null;
-  }
-
   public updateAllViews(): void {
     updateViews([
       this.paneView,
@@ -511,6 +457,10 @@ export class Ruler implements ISeriesDrawing {
     };
   }
 
+  public hitTest(x: number, y: number): PrimitiveHoveredItem | null {
+    return null;
+  }
+
   private findIndexByTime(time: Time): number {
     const data = this.series.data() ?? [];
 
@@ -523,22 +473,7 @@ export class Ruler implements ISeriesDrawing {
     });
   }
 
-  private setCrosshairVisible(visible: boolean): void {
-    this.chart.applyOptions({
-      crosshair: {
-        vertLine: {
-          visible,
-          labelVisible: visible,
-        },
-        horzLine: {
-          visible,
-          labelVisible: visible,
-        },
-      },
-    });
-  }
-
-  private bindEvents(): void {
+  protected bindEvents(): void {
     if (this.isBound) {
       return;
     }
@@ -548,7 +483,11 @@ export class Ruler implements ISeriesDrawing {
     this.chart.subscribeCrosshairMove(this.moveHandler);
   }
 
-  private unbindEvents(): void {
+  protected getGeometry(): void {
+    console.log('stub');
+  }
+
+  protected unbindEvents(): void {
     if (!this.isBound) {
       return;
     }
@@ -558,11 +497,6 @@ export class Ruler implements ISeriesDrawing {
     this.chart.unsubscribeCrosshairMove(this.moveHandler);
   }
 
-  private render(): void {
-    this.updateAllViews();
-    this.requestUpdate?.();
-  }
-
   private handleClick(params: MouseEventParams<Time>): void {
     if (this.hidden || !params.point) {
       return;
@@ -584,7 +518,7 @@ export class Ruler implements ISeriesDrawing {
       this.endAnchor = anchor;
       this.mode = 'placingEnd';
 
-      this.setCrosshairVisible(false);
+      this.hideCrosshair();
       this.render();
       return;
     }
@@ -592,8 +526,8 @@ export class Ruler implements ISeriesDrawing {
     if (this.mode === 'placingEnd') {
       this.endAnchor = anchor;
       this.mode = 'ready';
-
-      this.setCrosshairVisible(true);
+      this.resolveReady?.();
+      this.showCrosshair();
       this.render();
     }
   }
diff --git a/src/core/Drawings/sliderPosition/sliderPosition.ts b/src/core/Drawings/sliderPosition/sliderPosition.ts
index fd444f1..4315382 100644
--- a/src/core/Drawings/sliderPosition/sliderPosition.ts
+++ b/src/core/Drawings/sliderPosition/sliderPosition.ts
@@ -1,4 +1,4 @@
-import { Observable, skip, Subscription } from 'rxjs';
+import { Observable, skip } from 'rxjs';
 
 import {
   CustomPriceAxisPaneView,
@@ -6,6 +6,7 @@ import {
   CustomTimeAxisPaneView,
   CustomTimeAxisView,
 } from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
 import {
   getPointerPoint as getPointerPointFromEvent,
   getPriceDelta as getPriceDeltaFromCoordinates,
@@ -38,17 +39,13 @@ import {
 
 import type { ISeriesDrawing } from '@core/Drawings/common';
 import type { AxisLabel, AxisSegment, Bounds, Point, SeriesApi } from '@core/Drawings/types';
-import type { ChartOptionsModel, SettingsTab, SettingsValues } from '@src/types';
+import type { ChartOptionsModel, SettingsTab } from '@src/types';
 import type {
-  AutoscaleInfo,
   IChartApi,
   IPrimitivePaneView,
-  Logical,
   MouseEventHandler,
   MouseEventParams,
   PrimitiveHoveredItem,
-  SeriesAttachedParameter,
-  SeriesOptionsMap,
   Time,
   UTCTimestamp,
 } from 'lightweight-charts';
@@ -114,17 +111,11 @@ const HIT_TOLERANCE = 8;
 const INITIAL_WIDTH_PX = 160;
 const MIN_DISTANCE = 0.00000001;
 
-export class SliderPosition implements ISeriesDrawing {
-  private chart: IChartApi;
-  private series: SeriesApi;
-  private container: HTMLElement;
+export class SliderPosition extends SeriesDrawingBase<SliderPositionSettings> implements ISeriesDrawing {
   private removeSelf?: () => void;
   private openSettings?: () => void;
 
-  private settings: SliderPositionSettings = createDefaultSettings();
-
-  private requestUpdate: (() => void) | null = null;
-  private subscriptions = new Subscription();
+  protected settings: SliderPositionSettings = createDefaultSettings();
 
   private displayFormat: ChartOptionsModel = {
     dateFormat: Defaults.dateFormat,
@@ -132,9 +123,7 @@ export class SliderPosition implements ISeriesDrawing {
     showTime: Defaults.showTime,
   };
 
-  private hidden = false;
-  private active = true;
-  private mode: SliderMode = 'idle';
+  protected mode: SliderMode = 'idle';
   private side: SliderSide;
 
   private startTime: Time | null = null;
@@ -171,10 +160,8 @@ export class SliderPosition implements ISeriesDrawing {
     series: SeriesApi,
     { side, container, formatObservable, resetTriggers = [], removeSelf, openSettings }: SliderPositionParams,
   ) {
-    this.chart = chart;
-    this.series = series;
+    super({ chart, series, container });
     this.side = side;
-    this.container = container;
     this.removeSelf = removeSelf;
     this.openSettings = openSettings;
 
@@ -235,49 +222,14 @@ export class SliderPosition implements ISeriesDrawing {
     this.series.attachPrimitive(this);
   }
 
-  public show(): void {
-    this.hidden = false;
-    this.render();
-  }
-
-  public hide(): void {
-    this.hidden = true;
-    this.render();
-  }
-
-  public destroy(): void {
-    this.unbindEvents();
-    this.subscriptions.unsubscribe();
-    this.series.detachPrimitive(this);
-    this.requestUpdate = null;
-    this.setCrosshairVisible(true);
-  }
-
-  public rebind(series: SeriesApi): void {
-    if (this.series === series) {
-      return;
-    }
-
-    this.unbindEvents();
-    this.series.detachPrimitive(this);
-    this.series = series;
-    this.requestUpdate = null;
-    this.series.attachPrimitive(this);
-    this.render();
-  }
-
   public isCreationPending(): boolean {
     return this.mode === 'idle';
   }
 
-  public shouldShowInObjectTree(): boolean {
-    return this.mode !== 'idle';
-  }
-
   public getState(): SliderPositionState {
     return {
       hidden: this.hidden,
-      active: this.active,
+      active: this.isActive.value,
       mode: this.mode,
       startTime: this.startTime,
       endTime: this.endTime,
@@ -295,7 +247,9 @@ export class SliderPosition implements ISeriesDrawing {
     const next = state as Partial<SliderPositionState>;
 
     this.hidden = next.hidden ?? this.hidden;
-    this.active = next.active ?? this.active;
+    if (typeof next.active === 'boolean') {
+      this.isActive.next(next.active);
+    }
     this.mode = next.mode ?? this.mode;
 
     this.startTime = next.startTime ?? this.startTime;
@@ -334,34 +288,10 @@ export class SliderPosition implements ISeriesDrawing {
     this.render();
   }
 
-  public getSettings(): SettingsValues {
-    return { ...this.settings };
-  }
-
   public getSettingsTabs(): SettingsTab[] {
     return getSliderPositionSettingsTabs(this.settings);
   }
 
-  public updateSettings(settings: SettingsValues): void {
-    this.settings = {
-      ...this.settings,
-      ...settings,
-    };
-
-    this.render();
-  }
-
-  public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
-    this.requestUpdate = param.requestUpdate;
-    this.bindEvents();
-  }
-
-  public detached(): void {
-    this.unbindEvents();
-    this.requestUpdate = null;
-    this.setCrosshairVisible(true);
-  }
-
   public updateAllViews(): void {
     updateViews([
       this.paneView,
@@ -395,10 +325,6 @@ export class SliderPosition implements ISeriesDrawing {
     return [this.targetPriceAxisView, this.entryPriceAxisView, this.stopPriceAxisView];
   }
 
-  public autoscaleInfo(_start: Logical, _end: Logical): AutoscaleInfo | null {
-    return null;
-  }
-
   public getRenderData(): SliderRenderData | null {
     if (this.hidden) {
       return null;
@@ -430,14 +356,14 @@ export class SliderPosition implements ISeriesDrawing {
       stopText: this.getStopText(geometry),
       centerBoxColor: pnl >= 0 ? colors.chartCandleUp : colors.chartCandleDown,
       showFill: true,
-      showHandles: this.active,
-      showLabels: this.active,
+      showHandles: this.isActive.value,
+      showLabels: this.isActive.value,
       ...this.settings,
     };
   }
 
   public getTimeAxisSegments(): AxisSegment[] {
-    if (!this.active) {
+    if (!this.isActive.value) {
       return [];
     }
 
@@ -459,7 +385,7 @@ export class SliderPosition implements ISeriesDrawing {
   }
 
   public getPriceAxisSegments(): AxisSegment[] {
-    if (!this.active) {
+    if (!this.isActive.value) {
       return [];
     }
 
@@ -486,7 +412,7 @@ export class SliderPosition implements ISeriesDrawing {
   }
 
   public getTimeAxisLabel(kind: string): AxisLabel | null {
-    if (!this.active || (kind !== 'start' && kind !== 'end')) {
+    if (!this.isActive.value || (kind !== 'start' && kind !== 'end')) {
       return null;
     }
 
@@ -622,7 +548,7 @@ export class SliderPosition implements ISeriesDrawing {
   public hitTest(x: number, y: number): PrimitiveHoveredItem | null {
     const point = { x, y };
 
-    if (!this.active) {
+    if (!this.isActive.value) {
       if (!this.containsPoint(point)) {
         return null;
       }
@@ -657,7 +583,7 @@ export class SliderPosition implements ISeriesDrawing {
     };
   }
 
-  private bindEvents(): void {
+  protected bindEvents(): void {
     if (this.isBound) {
       return;
     }
@@ -673,7 +599,7 @@ export class SliderPosition implements ISeriesDrawing {
     window.addEventListener('pointercancel', this.handlePointerUp);
   }
 
-  private unbindEvents(): void {
+  protected unbindEvents(): void {
     if (!this.isBound) {
       return;
     }
@@ -689,23 +615,8 @@ export class SliderPosition implements ISeriesDrawing {
     window.removeEventListener('pointercancel', this.handlePointerUp);
   }
 
-  private setCrosshairVisible(visible: boolean): void {
-    this.chart.applyOptions({
-      crosshair: {
-        vertLine: {
-          visible,
-          labelVisible: visible,
-        },
-        horzLine: {
-          visible,
-          labelVisible: visible,
-        },
-      },
-    });
-  }
-
   private handleDoubleClick = (event: MouseEvent): void => {
-    if (this.hidden || this.mode !== 'ready' || !this.active) {
+    if (this.hidden || this.mode !== 'ready' || !this.isActive.value) {
       return;
     }
 
@@ -752,19 +663,20 @@ export class SliderPosition implements ISeriesDrawing {
         distance,
       );
 
-      this.active = true;
+      this.isActive.next(true);
       this.mode = 'ready';
+      this.resolveReady?.();
 
       this.render();
       return;
     }
 
-    this.active = this.containsPoint({ x: params.point.x, y: params.point.y });
+    this.isActive.next(this.containsPoint({ x: params.point.x, y: params.point.y }));
     this.render();
   }
 
   private handlePointerDown = (event: PointerEvent): void => {
-    if (this.hidden || this.mode !== 'ready' || !this.active) {
+    if (this.hidden || this.mode !== 'ready' || !this.isActive.value) {
       return;
     }
 
@@ -811,6 +723,7 @@ export class SliderPosition implements ISeriesDrawing {
     this.dragStateSnapshot = null;
     this.didDrag = false;
     this.mode = 'ready';
+    this.resolveReady?.();
 
     this.render();
   };
@@ -1102,7 +1015,7 @@ export class SliderPosition implements ISeriesDrawing {
     return lastPrice;
   }
 
-  private getGeometry(): SliderGeometry | null {
+  protected getGeometry(): SliderGeometry | null {
     if (this.startTime === null || this.endTime === null) {
       return null;
     }
@@ -1242,9 +1155,4 @@ export class SliderPosition implements ISeriesDrawing {
   private getLocalPoint(event: PointerEvent): Point {
     return getPointerPointFromEvent(this.container, event);
   }
-
-  private render(): void {
-    this.updateAllViews();
-    this.requestUpdate?.();
-  }
 }
diff --git a/src/core/Drawings/text/text.ts b/src/core/Drawings/text/text.ts
index 4c9d7fc..3711206 100644
--- a/src/core/Drawings/text/text.ts
+++ b/src/core/Drawings/text/text.ts
@@ -1,31 +1,22 @@
-import {
-  AutoscaleInfo,
-  CrosshairMode,
-  IChartApi,
-  IPrimitivePaneView,
-  PrimitiveHoveredItem,
-  SeriesAttachedParameter,
-  SeriesOptionsMap,
-  Time,
-  UTCTimestamp,
-} from 'lightweight-charts';
-import { Observable, Subscription } from 'rxjs';
+import { IChartApi, IPrimitivePaneView, PrimitiveHoveredItem, UTCTimestamp } from 'lightweight-charts';
+import { Observable } from 'rxjs';
 
 import { CustomPriceAxisView, CustomTimeAxisView } from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
 import {
   clamp,
   clampPointToContainer as clampPointToContainerInElement,
   getAnchorFromPoint,
   getContainerSize as getElementContainerSize,
-  getPointerPoint as getPointerPointFromEvent,
   getXCoordinateFromTime,
   getYCoordinateFromPrice,
   isPointInBounds,
 } from '@core/Drawings/helpers';
+import { AxisSegment } from '@core/Drawings/types';
 import { updateViews } from '@core/Drawings/utils';
 
 import { getThemeStore } from '@src/theme';
-import { SettingsTab, SettingsValues } from '@src/types';
+import { SettingsTab } from '@src/types';
 
 import { Defaults } from '@src/types/defaults';
 
@@ -79,21 +70,13 @@ const UI = {
 
 let measureCanvas: HTMLCanvasElement | null = null;
 
-export class Text implements ISeriesDrawing {
-  private chart: IChartApi;
-  private series: SeriesApi;
-  private readonly container: HTMLElement;
+export class Text extends SeriesDrawingBase<TextSettings> implements ISeriesDrawing {
   private readonly removeSelf?: () => void;
   private readonly openSettings?: () => void;
 
-  private requestUpdate: (() => void) | null = null;
   private isBound = false;
 
-  private hidden = false;
-  private isActive = false;
-  private mode: TextMode = 'idle';
-
-  private subscriptions = new Subscription();
+  protected mode: TextMode = 'idle';
 
   private displayFormat: ChartOptionsModel = {
     dateFormat: Defaults.dateFormat,
@@ -102,7 +85,7 @@ export class Text implements ISeriesDrawing {
   };
 
   private point: Anchor | null = null;
-  private settings: TextSettings = createDefaultSettings();
+  protected settings: TextSettings = createDefaultSettings();
 
   private dragPointerId: number | null = null;
   private dragStartPoint: Point | null = null;
@@ -113,11 +96,9 @@ export class Text implements ISeriesDrawing {
   private readonly priceAxisView: CustomPriceAxisView;
 
   constructor(chart: IChartApi, series: SeriesApi, params: TextParams) {
-    const { container, formatObservable, removeSelf, openSettings } = params;
+    super({ chart, series, container: params.container });
+    const { formatObservable, removeSelf, openSettings } = params;
 
-    this.chart = chart;
-    this.series = series;
-    this.container = container;
     this.removeSelf = removeSelf;
     this.openSettings = openSettings;
 
@@ -145,70 +126,18 @@ export class Text implements ISeriesDrawing {
     this.series.attachPrimitive(this);
   }
 
-  public show(): void {
-    this.hidden = false;
-    this.render();
-  }
-
-  public hide(): void {
-    this.hidden = true;
-    this.showCrosshair();
-    this.render();
-  }
-
-  public destroy(): void {
-    this.showCrosshair();
-    this.unbindEvents();
-    this.subscriptions.unsubscribe();
-    this.series.detachPrimitive(this);
-    this.requestUpdate = null;
-  }
-
-  public rebind(series: SeriesApi): void {
-    if (this.series === series) {
-      return;
-    }
-
-    this.showCrosshair();
-    this.unbindEvents();
-    this.series.detachPrimitive(this);
-
-    this.series = series;
-    this.requestUpdate = null;
-
-    this.series.attachPrimitive(this);
-    this.render();
-  }
-
   public isCreationPending(): boolean {
     return this.mode === 'idle';
   }
 
-  public shouldShowInObjectTree(): boolean {
-    return this.mode !== 'idle';
-  }
-
-  public getSettings(): TextSettings {
-    return { ...this.settings };
-  }
-
   public getSettingsTabs(): SettingsTab[] {
     return getTextSettingsTabs(this.settings);
   }
 
-  public updateSettings(settings: SettingsValues): void {
-    this.settings = {
-      ...this.settings,
-      ...settings,
-    };
-
-    this.render();
-  }
-
   public getState(): TextState {
     return {
       hidden: this.hidden,
-      isActive: this.isActive,
+      isActive: this.isActive.value,
       mode: this.mode,
       point: this.point,
       settings: { ...this.settings },
@@ -223,7 +152,9 @@ export class Text implements ISeriesDrawing {
     const nextState = state as Partial<TextState>;
 
     this.hidden = typeof nextState.hidden === 'boolean' ? nextState.hidden : this.hidden;
-    this.isActive = typeof nextState.isActive === 'boolean' ? nextState.isActive : this.isActive;
+    if (typeof nextState.isActive === 'boolean') {
+      this.isActive.next(nextState.isActive);
+    }
     this.mode = nextState.mode ?? this.mode;
     this.point = nextState.point ?? this.point;
 
@@ -237,17 +168,6 @@ export class Text implements ISeriesDrawing {
     this.render();
   }
 
-  public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
-    this.requestUpdate = param.requestUpdate;
-    this.bindEvents();
-  }
-
-  public detached(): void {
-    this.showCrosshair();
-    this.unbindEvents();
-    this.requestUpdate = null;
-  }
-
   public updateAllViews(): void {
     updateViews([this.paneView, this.timeAxisView, this.priceAxisView]);
   }
@@ -272,10 +192,6 @@ export class Text implements ISeriesDrawing {
     return [this.priceAxisView];
   }
 
-  public autoscaleInfo(): AutoscaleInfo | null {
-    return null;
-  }
-
   public getRenderData(): TextRenderData | null {
     if (this.hidden) {
       return null;
@@ -290,12 +206,12 @@ export class Text implements ISeriesDrawing {
     return {
       ...geometry,
       ...this.settings,
-      showHandles: this.isActive,
+      showHandles: this.isActive.value,
     };
   }
 
   public getTimeAxisLabel(kind: string): AxisLabel | null {
-    if (kind !== 'main' || !this.isActive || !this.point || typeof this.point.time !== 'number') {
+    if (kind !== 'main' || !this.isActive.value || !this.point || typeof this.point.time !== 'number') {
       return null;
     }
 
@@ -321,7 +237,7 @@ export class Text implements ISeriesDrawing {
   }
 
   public getPriceAxisLabel(kind: string): AxisLabel | null {
-    if (kind !== 'main' || !this.isActive || !this.point) {
+    if (kind !== 'main' || !this.isActive.value || !this.point) {
       return null;
     }
 
@@ -357,7 +273,15 @@ export class Text implements ISeriesDrawing {
     };
   }
 
-  private bindEvents(): void {
+  public getPriceAxisSegments(): AxisSegment[] {
+    return [];
+  }
+
+  public getTimeAxisSegments(): AxisSegment[] {
+    return [];
+  }
+
+  protected bindEvents(): void {
     if (this.isBound) {
       return;
     }
@@ -371,7 +295,7 @@ export class Text implements ISeriesDrawing {
     window.addEventListener('pointercancel', this.handlePointerUp);
   }
 
-  private unbindEvents(): void {
+  protected unbindEvents(): void {
     if (!this.isBound) {
       return;
     }
@@ -406,7 +330,7 @@ export class Text implements ISeriesDrawing {
 
     const containsPoint = this.containsPoint(point);
 
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       if (!containsPoint) {
         return;
       }
@@ -414,7 +338,7 @@ export class Text implements ISeriesDrawing {
       event.preventDefault();
       event.stopPropagation();
 
-      this.isActive = true;
+      this.isActive.next(true);
       this.render();
       return;
     }
@@ -427,12 +351,12 @@ export class Text implements ISeriesDrawing {
       return;
     }
 
-    this.isActive = false;
+    this.isActive.next(false);
     this.render();
   };
 
   private handleDoubleClick = (event: MouseEvent): void => {
-    if (this.hidden || this.mode !== 'ready' || !this.isActive) {
+    if (this.hidden || this.mode !== 'ready' || !this.isActive.value) {
       return;
     }
 
@@ -477,8 +401,10 @@ export class Text implements ISeriesDrawing {
     }
 
     this.point = anchor;
-    this.isActive = true;
+    this.isActive.next(true);
     this.mode = 'ready';
+    this.resolveReady?.();
+
     this.render();
   }
 
@@ -494,6 +420,8 @@ export class Text implements ISeriesDrawing {
 
   private finishDragging(): void {
     this.mode = 'ready';
+    this.resolveReady?.();
+
     this.dragPointerId = null;
     this.dragStartPoint = null;
     this.dragGeometrySnapshot = null;
@@ -531,7 +459,7 @@ export class Text implements ISeriesDrawing {
     return getAnchorFromPoint(this.chart, this.series, point);
   }
 
-  private getGeometry(): TextGeometry | null {
+  protected getGeometry(): TextGeometry | null {
     if (!this.point) {
       return null;
     }
@@ -587,22 +515,6 @@ export class Text implements ISeriesDrawing {
     return isPointInBounds(point, geometry, 2);
   }
 
-  private hideCrosshair(): void {
-    this.chart.applyOptions({
-      crosshair: {
-        mode: CrosshairMode.Hidden,
-      },
-    });
-  }
-
-  private showCrosshair(): void {
-    this.chart.applyOptions({
-      crosshair: {
-        mode: CrosshairMode.Normal,
-      },
-    });
-  }
-
   private getContainerSize(): { width: number; height: number } {
     return getElementContainerSize(this.container);
   }
@@ -610,15 +522,6 @@ export class Text implements ISeriesDrawing {
   private clampPointToContainer(point: Point): Point {
     return clampPointToContainerInElement(point, this.container);
   }
-
-  private getEventPoint(event: PointerEvent): Point {
-    return getPointerPointFromEvent(this.container, event);
-  }
-
-  private render(): void {
-    this.updateAllViews();
-    this.requestUpdate?.();
-  }
 }
 
 function getTextLines(text: string): string[] {
diff --git a/src/core/Drawings/traectory/traectory.ts b/src/core/Drawings/traectory/traectory.ts
index 7d9b61a..968f22a 100644
--- a/src/core/Drawings/traectory/traectory.ts
+++ b/src/core/Drawings/traectory/traectory.ts
@@ -1,26 +1,18 @@
-import {
-  AutoscaleInfo,
-  CrosshairMode,
-  IChartApi,
-  IPrimitivePaneView,
-  PrimitiveHoveredItem,
-  SeriesAttachedParameter,
-  SeriesOptionsMap,
-  Time,
-} from 'lightweight-charts';
+import { IChartApi, IPrimitivePaneView, PrimitiveHoveredItem } from 'lightweight-charts';
 import { Observable } from 'rxjs';
 
 import { CustomPriceAxisPaneView, CustomTimeAxisPaneView } from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
 import {
   clamp,
   clampPointToContainer as clampPointToContainerInElement,
   getAnchorFromPoint,
   getContainerSize as getElementContainerSize,
-  getPointerPoint as getPointerPointFromEvent,
   getXCoordinateFromTime,
   getYCoordinateFromPrice,
   isNearPoint,
 } from '@core/Drawings/helpers';
+import { AxisLabel } from '@core/Drawings/types';
 import { updateViews } from '@core/Drawings/utils';
 
 import { getThemeStore } from '@src/theme';
@@ -31,7 +23,7 @@ import { createDefaultSettings, getTraectorySettingsTabs, TraectorySettings, Tra
 
 import type { ISeriesDrawing } from '@core/Drawings/common';
 import type { Anchor, AxisSegment, Point, SeriesApi } from '@core/Drawings/types';
-import type { ChartOptionsModel, SettingsTab, SettingsValues } from '@src/types';
+import type { ChartOptionsModel, SettingsTab } from '@src/types';
 
 type TraectoryMode = 'idle' | 'drawing' | 'ready' | 'dragging-point' | 'dragging-body';
 
@@ -68,21 +60,15 @@ const POINT_HIT_TOLERANCE = 8;
 const SEGMENT_HIT_TOLERANCE = 6;
 const MIN_POINTS_COUNT = 2;
 
-export class Traectory implements ISeriesDrawing {
-  private chart: IChartApi;
-  private series: SeriesApi;
-  private container: HTMLElement;
+export class Traectory extends SeriesDrawingBase<TraectorySettings> implements ISeriesDrawing {
   private removeSelf?: () => void;
   private openSettings?: () => void;
 
-  private settings: TraectorySettings = createDefaultSettings();
+  protected settings: TraectorySettings = createDefaultSettings();
 
-  private requestUpdate: (() => void) | null = null;
   private isBound = false;
 
-  private hidden = false;
-  private isActive = false;
-  private mode: TraectoryMode = 'idle';
+  protected mode: TraectoryMode = 'idle';
 
   private points: Anchor[] = [];
   private previewAnchor: Anchor | null = null;
@@ -97,11 +83,9 @@ export class Traectory implements ISeriesDrawing {
   private readonly priceAxisPaneView: CustomPriceAxisPaneView;
 
   constructor(chart: IChartApi, series: SeriesApi, params: TraectoryParams) {
-    const { container, removeSelf, openSettings } = params;
+    super({ chart, series, container: params.container });
+    const { removeSelf, openSettings } = params;
 
-    this.chart = chart;
-    this.series = series;
-    this.container = container;
     this.removeSelf = removeSelf;
     this.openSettings = openSettings;
 
@@ -118,52 +102,14 @@ export class Traectory implements ISeriesDrawing {
     this.series.attachPrimitive(this);
   }
 
-  public show(): void {
-    this.hidden = false;
-    this.render();
-  }
-
-  public hide(): void {
-    this.hidden = true;
-    this.showCrosshair();
-    this.render();
-  }
-
-  public destroy(): void {
-    this.showCrosshair();
-    this.unbindEvents();
-    this.series.detachPrimitive(this);
-    this.requestUpdate = null;
-  }
-
-  public rebind(series: SeriesApi): void {
-    if (this.series === series) {
-      return;
-    }
-
-    this.showCrosshair();
-    this.unbindEvents();
-    this.series.detachPrimitive(this);
-
-    this.series = series;
-    this.requestUpdate = null;
-
-    this.series.attachPrimitive(this);
-    this.render();
-  }
-
   public isCreationPending(): boolean {
     return this.mode === 'idle' || this.mode === 'drawing';
   }
 
-  public shouldShowInObjectTree(): boolean {
-    return this.mode !== 'idle';
-  }
-
   public getState(): TraectoryState {
     return {
       hidden: this.hidden,
-      isActive: this.isActive,
+      isActive: this.isActive.value,
       mode: this.mode,
       points: this.points,
       settings: { ...this.settings },
@@ -178,7 +124,9 @@ export class Traectory implements ISeriesDrawing {
     const nextState = state as Partial<TraectoryState>;
 
     this.hidden = typeof nextState.hidden === 'boolean' ? nextState.hidden : this.hidden;
-    this.isActive = typeof nextState.isActive === 'boolean' ? nextState.isActive : this.isActive;
+    if (typeof nextState.isActive === 'boolean') {
+      this.isActive.next(nextState.isActive);
+    }
     this.mode = nextState.mode ?? this.mode;
 
     this.points = Array.isArray(nextState.points) ? nextState.points : this.points;
@@ -193,34 +141,10 @@ export class Traectory implements ISeriesDrawing {
     this.render();
   }
 
-  public getSettings(): SettingsValues {
-    return { ...this.settings };
-  }
-
   public getSettingsTabs(): SettingsTab[] {
     return getTraectorySettingsTabs(this.settings);
   }
 
-  public updateSettings(settings: SettingsValues): void {
-    this.settings = {
-      ...this.settings,
-      ...settings,
-    };
-
-    this.render();
-  }
-
-  public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
-    this.requestUpdate = param.requestUpdate;
-    this.bindEvents();
-  }
-
-  public detached(): void {
-    this.showCrosshair();
-    this.unbindEvents();
-    this.requestUpdate = null;
-  }
-
   public updateAllViews(): void {
     updateViews([this.paneView, this.timeAxisPaneView, this.priceAxisPaneView]);
   }
@@ -245,10 +169,6 @@ export class Traectory implements ISeriesDrawing {
     return [];
   }
 
-  public autoscaleInfo(): AutoscaleInfo | null {
-    return null;
-  }
-
   public getRenderData(): TraectoryRenderData | null {
     if (this.hidden) {
       return null;
@@ -263,14 +183,14 @@ export class Traectory implements ISeriesDrawing {
     return {
       ...geometry,
       previewPoint: this.getPreviewPoint(),
-      showHandles: this.mode === 'drawing' || this.isActive,
+      showHandles: this.mode === 'drawing' || this.isActive.value,
       showArrow: this.mode !== 'drawing' && geometry.points.length > 1,
       ...this.settings,
     };
   }
 
   public getTimeAxisSegments(): AxisSegment[] {
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       return [];
     }
 
@@ -292,7 +212,7 @@ export class Traectory implements ISeriesDrawing {
   }
 
   public getPriceAxisSegments(): AxisSegment[] {
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       return [];
     }
 
@@ -340,7 +260,15 @@ export class Traectory implements ISeriesDrawing {
     };
   }
 
-  private bindEvents(): void {
+  public getPriceAxisLabel(kind: string): AxisLabel | null {
+    return null;
+  }
+
+  public getTimeAxisLabel(kind: string): AxisLabel | null {
+    return null;
+  }
+
+  protected bindEvents(): void {
     if (this.isBound) {
       return;
     }
@@ -355,7 +283,7 @@ export class Traectory implements ISeriesDrawing {
     window.addEventListener('pointercancel', this.handlePointerUp);
   }
 
-  private unbindEvents(): void {
+  protected unbindEvents(): void {
     if (!this.isBound) {
       return;
     }
@@ -386,7 +314,7 @@ export class Traectory implements ISeriesDrawing {
       return;
     }
 
-    if (this.mode !== 'ready' || !this.isActive) {
+    if (this.mode !== 'ready' || !this.isActive.value) {
       return;
     }
 
@@ -447,7 +375,7 @@ export class Traectory implements ISeriesDrawing {
     const pointIndex = this.getPointIndexAt(point);
     const isInsideTraectory = pointIndex !== null || this.isPointNearTraectory(point);
 
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       if (!isInsideTraectory) {
         return;
       }
@@ -455,7 +383,7 @@ export class Traectory implements ISeriesDrawing {
       event.preventDefault();
       event.stopPropagation();
 
-      this.isActive = true;
+      this.isActive.next(true);
       this.render();
       return;
     }
@@ -469,7 +397,7 @@ export class Traectory implements ISeriesDrawing {
     }
 
     if (!this.isPointNearTraectory(point)) {
-      this.isActive = false;
+      this.isActive.next(false);
       this.render();
       return;
     }
@@ -525,7 +453,7 @@ export class Traectory implements ISeriesDrawing {
 
     this.points = [anchor];
     this.previewAnchor = anchor;
-    this.isActive = true;
+    this.isActive.next(true);
     this.mode = 'drawing';
 
     this.render();
@@ -574,8 +502,9 @@ export class Traectory implements ISeriesDrawing {
     }
 
     this.previewAnchor = null;
-    this.isActive = true;
+    this.isActive.next(true);
     this.mode = 'ready';
+    this.resolveReady?.();
 
     this.render();
   }
@@ -606,6 +535,7 @@ export class Traectory implements ISeriesDrawing {
 
   private finishDragging(): void {
     this.mode = 'ready';
+    this.resolveReady?.();
 
     this.dragPointerId = null;
     this.dragStartPoint = null;
@@ -617,7 +547,7 @@ export class Traectory implements ISeriesDrawing {
   }
 
   private resetToIdle(): void {
-    this.isActive = false;
+    this.isActive.next(false);
     this.mode = 'idle';
 
     this.points = [];
@@ -693,7 +623,7 @@ export class Traectory implements ISeriesDrawing {
     return getAnchorFromPoint(this.chart, this.series, point);
   }
 
-  private getGeometry(): TraectoryGeometry | null {
+  protected getGeometry(): TraectoryGeometry | null {
     if (!this.points.length) {
       return null;
     }
@@ -805,22 +735,6 @@ export class Traectory implements ISeriesDrawing {
     return Math.hypot(point.x - projectionX, point.y - projectionY);
   }
 
-  private hideCrosshair(): void {
-    this.chart.applyOptions({
-      crosshair: {
-        mode: CrosshairMode.Hidden,
-      },
-    });
-  }
-
-  private showCrosshair(): void {
-    this.chart.applyOptions({
-      crosshair: {
-        mode: CrosshairMode.Normal,
-      },
-    });
-  }
-
   private getContainerSize(): { width: number; height: number } {
     return getElementContainerSize(this.container);
   }
@@ -828,13 +742,4 @@ export class Traectory implements ISeriesDrawing {
   private clampPointToContainer(point: Point): Point {
     return clampPointToContainerInElement(point, this.container);
   }
-
-  private getEventPoint(event: PointerEvent): Point {
-    return getPointerPointFromEvent(this.container, event);
-  }
-
-  private render(): void {
-    this.updateAllViews();
-    this.requestUpdate?.();
-  }
 }
diff --git a/src/core/Drawings/trendLine/trendLine.ts b/src/core/Drawings/trendLine/trendLine.ts
index 508ade0..514e804 100644
--- a/src/core/Drawings/trendLine/trendLine.ts
+++ b/src/core/Drawings/trendLine/trendLine.ts
@@ -1,16 +1,5 @@
-import {
-  AutoscaleInfo,
-  CrosshairMode,
-  IChartApi,
-  IPrimitivePaneView,
-  Logical,
-  PrimitiveHoveredItem,
-  SeriesAttachedParameter,
-  SeriesOptionsMap,
-  Time,
-  UTCTimestamp,
-} from 'lightweight-charts';
-import { Observable, Subscription } from 'rxjs';
+import { IChartApi, IPrimitivePaneView, PrimitiveHoveredItem, UTCTimestamp } from 'lightweight-charts';
+import { Observable } from 'rxjs';
 
 import {
   CustomPriceAxisPaneView,
@@ -18,9 +7,9 @@ import {
   CustomTimeAxisPaneView,
   CustomTimeAxisView,
 } from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
 import {
   getAnchorFromPoint,
-  getPointerPoint as getPointerPointFromEvent,
   getPriceDelta as getPriceDeltaFromCoordinates,
   getXCoordinateFromTime,
   getYCoordinateFromPrice,
@@ -46,7 +35,7 @@ import {
 
 import type { ISeriesDrawing } from '@core/Drawings/common';
 import type { Anchor, AxisLabel, AxisSegment, Point, SeriesApi } from '@core/Drawings/types';
-import type { ChartOptionsModel, SettingsTab, SettingsValues } from '@src/types';
+import type { ChartOptionsModel, SettingsTab } from '@src/types';
 
 type TrendLineMode = 'idle' | 'drawing' | 'ready' | 'dragging-start' | 'dragging-end' | 'dragging-body';
 type TimeLabelKind = 'start' | 'end';
@@ -84,22 +73,15 @@ export interface TrendLineRenderData extends TrendLineGeometry, TrendLineStyle,
 const LINE_HIT_TOLERANCE = 6;
 const MIN_LINE_SIZE = 4;
 
-export class TrendLine implements ISeriesDrawing {
-  private chart: IChartApi;
-  private series: SeriesApi;
-  private container: HTMLElement;
+export class TrendLine extends SeriesDrawingBase<TrendLineSettings> implements ISeriesDrawing {
   private removeSelf?: () => void;
   private openSettings?: () => void;
 
-  private settings: TrendLineSettings = createDefaultSettings();
+  protected settings: TrendLineSettings = createDefaultSettings();
 
-  private requestUpdate: (() => void) | null = null;
-  private subscriptions = new Subscription();
   private isBound = false;
 
-  private hidden = false;
-  private isActive = false;
-  private mode: TrendLineMode = 'idle';
+  protected mode: TrendLineMode = 'idle';
 
   private startAnchor: Anchor | null = null;
   private endAnchor: Anchor | null = null;
@@ -127,9 +109,7 @@ export class TrendLine implements ISeriesDrawing {
     series: SeriesApi,
     { container, formatObservable, removeSelf, openSettings }: TrendLineParams,
   ) {
-    this.chart = chart;
-    this.series = series;
-    this.container = container;
+    super({ chart, series, container });
     this.removeSelf = removeSelf;
     this.openSettings = openSettings;
 
@@ -175,53 +155,14 @@ export class TrendLine implements ISeriesDrawing {
     this.series.attachPrimitive(this);
   }
 
-  public show(): void {
-    this.hidden = false;
-    this.render();
-  }
-
-  public hide(): void {
-    this.hidden = true;
-    this.showCrosshair();
-    this.render();
-  }
-
-  public destroy(): void {
-    this.showCrosshair();
-    this.unbindEvents();
-    this.subscriptions.unsubscribe();
-    this.series.detachPrimitive(this);
-    this.requestUpdate = null;
-  }
-
-  public rebind(series: SeriesApi): void {
-    if (this.series === series) {
-      return;
-    }
-
-    this.showCrosshair();
-    this.unbindEvents();
-    this.series.detachPrimitive(this);
-
-    this.series = series;
-    this.requestUpdate = null;
-
-    this.series.attachPrimitive(this);
-    this.render();
-  }
-
   public isCreationPending(): boolean {
     return this.mode === 'idle' || this.mode === 'drawing';
   }
 
-  public shouldShowInObjectTree(): boolean {
-    return this.mode !== 'idle';
-  }
-
   public getState(): TrendLineState {
     return {
       hidden: this.hidden,
-      isActive: this.isActive,
+      isActive: this.isActive.value,
       mode: this.mode,
       startAnchor: this.startAnchor,
       endAnchor: this.endAnchor,
@@ -237,7 +178,7 @@ export class TrendLine implements ISeriesDrawing {
     }
 
     if ('isActive' in nextState && typeof nextState.isActive === 'boolean') {
-      this.isActive = nextState.isActive;
+      this.isActive.next(nextState.isActive);
     }
 
     if ('mode' in nextState && nextState.mode) {
@@ -262,34 +203,10 @@ export class TrendLine implements ISeriesDrawing {
     this.render();
   }
 
-  public getSettings(): SettingsValues {
-    return { ...this.settings };
-  }
-
-  public updateSettings(settings: SettingsValues): void {
-    this.settings = {
-      ...this.settings,
-      ...settings,
-    };
-
-    this.render();
-  }
-
   public getSettingsTabs(): SettingsTab[] {
     return getTrendLineSettingsTabs(this.settings);
   }
 
-  public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
-    this.requestUpdate = param.requestUpdate;
-    this.bindEvents();
-  }
-
-  public detached(): void {
-    this.showCrosshair();
-    this.unbindEvents();
-    this.requestUpdate = null;
-  }
-
   public updateAllViews(): void {
     updateViews([
       this.paneView,
@@ -322,10 +239,6 @@ export class TrendLine implements ISeriesDrawing {
     return [this.startPriceAxisView, this.endPriceAxisView];
   }
 
-  public autoscaleInfo(_start: Logical, _end: Logical): AutoscaleInfo | null {
-    return null;
-  }
-
   public getRenderData(): TrendLineRenderData | null {
     if (this.hidden) {
       return null;
@@ -339,7 +252,7 @@ export class TrendLine implements ISeriesDrawing {
 
     return {
       ...geometry,
-      showHandles: this.isActive,
+      showHandles: this.isActive.value,
       ...this.settings,
     };
   }
@@ -371,7 +284,7 @@ export class TrendLine implements ISeriesDrawing {
   }
 
   public getTimeAxisSegments(): AxisSegment[] {
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       return [];
     }
 
@@ -393,7 +306,7 @@ export class TrendLine implements ISeriesDrawing {
   }
 
   public getPriceAxisSegments(): AxisSegment[] {
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       return [];
     }
 
@@ -415,7 +328,7 @@ export class TrendLine implements ISeriesDrawing {
   }
 
   public getTimeAxisLabel(kind: string): AxisLabel | null {
-    if (!this.isActive || (kind !== 'start' && kind !== 'end')) {
+    if (!this.isActive.value || (kind !== 'start' && kind !== 'end')) {
       return null;
     }
 
@@ -437,7 +350,7 @@ export class TrendLine implements ISeriesDrawing {
   }
 
   public getPriceAxisLabel(kind: string): AxisLabel | null {
-    if (!this.isActive || (kind !== 'start' && kind !== 'end')) {
+    if (!this.isActive.value || (kind !== 'start' && kind !== 'end')) {
       return null;
     }
 
@@ -458,7 +371,7 @@ export class TrendLine implements ISeriesDrawing {
     };
   }
 
-  private bindEvents(): void {
+  protected bindEvents(): void {
     if (this.isBound) {
       return;
     }
@@ -472,7 +385,7 @@ export class TrendLine implements ISeriesDrawing {
     window.addEventListener('pointercancel', this.handlePointerUp);
   }
 
-  private unbindEvents(): void {
+  protected unbindEvents(): void {
     if (!this.isBound) {
       return;
     }
@@ -537,7 +450,7 @@ export class TrendLine implements ISeriesDrawing {
 
     const pointTarget = this.getPointTarget(point);
 
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       if (!pointTarget && !this.isPointNearLine(point)) {
         return;
       }
@@ -545,7 +458,7 @@ export class TrendLine implements ISeriesDrawing {
       event.preventDefault();
       event.stopPropagation();
 
-      this.isActive = true;
+      this.isActive.next(true);
       this.render();
       return;
     }
@@ -574,7 +487,7 @@ export class TrendLine implements ISeriesDrawing {
       return;
     }
 
-    this.isActive = false;
+    this.isActive.next(false);
     this.render();
   };
 
@@ -627,7 +540,7 @@ export class TrendLine implements ISeriesDrawing {
 
     this.startAnchor = anchor;
     this.endAnchor = anchor;
-    this.isActive = true;
+    this.isActive.next(true);
     this.mode = 'drawing';
 
     this.render();
@@ -662,6 +575,7 @@ export class TrendLine implements ISeriesDrawing {
     }
 
     this.mode = 'ready';
+    this.resolveReady?.();
 
     this.render();
   }
@@ -679,6 +593,7 @@ export class TrendLine implements ISeriesDrawing {
 
   private finishDragging(): void {
     this.mode = 'ready';
+    this.resolveReady?.();
 
     this.dragPointerId = null;
     this.dragStartPoint = null;
@@ -736,7 +651,7 @@ export class TrendLine implements ISeriesDrawing {
     return getAnchorFromPoint(this.chart, this.series, point);
   }
 
-  private getGeometry(): TrendLineGeometry | null {
+  protected getGeometry(): TrendLineGeometry | null {
     if (!this.startAnchor || !this.endAnchor) {
       return null;
     }
@@ -865,29 +780,4 @@ export class TrendLine implements ISeriesDrawing {
 
     return formatPrice(anchor.price) ?? '';
   }
-
-  private hideCrosshair(): void {
-    this.chart.applyOptions({
-      crosshair: {
-        mode: CrosshairMode.Hidden,
-      },
-    });
-  }
-
-  private showCrosshair(): void {
-    this.chart.applyOptions({
-      crosshair: {
-        mode: CrosshairMode.Normal,
-      },
-    });
-  }
-
-  private getEventPoint(event: PointerEvent): Point {
-    return getPointerPointFromEvent(this.container, event);
-  }
-
-  private render(): void {
-    this.updateAllViews();
-    this.requestUpdate?.();
-  }
 }
diff --git a/src/core/Drawings/volumeProfile/volumeProfile.ts b/src/core/Drawings/volumeProfile/volumeProfile.ts
index 34b9008..3b4ce51 100644
--- a/src/core/Drawings/volumeProfile/volumeProfile.ts
+++ b/src/core/Drawings/volumeProfile/volumeProfile.ts
@@ -1,17 +1,6 @@
-import {
-  AutoscaleInfo,
-  CrosshairMode,
-  IChartApi,
-  IPrimitivePaneView,
-  Logical,
-  PrimitiveHoveredItem,
-  SeriesAttachedParameter,
-  SeriesOptionsMap,
-  Time,
-  UTCTimestamp,
-} from 'lightweight-charts';
-
-import { Observable, Subscription } from 'rxjs';
+import { IChartApi, IPrimitivePaneView, PrimitiveHoveredItem, Time, UTCTimestamp } from 'lightweight-charts';
+
+import { Observable } from 'rxjs';
 
 import {
   CustomPriceAxisPaneView,
@@ -19,12 +8,12 @@ import {
   CustomTimeAxisPaneView,
   CustomTimeAxisView,
 } from '@core/Drawings/axis';
+import { SeriesDrawingBase } from '@core/Drawings/common';
 import {
   clamp,
   clampPointToContainer as clampPointToContainerInElement,
   getAnchorFromPoint,
   getContainerSize as getElementContainerSize,
-  getPointerPoint as getPointerPointFromEvent,
   getXCoordinateFromTime,
   getYCoordinateFromPrice,
   isNearPoint,
@@ -33,7 +22,7 @@ import {
 import { updateViews } from '@core/Drawings/utils';
 import { getThemeStore } from '@src/theme';
 
-import { SettingsTab, SettingsValues } from '@src/types';
+import { SettingsTab } from '@src/types';
 import { Defaults } from '@src/types/defaults';
 import { formatPrice } from '@src/utils';
 import { formatDate } from '@src/utils/formatter';
@@ -127,23 +116,15 @@ const BODY_HIT_TOLERANCE = 4;
 const MIN_PROFILE_SIZE = 8;
 const MAX_VISIBLE_RANGE_START_RATIO = 0.95;
 
-export class VolumeProfile implements ISeriesDrawing {
-  private chart: IChartApi;
-  private series: SeriesApi;
-  private container: HTMLElement;
+export class VolumeProfile extends SeriesDrawingBase<VolumeProfileSettings> implements ISeriesDrawing {
   private removeSelf?: () => void;
   private openSettings?: () => void;
 
   private readonly profileKind: VolumeProfileKind;
-  private settings: VolumeProfileSettings = createDefaultSettings();
+  protected settings: VolumeProfileSettings = createDefaultSettings();
 
-  private requestUpdate: (() => void) | null = null;
   private isBound = false;
-  private subscriptions = new Subscription();
-
-  private hidden = false;
-  private isActive = false;
-  private mode: VolumeProfileMode = 'idle';
+  protected mode: VolumeProfileMode = 'idle';
 
   private startAnchor: Anchor | null = null;
   private endAnchor: Anchor | null = null;
@@ -178,16 +159,14 @@ export class VolumeProfile implements ISeriesDrawing {
     series: SeriesApi,
     { container, profileKind = 'fixedRange', formatObservable, removeSelf, openSettings }: VolumeProfileParams,
   ) {
-    this.chart = chart;
-    this.series = series;
-    this.container = container;
+    super({ chart, series, container });
     this.profileKind = profileKind;
     this.removeSelf = removeSelf;
     this.openSettings = openSettings;
 
     if (this.profileKind === 'visibleRange') {
       this.mode = 'ready';
-      this.isActive = true;
+      this.isActive.next(true);
     }
 
     this.paneView = new VolumeProfilePaneView(this);
@@ -237,45 +216,11 @@ export class VolumeProfile implements ISeriesDrawing {
     this.series.attachPrimitive(this);
   }
 
-  public show(): void {
-    this.hidden = false;
-    this.calculateProfile();
-    this.render();
-  }
-
-  public hide(): void {
-    this.hidden = true;
-    this.showCrosshair();
-    this.render();
-  }
-
   public destroy(): void {
     if (this.profileKind === 'visibleRange') {
       this.chart.timeScale().unsubscribeVisibleLogicalRangeChange(this.handleVisibleLogicalRangeChange);
     }
-
-    this.showCrosshair();
-    this.unbindEvents();
-    this.subscriptions.unsubscribe();
-    this.series.detachPrimitive(this);
-    this.requestUpdate = null;
-  }
-
-  public rebind(series: SeriesApi): void {
-    if (this.series === series) {
-      return;
-    }
-
-    this.showCrosshair();
-    this.unbindEvents();
-    this.series.detachPrimitive(this);
-
-    this.series = series;
-    this.requestUpdate = null;
-
-    this.series.attachPrimitive(this);
-    this.calculateProfile();
-    this.render();
+    super.destroy();
   }
 
   public isCreationPending(): boolean {
@@ -291,13 +236,13 @@ export class VolumeProfile implements ISeriesDrawing {
       return true;
     }
 
-    return this.mode !== 'idle';
+    return super.shouldShowInObjectTree();
   }
 
   public getState(): VolumeProfileState {
     return {
       hidden: this.hidden,
-      isActive: this.isActive,
+      isActive: this.isActive.value,
       mode: this.mode,
       startAnchor: this.startAnchor,
       endAnchor: this.endAnchor,
@@ -314,7 +259,9 @@ export class VolumeProfile implements ISeriesDrawing {
     const nextState = state as Partial<VolumeProfileState>;
 
     this.hidden = typeof nextState.hidden === 'boolean' ? nextState.hidden : this.hidden;
-    this.isActive = typeof nextState.isActive === 'boolean' ? nextState.isActive : this.isActive;
+    if (typeof nextState.isActive === 'boolean') {
+      this.isActive.next(nextState.isActive);
+    }
 
     if (this.profileKind === 'fixedRange') {
       this.mode = nextState.mode ?? this.mode;
@@ -324,6 +271,7 @@ export class VolumeProfile implements ISeriesDrawing {
 
     if (this.profileKind === 'visibleRange') {
       this.mode = 'ready';
+      this.resolveReady?.();
 
       if (typeof nextState.visibleRangeStartRatio === 'number') {
         this.visibleRangeStartRatio = clamp(nextState.visibleRangeStartRatio, 0, MAX_VISIBLE_RANGE_START_RATIO);
@@ -341,34 +289,10 @@ export class VolumeProfile implements ISeriesDrawing {
     this.render();
   }
 
-  public getSettings(): SettingsValues {
-    return { ...this.settings };
-  }
-
   public getSettingsTabs(): SettingsTab[] {
     return getVolumeProfileSettingsTabs(this.settings);
   }
 
-  public updateSettings(settings: SettingsValues): void {
-    this.settings = {
-      ...this.settings,
-      ...settings,
-    };
-
-    this.render();
-  }
-
-  public attached(param: SeriesAttachedParameter<Time, keyof SeriesOptionsMap>): void {
-    this.requestUpdate = param.requestUpdate;
-    this.bindEvents();
-  }
-
-  public detached(): void {
-    this.showCrosshair();
-    this.unbindEvents();
-    this.requestUpdate = null;
-  }
-
   public updateAllViews(): void {
     updateViews([
       this.paneView,
@@ -417,10 +341,6 @@ export class VolumeProfile implements ISeriesDrawing {
     return [this.startPriceAxisView, this.endPriceAxisView];
   }
 
-  public autoscaleInfo(_start: Logical, _end: Logical): AutoscaleInfo | null {
-    return null;
-  }
-
   public getRenderData(): VolumeProfileRenderData | null {
     if (this.hidden) {
       return null;
@@ -439,13 +359,13 @@ export class VolumeProfile implements ISeriesDrawing {
       profileKind: this.profileKind,
       rows,
       pocY,
-      showHandles: this.isActive || this.mode === 'drawing',
+      showHandles: this.isActive.value || this.mode === 'drawing',
       ...this.settings,
     };
   }
 
   public getTimeAxisSegments(): AxisSegment[] {
-    if (this.profileKind === 'visibleRange' || !this.isActive) {
+    if (this.profileKind === 'visibleRange' || !this.isActive.value) {
       return [];
     }
 
@@ -467,7 +387,7 @@ export class VolumeProfile implements ISeriesDrawing {
   }
 
   public getPriceAxisSegments(): AxisSegment[] {
-    if (this.profileKind === 'visibleRange' || !this.isActive) {
+    if (this.profileKind === 'visibleRange' || !this.isActive.value) {
       return [];
     }
 
@@ -493,7 +413,7 @@ export class VolumeProfile implements ISeriesDrawing {
       return null;
     }
 
-    if (!this.isActive || (kind !== 'start' && kind !== 'end')) {
+    if (!this.isActive.value || (kind !== 'start' && kind !== 'end')) {
       return null;
     }
 
@@ -529,7 +449,7 @@ export class VolumeProfile implements ISeriesDrawing {
       return null;
     }
 
-    if (!this.isActive || (kind !== 'start' && kind !== 'end')) {
+    if (!this.isActive.value || (kind !== 'start' && kind !== 'end')) {
       return null;
     }
 
@@ -575,7 +495,7 @@ export class VolumeProfile implements ISeriesDrawing {
     }
 
     return {
-      cursorStyle: this.isActive ? 'grab' : 'pointer',
+      cursorStyle: this.isActive.value ? 'grab' : 'pointer',
       externalId: 'volume-profile',
       zOrder: 'top',
     };
@@ -590,7 +510,7 @@ export class VolumeProfile implements ISeriesDrawing {
     this.render();
   };
 
-  private bindEvents(): void {
+  protected bindEvents(): void {
     if (this.isBound) {
       return;
     }
@@ -604,7 +524,7 @@ export class VolumeProfile implements ISeriesDrawing {
     window.addEventListener('pointercancel', this.handlePointerUp);
   }
 
-  private unbindEvents(): void {
+  protected unbindEvents(): void {
     if (!this.isBound) {
       return;
     }
@@ -619,7 +539,7 @@ export class VolumeProfile implements ISeriesDrawing {
   }
 
   private handleDoubleClick = (event: MouseEvent): void => {
-    if (this.hidden || this.mode !== 'ready' || !this.isActive) {
+    if (this.hidden || this.mode !== 'ready' || !this.isActive.value) {
       return;
     }
 
@@ -654,7 +574,7 @@ export class VolumeProfile implements ISeriesDrawing {
     const dragTarget = this.getVisibleRangeDragTarget(point);
 
     if (!dragTarget) {
-      this.isActive = false;
+      this.isActive.next(false);
       this.render();
       return;
     }
@@ -662,7 +582,7 @@ export class VolumeProfile implements ISeriesDrawing {
     event.preventDefault();
     event.stopPropagation();
 
-    this.isActive = true;
+    this.isActive.next(true);
 
     if (dragTarget !== 'poc') {
       this.render();
@@ -701,7 +621,7 @@ export class VolumeProfile implements ISeriesDrawing {
 
     const dragTarget = this.getFixedRangeDragTarget(point);
 
-    if (!this.isActive) {
+    if (!this.isActive.value) {
       if (!dragTarget) {
         return;
       }
@@ -709,13 +629,13 @@ export class VolumeProfile implements ISeriesDrawing {
       event.preventDefault();
       event.stopPropagation();
 
-      this.isActive = true;
+      this.isActive.next(true);
       this.render();
       return;
     }
 
     if (!dragTarget) {
-      this.isActive = false;
+      this.isActive.next(false);
       this.render();
       return;
     }
@@ -788,7 +708,7 @@ export class VolumeProfile implements ISeriesDrawing {
 
     this.startAnchor = anchor;
     this.endAnchor = anchor;
-    this.isActive = true;
+    this.isActive.next(true);
     this.mode = 'drawing';
 
     this.calculateProfile();
@@ -825,6 +745,8 @@ export class VolumeProfile implements ISeriesDrawing {
     }
 
     this.mode = 'ready';
+    this.resolveReady?.();
+
     this.showCrosshair();
     this.render();
   }
@@ -855,7 +777,7 @@ export class VolumeProfile implements ISeriesDrawing {
 
   private resetToIdle(): void {
     this.hidden = false;
-    this.isActive = false;
+    this.isActive.next(false);
     this.mode = 'idle';
 
     this.startAnchor = null;
@@ -1259,7 +1181,7 @@ export class VolumeProfile implements ISeriesDrawing {
     return typeof value === 'number' && Number.isFinite(value) ? value : null;
   }
 
-  private getGeometry(): VolumeProfileGeometry | null {
+  protected getGeometry(): VolumeProfileGeometry | null {
     if (this.profileKind === 'visibleRange') {
       return this.getVisibleRangeGeometry();
     }
@@ -1401,22 +1323,6 @@ export class VolumeProfile implements ISeriesDrawing {
     return getAnchorFromPoint(this.chart, this.series, point);
   }
 
-  private hideCrosshair(): void {
-    this.chart.applyOptions({
-      crosshair: {
-        mode: CrosshairMode.Hidden,
-      },
-    });
-  }
-
-  private showCrosshair(): void {
-    this.chart.applyOptions({
-      crosshair: {
-        mode: CrosshairMode.Normal,
-      },
-    });
-  }
-
   private getContainerSize(): { width: number; height: number } {
     return getElementContainerSize(this.container);
   }
@@ -1424,13 +1330,4 @@ export class VolumeProfile implements ISeriesDrawing {
   private clampPointToContainer(point: Point): Point {
     return clampPointToContainerInElement(point, this.container);
   }
-
-  private getEventPoint(event: PointerEvent): Point {
-    return getPointerPointFromEvent(this.container, event);
-  }
-
-  private render(): void {
-    this.updateAllViews();
-    this.requestUpdate?.();
-  }
 }
diff --git a/src/core/DrawingsManager.tsx b/src/core/DrawingsManager.tsx
index b7a85de..a879e99 100644
--- a/src/core/DrawingsManager.tsx
+++ b/src/core/DrawingsManager.tsx
@@ -4,6 +4,7 @@ import { BehaviorSubject, Observable, Subscription } from 'rxjs';
 import { EventManager } from '@core';
 import { DOMModel } from '@core/DOMModel';
 import { Drawing } from '@core/Drawings';
+import { Hotkeys, Keys } from '@core/Hotkeys';
 
 import { EntitySettingsModal } from '@src/components/EntitySettingsModal';
 import { drawingLabelById, drawingsMap, DrawingsNames } from '@src/constants';
@@ -19,6 +20,7 @@ interface DrawingsManagerParams {
   container: HTMLElement;
   modalRenderer: ModalRenderer;
   paneId: number;
+  hotkeys: Hotkeys;
 }
 
 export interface DrawingSnapshotItem {
@@ -51,13 +53,27 @@ export class DrawingsManager {
   private pendingSnapshot: DrawingsManagerSnapshot | null = null;
   private paneId: number;
 
-  constructor({ eventManager, mainSeries$, lwcChart, DOM, container, modalRenderer, paneId }: DrawingsManagerParams) {
+  private hotkeys: Hotkeys;
+  private copyPasteBuffer: DrawingSnapshotItem | null = null;
+  private escapeUnregisterHash: string | null = null;
+
+  constructor({
+    eventManager,
+    mainSeries$,
+    lwcChart,
+    DOM,
+    container,
+    modalRenderer,
+    paneId,
+    hotkeys,
+  }: DrawingsManagerParams) {
     this.DOM = DOM;
     this.eventManager = eventManager;
     this.paneId = paneId;
     this.lwcChart = lwcChart;
     this.container = container;
     this.modalRenderer = modalRenderer;
+    this.hotkeys = hotkeys;
 
     this.subscriptions.add(
       mainSeries$.subscribe((series) => {
@@ -79,6 +95,30 @@ export class DrawingsManager {
     window.addEventListener('pointerup', this.handlePointerUp);
     this.container.addEventListener('click', this.handleClick);
     this.container.addEventListener('pointerdown', this.handlePointerDown);
+    // todo: implement ctrl+v
+    // hotkeys.register({
+    //   keys: [Keys.control, Keys.v],
+    //   callback: () => {
+    //     const bufferWithAppliedPosition = {
+    //       ...this.copyPasteBuffer,
+    //       state: {
+    //         ...this.copyPasteBuffer?.state,
+    //         startAnchor: {
+    //           price: 73.36210252637723,
+    //           time: 1783679170
+    //         }
+    //       }
+    //     }
+    //
+    //     this.setSnapshot([
+    //       ...this.getSnapshot(),
+    //       bufferWithAppliedPosition
+    //     ])
+    //     // hotkeys.unregister({ // todo: unregister all else ctrl+c's
+    //     //   keys: [Keys.control, Keys.c]
+    //     // })
+    //   }
+    // })
   }
 
   private handlePointerDown = (): void => {
@@ -184,7 +224,7 @@ export class DrawingsManager {
     this.DOM.refreshEntities();
   }
 
-  public addDrawingForce = (name: DrawingsNames): void => {
+  public addDrawingForce = (name: DrawingsNames): Promise<void> => {
     this.removePendingDrawings(false);
 
     if (drawingsMap[name].singleInstance) {
@@ -192,8 +232,10 @@ export class DrawingsManager {
     }
 
     this.activeTool$.next(name);
-    this.createDrawing(name);
+    const drawing = this.createDrawing(name);
     this.DOM.refreshEntities();
+
+    return drawing.waitForCreation();
   };
 
   private createDrawing(name: DrawingsNames, options: CreateDrawingOptions = {}): Drawing {
@@ -235,6 +277,13 @@ export class DrawingsManager {
         moveUp,
         construct,
         paneId: this.paneId,
+        hotkeys: this.hotkeys,
+        setCopyPasteBuffer: (copyPasteBuffer) => {
+          this.copyPasteBuffer = copyPasteBuffer;
+        },
+        resetActiveTool: () => {
+          this.activeTool$.next('crosshair');
+        },
       });
 
     const entity = this.DOM.setEntity<Drawing>(drawingFactory);
@@ -291,6 +340,19 @@ export class DrawingsManager {
   }
 
   public setEndlessDrawingMode = (value: boolean): void => {
+    if (value) {
+      this.escapeUnregisterHash = this.hotkeys.register({
+        keys: [Keys.escape],
+        callback: () => {
+          this.setEndlessDrawingMode(false);
+        },
+      });
+    } else {
+      this.hotkeys.unregister({
+        keys: [Keys.escape],
+        hash: this.escapeUnregisterHash,
+      });
+    }
     this.endlessMode$.next(value);
   };
 
@@ -348,6 +410,10 @@ export class DrawingsManager {
   }
 
   public destroy(): void {
+    this.hotkeys.unregister({
+      keys: [Keys.escape],
+      hash: this.escapeUnregisterHash,
+    });
     window.removeEventListener('pointerup', this.handlePointerUp);
     this.container.removeEventListener('click', this.handleClick);
     this.container.removeEventListener('pointerdown', this.handlePointerDown);
diff --git a/src/core/Hotkeys.ts b/src/core/Hotkeys.ts
new file mode 100644
index 0000000..4b02df7
--- /dev/null
+++ b/src/core/Hotkeys.ts
@@ -0,0 +1,174 @@
+export enum Keys { // todo: add macOS buttons support
+  'escape' = 'escape',
+  'delete' = 'delete',
+  'tab' = 'tab',
+  'shift' = 'shiftleft',
+  'control' = 'controlleft',
+  'alt' = 'altleft',
+  't' = 'keyt',
+  'h' = 'keyh',
+  'v' = 'keyv',
+  'f' = 'keyf',
+  'z' = 'keyz',
+  'c' = 'keyc',
+  'mousedown' = 'mousedown',
+}
+
+export interface IHotkeys {
+  register: ({ keys, callback }: { keys: Keys[]; callback: () => void }) => void;
+}
+
+export interface HotkeysParams {
+  listenTarget: HTMLElement;
+}
+
+type Tree = {
+  children: Map<Keys, Tree>;
+  callbacks: Map<string, () => void> | null;
+  longPress?: boolean;
+};
+
+// todo: возможно можно сделать синглтоном и использовать импортируя экземпляр класса,
+//  там где это нужно вместо props drilling, как сейчас
+export class Hotkeys implements IHotkeys {
+  private tree: Tree = { children: new Map(), callbacks: null, longPress: false };
+
+  private buffer: Keys[] = [];
+  private prevKey: null | Keys = null;
+
+  constructor(_: HotkeysParams) {
+    document.addEventListener('keydown', this.handleKeyDown.bind(this));
+    document.addEventListener('keyup', this.handleKeyUp.bind(this));
+  }
+
+  public unregister({ keys, hash }: { keys: Keys[]; hash?: string | null }) {
+    if (!hash) {
+      return;
+    }
+    let node = this.tree;
+
+    keys.forEach((key: Keys) => {
+      const child = node.children.get(key);
+      if (child) {
+        node = child;
+      } else {
+        throw new Error('[Hotkeys]: ошибка при отписке от хоткея');
+      }
+    });
+
+    if (!hash) {
+      node.callbacks = null;
+    }
+
+    node.callbacks?.delete(hash);
+
+    if (node.callbacks?.size === 0) {
+      node.callbacks = null;
+    }
+  }
+
+  public register({
+    keys,
+    callback,
+    longPress = false,
+  }: {
+    keys: Keys[];
+    callback: () => void;
+    longPress?: boolean;
+  }): string | null {
+    if (keys.length === 0) {
+      console.error('[Hotkeys] попытка задать пустой хоткей');
+      return null;
+    }
+
+    let node = this.tree; // todo: remove any from type
+
+    keys.forEach((key: Keys) => {
+      if (!node.children.has(key)) {
+        node.children.set(key, { children: new Map(), callbacks: null, longPress });
+      }
+
+      node = node.children.get(key) as Tree;
+    });
+
+    if (node.callbacks === null || node.callbacks === undefined) {
+      node.callbacks = new Map();
+    }
+
+    const hash = `hotkeyCallback-${crypto.randomUUID()}`;
+    node.callbacks?.set(hash, callback);
+    return hash;
+  }
+
+  private findNode(keys: Keys[]) {
+    let node = this.tree;
+
+    keys.forEach((key) => {
+      const k = normalize(key);
+      const child = node.children.get(k);
+      if (child) {
+        node = child;
+      } else {
+        return null;
+      }
+    });
+
+    return node;
+  }
+
+  private handleKeyUp = async () => {
+    const node = this.findNode(this.buffer);
+
+    if (node.callbacks && node.longPress) {
+      await this.handleKeyDown.bind(this)({ code: 'Escape' } as KeyboardEvent);
+    }
+    this.buffer = [];
+    this.prevKey = null;
+  };
+
+  private handleKeyDown = async (event: KeyboardEvent) => {
+    const key = normalize(event.code);
+
+    if (key === this.prevKey) return;
+
+    this.prevKey = key;
+
+    if (key === Keys.escape) {
+      this.buffer = [];
+    }
+
+    this.buffer.push(key);
+
+    const node = this.findNode(this.buffer);
+
+    if (!node) {
+      console.log('[Hotkeys] нет такой комбинации');
+      this.buffer = [];
+      this.prevKey = null;
+      return;
+    }
+
+    if (node.callbacks && node.children.size <= 0) {
+      event.preventDefault?.();
+      const callbacks = [];
+
+      for (const [_, cb] of node.callbacks) {
+        callbacks.push(cb);
+      }
+
+      const reversedCallbacks = [...callbacks].reverse();
+
+      for (const cb of reversedCallbacks) {
+        // eslint-disable-next-line no-await-in-loop
+        await cb();
+      }
+
+      this.buffer = [];
+      this.prevKey = null;
+    }
+  };
+}
+
+function normalize(key: string): Keys {
+  return key.toLowerCase() as Keys;
+}
diff --git a/src/core/MoexChart.tsx b/src/core/MoexChart.tsx
index 2869e77..26aaa4e 100644
--- a/src/core/MoexChart.tsx
+++ b/src/core/MoexChart.tsx
@@ -5,6 +5,7 @@ import { Footer } from '@components/Footer';
 
 import { Header } from '@components/Header';
 import { DataSource, DataSourceParams } from '@core/DataSource';
+import { Hotkeys } from '@core/Hotkeys';
 import { ModalRenderer } from '@core/ModalRenderer';
 import { SettingsModal } from '@src/components/SettingsModal';
 
@@ -101,6 +102,7 @@ export class MoexChart implements ISerializable<MoexChartSnapshot> {
   private chart!: Chart;
   private resizeObserver?: ResizeObserver;
   private eventManager!: EventManager;
+  private hotkeys!: Hotkeys;
   private rootContainer!: HTMLElement;
 
   private headerRenderer!: UIRenderer;
@@ -169,6 +171,8 @@ export class MoexChart implements ISerializable<MoexChartSnapshot> {
       showMenuButton: config.chartCollectionPreset.showMenuButton,
     });
 
+    this.hotkeys = new Hotkeys({ listenTarget: this.rootContainer });
+
     this.modalRenderer = new ModalRenderer(modalContainer);
 
     this.chart = new Chart({
@@ -179,6 +183,7 @@ export class MoexChart implements ISerializable<MoexChartSnapshot> {
         ohlcConfig: config.chartCollectionPreset.ohlc, // todo: omptimize
         tooltipConfig: config.chartCollectionPreset.tooltipConfig ?? {},
         panes: config.snapshot.charts[0].panes,
+        hotkeys: this.hotkeys,
       },
       lwcChartConfig: {
         container: chartAreaContainer,
@@ -322,14 +327,12 @@ export class MoexChart implements ISerializable<MoexChartSnapshot> {
       this.toolbarRenderer.renderComponent(
         <Toolbar
           toggleDOM={this.chart.getDom().toggleDOM}
-          addDrawing={(name) => {
-            // todo: deal with new panes logic
-            this.chart.getDrawingsManager().addDrawingForce(name);
-          }}
+          addDrawing={this.chart.getDrawingsManager().addDrawingForce}
           setEndlessDrawingsMode={this.chart.getDrawingsManager().setEndlessDrawingMode}
           isEndlessDrawingsMode$={this.chart.getDrawingsManager().isEndlessDrawingsMode()}
           activateCrosshair={() => this.chart.getDrawingsManager().activateCrosshair()}
           activeTool$={this.chart.getDrawingsManager().getActiveTool()}
+          hotkeys={this.hotkeys}
         />,
       );
     }
diff --git a/src/core/Pane.tsx b/src/core/Pane.tsx
index 84fe454..2b228aa 100644
--- a/src/core/Pane.tsx
+++ b/src/core/Pane.tsx
@@ -10,6 +10,7 @@ import { DataSource } from '@core/DataSource';
 import { DOMModel } from '@core/DOMModel';
 import { DrawingsManager, DrawingsManagerSnapshot } from '@core/DrawingsManager';
 import { EventManager } from '@core/EventManager';
+import { Hotkeys } from '@core/Hotkeys';
 import { Indicator } from '@core/Indicator';
 import { Legend } from '@core/Legend';
 import { PriceScale, PriceScaleControls } from '@core/PriceScale';
@@ -50,6 +51,7 @@ export interface PaneParams {
   onPriceScaleStateChange: () => void;
   leftPriceScaleVisible: boolean;
   rightPriceScaleVisible: boolean;
+  hotkeys: Hotkeys;
 }
 
 // todo: Pane, ему должна принадлежать mainSerie, а также IndicatorManager и drawingsManager, mouseEvents. Также перекинуть соответствующие/необходимые свойства из чарта, и из чарта удалить
@@ -102,6 +104,7 @@ export class Pane implements ISerializable<PaneSnapshot> {
     onPriceScaleStateChange,
     leftPriceScaleVisible,
     rightPriceScaleVisible,
+    hotkeys,
   }: PaneParams) {
     this.onDelete = onDelete;
     this.onPriceScaleStateChange = onPriceScaleStateChange;
@@ -166,6 +169,7 @@ export class Pane implements ISerializable<PaneSnapshot> {
       container: chartContainer,
       modalRenderer: this.modalRenderer,
       paneId: this.id,
+      hotkeys,
     });
 
     this.subscriptions.add(
diff --git a/src/modules/README.md b/src/modules/README.md
deleted file mode 100644
index 0097b46..0000000
--- a/src/modules/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# Modules
-
-Модули функциональности библиотеки.
-
-## Назначение
-
-Этот модуль содержит специализированную функциональность библиотеки. Каждый модуль имеет четко определенную ответственность и может быть использован независимо.
diff --git a/src/modules/indicators/IndicatorFactory.ts b/src/modules/indicators/IndicatorFactory.ts
deleted file mode 100644
index 0adc96e..0000000
--- a/src/modules/indicators/IndicatorFactory.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-// import { EventManager } from '@src/core';
-//
-// import { IndicatorConfig } from '../../types';
-//
-// import { Indicator } from './base/indicator';
-// import { SMA } from './implementations/SMA';
-// import { VolumeIndicator } from './implementations/Volume';
-//
-// export type IndicatorFactory = (config: IndicatorConfig, eventManager: EventManager) => Indicator;
-//
-// export interface IndicatorRegistration {
-//   type: IndicatorConfig['type'];
-//   create: IndicatorFactory;
-// }
-//
-// const registry = new Map<IndicatorConfig['type'], IndicatorFactory>([
-//   ['SMA', (config) => new SMA(config)],
-//   ['VOL', (config: IndicatorConfig, eventManager: EventManager) => new VolumeIndicator(config, eventManager)],
-// ]);
-//
-// export function registerIndicator({ type, create }: IndicatorRegistration): void {
-//   registry.set(type, create);
-// }
-//
-// export function createIndicator(config: IndicatorConfig, eventManager: EventManager): Indicator {
-//   const { type } = config;
-//
-//   switch (type) {
-//     case 'SMA':
-//       return new SMA(config);
-//     case 'VOL':
-//       return new VolumeIndicator(config, eventManager); // todo: тут не должно быть ни eventManger, ни таймфрейма
-//     default:
-//       throw new Error(`Unknown indicator type: ${type}`);
-//   }
-// }
diff --git a/src/modules/indicators/implementations/SMA.ts b/src/modules/indicators/implementations/SMA.ts
deleted file mode 100644
index b6c9222..0000000
--- a/src/modules/indicators/implementations/SMA.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-// import { Time } from 'lightweight-charts';
-//
-// import { Candle } from '../../../types';
-//
-// import { IndicatorConfig, IndicatorData } from '../../../types/indicator';
-// import { Indicator } from '../base/indicator';
-//
-// // Простая скользящая средняя (Simple Moving Average)
-// export class SMA extends Indicator {
-//   constructor(config: IndicatorConfig) {
-//     super(config);
-//   }
-//
-//   /**
-//    * Расчет простой скользящей средней
-//    */
-//   calculate(candles: Candle[]): IndicatorData[] {
-//     const period = this.config.period || 20;
-//     const result: IndicatorData[] = [];
-//
-//     if (candles.length < period) {
-//       return result;
-//     }
-//
-//     for (let i = period - 1; i < candles.length; i + 1) {
-//       let sum = 0;
-//
-//       for (let j = i - period + 1; j <= i; j += 1) {
-//         sum += candles[j].close;
-//       }
-//
-//       const average = sum / period;
-//
-//       result.push({
-//         time: candles[i].time as Time,
-//         value: parseFloat(average.toFixed(4)),
-//       });
-//     }
-//
-//     this.data = result;
-//     return result;
-//   }
-//
-//   /**
-//    * Добавить одну новую свечу и пересчитать последнее значение SMA
-//    */
-//   addCandle(candle: Candle, allCandles: Candle[]): void {
-//     const period = this.config.period || 20;
-//
-//     if (allCandles.length < period) {
-//       return;
-//     }
-//
-//     // Берем последние 'period' свечей включая новую
-//     const slice = allCandles.slice(-period);
-//     const sum = slice.reduce((acc, c: Candle) => acc + c.close, 0);
-//     const average = sum / period;
-//
-//     const newPoint: IndicatorData = {
-//       time: candle.time as Time,
-//       value: parseFloat(average.toFixed(4)),
-//     };
-//
-//     this.data.push(newPoint);
-//
-//     // Ограничиваем количество хранимых точек (например, последние 1000)
-//     if (this.data.length > 1000) {
-//       this.data = this.data.slice(-1000);
-//     }
-//   }
-//
-//   updateConfig(config: Partial<IndicatorConfig>): void {
-//     this.config = { ...this.config, ...config };
-//     // Пересчитать данные при изменении конфигурации
-//     this.data = [];
-//   }
-//
-//   /**
-//    * Получить текущие данные индикатора
-//    */
-//   getData(): IndicatorData[] {
-//     return this.data;
-//   }
-//
-//   /**
-//    * Получить конфигурацию индикатора
-//    */
-//   getConfig(): IndicatorConfig {
-//     return this.config;
-//   }
-//
-//   /**
-//    * Очистить данные индикатора
-//    */
-//   clear(): void {
-//     this.data = [];
-//   }
-// }
diff --git a/src/modules/indicators/implementations/Volume.ts b/src/modules/indicators/implementations/Volume.ts
deleted file mode 100644
index 3ac1656..0000000
--- a/src/modules/indicators/implementations/Volume.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-// import dayjs from 'dayjs';
-// import { HistogramSeriesPartialOptions, SeriesType } from 'lightweight-charts';
-//
-// import { Subscription } from 'rxjs';
-//
-// import { EventManager } from '@src/core';
-// import { getThemeStore } from '@src/theme/store';
-// import { Candle, IndicatorConfig, IndicatorData } from '@src/types';
-// import { Timeframes } from '@src/types/timeframes';
-// import { parseTimeframe } from '@src/utils';
-//
-// import { Indicator } from '../base/indicator';
-//
-// export class VolumeIndicator extends Indicator {
-//   private currentTimeframe!: Timeframes;
-//   private timeframeSubscription: Subscription;
-//
-//   // todo: тут не должно быть ни eventManger, ни таймфрейма
-//   constructor(config: IndicatorConfig, eventManager: EventManager) {
-//     super({ ...config, pane: 'new' });
-//     this.timeframeSubscription = eventManager.subscribeTimeframe((newTimeframe) => {
-//       this.setTimeframe(newTimeframe);
-//     });
-//   }
-//
-//   public getSeriesType(): SeriesType {
-//     return 'Histogram';
-//   }
-//
-//   /**
-//    * Переопределяем метод, чтобы задать специальные опции для гистограммы Volume.
-//    */
-//   public getSeriesOptions(): HistogramSeriesPartialOptions {
-//     const baseOptions = {};
-//
-//     return {
-//       ...baseOptions,
-//       priceFormat: {
-//         type: 'volume',
-//       },
-//       priceScaleId: 'new',
-//       autoscaleInfoProvider: () => null,
-//     };
-//   }
-//
-//   /**
-//    * Обновить таймфрейм для агрегации данных
-//    */
-//   public setTimeframe(timeframe: Timeframes): void {
-//     this.currentTimeframe = timeframe;
-//   }
-//
-//   /**
-//    * Логика расчета данных для Volume с агрегацией по таймфрейму.
-//    */
-//   public calculate(candles: Candle[]): IndicatorData[] {
-//     if (candles.length === 0) {
-//       this.data = [];
-//       return this.data;
-//     }
-//
-//     const { colors } = getThemeStore();
-//
-//     const { dayjsUnit } = parseTimeframe(this.currentTimeframe);
-//
-//     const timeframeGroups = new Map();
-//
-//     candles.forEach((candle) => {
-//       const timeframeStart = dayjs.unix(candle.time).startOf(dayjsUnit).unix();
-//       const existing = timeframeGroups.get(timeframeStart);
-//
-//       if (existing) {
-//         existing.value += candle.volume ?? 0;
-//
-//         existing.color = candle.close >= candle.open ? colors.chartCandleWickUp : colors.chartCandleWickDown;
-//       } else {
-//         // Новый таймфрейм
-//         timeframeGroups.set(timeframeStart, {
-//           time: timeframeStart,
-//           value: candle.volume || 0,
-//           color: candle.close >= candle.open ? colors.chartCandleWickUp : colors.chartCandleWickDown,
-//         });
-//       }
-//     });
-//
-//     this.data = Array.from(timeframeGroups.values()).sort((a, b) => a.time - b.time);
-//
-//     return this.data;
-//   }
-//
-//   /**
-//    * Добавить новую свечу с правильной агрегацией по таймфрейму
-//    */
-//   public addCandle(candle: Candle, allCandles: Candle[]): void {
-//     // Пересчитываем все данные с учетом нового таймфрейма
-//     this.calculate(allCandles);
-//   }
-//
-//   public destroy() {
-//     this.timeframeSubscription.unsubscribe();
-//   }
-// }
diff --git a/src/modules/indicators/index.ts b/src/modules/indicators/index.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/src/modules/series-strategies/SeriesManager.ts b/src/modules/series-strategies/SeriesManager.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/src/types/settings.ts b/src/types/settings.ts
index c887b77..5904562 100644
--- a/src/types/settings.ts
+++ b/src/types/settings.ts
@@ -1,4 +1,4 @@
-export type SettingValue = string | number | boolean;
+export type SettingValue = string | number | boolean | any;
 export type SettingsValues = Record<string, SettingValue>;
 
 interface BaseSettingField {