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

refactor: Performance tuning (stream + incr experiments) #1230

Merged
merged 46 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
593da08
cosmetic: rename removal test
DaveSkender Jul 29, 2024
bbc2332
docs: fix BasicData internal link refs
DaveSkender Jul 29, 2024
0607d69
simplify ATR Stop result class (to double)
DaveSkender Jul 29, 2024
71c453f
fix Use.Api.cs
DaveSkender Jul 29, 2024
d59cac6
Merge branch 'v3-increments' into 'perf-tuning'
DaveSkender Jul 29, 2024
e99cd10
Merge branch 'v3-increments' into perf-tuning
DaveSkender Jul 30, 2024
9eace3b
tuneup Dynamic
DaveSkender Jul 30, 2024
9448839
tuneup CMO
DaveSkender Jul 30, 2024
64c557c
tuneup MACD
DaveSkender Jul 30, 2024
49d3a7f
tune KAMA
DaveSkender Jul 30, 2024
052bad1
tuneup Alligator
DaveSkender Jul 30, 2024
4951454
tuneup EMA
DaveSkender Jul 30, 2024
c13f772
refactor: update convergence tests
DaveSkender Jul 30, 2024
16984a3
update incrementing (experiemental)
DaveSkender Jul 30, 2024
17e284f
restore Add() syntax
DaveSkender Jul 30, 2024
e044357
add null reference checks
DaveSkender Jul 30, 2024
91eab0e
update Incrementals to use System.Queue
DaveSkender Jul 30, 2024
5e3333b
code cleanup: CMO boolean
DaveSkender Jul 31, 2024
d911909
rename test file
DaveSkender Aug 5, 2024
83cef30
Merge branch 'v3-increments' into perf-tuning
DaveSkender Aug 5, 2024
81970fd
Merge branch 'v3-increments' into perf-tuning
DaveSkender Aug 12, 2024
4679254
Merge branch 'v3-increments' into perf-tuning
DaveSkender Sep 3, 2024
9bbc60e
Merge branch 'v3-increments' into perf-tuning
DaveSkender Oct 6, 2024
25ef259
refactor: Performance tuning (simplify caching analysis) (#1235)
DaveSkender Oct 9, 2024
8b02491
minor static EMA edits
DaveSkender Oct 10, 2024
c0cd0ec
revert: rename of GetEma to ToEma (for now)
DaveSkender Oct 11, 2024
a027f30
array-ize static TR
DaveSkender Oct 11, 2024
d24a41f
refactor static alligator
DaveSkender Oct 13, 2024
7e5a213
revert: last commit (slower)
DaveSkender Oct 13, 2024
9e64796
revert concrete Reusable abstract
DaveSkender Oct 13, 2024
d18ea37
remove experimental EMA primitive buffer
DaveSkender Oct 13, 2024
e5c4a4c
add readonly quotes validation + tests
DaveSkender Oct 13, 2024
5df1ffd
use IReadOnly result inputs
DaveSkender Oct 14, 2024
70c6d60
convert to naming for static series
DaveSkender Oct 14, 2024
5711cc5
improve quote validation tests
DaveSkender Oct 14, 2024
6b4c81c
refactor: use readonly list input a-z, common
DaveSkender Oct 14, 2024
43efa96
refactor: use readonly list input e-k
DaveSkender Oct 14, 2024
8412ffc
fix: invariant culture in tests
DaveSkender Oct 14, 2024
797d117
rename Increment interfaces
DaveSkender Oct 27, 2024
ddd6960
Merge branch 'v3-increments' into perf-tuning
DaveSkender Oct 27, 2024
8ed3b54
Merge branch 'perf-tuning' of https://github.com/DaveSkender/Stock.In…
DaveSkender Oct 27, 2024
e10c321
refactor: use readonly list input m-r
DaveSkender Oct 27, 2024
b110de3
refactor: use readonly list input s-z
DaveSkender Oct 27, 2024
84c8f8e
refactor: IReadOnly for utilities
DaveSkender Oct 27, 2024
214ce05
leave note on CMO tests
DaveSkender Oct 27, 2024
abde78a
code cleanup
DaveSkender Oct 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ body:
render: csharp
placeholder: |
// example (put your own code here)
IEnumerable<EmaResult> results = quotes.GetEma(14);
IReadOnlyList<EmaResult> results = quotes.GetEma(14);
validations:
required: false
- type: textarea
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/test-performance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
run: dotnet run -c Release

- name: Save test results
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: test-summaries
path: tests/performance/BenchmarkDotNet.Artifacts/results
Expand All @@ -62,3 +62,9 @@ jobs:

echo "## Stream indicators (with Quote caching)" >> $GITHUB_STEP_SUMMARY
cat Performance.StreamIndicators-report-github.md >> $GITHUB_STEP_SUMMARY

echo "## Incremental indicators (with buffer)" >> $GITHUB_STEP_SUMMARY
cat Performance.Incrementals-report-github.md >> $GITHUB_STEP_SUMMARY

echo "## Utilities" >> $GITHUB_STEP_SUMMARY
cat Performance.Utility-report-github.md >> $GITHUB_STEP_SUMMARY
13 changes: 7 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,13 @@ $tf/
# Guidance Automation Toolkit
*.gpState

# ReSharper is a .NET coding add-in
# ReSharper IDE extension
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
*.DotSettings

# JustCode is a .NET coding add-in
# JustCode IDE extension
.JustCode

# TeamCity is a build add-in
Expand Down Expand Up @@ -218,7 +219,7 @@ ClientBin/
*.publishsettings
orleans.codegen.cs

# Including strong name files can present a security risk
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk

Expand Down Expand Up @@ -314,7 +315,7 @@ __pycache__/
# OpenCover UI analysis results
OpenCover/

# Azure Stream Analytics local run output
# Azure Stream Analytics local run output
ASALocalRun/

# MSBuild Binary and Structured Log
Expand All @@ -323,11 +324,11 @@ ASALocalRun/
# NVidia Nsight GPU debugger configuration file
*.nvuser

# MFractors (Xamarin productivity tool) working folder
# MFractors (Xamarin productivity tool) working folder
.mfractor/

# Jekyll site
_site/

# zip artifacts
.DS_Store
.DS_Store
10 changes: 5 additions & 5 deletions docs/_data/aliases.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,23 +59,23 @@
type: price-characteristic

- title: HL2
permalink: /indicators/BasicQuote/
permalink: /indicators/quotepart/
type: price-transform

- title: HLC3
permalink: /indicators/BasicQuote/
permalink: /indicators/quotepart/
type: price-transform

- title: OC2
permalink: /indicators/BasicQuote/
permalink: /indicators/quotepart/
type: price-transform

- title: OHL3
permalink: /indicators/BasicQuote/
permalink: /indicators/quotepart/
type: price-transform

- title: OHLC4
permalink: /indicators/BasicQuote/
permalink: /indicators/quotepart/
type: price-transform

- title: Price Channels
Expand Down
6 changes: 3 additions & 3 deletions docs/_indicators/AtrStop.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ IReadOnlyList<AtrStopResult>

**`Timestamp`** _`DateTime`_ - date from evaluated `TQuote`

**`AtrStop`** _`decimal`_ - ATR Trailing Stop line contains both Upper and Lower segments
**`AtrStop`** _`double`_ - ATR Trailing Stop line contains both Upper and Lower segments

**`BuyStop`** _`decimal`_ - Upper band only (green)
**`BuyStop`** _`double`_ - Upper band only (green)

**`SellStop`** _`decimal`_ - Lower band only (red)
**`SellStop`** _`double`_ - Lower band only (red)

**`Atr`** _`double`_ - Average True Range

Expand Down
2 changes: 1 addition & 1 deletion docs/_indicators/Beta.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ IReadOnlyList<BetaResult> results = quotesEval

## Parameters

**`quotesMarket`** _`IEnumerable<TQuote>`_ - [Historical quotes]({{site.baseurl}}/guide/#historical-quotes) market data should be at any consistent frequency (day, hour, minute, etc). This `market` quotes will be used to establish the baseline.
**`quotesMarket`** _`IReadOnlyList<TQuote>`_ - [Historical quotes]({{site.baseurl}}/guide/#historical-quotes) market data should be at any consistent frequency (day, hour, minute, etc). This `market` quotes will be used to establish the baseline.

**`lookbackPeriods`** _`int`_ - Number of periods (`N`) in the lookback window. Must be greater than 0 to calculate; however we suggest a larger period for statistically appropriate sample size and especially when using Beta +/-.

Expand Down
2 changes: 1 addition & 1 deletion docs/_indicators/Correlation.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ IReadOnlyList<CorrResult> results =

You must have at least `N` periods for both versions of `quotes` to cover the warmup periods. Mismatch histories will produce a `InvalidQuotesException`. Historical price quotes should have a consistent frequency (day, hour, minute, etc).

`quotesA` is an `IEnumerable<TQuote>` collection of historical price quotes. It should have a consistent frequency (day, hour, minute, etc). See [the Guide]({{site.baseurl}}/guide/#historical-quotes) for more information.
`quotesA` is an `IReadOnlyList<TQuote>` collection of historical price quotes. It should have a consistent frequency (day, hour, minute, etc). See [the Guide]({{site.baseurl}}/guide/#historical-quotes) for more information.

## Response

Expand Down
13 changes: 10 additions & 3 deletions docs/_indicators/Use.md → docs/_indicators/QuotePart.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
---
title: Basic quote transform
title: Quote parts and basic transforms
description: Basic quote transforms (e.g. HL2, OHL3, etc.) and isolation of individual price quote candle parts from a full OHLCV quote.
permalink: /indicators/Use/
permalink: /indicators/quotepart/
redirect-from:
- /indicators/Use/
- /indicators/BasicQuote/
type: price-transform
layout: indicator
---

# {{ page.title }}

Returns a reusable (chainable) basic quote transform (e.g. HL2, OHL3, etc.) by isolating a single value or calculated value from the full OHLCV quote candle parts.
Returns a reusable (chainable) basic quote transform (e.g. HL2, OHL3, etc.) by isolating a single component part value or calculated value from the full OHLCV quote candle parts.

```csharp
// C# usage syntax
IReadOnlyList<QuotePart> results =
quotes.Use(candlePart);

// alternate syntax
IReadOnlyList<QuotePart> results =
quotes.GetQuotePart(candlePart);
```

## Parameters
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/CustomIndicators/AtrWma.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static class CustomIndicators
{
// Custom ATR WMA calculation
public static IReadOnlyList<AtrWmaResult> GetAtrWma<TQuote>(
this IEnumerable<TQuote> quotes,
this IReadOnlyList<TQuote> quotes,
int lookbackPeriods)
where TQuote : IQuote
{
Expand Down
12 changes: 6 additions & 6 deletions docs/pages/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Most indicators require that you provide historical quote data and additional co

You must get historical quotes from your own market data provider. For clarification, the `GetQuotesFromFeed()` method shown in the example below **is not part of this library**, but rather an example to represent your own acquisition of historical quotes.

Historical price data can be provided as a `List`, `IEnumerable`, or `ICollection` of the `Quote` class ([see below](#historical-quotes)); however, it can also be supplied as a generic [custom TQuote type](#using-custom-quote-classes) if you prefer to use your own quote model.
Historical price data can be provided as a `List`, `IReadOnlyList`, or `ICollection` of the `Quote` class ([see below](#historical-quotes)); however, it can also be supplied as a generic [custom TQuote type](#using-custom-quote-classes) if you prefer to use your own quote model.

For additional configuration parameters, default values are provided when there is an industry standard. You can, of course, override these and provide your own values.

Expand All @@ -57,7 +57,7 @@ using Skender.Stock.Indicators;
[..]

// fetch historical quotes from your feed (your method)
IEnumerable<Quote> quotes = GetQuotesFromFeed("MSFT");
IReadOnlyList<Quote> quotes = GetQuotesFromFeed("MSFT");

// calculate 20-period SMA
IReadOnlyList<SmaResult> results = quotes
Expand Down Expand Up @@ -89,7 +89,7 @@ More examples available:

## Historical quotes

You must provide historical price quotes to the library in the standard OHLCV `IEnumerable<Quote>` or a compatible `List` or `ICollection` format. It should have a consistent period frequency (day, hour, minute, etc). See [using custom quote classes](#using-custom-quote-classes) if you prefer to use your own quote class.
You must provide historical price quotes to the library in the standard OHLCV `IReadOnlyList<Quote>` or a compatible `List` or `ICollection` format. It should have a consistent period frequency (day, hour, minute, etc). See [using custom quote classes](#using-custom-quote-classes) if you prefer to use your own quote class.

| name | type | notes
| -- |-- |--
Expand Down Expand Up @@ -194,10 +194,10 @@ public class MyCustomQuote : IQuote
```csharp
// USAGE
// fetch historical quotes from your favorite feed
IEnumerable<MyCustomQuote> myQuotes = GetQuotesFromFeed("MSFT");
IReadOnlyList<MyCustomQuote> myQuotes = GetQuotesFromFeed("MSFT");

// example: get 20-period simple moving average
IEnumerable<SmaResult> results = myQuotes.GetSma(20);
IReadOnlyList<SmaResult> results = myQuotes.GetSma(20);
```

#### Using custom quote property names
Expand Down Expand Up @@ -247,7 +247,7 @@ Example:

```csharp
// fetch historical quotes from your feed (your method)
IEnumerable<Quote> quotes = GetQuotesFromFeed("SPY");
IReadOnlyList<Quote> quotes = GetQuotesFromFeed("SPY");

// calculate RSI of OBV
IReadOnlyList<RsiResult> results
Expand Down
10 changes: 5 additions & 5 deletions docs/pages/utilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,23 @@ var results = quotes

### Sort quotes

`quotes.ToSortedCollection()` sorts any collection of `TQuote` or `ISeries` and returns it as a `Collection` sorted by ascending `Timestamp`. You do not need to sort quotes before using library indicators; however, if you are creating [custom indicators]({{site.baseurl}}/custom-indicators/#content) it's important to analyze `quotes` in a proper sequence.
`quotes.ToSortedList()` sorts any collection of `TQuote` or `ISeries` and returns it as a `IReadOnlyList` sorted by ascending `Timestamp`. You **do need to sort quotes** before using library indicators.

### Resize quote history

`quotes.Aggregate(newSize)` is a tool to convert quotes to larger bar sizes. For example if you have minute bar sizes in `quotes`, but want to convert it to hourly or daily.

```csharp
// aggregate into larger bars
IEnumerable<Quote> dayBarQuotes =
IReadOnlyList<Quote> dayBarQuotes =
minuteBarQuotes.Aggregate(PeriodSize.Day);
```

An alternate version of this utility is provided where you can use any native `TimeSpan` value that is greater than `TimeSpan.Zero`.

```csharp
// alternate usage with TimeSpan
IEnumerable<Quote> dayBarQuotes =
IReadOnlyList<Quote> dayBarQuotes =
minuteBarQuotes.Aggregate(TimeSpan timeSpan);
```

Expand Down Expand Up @@ -87,7 +87,7 @@ IReadOnlyList<CandleProperties> candles = quotes.ToCandles();

```csharp
// advanced validation
IEnumerable<Quote> validatedQuotes = quotes.Validate();
IReadOnlyList<Quote> validatedQuotes = quotes.Validate();

// and can be used inline with chaining
var results = quotes
Expand Down Expand Up @@ -148,7 +148,7 @@ See [individual indicator pages]({{site.baseurl}}/indicators/#content) for infor

### Sort results

`results.ToSortedCollection()` sorts any collection of indicator results and returns it as a `Collection` sorted by ascending `Timestamp`. Results from the library indicators are already sorted, so you'd only potentially need this if you're creating [custom indicators]({{site.baseurl}}/custom-indicators/#content).
`results.ToSortedList()` sorts any collection of indicator results and returns it as a `IReadOnlyList` sorted by ascending `Timestamp`. Results from the library indicators are already sorted, so you'd only potentially need this if you're creating [custom indicators]({{site.baseurl}}/custom-indicators/#content).

## Utilities for numerical analysis

Expand Down
110 changes: 110 additions & 0 deletions src/_common/BinarySettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
namespace Skender.Stock.Indicators;

/// <summary>
/// Binary on/off switches for high performance access
/// to behaviors and characteristics.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="BinarySettings"/> struct.
/// The Mask parameter is optional and defaults to 0b11111111 where all bits
/// pass through to "combinor" sets.
///
/// Example of accessing a specific bit:
/// <code>
/// BinarySettings settings = new(0b00000001); // bit 0 is set to 1
/// bool isBit0Set = settings[0]; // true
/// bool isBit1Set = settings[1]; // false
/// </code>
///
/// Example of re/setting a specific bit:
/// <code>
/// BinarySettings settings = new(0);
/// settings = settings with { [0] = true }; // set bit 0 to true
/// settings = settings with { [1] = false }; // set bit 1 to false
/// </code>
/// </remarks>
/// <param name="settings">
/// Binary settings.
/// Default is 0b00000000 (binary literal of 0).
/// </param>
/// <param name="mask">
/// Mask for settings inheritence.
/// Default is 0b11111111 (binary literal of 255).
/// </param>
public readonly struct BinarySettings(
byte settings,
byte mask = 0b11111111) : IEquatable<BinarySettings>
{
public byte Settings { get; } = settings;
public byte Mask { get; } = mask;

// use default settings (none) and mask
// important: this explicit parameterless ctor required for struct
public BinarySettings() : this(settings: 0b00000000) { }

/// <summary>
/// Gets the value of the bit at the specified index.
/// </summary>
/// <param name="index">The index of the bit to get.</param>
/// <returns>True if the bit is set; otherwise, false.</returns>
public bool this[short index]
=> (Settings & (1 << index)) != 0;

/// <summary>
/// Combines the current settings with another <see cref="BinarySettings"/> instance
/// using a bitwise OR operation, excluding the bits masked by the parent settings.
/// </summary>
/// <param name="parentSettings">The parent <see cref="BinarySettings"/> instance to combine with.</param>
/// <returns>
/// A new <see cref="BinarySettings"/> instance with combined settings.
/// Notably, it does not modify the current read-only instance.
/// </returns>
///
/// <remarks>
/// The mask is used to determine which bits from the parent settings should be excluded
/// during the combination. By default, the mask is set to 0b11111111, meaning all bits
/// are included. If a different mask is provided, the corresponding bits in the parent
/// settings will be excluded based on the mask.
/// <para>
/// In other words, the mask you provide on instantiation will determine which bits are
/// genetic material passed on to the "combinor" child settings. The child settings will inherit the
/// bits from the parent settings that the parent decides to pass along.
/// </para>
///
/// Usage example (default mask):
/// <code>
/// BinarySettings srcSettings = new(0b01101001);
/// BinarySettings defSettings = new(0b00000010);
/// BinarySettings newSettings = defSettings.Combine(srcSettings); // result: 0b01101011
/// </code>
/// Using a custom mask:
/// <code>
/// BinarySettings customMaskSettings = new(0b01101001, 0b11111110); // do not pass 0th bit value
/// BinarySettings newSettingsWithCustomMask = defSettings.Combine(customMaskSettings); // result: 0b01101010
/// </code>
/// </remarks>
public BinarySettings Combine(BinarySettings parentSettings)
{
// add parent bits according to their mask template
byte maskedParentSettings = (byte)(parentSettings.Settings & parentSettings.Mask);

// combine the settings
return new BinarySettings((byte)(Settings | maskedParentSettings), parentSettings.Mask);
}

public override bool Equals(object? obj)
=> obj is BinarySettings other && Equals(other);

public bool Equals(BinarySettings other)
=> Settings == other.Settings && Mask == other.Mask;

public override int GetHashCode()
=> HashCode.Combine(Settings, Mask);

public static bool operator ==(BinarySettings left, BinarySettings right)
=> left.Equals(right);

public static bool operator !=(BinarySettings left, BinarySettings right)
=> !(left == right);
}

Loading