-
Notifications
You must be signed in to change notification settings - Fork 229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
auto-prune in-memory stream data #1095
Comments
I've dared to provide a proposal to implement the auto-pruning option in the observable: public class QuoteProviderSettings
{
/// <summary>
/// Gets or sets max size of <see cref="QuoteProvider.Quotes"/> collection.
/// Once the collection reaches the configured size, the provider will automatically remove from
/// the collection the oldest quote when a new one is added as an atomic operation.
/// If the quote to be added is the oldest, it won't get to the collection.
/// Default is -1 (unlimited).
/// </summary>
public int MaxCollectionSize { get; set; } = -1;
}
public class QuoteProvider : IObservable<Quote>
{
// fields
private readonly List<IObserver<Quote>> observers;
private readonly QuoteProviderSettings settings;
// initialize
public QuoteProvider()
{
observers = new();
ProtectedQuotes = new();
}
// initialize
public QuoteProvider(Action<QuoteProviderSettings> cfgAction)
: this()
{
if (cfgAction == null)
{
throw new ArgumentNullException(nameof(cfgAction));
}
settings = new QuoteProviderSettings();
cfgAction.Invoke(settings);
ValidateSettings(settings);
}
private static void ValidateSettings(QuoteProviderSettings settings)
{
if (settings.MaxCollectionSize is < 1 and not -1)
{
throw new ArgumentException(
$"{nameof(settings.MaxCollectionSize)} must be a positive integer greater than 0 or -1 (unlimited)",
nameof(settings));
}
}
// properties
public IEnumerable<Quote> Quotes => ProtectedQuotes;
internal List<Quote> ProtectedQuotes { get; private set; }
private int OverflowCount { get; set; }
// METHODS
// add one
public void Add(Quote quote)
{
// validate quote
if (quote == null)
{
throw new ArgumentNullException(nameof(quote), "Quote cannot be null.");
}
// note: I don't think it's a good idea to manipulate a public collection, ProtectedQuotes, that may be potentially enumerated
// while being modified. Also to guarantee that the addition and removal are executed as an atomic operation, I'd suggest to make
// ProtectedQuotes immutable and replace its value after the new one is calculated
int length = ProtectedQuotes.Count;
if (length == 0)
{
// add new quote
ProtectedQuotes.Add(quote);
// notify observers
NotifyObservers(quote);
return;
}
Quote last = ProtectedQuotes[length - 1];
bool maxSizeReached = settings.MaxCollectionSize != -1 && settings.MaxCollectionSize == length;
// add quote
if (quote.Date > last.Date)
{
// remove oldest if max size reached
if (maxSizeReached)
{
ProtectedQuotes.RemoveAt(0);
}
// add new quote
ProtectedQuotes.Add(quote);
// notify observers
NotifyObservers(quote);
}
// same date or quote recieved
else if (quote.Date <= last.Date)
{
// check for overflow condition
// where same quote continues (possible circular condition)
if (quote.Date == last.Date)
{
OverflowCount++;
if (OverflowCount > 100)
{
string msg = "A repeated Quote update exceeded the 100 attempt threshold. "
+ "Check and remove circular chains or check your Quote provider.";
EndTransmission();
throw new OverflowException(msg);
}
}
else
{
OverflowCount = 0;
}
// seek old quote
int foundIndex = ProtectedQuotes
.FindIndex(x => x.Date == quote.Date);
// found
if (foundIndex >= 0)
{
Quote old = ProtectedQuotes[foundIndex];
old.Open = quote.Open;
old.High = quote.High;
old.Low = quote.Low;
old.Close = quote.Close;
old.Volume = quote.Volume;
}
// add missing quote
else
{
// remove oldest if max size reached
if (maxSizeReached)
{
ProtectedQuotes.RemoveAt(0);
}
ProtectedQuotes.Add(quote);
// re-sort cache
ProtectedQuotes = ProtectedQuotes
.ToSortedList();
}
// let observer handle old + duplicates
NotifyObservers(quote);
}
}
// add many
public void Add(IEnumerable<Quote> quotes)
{
List<Quote> added = quotes
.ToSortedList();
for (int i = 0; i < added.Count; i++)
{
Add(added[i]);
}
}
// subscribe observer
public IDisposable Subscribe(IObserver<Quote> observer)
{
if (!observers.Contains(observer))
{
observers.Add(observer);
}
return new Unsubscriber(observers, observer);
}
// close all observations
public void EndTransmission()
{
foreach (IObserver<Quote> observer in observers.ToArray())
{
if (observers.Contains(observer))
{
observer.OnCompleted();
}
}
observers.Clear();
}
// notify observers
private void NotifyObservers(Quote quote)
{
List<IObserver<Quote>> obsList = observers.ToList();
for (int i = 0; i < obsList.Count; i++)
{
IObserver<Quote> obs = obsList[i];
obs.OnNext(quote);
}
}
// unsubscriber
private class Unsubscriber : IDisposable
{
private readonly List<IObserver<Quote>> observers;
private readonly IObserver<Quote> observer;
// identify and save observer
public Unsubscriber(List<IObserver<Quote>> observers, IObserver<Quote> observer)
{
this.observers = observers;
this.observer = observer;
}
// remove single observer
public void Dispose()
{
if (observer != null && observers.Contains(observer))
{
observers.Remove(observer);
}
}
}
} I'd also provide one for the observer, but I'd need to understand first why you added a dependency on observable's protected tuples inside the SMA observer to calculate the increment, because it seems that this calculation depends on observable's collection length. |
Seriously, I'd love to have more contributors helping out. Thank you for the contribution. If you want to open a PR against the And yes, I too am trying to better address the observer related issues as this has a rather complex arrangement when further considering indicator chaining. The protected |
I've been having a deeper look at the current design and I'd like to share some thoughts with you:
Hmm, I think I stop here and I'll try to show you what I have in mind in a dev branch. |
The reason I'd made The reason Regarding max size, I'm thinking it'd just be a base property of each of these. If it's set at the public class QuoteProvider : IObservable<Quote>
{
// constructor
public QuoteProvider()
{
...
}
public QuoteProvider(int maxSize)
{
...
MaxSize = maxSize;
}
internal int? MaxSize { get; set; }
... And a user would optionally initialize it: QuoteProvider quotes = new(maxSize: 750); Finally, any subscriber can just lookup |
These are the scenarios I'm trying to enable:
For example, RENKO is a |
Originally posted by @elpht in #1018 (comment)
The idea of these collections growing in an uncontrolled manner has crossed my mind. Since these are instantiated classes, I think we can add an optional setting so you can supply a max size that'll trigger some auto-pruning after new data arrives. Makes sense.
Objective:
See also:
The text was updated successfully, but these errors were encountered: