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

streaming preview: initial baseline #824

Merged
merged 75 commits into from
Feb 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
1a5a144
public EmaBase methods
DaveSkender Jun 19, 2022
0238639
add Null2Nan helper
DaveSkender Jun 19, 2022
12c30b9
add UUID, base stream interface
DaveSkender Jun 19, 2022
7860dae
Merge branch 'main' into streaming-preview
DaveSkender Jun 20, 2022
16508e3
Merge branch 'main' into streaming-preview
DaveSkender Jun 26, 2022
d1bcf61
Merge branch 'main' into streaming-preview
DaveSkender Jun 26, 2022
9db81cc
Merge branch 'main' into streaming-preview
DaveSkender Jun 27, 2022
536a2c1
Merge branch 'main' into streaming-preview
DaveSkender Jun 27, 2022
87033bc
Merge branch 'main' into streaming-preview
DaveSkender Jul 3, 2022
990928c
Merge branch 'main' into streaming-preview
DaveSkender Jul 3, 2022
cfc9845
update lgtm
DaveSkender Jul 4, 2022
4214330
Merge branch 'main' into streaming-preview
DaveSkender Jul 10, 2022
e3ce67d
Merge branch 'main' into streaming-preview
DaveSkender Jul 16, 2022
7128c1c
Merge branch 'main' into streaming-preview
DaveSkender Jul 27, 2022
cfdaa3f
Nan2Null on final EMA result
DaveSkender Aug 6, 2022
09724a6
Merge branch 'main' into streaming-preview
DaveSkender Aug 13, 2022
f9414c1
Merge branch 'main' into streaming-preview
DaveSkender Aug 21, 2022
1ff3bee
Merge branch 'main' into streaming-preview
DaveSkender Aug 21, 2022
124b987
Merge branch 'main' into streaming-preview
DaveSkender Aug 23, 2022
77af2cd
Merge branch 'main' into streaming-preview
DaveSkender Aug 28, 2022
b251a55
Merge branch 'main' into streaming-preview
DaveSkender Sep 7, 2022
7c39bc8
Merge branch 'main' into streaming-preview
DaveSkender Sep 17, 2022
4f382e4
Merge branch 'main' into streaming-preview
DaveSkender Sep 24, 2022
6b6726d
cache quotes, start empty
DaveSkender Sep 26, 2022
6a83bcc
Merge branch 'main' into streaming-preview
DaveSkender Oct 9, 2022
491c64f
Merge branch 'main' into streaming-preview
DaveSkender Oct 9, 2022
72d6106
dropping .NET 5.0 (end of support)
DaveSkender Oct 15, 2022
e14a57f
Merge branch 'main' into streaming-preview
DaveSkender Oct 15, 2022
ab30f25
initial EMA documentation
DaveSkender Oct 15, 2022
4bb9a31
Update README.md
DaveSkender Oct 16, 2022
7f8e2ae
Merge branch 'main' into streaming-preview
DaveSkender Oct 23, 2022
01d209b
Merge branch 'main' into streaming-preview
DaveSkender Oct 23, 2022
761e281
remove lgtm.yml for upstream conflict
DaveSkender Nov 27, 2022
300d349
Merge branch 'main' into streaming-preview
DaveSkender Nov 27, 2022
96e823a
Merge branch 'main' into streaming-preview
DaveSkender Dec 19, 2022
d7357c9
cleanup tests
DaveSkender Dec 19, 2022
9e5f532
Merge branch 'main' into streaming-preview
DaveSkender Dec 29, 2022
daceffe
add tuple API
DaveSkender Dec 29, 2022
d394c36
Merge branch 'main' into streaming-preview
DaveSkender Dec 29, 2022
19f4709
Merge branch 'main' into streaming-preview
DaveSkender Dec 29, 2022
f15627e
update perf test cases
DaveSkender Dec 29, 2022
031959e
misc performance tests
DaveSkender Dec 30, 2022
f97599a
refactor: reorganize test files
DaveSkender Dec 30, 2022
f354eb4
add observable for full Quote
DaveSkender Dec 31, 2022
9746372
add performance test for new static EMA method
DaveSkender Dec 31, 2022
75d84a4
add more tests
DaveSkender Dec 31, 2022
4af2b2d
add reset option
DaveSkender Dec 31, 2022
d79bf57
allow old quotes in stream
DaveSkender Dec 31, 2022
292b675
oops, missed a file
DaveSkender Dec 31, 2022
dc74c09
cleanup perf test configs
DaveSkender Dec 31, 2022
585f16e
update helper perf tests
DaveSkender Dec 31, 2022
ed19fa4
update unit tests for base case
DaveSkender Jan 1, 2023
b3d7901
handle quote overflows
DaveSkender Jan 1, 2023
e6e5d59
add Use observer
DaveSkender Jan 1, 2023
b3089e4
refactor reorganize
DaveSkender Jan 1, 2023
1f3b70a
add chain from tuple
DaveSkender Jan 1, 2023
173b24a
refactor chainable EMA
DaveSkender Jan 1, 2023
08bf97b
add SMA observer
DaveSkender Jan 2, 2023
54e3dfd
Merge branch 'main' into streaming-preview
DaveSkender Jan 2, 2023
ce08d37
remove Reset concept
DaveSkender Jan 2, 2023
4d58e56
refactor rename perf tests
DaveSkender Jan 3, 2023
47e7f58
add FindIndex by date, for series
DaveSkender Jan 7, 2023
67c170e
Merge branch 'main' into streaming-preview
DaveSkender Jan 15, 2023
f9fc692
merge from main
DaveSkender Jan 22, 2023
b01a4f9
fixes to catch up from main refresh
DaveSkender Jan 22, 2023
8a43feb
restore static types in perf tests
DaveSkender Jan 22, 2023
73e4fac
code cleanup
DaveSkender Jan 22, 2023
7f975eb
Merge branch 'main' into streaming-preview
DaveSkender Jan 22, 2023
2721e43
add EMA, SMA chainee observer API
DaveSkender Jan 23, 2023
270f19e
add Websocket to proto-sample
DaveSkender Jan 23, 2023
3248998
update next version
DaveSkender Jan 28, 2023
e2f810a
Merge branch 'v3' into streaming-preview
DaveSkender Jan 28, 2023
61325d0
cleanup
DaveSkender Jan 29, 2023
a3f3ecb
update Use unit tests
DaveSkender Feb 4, 2023
292de56
update packages
DaveSkender Feb 5, 2023
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
12 changes: 11 additions & 1 deletion Stock.Indicators.sln
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Performance", "tests\
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{3A4158F9-4165-4823-9526-0CFAACCF1ACC}"
ProjectSection(SolutionItems) = preProject
.lgtm.yml = .lgtm.yml
.github\build.main.yml = .github\build.main.yml
.github\dependabot.yml = .github\dependabot.yml
gitversion.yml = gitversion.yml
EndProjectSection
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
Expand All @@ -48,6 +54,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
20 changes: 20 additions & 0 deletions src/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,23 @@
Justification = "Not compatible with `or` statement (Microsoft bug)",
Scope = "member",
Target = "~M:Skender.Stock.Indicators.ResultUtility.Condense``1(System.Collections.Generic.IEnumerable{``0})~System.Collections.Generic.IEnumerable{``0}")]

[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 = new();
ProtectedChain = new();
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