Skip to content

Commit 4da1def

Browse files
authored
Merge 39bf115 into 7020552
2 parents 7020552 + 39bf115 commit 4da1def

44 files changed

Lines changed: 2171 additions & 590 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/build.main.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
trigger:
22
- main
3+
- v3
34

45
pr:
56
- main
7+
- v3
68

79
pool:
810
name: Azure Pipelines

Stock.Indicators.sln

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests.Performance", "tests\
2121
EndProject
2222
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{3A4158F9-4165-4823-9526-0CFAACCF1ACC}"
2323
ProjectSection(SolutionItems) = preProject
24-
.lgtm.yml = .lgtm.yml
2524
.github\build.main.yml = .github\build.main.yml
2625
.github\dependabot.yml = .github\dependabot.yml
26+
gitversion.yml = gitversion.yml
27+
EndProjectSection
28+
EndProject
29+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Observe.Streaming", "tests\observe\Observe.Streaming.csproj", "{14DEC3AF-9AF2-4A66-8BEE-C342C6CC4307}"
30+
ProjectSection(ProjectDependencies) = postProject
31+
{11CD6C7E-871F-4903-AEAD-58E034C6521D} = {11CD6C7E-871F-4903-AEAD-58E034C6521D}
32+
{8D0F1781-EDA3-4C51-B05D-D33FF1156E49} = {8D0F1781-EDA3-4C51-B05D-D33FF1156E49}
2733
EndProjectSection
2834
EndProject
2935
Global
@@ -48,6 +54,10 @@ Global
4854
{3BD4837B-D197-41FD-A286-A3256D0770E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
4955
{3BD4837B-D197-41FD-A286-A3256D0770E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
5056
{3BD4837B-D197-41FD-A286-A3256D0770E1}.Release|Any CPU.Build.0 = Release|Any CPU
57+
{14DEC3AF-9AF2-4A66-8BEE-C342C6CC4307}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
58+
{14DEC3AF-9AF2-4A66-8BEE-C342C6CC4307}.Debug|Any CPU.Build.0 = Debug|Any CPU
59+
{14DEC3AF-9AF2-4A66-8BEE-C342C6CC4307}.Release|Any CPU.ActiveCfg = Release|Any CPU
60+
{14DEC3AF-9AF2-4A66-8BEE-C342C6CC4307}.Release|Any CPU.Build.0 = Release|Any CPU
5161
EndGlobalSection
5262
GlobalSection(SolutionProperties) = preSolution
5363
HideSolutionNode = FALSE

gitversion.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mode: ContinuousDelivery
2+
next-version: 3.0.0
23

34
commit-message-incrementing: Enabled
45
major-version-bump-message: '\+semver:\s?(breaking|major)'

src/GlobalSuppressions.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,23 @@
3939
Justification = "Not compatible with `or` statement (Microsoft bug)",
4040
Scope = "member",
4141
Target = "~M:Skender.Stock.Indicators.ResultUtility.Condense``1(System.Collections.Generic.IEnumerable{``0})~System.Collections.Generic.IEnumerable{``0}")]
42+
43+
[assembly: SuppressMessage(
44+
"Naming",
45+
"CA1725:Parameter names should match base declaration",
46+
Justification = "The microsoft OnError implementation uses reserved word Error",
47+
Scope = "member",
48+
Target = "~M:Skender.Stock.Indicators.QuoteObserver.OnError(System.Exception)")]
49+
50+
[assembly: SuppressMessage(
51+
"Naming",
52+
"CA1716:Identifiers should not match keywords",
53+
Justification = "The microsoft OnError implementation uses reserved word Error",
54+
Scope = "member",
55+
Target = "~M:Skender.Stock.Indicators.QuoteObserver.OnError(System.Exception)")]
56+
57+
[assembly: SuppressMessage(
58+
"Naming",
59+
"CA1716:Identifiers should not match keywords",
60+
Justification = "The microsoft OnError implementation uses reserved word Error",
61+
Scope = "member", Target = "~M:Skender.Stock.Indicators.TupleObserver.OnError(System.Exception)")]

src/_common/Generics/Seek.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,22 @@ namespace Skender.Stock.Indicators;
44

