diff --git a/Core/Collections/ObservableGroupCollection.cs b/Core/Collections/ObservableGroupCollection.cs
new file mode 100644
index 000000000..aa99658d8
--- /dev/null
+++ b/Core/Collections/ObservableGroupCollection.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace Terminal.Core.Collections
+{
+ public interface IGroup
+ {
+ ///
+ /// Group index
+ ///
+ ///
+ long GetIndex();
+
+ ///
+ /// Grouping implementation
+ ///
+ ///
+ ///
+ IGroup Update(IGroup previous);
+ }
+
+ public class ObservableGroupCollection : ObservableCollection where T : IGroup
+ {
+ ///
+ /// Groups
+ ///
+ protected virtual IDictionary Groups { get; set; }
+
+ ///
+ /// Constructor
+ ///
+ public ObservableGroupCollection()
+ {
+ Groups = new Dictionary();
+ }
+
+ ///
+ /// Grouping implementation
+ ///
+ ///
+ ///
+ public virtual void Add(IGroup item, TimeSpan? span)
+ {
+ var index = item.GetIndex();
+
+ if (Groups.TryGetValue(index, out var position))
+ {
+ this[position] = (T)item.Update(this[position]);
+ return;
+ }
+
+ Groups[index] = Count;
+
+ Add((T)item.Update(null));
+ }
+ }
+}
diff --git a/Core/Collections/ObservableTimeCollection.cs b/Core/Collections/ObservableTimeCollection.cs
deleted file mode 100644
index b05e643ea..000000000
--- a/Core/Collections/ObservableTimeCollection.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Linq;
-using Terminal.Core.Extensions;
-using Terminal.Core.Models;
-
-namespace Terminal.Core.Collections
-{
- ///
- /// Collection with aggregation by date and time
- ///
- ///
- public class ObservableTimeCollection : ObservableCollection where T : PointModel
- {
- ///
- /// Internal tracker to identify new or existing point in time
- ///
- protected virtual IDictionary Indices { get; set; }
-
- ///
- /// Constructor
- ///
- public ObservableTimeCollection()
- {
- Indices = new Dictionary();
- }
-
- ///
- /// Update or add item to the collection depending on its date and time
- ///
- ///
- ///
- public virtual void Add(T item, TimeSpan? span)
- {
- var previous = this.LastOrDefault();
-
- if (span is not null && previous is not null)
- {
- var nextTime = item.Time.Round(span);
- var previousTime = previous.Time.Round(span);
-
- if (Equals(previousTime, nextTime))
- {
- this[Count - 1] = item;
- return;
- }
- }
-
- Add(item);
- }
-
- ///
- /// Update or add item to the collection depending on its date and time
- ///
- ///
- ///
- ///
- public virtual void Add(T item, TimeSpan? span, bool combine)
- {
- if (span is null)
- {
- Add(item);
- return;
- }
-
- var currentTime = item.Time.Round(span);
- var previousTime = (item.Time - span.Value).Round(span);
- var currentGroup = Indices.TryGetValue(currentTime.Value.Ticks, out int currentIndex) ? this[currentIndex] : default;
- var previousGroup = Indices.TryGetValue(previousTime.Value.Ticks, out int previousIndex) ? this[previousIndex] : default;
- var group = CreateGroup(currentGroup, item, previousGroup, span);
-
- if (group is not null)
- {
- if (currentGroup is not null)
- {
- this[currentIndex] = group;
- return;
- }
-
- Add(group);
-
- Indices[currentTime.Value.Ticks] = Count - 1;
- }
- }
-
- ///
- /// Group items by time
- ///
- ///
- ///
- ///
- ///
- ///
- protected virtual T CreateGroup(T currentPoint, T nextPoint, T previousPoint, TimeSpan? span)
- {
- if (nextPoint.Ask is null && nextPoint.Bid is null)
- {
- return default;
- }
-
- var nextGroup = (T)nextPoint.Clone();
-
- if (currentPoint is not null)
- {
- nextGroup.AskSize += currentPoint.AskSize ?? 0.0;
- nextGroup.BidSize += currentPoint.BidSize ?? 0.0;
- nextGroup.Bar.Open = currentPoint.Bar.Open;
- }
-
- nextGroup.Ask = nextPoint.Ask ?? nextPoint.Bid;
- nextGroup.Bid = nextPoint.Bid ?? nextPoint.Ask;
- nextGroup.Last = nextGroup.Bid ?? nextGroup.Ask;
-
- nextGroup.Bar.Close = nextGroup.Last;
- nextGroup.Bar.Open = nextGroup.Bar.Open ?? previousPoint?.Last ?? nextGroup.Last;
- nextGroup.Bar.Low = Math.Min((nextGroup.Bar.Low ?? nextGroup.Last).Value, nextGroup.Last.Value);
- nextGroup.Bar.High = Math.Max((nextGroup.Bar.High ?? nextGroup.Last).Value, nextGroup.Last.Value);
-
- nextGroup.TimeFrame = span;
- nextGroup.Time = nextPoint.Time.Round(span);
-
- return nextGroup;
- }
- }
-}
diff --git a/Core/Domains/Instrument.cs b/Core/Domains/Instrument.cs
index 1eaeb38bc..b14198e1f 100644
--- a/Core/Domains/Instrument.cs
+++ b/Core/Domains/Instrument.cs
@@ -49,12 +49,12 @@ public interface IInstrument
///
/// List of all ticks from the server
///
- ObservableTimeCollection Points { get; set; }
+ ObservableGroupCollection Points { get; set; }
///
/// List of all ticks from the server aggregated into bars
///
- ObservableTimeCollection PointGroups { get; set; }
+ ObservableGroupCollection PointGroups { get; set; }
}
public class Instrument : IInstrument
@@ -102,12 +102,12 @@ public class Instrument : IInstrument
///
/// List of all ticks from the server
///
- public virtual ObservableTimeCollection Points { get; set; }
+ public virtual ObservableGroupCollection Points { get; set; }
///
/// List of all ticks from the server aggregated into bars
///
- public virtual ObservableTimeCollection PointGroups { get; set; }
+ public virtual ObservableGroupCollection PointGroups { get; set; }
///
/// Constructor
diff --git a/Core/Models/Points/PointModel.cs b/Core/Models/Points/PointModel.cs
index 36c725a07..82559fddc 100644
--- a/Core/Models/Points/PointModel.cs
+++ b/Core/Models/Points/PointModel.cs
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
+using Terminal.Core.Collections;
using Terminal.Core.Domains;
+using Terminal.Core.Extensions;
namespace Terminal.Core.Models
{
- public class PointModel : ICloneable
+ public class PointModel : ICloneable, IGroup
{
///
/// Bid
@@ -77,5 +79,39 @@ public virtual object Clone()
return clone;
}
+
+ ///
+ /// Grouping index
+ ///
+ ///
+ public virtual long GetIndex() => Time.Value.Ticks;
+
+ ///
+ /// Grouping implementation
+ ///
+ ///
+ ///
+ public virtual IGroup Update(IGroup previous)
+ {
+ if (previous is not null)
+ {
+ var o = previous as PointModel;
+ var price = (Last ?? Bid ?? Ask ?? o.Last ?? o.Bid ?? o.Ask).Value;
+
+ Ask ??= o.Ask ?? price;
+ Bid ??= o.Bid ?? price;
+ AskSize += o.AskSize ?? 0.0;
+ BidSize += o.BidSize ?? 0.0;
+ Bar ??= new BarModel();
+ Bar.Close = Last = price;
+ Bar.Open = o.Bar?.Open ?? Bar.Open ?? price;
+ Bar.Low = Math.Min(Bar.Low ?? price, o.Bar?.Low ?? price);
+ Bar.High = Math.Max(Bar.High ?? price, o.Bar?.High ?? price);
+ TimeFrame ??= o.TimeFrame;
+ Time = Time.Round(TimeFrame) ?? o.Time;
+ }
+
+ return this;
+ }
}
}
diff --git a/Core/Services/NotificationService.cs b/Core/Services/NotificationService.cs
new file mode 100644
index 000000000..4f2fdc168
--- /dev/null
+++ b/Core/Services/NotificationService.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Terminal.Core.Services
+{
+ public class NotificationService
+ {
+ public virtual Action OnMessage { get; set; }
+ }
+}
diff --git a/Gateways/Alpaca/Libs/Adapter.cs b/Gateways/Alpaca/Libs/Adapter.cs
index a29ebf67a..c1956c838 100644
--- a/Gateways/Alpaca/Libs/Adapter.cs
+++ b/Gateways/Alpaca/Libs/Adapter.cs
@@ -223,13 +223,16 @@ public async Task GetAccountData()
var positions = await SendData("/v2/positions");
var orders = await SendData("/v2/orders");
- Account.Balance = account.Data.Equity;
- Account.Descriptor = account.Data.AccountNumber;
- Account.ActiveOrders = orders.Data.Select(GetInternalOrder).ToDictionary(o => o.Transaction.Id, o => o);
- Account.ActivePositions = positions.Data.Select(GetInternalPosition).ToDictionary(o => o.Order.Transaction.Id, o => o);
+ if (account.Data is not null)
+ {
+ Account.Balance = account.Data.Equity;
+ Account.Descriptor = account.Data.AccountNumber;
+ Account.ActiveOrders = orders.Data.Select(GetInternalOrder).ToDictionary(o => o.Transaction.Id, o => o);
+ Account.ActivePositions = positions.Data.Select(GetInternalPosition).ToDictionary(o => o.Order.Transaction.Id, o => o);
- Account.ActiveOrders.ForEach(async o => await Subscribe(o.Value.Transaction.Instrument.Name));
- Account.ActivePositions.ForEach(async o => await Subscribe(o.Value.Order.Transaction.Instrument.Name));
+ Account.ActiveOrders.ForEach(async o => await Subscribe(o.Value.Transaction.Instrument.Name));
+ Account.ActivePositions.ForEach(async o => await Subscribe(o.Value.Order.Transaction.Instrument.Name));
+ }
}
///
diff --git a/Gateways/Simulation/Libs/Adapter.cs b/Gateways/Simulation/Libs/Adapter.cs
index 46ecbeeb6..868b396a3 100644
--- a/Gateways/Simulation/Libs/Adapter.cs
+++ b/Gateways/Simulation/Libs/Adapter.cs
@@ -104,7 +104,7 @@ public override async Task> Subscribe(string name)
point.TimeFrame = instrument.TimeFrame;
instrument.Points.Add(point);
- instrument.PointGroups.Add(point, instrument.TimeFrame, true);
+ instrument.PointGroups.Add(point, instrument.TimeFrame);
}
interval.Enabled = true;
diff --git a/Gateways/Simulation/Tests/SendPendingOrder.cs b/Gateways/Simulation/Tests/SendPendingOrder.cs
index 8751cfba2..9f5ed60e3 100644
--- a/Gateways/Simulation/Tests/SendPendingOrder.cs
+++ b/Gateways/Simulation/Tests/SendPendingOrder.cs
@@ -44,7 +44,7 @@ public void CreatePendingOrder(
Instrument = new Instrument()
{
Name = "X",
- Points = new ObservableTimeCollection
+ Points = new ObservableGroupCollection
{
new() { Bid = price, Ask = price }
}