Skip to content

Commit

Permalink
add custom indicator example (#759)
Browse files Browse the repository at this point in the history
  • Loading branch information
DaveSkender authored Mar 28, 2022
1 parent 71bef73 commit 15e3568
Show file tree
Hide file tree
Showing 11 changed files with 2,314 additions and 10 deletions.
8 changes: 4 additions & 4 deletions docs/GemFile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (6.0.4.6)
activesupport (6.0.4.7)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
Expand All @@ -15,7 +15,7 @@ GEM
coffee-script-source (1.11.1)
colorator (1.1.0)
commonmarker (0.23.4)
concurrent-ruby (1.1.9)
concurrent-ruby (1.1.10)
dnsruby (1.61.9)
simpleidn (~> 0.1)
em-websocket (0.5.3)
Expand Down Expand Up @@ -247,7 +247,7 @@ GEM
octokit (4.22.0)
faraday (>= 0.9)
sawyer (~> 0.8.0, >= 0.5.3)
parallel (1.21.0)
parallel (1.22.1)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
posix-spawn (0.3.15)
Expand Down Expand Up @@ -281,7 +281,7 @@ GEM
thread_safe (~> 0.1)
unf (0.1.4)
unf_ext
unf_ext (0.0.8-x64-mingw32)
unf_ext (0.0.8.1-x64-mingw32)
unicode-display_width (1.8.0)
wdm (0.1.1)
yell (2.2.2)
Expand Down
67 changes: 67 additions & 0 deletions docs/examples/CustomIndicators/AtrWma.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using Skender.Stock.Indicators;

namespace Custom.Stock.Indicators;

// Custom results class
// This inherits many of the extension methods, like .RemoveWarmupPeriods()
public class AtrWmaResult : ResultBase
{
public decimal? AtrWma { get; set; }
}

public static class CustomIndicators
{
// Custom ATR WMA calculation
public static IEnumerable<AtrWmaResult> GetAtrWma<TQuote>(
this IEnumerable<TQuote> quotes,
int lookbackPeriods = 10)
where TQuote : IQuote
{
// sort quotes and convert to list
List<TQuote> quotesList = quotes
.OrderBy(x => x.Date)
.ToList();

// initialize results
List<AtrWmaResult> results = new(quotesList.Count);

// perform pre-requisite calculations to get ATR values
List<AtrResult> atrResults = quotes
.GetAtr(lookbackPeriods)
.ToList();

// roll through quotes
for (int i = 0; i < quotesList.Count; i++)
{
TQuote q = quotesList[i];

AtrWmaResult r = new()
{
Date = q.Date
};

// only do calculations after uncalculable periods
if (i >= lookbackPeriods - 1)
{
decimal? sumWma = 0;
decimal? sumAtr = 0;

for (int p = i - lookbackPeriods + 1; p <= i; p++)
{
decimal close = quotesList[p].Close;
decimal? atr = atrResults[p]?.Atr;

sumWma += atr * close;
sumAtr += atr;
}

r.AtrWma = sumWma / sumAtr;
}

// add record to results
results.Add(r);
}

return results;
}
}
13 changes: 13 additions & 0 deletions docs/examples/CustomIndicators/CustomIndicatorsLibrary.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Skender.Stock.Indicators" Version="1.22.2" />
</ItemGroup>

</Project>
122 changes: 122 additions & 0 deletions docs/examples/CustomIndicators/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
---
title: Custom Indicators
permalink: /custom-indicators/
layout: default
---

# {{ page.title }}

At some point in your journey, you may want to create your own custom indicators.
The following is an example of how you'd create your own.
This example is also included in our [example usage code]({{site.baseurl}}/examples/#content).

## STEP 1: Create the Results class

Create your results class by extending the library `ResultBase` class. This will allow you to inherit many of the [utility functions]({{site.baseurl}}/utilities/#utilities-for-indicator-results), such as `RemoveWarmupPeriods()`.

```csharp
using Skender.Stock.Indicators;
namespace Custom.Stock.Indicators;

// custom results class
public class AtrWmaResult : ResultBase
{
// date property is inherited here,
// so you only need to add custom items
public decimal? AtrWma { get; set; }
}
```

## STEP 2: Create the your custom indicator

Create your custom algorithm in the same style as our main library so the API functions identically.

```csharp
public static class CustomIndicators
{
// Custom ATR WMA calculation
public static IEnumerable<AtrWmaResult> GetAtrWma<TQuote>(
this IEnumerable<TQuote> quotes,
int lookbackPeriods = 10)
where TQuote : IQuote
{
// sort quotes and convert to list
List<TQuote> quotesList = quotes
.OrderBy(x => x.Date)
.ToList();

// initialize results
List<AtrWmaResult> results = new(quotesList.Count);

// perform pre-requisite calculations to get ATR values
List<AtrResult> atrResults = quotes
.GetAtr(lookbackPeriods)
.ToList();

// roll through quotes
for (int i = 0; i < quotesList.Count; i++)
{
TQuote q = quotesList[i];

AtrWmaResult r = new()
{
Date = q.Date
};

// only do calculations after uncalculable periods
if (i >= lookbackPeriods - 1)
{
decimal? sumWma = 0;
decimal? sumAtr = 0;

for (int p = i - lookbackPeriods + 1; p <= i; p++)
{
decimal close = quotesList[p].Close;
decimal? atr = atrResults[p]?.Atr;

sumWma += atr * close;
sumAtr += atr;
}

r.AtrWma = sumWma / sumAtr;
}

// add record to results
results.Add(r);
}

return results;
}
}
```

## STEP 3: Use your indicator just like the others

```csharp
using Skender.Stock.Indicators;
using Custom.Stock.Indicators; // your custom library
[..]

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

// calculate 10-period ATR WMA
IEnumerable<AtrWmaResult> results = quotes.GetAtrWma(10);

// use results as needed for your use case (example only)
foreach (AtrWmaResult r in results)
{
Console.WriteLine($"ATR WMA on {r.Date:d} was ${r.AtrWma:N4}");
}
```

```console
ATR WMA on 4/19/2018 was $255.0590
ATR WMA on 4/20/2018 was $255.2015
ATR WMA on 4/23/2018 was $255.6135
ATR WMA on 4/24/2018 was $255.5105
ATR WMA on 4/25/2018 was $255.6570
ATR WMA on 4/26/2018 was $255.9705
..
```
24 changes: 24 additions & 0 deletions docs/examples/CustomIndicatorsUsage/CustomIndicatorsUsage.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CustomIndicators\CustomIndicatorsLibrary.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="quotes.data.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
58 changes: 58 additions & 0 deletions docs/examples/CustomIndicatorsUsage/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using Custom.Stock.Indicators;
using Newtonsoft.Json;
using Skender.Stock.Indicators;

namespace ConsoleApp;

// USE CUSTOM INDICATORS exactly the same as
// other indicators in the library

public class Program
{
public static void Main()
{

// fetch historical quotes from data provider
IEnumerable<Quote> quotes = GetHistoryFromFeed();

// calculate 10-period custom AtrWma
IEnumerable<AtrWmaResult> results = quotes.GetAtrWma(10);

// show results
Console.WriteLine("ATR WMA Results ---------------------------");

foreach (AtrWmaResult r in results.TakeLast(25))
// only showing last 25 records for brevity
{
Console.WriteLine($"ATR WMA on {r.Date:u} was ${r.AtrWma:N3}");
}
}


private static IEnumerable<Quote> GetHistoryFromFeed()
{
/************************************************************
We're mocking a data provider here by simply importing a
JSON file, a similar format of many public APIs.
This approach will vary widely depending on where you are
getting your quote history.
See https://github.com/DaveSkender/Stock.Indicators/discussions/579
for free or inexpensive market data providers and examples.
The return type of IEnumerable<Quote> can also be List<Quote>
or ICollection<Quote> or other IEnumerable compatible types.
************************************************************/

string json = File.ReadAllText("quotes.data.json");

List<Quote> quotes = JsonConvert.DeserializeObject<IReadOnlyCollection<Quote>>(json)
.OrderBy(x => x.Date)
.ToList();

return quotes;
}
}
Loading

0 comments on commit 15e3568

Please sign in to comment.