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


diff --git a/CHANGELOG.md b/CHANGELOG.md
index 04d654637e831bd14ea6e962d2d302d44e674394..b1f96b1fc018e4887e0e6b91a80f1b2dceddf7a1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,13 @@
 # latest
 
+- Вместе с индикатороми теперь сохраняются и их настройки
+
+# 0.1.17
+
 - Продлена ось x вправо
 - Дродаун таймфреймов теперь по умолчанию раскрыт
 - Применены стили к скролбару интервалов
+- Исправлен баг с занулением ema индикатора, как слева(при неудачных попытках его расчета) так и справа(в realtime)
 - Исправлена работа Undo/Redo для дровингов
 
 # 0.1.16
diff --git a/src/core/Chart.ts b/src/core/Chart.ts
index 28502b1b279687e696ce6f9e79d5e6e2af150d77..eac6f68cfd52fe8ed880d17045559410b3bd1ef5 100644
--- a/src/core/Chart.ts
+++ b/src/core/Chart.ts
@@ -42,7 +42,7 @@ import {
 } from '@src/types';
 import { Defaults } from '@src/types/defaults';
 import { DayjsOffset, Intervals, intervalsToDayjs } from '@src/types/intervals';
-import { ChartSnapshot, ISerializable, PaneSnapshot } from '@src/types/snapshot';
+import { ChartSnapshot, CompareSnapshot, IndicatorSnapshot, ISerializable, PaneSnapshot } from '@src/types/snapshot';
 import { formatCompactNumber } from '@src/utils';
 import { createTickMarkFormatter, formatDate } from '@src/utils/formatter';
 
@@ -75,7 +75,10 @@ interface ChartParams {
   lwcChartConfig: ChartConfig;
 }
 
-function splitIndicatorSnapshots(panes: PaneSnapshot[]) {
+function splitIndicatorSnapshots(panes: PaneSnapshot[]): {
+  compareSnapshots: CompareSnapshot[];
+  indicatorSnapshots: IndicatorSnapshot[];
+} {
   const snapshots = panes.flatMap(({ id, indicators }) =>
     indicators.map((indicator) => ({
       ...indicator,
@@ -83,9 +86,20 @@ function splitIndicatorSnapshots(panes: PaneSnapshot[]) {
     })),
   );
 
+  function isIndicatorSnapshot(
+    input: (IndicatorSnapshot | CompareSnapshot) & { indicatorType?: unknown },
+  ): input is IndicatorSnapshot {
+    return input.indicatorType !== undefined;
+  }
+  function isCompareSnapshot(
+    input: (IndicatorSnapshot | CompareSnapshot) & { indicatorType?: unknown },
+  ): input is CompareSnapshot {
+    return input.indicatorType === undefined;
+  }
+
   return {
-    indicatorSnapshots: snapshots.filter(({ indicatorType }) => indicatorType !== undefined),
-    compareSnapshots: snapshots.filter(({ indicatorType }) => indicatorType === undefined),
+    indicatorSnapshots: snapshots.filter((x) => isIndicatorSnapshot(x)),
+    compareSnapshots: snapshots.filter((x) => isCompareSnapshot(x)),
   };
 }
 
@@ -181,22 +195,24 @@ export class Chart implements ISerializable<ChartSnapshot> {
     const { indicatorSnapshots, compareSnapshots } = splitIndicatorSnapshots(panesSnapshot);
 
     this.indicatorManager = new IndicatorManager({
+      lwcChart: this.lwcChart,
       eventManager,
-      initialIndicators: indicatorSnapshots,
-      DOM: this.DOM,
       dataSource: this.dataSource,
-      lwcChart: this.lwcChart,
       paneManager: this.paneManager,
+
+      initialIndicators: indicatorSnapshots,
+      DOM: this.DOM,
       chartOptions: lwcChartConfig.chartOptions,
     });
 
     this.compareManager = new CompareManager({
       chart: this.lwcChart,
-      initialIndicators: compareSnapshots,
-      eventManager: this.eventManager,
+      eventManager,
       dataSource: this.dataSource,
-      indicatorManager: this.indicatorManager,
       paneManager: this.paneManager,
+
+      initialIndicators: compareSnapshots,
+      indicatorManager: this.indicatorManager,
     });
 
     this.paneManager.start({
diff --git a/src/core/CompareManager.ts b/src/core/CompareManager.ts
index 9a4e68d4ef065ed98818615493a8d8cc93644afe..6c12e9991457f56f8d25bf14030e80f0901fabdc 100644
--- a/src/core/CompareManager.ts
+++ b/src/core/CompareManager.ts
@@ -11,7 +11,7 @@ import { PaneManager } from '@core/PaneManager';
 import { PriceScale } from '@core/PriceScale';
 import { COMPARE_COLOR_PALETTE } from '@src/theme';
 import { CompareItem, CompareMode, Direction, IndicatorConfig, SymbolInfo, SymbolInfoInput } from '@src/types';
-import { IndicatorSnapshot } from '@src/types/snapshot';
+import { CompareSnapshot } from '@src/types/snapshot';
 import { createFallbackColor, normalizeColor, normalizeSymbol, normalizeSymbolInfo } from '@src/utils';
 
 interface CompareEntry extends CompareItem {
@@ -25,7 +25,7 @@ interface CompareManagerParams {
   dataSource: DataSource;
   indicatorManager: IndicatorManager;
   paneManager: PaneManager;
-  initialIndicators?: IndicatorSnapshot[];
+  initialIndicators?: CompareSnapshot[];
 }
 
 export class CompareManager {
@@ -245,39 +245,37 @@ export class CompareManager {
     this.entitiesSubject.complete();
   }
 
-  private async setup(initialIndicators: IndicatorSnapshot[]): Promise<void> {
+  private setup = async (compareIndicators: CompareSnapshot[]): Promise<void> => {
     this.restoringInitialIndicators = true;
 
     try {
-      for (const indicator of initialIndicators) {
-        const { config } = indicator;
+      for (const compareIndicator of compareIndicators) {
+        const { scale, symbolInfo, seriesName, paneId } = compareIndicator;
 
-        if (indicator.indicatorType !== undefined || !config?.symbolInfo) {
-          continue;
-        }
-
-        const symbolInfo = normalizeSymbolInfo(config.symbolInfo);
+        const symbolInfoNormalized = normalizeSymbolInfo(symbolInfo);
 
-        if (!symbolInfo) {
+        if (!symbolInfoNormalized) {
           continue;
         }
 
-        const series = config.series[0];
+        if (this.paneManager.getMainPane().getId() !== paneId && scale === Direction.Left) {
+          throw new Error('[CompareManager]: несколько шкал на второстеменном пейне не поддерживаются');
+        }
 
         const compareMode =
-          series.seriesOptions?.priceScaleId === Direction.Left
+          scale === Direction.Left
             ? CompareMode.NewScale
-            : config.newPane
-              ? CompareMode.NewPane
-              : CompareMode.Percentage;
+            : this.paneManager.getMainPane().getId() === paneId
+              ? CompareMode.Percentage
+              : CompareMode.NewPane;
 
         // eslint-disable-next-line no-await-in-loop
-        await this.setSymbolMode(series.name, symbolInfo, compareMode, indicator.paneId);
+        await this.setSymbolMode(seriesName, symbolInfoNormalized, compareMode, paneId!);
       }
     } finally {
       this.restoringInitialIndicators = false;
     }
-  }
+  };
 
   private removeEntry(key: string): boolean {
     const entry = this.entries.get(key);
diff --git a/src/core/Indicator.ts b/src/core/Indicator.ts
index 2b49366cd244807b20d410600fad3fce6641aacb..6aa60bcb058c2735bb8f93591c0d52b6eeb5712d 100644
--- a/src/core/Indicator.ts
+++ b/src/core/Indicator.ts
@@ -7,8 +7,8 @@ import { Pane } from '@core/Pane';
 
 import { indicatorLabelById, indicatorSeriesLabelById, IndicatorsIds } from '@src/constants';
 import { SeriesFactory, SeriesStrategies } from '@src/modules/series-strategies/SeriesFactory';
-import { ChartTypeOptions, IndicatorConfig, SettingsValues } from '@src/types';
-import { DOMObjectSnapshot, IndicatorSnapshot, ISerializable } from '@src/types/snapshot';
+import { ChartTypeOptions, Direction, IndicatorConfig, SettingsValues } from '@src/types';
+import { CompareSnapshot, DOMObjectSnapshot, IndicatorSnapshot, ISerializable } from '@src/types/snapshot';
 
 type IIndicator = DOMObject;
 
@@ -19,11 +19,13 @@ export interface IndicatorParams extends DOMObjectParams {
   dataSource: DataSource;
   associatedPane: Pane;
   config: IndicatorConfig;
+  settings?: SettingsValues;
   type?: IndicatorsIds;
   chartOptions?: ChartTypeOptions;
 }
 
-export class Indicator extends DOMObject implements ISerializable<IndicatorSnapshot> {
+// todo: сделать отдельно представление для Compare, и попобовать убрать CompareManager из Chart
+export class Indicator extends DOMObject implements ISerializable<IndicatorSnapshot | CompareSnapshot> {
   private indicatorType?: IndicatorsIds;
   private series: SeriesStrategies[] = [];
   private seriesMap: Map<string, SeriesStrategies> = new Map();
@@ -52,6 +54,7 @@ export class Indicator extends DOMObject implements ISerializable<IndicatorSnaps
     associatedPane,
     paneId,
     config,
+    settings,
   }: IndicatorParams) {
     super({ id, name: config?.label ?? id, zIndex, onDelete, moveUp, moveDown, paneId });
     this.lwcChart = lwcChart;
@@ -62,7 +65,7 @@ export class Indicator extends DOMObject implements ISerializable<IndicatorSnaps
     this.config = config;
     this.name = this.getLabel();
 
-    this.settings = this.getDefaultSettings();
+    this.settings = { ...this.getDefaultSettings(), ...settings };
 
     this.associatedPane = associatedPane;
 
@@ -72,12 +75,7 @@ export class Indicator extends DOMObject implements ISerializable<IndicatorSnaps
   }
 
   public recreateSeries() {
-    this.series.forEach((s) => {
-      s.destroy();
-    });
-
-    this.series = [];
-    this.seriesMap.clear();
+    this.destroySeries();
     this.createSeries();
   }
 
@@ -133,15 +131,15 @@ export class Indicator extends DOMObject implements ISerializable<IndicatorSnaps
     return this.config;
   }
 
-  public getIndicatorType(): IndicatorSnapshot['indicatorType'] {
+  public getIndicatorType(): IndicatorSnapshot['indicatorType'] | undefined {
     return this.indicatorType;
   }
 
-  public getSettings(): SettingsValues {
+  public getSettingsValues(): SettingsValues {
     return { ...this.settings };
   }
 
-  public getSettingsConfig() {
+  public getSettingsFields() {
     return this.config.settings ?? [];
   }
 
@@ -153,8 +151,7 @@ export class Indicator extends DOMObject implements ISerializable<IndicatorSnaps
     this.settings = settings;
 
     // todo: обновлять данные серий без удаления и повторного создания
-    this.destroySeries();
-    this.createSeries();
+    this.recreateSeries();
 
     this.notifyDataChanged();
   }
@@ -173,14 +170,27 @@ export class Indicator extends DOMObject implements ISerializable<IndicatorSnaps
     super.hide();
   }
 
-  public override getSnapshot(): DOMObjectSnapshot & IndicatorSnapshot {
+  public override getSnapshot(): DOMObjectSnapshot & (IndicatorSnapshot | CompareSnapshot) {
     const domSnap = super.getSnapshot();
 
+    if (this.indicatorType) {
+      return {
+        ...domSnap,
+        indicatorType: this.indicatorType,
+        settings: this.settings,
+      };
+    }
+
+    const seriesName = this.config.series[0]?.name;
+    const scale = this.config.series[0]?.seriesOptions?.priceScaleId as Direction;
+    if (!scale || !seriesName) {
+      throw new Error('[Indicator]: невозможно сохранить состояние compare индикатора');
+    }
     return {
       ...domSnap,
-      dataSource: this.dataSource,
-      indicatorType: this.indicatorType,
-      config: this.config,
+      symbolInfo: this.config.symbolInfo!,
+      seriesName,
+      scale,
     };
   }
 
diff --git a/src/core/IndicatorManager.ts b/src/core/IndicatorManager.ts
index 0ce39369c0f956b96ef1ca184b58657a4bad7cfd..ee4b55953d180a47d20a31188cc5bf91aa742d98 100644
--- a/src/core/IndicatorManager.ts
+++ b/src/core/IndicatorManager.ts
@@ -6,6 +6,7 @@ import { DOMModel } from '@core/DOMModel';
 import { EventManager } from '@core/EventManager';
 import { Indicator } from '@core/Indicator';
 import { PaneManager } from '@core/PaneManager';
+import { IndicatorsIds } from '@src/constants';
 import { DOMObject } from '@src/core/DOMObject';
 import { indicatorsMap as indicatorsConfigMap } from '@src/core/Indicators';
 import { ChartTypeOptions, IndicatorConfig } from '@src/types';
@@ -63,11 +64,10 @@ export class IndicatorManager {
 
     const id = snap.id ?? `${snap.indicatorType}-${crypto.randomUUID()}`;
 
-    const baseConfig = snap.config ?? (indicatorsConfigMap()[snap.indicatorType] as IndicatorConfig);
-
-    const config = snap.config
-      ? baseConfig
-      : applyNextIndicatorColors(baseConfig, this.getUsedIndicatorColorsByType(snap.indicatorType));
+    const config = getConfigByIndicatorType({
+      indicatorType: snap.indicatorType,
+      existedIndicators: this.indicatorsMap$.value,
+    });
 
     const associatedPane =
       snap.paneId !== undefined
@@ -91,6 +91,7 @@ export class IndicatorManager {
           dataSource: this.dataSource,
           associatedPane,
           config,
+          settings: snap.settings,
           type: snap.indicatorType,
           chartOptions: this.chartOptions,
         });
@@ -103,20 +104,6 @@ export class IndicatorManager {
     this.entities$.next(Array.from(indicatorsMap.values()));
   }
 
-  private getUsedIndicatorColorsByType(indicatorType: IndicatorSnapshot['indicatorType']): string[] {
-    const colors: string[] = [];
-
-    this.indicatorsMap$.value.forEach((indicator) => {
-      if (indicator.getIndicatorType() !== indicatorType) {
-        return;
-      }
-
-      colors.push(...getIndicatorColors(indicator.getConfig()));
-    });
-
-    return colors;
-  }
-
   public getIndicators() {
     return this.indicatorsMap$;
   }
@@ -145,3 +132,36 @@ export class IndicatorManager {
     return this.entities$.asObservable();
   }
 }
+
+function getConfigByIndicatorType({
+  indicatorType,
+  existedIndicators,
+}: {
+  indicatorType: IndicatorsIds;
+  existedIndicators: Map<string, Indicator>;
+}): IndicatorConfig {
+  const configWithAppliesSettings = indicatorsConfigMap()[indicatorType] as IndicatorConfig;
+  const usedColors = getUsedIndicatorColorsByType({ indicatorType, existedIndicators });
+
+  return applyNextIndicatorColors(configWithAppliesSettings, usedColors);
+}
+
+function getUsedIndicatorColorsByType({
+  indicatorType,
+  existedIndicators,
+}: {
+  indicatorType: IndicatorSnapshot['indicatorType'];
+  existedIndicators: Map<string, Indicator>;
+}): string[] {
+  const colors: string[] = [];
+
+  existedIndicators.forEach((indicator) => {
+    if (indicator.getIndicatorType() !== indicatorType) {
+      return;
+    }
+
+    colors.push(...getIndicatorColors(indicator.getConfig()));
+  });
+
+  return colors;
+}
diff --git a/src/core/Pane.tsx b/src/core/Pane.tsx
index 25cf2688736b7839c20fc75c9143d030c5018b10..e24286fb3413675bcfaf735c87ff716b7c8eaaed 100644
--- a/src/core/Pane.tsx
+++ b/src/core/Pane.tsx
@@ -24,6 +24,7 @@ import { SeriesFactory, SeriesStrategies } from '@src/modules/series-strategies/
 import { t } from '@src/translations';
 import { Direction, OHLCConfig, TooltipConfig } from '@src/types';
 import {
+  CompareSnapshot,
   DOMObjectSnapshot,
   IndicatorSnapshot,
   ISerializable,
@@ -258,7 +259,7 @@ export class Pane implements ISerializable<PaneSnapshot> {
   }
 
   public getSnapshot(): PaneSnapshot {
-    const indicators: (DOMObjectSnapshot & IndicatorSnapshot)[] = [];
+    const indicators: (DOMObjectSnapshot & (IndicatorSnapshot | CompareSnapshot))[] = [];
 
     this.indicatorsMap.value.forEach((indicator) => {
       indicators.push(indicator.getSnapshot());
@@ -363,7 +364,7 @@ export class Pane implements ISerializable<PaneSnapshot> {
       paneId: this.id,
       paneIndex: this.paneIndex,
       openIndicatorSettings: (indicatorId, indicator) => {
-        let settings = indicator.getSettings();
+        let settings = indicator.getSettingsValues();
 
         this.modalRenderer.renderComponent(
           <EntitySettingsModal
@@ -371,7 +372,7 @@ export class Pane implements ISerializable<PaneSnapshot> {
               {
                 key: 'arguments',
                 label: t('Arguments'),
-                fields: indicator.getSettingsConfig(),
+                fields: indicator.getSettingsFields(),
               },
             ]}
             values={settings}
diff --git a/src/types/snapshot.ts b/src/types/snapshot.ts
index 5349f052eb58db54515c4c24c9064853995fd911..a1d04436fe8120ae664f6cb40f465cc26430eb04 100644
--- a/src/types/snapshot.ts
+++ b/src/types/snapshot.ts
@@ -1,11 +1,11 @@
-import { DataSource } from '@core/DataSource';
 import { DrawingsManagerSnapshot } from '@core/DrawingsManager';
 import { ChartSeriesType, DateFormat, IndicatorsIds, Intervals, Timeframes } from '@lib';
 import { Direction } from '@src/types/chart';
-import { IndicatorConfig } from '@src/types/indicator';
 import { SymbolInfo, SymbolInfoInput } from '@src/types/symbol';
 import { TimeFormat } from '@src/types/timeScale';
 
+import { SettingsValues } from './settings';
+
 import type { PriceScaleMode } from 'lightweight-charts';
 
 export type PriceScaleSide = Direction.Left | Direction.Right;
@@ -49,15 +49,20 @@ export interface PriceScaleSnapshot {
 export interface PaneSnapshot {
   isMain: boolean;
   id: number;
-  indicators: IndicatorSnapshot[];
+  indicators: (IndicatorSnapshot | CompareSnapshot)[];
   drawings: DrawingsManagerSnapshot;
   priceScales?: PriceScaleSnapshot[];
 }
 
+export interface CompareSnapshot extends Partial<DOMObjectSnapshot> {
+  symbolInfo: SymbolInfoInput;
+  seriesName: ChartSeriesType;
+  scale: Direction;
+}
+
 export interface IndicatorSnapshot extends Partial<DOMObjectSnapshot> {
-  dataSource?: DataSource;
-  indicatorType: IndicatorsIds | undefined; // if indicatorType is undefined, then its compareIndicator
-  config?: IndicatorConfig;
+  indicatorType: IndicatorsIds; // if indicatorType is undefined, then its compareIndicator
+  settings?: SettingsValues;
 }
 
 // todo: move DrawingsManagerSnapshot here
diff --git a/stories/TradeRadar/TradeRadar.stories.tsx b/stories/TradeRadar/TradeRadar.stories.tsx
index d097064b696d204727cc0c3c75f1b8032b839774..fcd77c813b49b6aea0b2b2b803971fc20b7d33d5 100644
--- a/stories/TradeRadar/TradeRadar.stories.tsx
+++ b/stories/TradeRadar/TradeRadar.stories.tsx
@@ -5,7 +5,7 @@ import { createPortal } from 'react-dom';
 import { CompareManager } from '@core/CompareManager';
 import { DateFormat, IMoexChart, Locale, MoexChart, Timeframes } from '@lib';
 import { IndicatorsIds } from '@lib/constants';
-import { CompareMode, SymbolInfoInput } from '@lib/types';
+import { CompareMode, Direction, SymbolInfoInput } from '@lib/types';
 
 // import { argTypes } from '../argTypes';
 
@@ -141,7 +141,12 @@ const args: TRProps = {
             id: 0,
             indicators: [
               {
-                indicatorType: IndicatorsIds.Volume,
+                indicatorType: IndicatorsIds.Volume, // if indicatorType is undefined, then its compareIndicator
+              },  
+              {
+                symbolInfo: { symbolId: 'TQBR:SBER' },
+                scale: Direction.Right,
+                seriesName: 'Line',
               },
             ],
             drawings: [],