55
public static class Seeking
66
{
7-
// FIND by DATE
8-
/// <include file='./info.xml' path='info/type[@name="Find"]/*' />
7+
// FIND SERIES by DATE
8+
/// <include file='./info.xml' path='info/type[@name="FindSeries"]/*' />
99
///
1010
public static TSeries? Find<TSeries>(
1111
this IEnumerable<TSeries> series,
1212
DateTime lookupDate)
1313
where TSeries : ISeries => series
1414
.FirstOrDefault(x => x.Date == lookupDate);
15+
16+
// FIND INDEX by DATE
17+
/// <include file='./info.xml' path='info/type[@name="FindIndex"]/*' />
18+
///
19+
public static int FindIndex<TSeries>(
20+
this List<TSeries> series,
21+
DateTime lookupDate)
22+
where TSeries : ISeries => series == null
23+
? -1
24+
: series.FindIndex(x => x.Date == lookupDate);
1525
}

src/_common/Generics/info.xml

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22

33
<info>
4-
<type name="Find">
4+
<type name="FindSeries">
55
<summary> Finds time series values on a specific date.
66
<para>
77
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.
@@ -10,8 +10,21 @@
1010
<typeparam name="TSeries">Any series type.</typeparam>
1111
<param name="series">Time series to evaluate.</param>
1212
<param name="lookupDate">Exact date to lookup.</param>
13-
<returns>First
14-
record in the series on the date specified.</returns>
13+
<returns>First record in the series on the date specified.</returns>
14+
</type>
15+
<type name="FindIndex">
16+
<summary>
17+
Finds time series index on a specific date.
18+
<para>
19+
See <see href="https://dotnet.StockIndicators.dev/utilities/#find-indicator-result-by-date?utm_source=library">documentation</see> for more information.
20+
</para>
21+
</summary>
22+
<typeparam name="TSeries">Any series type.</typeparam>
23+
<param name="series">Time series to evaluate.</param>
24+
<param name="lookupDate">Exact date to lookup.</param>
25+
<returns>
26+
First index in the series of the date specified or -1 if not found.
27+
</returns>
1528
</type>
1629
<type name="Prune">
1730
<summary> Removes a specific quantity from the beginning of the time series list.
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
namespace Skender.Stock.Indicators;
2+
3+
// TUPLE OBSERVER and TUPLE PROVIDER (CHAIN STREAM)
4+
5+
public abstract class ChainProvider
6+
: TupleObserver, IObservable<(DateTime Date, double Value)>
7+
{
8+
// fields
9+
private readonly List<IObserver<(DateTime Date, double Value)>> observers;
10+
11+
// initialize
12+
protected ChainProvider()
13+
{
14+
observers = new();
15+
ProtectedChain = new();
16+
Warmup = true;
17+
}
18+
19+
// properties
20+
internal IEnumerable<(DateTime Date, double Value)> Output => ProtectedChain;
21+
22+
internal List<(DateTime Date, double Value)> ProtectedChain { get; set; }
23+
24+
private int OverflowCount { get; set; }
25+
26+
private bool Warmup { get; set; }
27+
28+
// METHODS
29+
30+
// subscribe observer
31+
public IDisposable Subscribe(IObserver<(DateTime Date, double Value)> observer)
32+
{
33+
if (!observers.Contains(observer))
34+
{
35+
observers.Add(observer);
36+
}
37+
38+
return new Unsubscriber(observers, observer);
39+
}
40+
41+
// close all observations
42+
public void EndTransmission()
43+
{
44+
foreach (IObserver<(DateTime Date, double Value)> observer in observers.ToArray())
45+
{
46+
if (observers.Contains(observer))
47+
{
48+
observer.OnCompleted();
49+
}
50+
}
51+
52+
observers.Clear();
53+
}
54+
55+
// add one
56+
internal void SendToChain<TResult>(TResult result)
57+
where TResult : IReusableResult
58+
{
59+
// candidate result
60+
(DateTime Date, double Value) r = new(result.Date, result.Value.Null2NaN());
61+
62+
int length = ProtectedChain.Count;
63+
64+
// initialize
65+
if (length == 0 && result.Value != null)
66+
{
67+
// add new tuple
68+
ProtectedChain.Add(r);
69+
Warmup = false;
70+
71+
// notify observers
72+
NotifyObservers(r);
73+
return;
74+
}
75+
76+
// do not proceed until first non-null Value recieved
77+
if (Warmup && result.Value == null)
78+
{
79+
return;
80+
}
81+
else
82+
{
83+
Warmup = false;
84+
}
85+
86+
(DateTime lastDate, _) = ProtectedChain[length - 1];
87+
88+
// add tuple
89+
if (r.Date > lastDate)
90+
{
91+
// add new tuple
92+
ProtectedChain.Add(r);
93+
94+
// notify observers
95+
NotifyObservers(r);
96+
}
97+
98+
// same date or tuple recieved
99+
else if (r.Date <= lastDate)
100+
{
101+
// check for overflow condition
102+
// where same tuple continues (possible circular condition)
103+
if (r.Date == lastDate)
104+
{
105+
OverflowCount++;
106+
107+
if (OverflowCount > 100)
108+
{
109+
string msg = "A repeated Chain update exceeded the 100 attempt threshold. "
110+
+ "Check and remove circular chains or check your Chain provider.";
111+
112+
EndTransmission();
113+
114+
throw new OverflowException(msg);
115+
}
116+
}
117+
else
118+
{
119+
OverflowCount = 0;
120+
}
121+
122+
// seek old tuple
123+
int foundIndex = ProtectedChain
124+
.FindIndex(x => x.Date == r.Date);
125+
126+
// found
127+
if (foundIndex >= 0)
128+
{
129+
ProtectedChain[foundIndex] = r;
130+
}
131+
132+
// add missing tuple
133+
else
134+
{
135+
ProtectedChain.Add(r);
136+
137+
// re-sort cache
138+
ProtectedChain = ProtectedChain
139+
.ToSortedList();
140+
}
141+
142+
// let observer handle old + duplicates
143+
NotifyObservers(r);
144+
}
145+
}
146+
147+
// add many
148+
internal void SendToChain<TResult>(IEnumerable<TResult> results)
149+
where TResult : IReusableResult
150+
{
151+
List<TResult> added = results
152+
.ToSortedList();
153+
154+
for (int i = 0; i < added.Count; i++)
155+
{
156+
SendToChain(added[i]);
157+
}
158+
}
159+
160+
// notify observers
161+
private void NotifyObservers((DateTime Date, double Value) tuple)
162+
{
163+
List<IObserver<(DateTime Date, double Value)>> obsList = observers.ToList();
164+
165+
for (int i = 0; i < obsList.Count; i++)
166+
{
167+
IObserver<(DateTime Date, double Value)> obs = obsList[i];
168+
obs.OnNext(tuple);
169+
}
170+
}
171+
172+
// unsubscriber
173+
private class Unsubscriber : IDisposable
174+
{
175+
private readonly List<IObserver<(DateTime Date, double Value)>> observers;
176+
private readonly IObserver<(DateTime Date, double Value)> observer;
177+
178+
// identify and save observer
179+
public Unsubscriber(List<IObserver<(DateTime Date, double Value)>> observers, IObserver<(DateTime Date, double Value)> observer)
180+
{
181+
this.observers = observers;
182+
this.observer = observer;
183+
}
184+
185+
// remove single observer
186+
public void Dispose()
187+
{
188+
if (observer != null && observers.Contains(observer))
189+
{
190+
observers.Remove(observer);
191+
}
192+
}
193+
}
194+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
namespace Skender.Stock.Indicators;
2+
3+
// OBSERVER of QUOTES (BOILERPLATE)
4+
5+
public abstract class QuoteObserver : IObserver<Quote>
6+
{
7+
// fields
8+
private IDisposable? unsubscriber;
9+
10+
// properites
11+
internal QuoteProvider? Supplier { get; set; }
12+
13+
// methods
14+
public virtual void Subscribe()
15+
=> unsubscriber = Supplier != null
16+
? Supplier.Subscribe(this)
17+
: throw new ArgumentNullException(nameof(Supplier));
18+
19+
public virtual void OnCompleted() => Unsubscribe();
20+
21+
public virtual void OnError(Exception error) => throw error;
22+
23+
public virtual void OnNext(Quote value)
24+
{
25+
// » handle new quote with override in observer
26+
}
27+
28+
public virtual void Unsubscribe() => unsubscriber?.Dispose();
29+
}

0 commit comments

Comments
 (0)