Skip to content
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

feat: Incremental & streaming quotes, v3.0.0 #1014

Draft
wants to merge 38 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
1791086
rebase version branch (#1013)
DaveSkender Jan 28, 2023
39bf115
streaming preview: quotes, use, EMA, SMA (#824)
DaveSkender Feb 5, 2023
235115e
Merge branch 'main' into v3
DaveSkender Feb 11, 2023
36e07c8
Merge branch 'main' into v3
DaveSkender Feb 26, 2023
2509522
Merge branch 'main' into v3
DaveSkender Apr 6, 2023
520a454
Merge remote-tracking branch 'origin/main' into v3
DaveSkender Apr 8, 2023
39c8419
Merge remote-tracking branch 'origin/main' into v3
DaveSkender Apr 20, 2023
fc34f3d
fix merge conflict
DaveSkender Apr 20, 2023
38aa771
Merge branch 'main' into v3
DaveSkender Jul 1, 2023
b5a8a89
Merge branch 'main' into v3
DaveSkender Aug 31, 2023
ce2e6df
Merge branch 'main' into v3
DaveSkender Oct 12, 2023
2f5e393
Merge branch 'main' into v3
DaveSkender Nov 5, 2023
187b144
Merge branch 'main' into v3
DaveSkender Nov 11, 2023
e6bc7eb
Merge branch 'main' into v3
DaveSkender Nov 13, 2023
2fbb7e9
Merge branch 'main' into v3
DaveSkender Nov 16, 2023
472f2ef
Merge branch 'main' into v3
DaveSkender Dec 19, 2023
5ab1dfd
Merge branch 'main' into v3
DaveSkender Jan 1, 2024
e38b43b
chore: Update packages (#1139)
DaveSkender Jan 3, 2024
ad39bc9
fix: Merge `main` into `v3` (#1142)
DaveSkender Jan 3, 2024
5d7714b
Merge branch 'main' into v3
DaveSkender Jan 3, 2024
41f314f
remove duplicate reference
DaveSkender Jan 3, 2024
92d50d6
Merge branch 'main' into v3
DaveSkender Jan 3, 2024
0faa3f4
fix Sln file references
DaveSkender Jan 3, 2024
852d57a
Merge branch 'main' into v3
DaveSkender Jan 4, 2024
2e5ffa3
Merge branch 'main' into v3
DaveSkender Jan 28, 2024
0b57cb5
Merge branch 'main' into v3
DaveSkender Jan 28, 2024
90af694
Merge branch 'main' into v3
DaveSkender Jan 29, 2024
8c18cf1
Merge branch 'main' into v3
DaveSkender Mar 2, 2024
d99f14d
Merge branch 'main' into v3
DaveSkender Mar 5, 2024
ab62ddc
Merge branch 'main' into v3
DaveSkender Mar 17, 2024
49f5eb3
fix: Accommodate base merge changes (#1177)
DaveSkender Mar 17, 2024
6dc2e68
Merge branch 'main' into v3
DaveSkender Mar 17, 2024
1c758cb
Merge branch 'main' into v3
DaveSkender Mar 24, 2024
92ed407
Merge branch 'main' into v3
DaveSkender Apr 1, 2024
7a13100
Merge branch 'main' into v3
DaveSkender Jul 1, 2024
d970690
Merge branch 'main' into v3
DaveSkender Jul 5, 2024
c95bc92
Merge branch 'main' into v3
DaveSkender Jul 6, 2024
807f1fe
Merge branch 'main' into v3
DaveSkender Jul 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Stock.Indicators.sln
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Other", "tests\other\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Performance", "tests\performance\Tests.Performance.csproj", "{3BD4837B-D197-41FD-A286-A3256D0770E1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Observe.Streaming", "tests\observe\Observe.Streaming.csproj", "{14DEC3AF-9AF2-4A66-8BEE-C342C6CC4307}"
ProjectSection(ProjectDependencies) = postProject
{11CD6C7E-871F-4903-AEAD-58E034C6521D} = {11CD6C7E-871F-4903-AEAD-58E034C6521D}
{8D0F1781-EDA3-4C51-B05D-D33FF1156E49} = {8D0F1781-EDA3-4C51-B05D-D33FF1156E49}
EndProjectSection
EndProject

Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -41,6 +48,10 @@ Global
{3BD4837B-D197-41FD-A286-A3256D0770E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3BD4837B-D197-41FD-A286-A3256D0770E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3BD4837B-D197-41FD-A286-A3256D0770E1}.Release|Any CPU.Build.0 = Release|Any CPU
{14DEC3AF-9AF2-4A66-8BEE-C342C6CC4307}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{14DEC3AF-9AF2-4A66-8BEE-C342C6CC4307}.Debug|Any CPU.Build.0 = Debug|Any CPU
{14DEC3AF-9AF2-4A66-8BEE-C342C6CC4307}.Release|Any CPU.ActiveCfg = Release|Any CPU
{14DEC3AF-9AF2-4A66-8BEE-C342C6CC4307}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
1 change: 1 addition & 0 deletions gitversion.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mode: ContinuousDelivery
next-version: 3.0.0

commit-message-incrementing: Enabled
major-version-bump-message: '\+semver:\s?(breaking|major)'
Expand Down
20 changes: 20 additions & 0 deletions src/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,23 @@
"StyleCop.CSharp.SpacingRules",
"SA1010:Opening square brackets should be spaced correctly",
Justification = "Invalid for new C# 12 [ collection ] syntax.")]

[assembly: SuppressMessage(
"Naming",
"CA1725:Parameter names should match base declaration",
Justification = "The microsoft OnError implementation uses reserved word Error",
Scope = "member",
Target = "~M:Skender.Stock.Indicators.QuoteObserver.OnError(System.Exception)")]

[assembly: SuppressMessage(
"Naming",
"CA1716:Identifiers should not match keywords",
Justification = "The microsoft OnError implementation uses reserved word Error",
Scope = "member",
Target = "~M:Skender.Stock.Indicators.QuoteObserver.OnError(System.Exception)")]

[assembly: SuppressMessage(
"Naming",
"CA1716:Identifiers should not match keywords",
Justification = "The microsoft OnError implementation uses reserved word Error",
Scope = "member", Target = "~M:Skender.Stock.Indicators.TupleObserver.OnError(System.Exception)")]
14 changes: 12 additions & 2 deletions src/_common/Generics/Seek.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,22 @@ namespace Skender.Stock.Indicators;

public static class Seeking
{
// FIND by DATE
/// <include file='./info.xml' path='info/type[@name="Find"]/*' />
// FIND SERIES by DATE
/// <include file='./info.xml' path='info/type[@name="FindSeries"]/*' />
///
public static TSeries? Find<TSeries>(
this IEnumerable<TSeries> series,
DateTime lookupDate)
where TSeries : ISeries => series
.FirstOrDefault(x => x.Date == lookupDate);

// FIND INDEX by DATE
/// <include file='./info.xml' path='info/type[@name="FindIndex"]/*' />
///
public static int FindIndex<TSeries>(
this List<TSeries> series,
DateTime lookupDate)
where TSeries : ISeries => series == null
? -1
: series.FindIndex(x => x.Date == lookupDate);
}
19 changes: 16 additions & 3 deletions src/_common/Generics/info.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>

<info>
<type name="Find">
<type name="FindSeries">
<summary> Finds time series values on a specific date.
<para>
See <see href="https://dotnet.StockIndicators.dev/utilities/#find-indicator-result-by-date?utm_source=library&amp;utm_medium=inline-help&amp;utm_campaign=embedded">documentation</see> for more information.
Expand All @@ -10,8 +10,21 @@
<typeparam name="TSeries">Any series type.</typeparam>
<param name="series">Time series to evaluate.</param>
<param name="lookupDate">Exact date to lookup.</param>
<returns>First
record in the series on the date specified.</returns>
<returns>First record in the series on the date specified.</returns>
</type>
<type name="FindIndex">
<summary>
Finds time series index on a specific date.
<para>
See <see href="https://dotnet.StockIndicators.dev/utilities/#find-indicator-result-by-date?utm_source=library">documentation</see> for more information.
</para>
</summary>
<typeparam name="TSeries">Any series type.</typeparam>
<param name="series">Time series to evaluate.</param>
<param name="lookupDate">Exact date to lookup.</param>
<returns>
First index in the series of the date specified or -1 if not found.
</returns>
</type>
<type name="Prune">
<summary> Removes a specific quantity from the beginning of the time series list.
Expand Down
194 changes: 194 additions & 0 deletions src/_common/Observables/ChainProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
namespace Skender.Stock.Indicators;

// TUPLE OBSERVER and TUPLE PROVIDER (CHAIN STREAM)

public abstract class ChainProvider
: TupleObserver, IObservable<(DateTime Date, double Value)>
{
// fields
private readonly List<IObserver<(DateTime Date, double Value)>> observers;

// initialize
protected ChainProvider()
{
observers = [];
ProtectedChain = [];
Warmup = true;
}

// properties
internal IEnumerable<(DateTime Date, double Value)> Output => ProtectedChain;

internal List<(DateTime Date, double Value)> ProtectedChain { get; set; }

private int OverflowCount { get; set; }

private bool Warmup { get; set; }

// METHODS

// subscribe observer
public IDisposable Subscribe(IObserver<(DateTime Date, double Value)> observer)
{
if (!observers.Contains(observer))
{
observers.Add(observer);
}

return new Unsubscriber(observers, observer);
}

// close all observations
public void EndTransmission()
{
foreach (IObserver<(DateTime Date, double Value)> observer in observers.ToArray())
{
if (observers.Contains(observer))
{
observer.OnCompleted();
}
}

observers.Clear();
}

// add one
internal void SendToChain<TResult>(TResult result)
where TResult : IReusableResult
{
// candidate result
(DateTime Date, double Value) r = new(result.Date, result.Value.Null2NaN());

int length = ProtectedChain.Count;

// initialize
if (length == 0 && result.Value != null)
{
// add new tuple
ProtectedChain.Add(r);
Warmup = false;

// notify observers
NotifyObservers(r);
return;
}

// do not proceed until first non-null Value recieved
if (Warmup && result.Value == null)
{
return;
}
else
{
Warmup = false;
}

(DateTime lastDate, _) = ProtectedChain[length - 1];

// add tuple
if (r.Date > lastDate)
{
// add new tuple
ProtectedChain.Add(r);

// notify observers
NotifyObservers(r);
}

// same date or tuple recieved
else if (r.Date <= lastDate)
{
// check for overflow condition
// where same tuple continues (possible circular condition)
if (r.Date == lastDate)
{
OverflowCount++;

if (OverflowCount > 100)
{
string msg = "A repeated Chain update exceeded the 100 attempt threshold. "
+ "Check and remove circular chains or check your Chain provider.";

EndTransmission();

throw new OverflowException(msg);
}
}
else
{
OverflowCount = 0;
}

// seek old tuple
int foundIndex = ProtectedChain
.FindIndex(x => x.Date == r.Date);

// found
if (foundIndex >= 0)
{
ProtectedChain[foundIndex] = r;
}

// add missing tuple
else
{
ProtectedChain.Add(r);

// re-sort cache
ProtectedChain = ProtectedChain
.ToSortedList();
}

// let observer handle old + duplicates
NotifyObservers(r);
}
}

// add many
internal void SendToChain<TResult>(IEnumerable<TResult> results)
where TResult : IReusableResult
{
List<TResult> added = results
.ToSortedList();

for (int i = 0; i < added.Count; i++)
{
SendToChain(added[i]);
}
}

// notify observers
private void NotifyObservers((DateTime Date, double Value) tuple)
{
List<IObserver<(DateTime Date, double Value)>> obsList = observers.ToList();

for (int i = 0; i < obsList.Count; i++)
{
IObserver<(DateTime Date, double Value)> obs = obsList[i];
obs.OnNext(tuple);
}
}

// unsubscriber
private class Unsubscriber : IDisposable
{
private readonly List<IObserver<(DateTime Date, double Value)>> observers;
private readonly IObserver<(DateTime Date, double Value)> observer;

// identify and save observer
public Unsubscriber(List<IObserver<(DateTime Date, double Value)>> observers, IObserver<(DateTime Date, double Value)> observer)
{
this.observers = observers;
this.observer = observer;
}

// remove single observer
public void Dispose()
{
if (observer != null && observers.Contains(observer))
{
observers.Remove(observer);
}
}
}
}
29 changes: 29 additions & 0 deletions src/_common/Observables/QuoteObserver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace Skender.Stock.Indicators;

// OBSERVER of QUOTES (BOILERPLATE)

public abstract class QuoteObserver : IObserver<Quote>
{
// fields
private IDisposable? unsubscriber;

// properites
internal QuoteProvider? Supplier { get; set; }

// methods
public virtual void Subscribe()
=> unsubscriber = Supplier != null
? Supplier.Subscribe(this)
: throw new ArgumentNullException(nameof(Supplier));

public virtual void OnCompleted() => Unsubscribe();

public virtual void OnError(Exception error) => throw error;

public virtual void OnNext(Quote value)
{
// » handle new quote with override in observer
}

public virtual void Unsubscribe() => unsubscriber?.Dispose();
}
Loading