public void ParseChart(ChartDefinition sourceChart) { // Update the title Name = sourceChart.Name; // Validate the chart if (sourceChart.Series.Count == 0) { return; } // Group series by their Index. // This index is the index of a chart they need to be drawn upon. foreach (var sourceSeriesGroup in sourceChart.Series .OrderByDescending(s => s.Value.Index) .GroupBy(s => s.Value.Index)) { // Get the model representing this index var childModel = Charts.FirstOrDefault(g => g.Index == sourceSeriesGroup.Key); if (childModel == null) { // Build a new model childModel = new ChartViewModel(sourceSeriesGroup.Key, this); Charts.Add(childModel); childModel.Index = sourceSeriesGroup.Key; // Create Y axis for the series childModel.YAxesCollection.Add(new Axis { // Title is with combined series Title = string.Join(", ", sourceSeriesGroup.Select(s => s.Value.Name).ToArray()), Position = AxisPosition.RightTop, Sections = new SectionsCollection { // Horizontal 0 value line new AxisSection { Value = 0, Stroke = Brushes.Gray, StrokeThickness = 1 } } }); } // Update the series foreach (var quantSeries in sourceSeriesGroup .Select(sg => sg.Value) .Where(v => v.Values.Count > 0)) { //var series = childModel.SeriesCollection[seriesIndex]; var series = childModel.SeriesCollection.FirstOrDefault(x => x.Title == quantSeries.Name); if (series == null) { series = _chartParser.BuildSeries(quantSeries); childModel.SeriesCollection.Add(series); } if (!childModel.LastUpdates.ContainsKey(series.Title)) { childModel.LastUpdates[series.Title] = Instant.MinValue; } var updates = quantSeries.Since(childModel.LastUpdates[series.Title]); _chartParser.UpdateSeries(series, updates); if (updates.Values.Any()) { childModel.LastUpdates[series.Title] = updates.Values.Last().X; } if (updates.Values.Any() && series.Values.Count == _seriesMaximum) { // This series is probably truncated by the LEAN engine. Add warning visual elemeent var lastValue = updates.Values.Last(); childModel.CreateTruncatedVisuaLElement(0, lastValue.X, lastValue.Y); _messenger.Send(new LogEntryReceivedMessage(DateTime.Now, $"Series { Name}.{series.Title} is possibly truncated by the LEAN engine", LogItemType.Monitor)); } } } var sourceScrollSeries = sourceChart.Series .Select(s => s.Value) .OrderByDescending(s => s.SeriesType == SeriesType.Line) .ThenByDescending(s => s.SeriesType == SeriesType.Candle) .First(s => s.Index == 0); var scrollSeries = (Series)ScrollSeriesCollection.FirstOrDefault(); if (scrollSeries == null && sourceScrollSeries.Values.Any()) { Zoom.StartPoint = sourceScrollSeries.Values[0].X; scrollSeries = _chartParser.BuildSeries(sourceScrollSeries); ScrollSeriesCollection.Add(scrollSeries); } if (!LastUpdates.ContainsKey("Scroll")) { LastUpdates["Scroll"] = Instant.MinValue; } var scrollSeriesUpdates = sourceScrollSeries.Since(LastUpdates["Scroll"]); if (scrollSeries != null) { _chartParser.UpdateSeries(scrollSeries, scrollSeriesUpdates); if (scrollSeriesUpdates.Values.Any()) { LastUpdates["Scroll"] = scrollSeriesUpdates.Values.Last().X; } } Zoom.AutoZoom(); }