From 8bfe24ac1d422e2c98b7a8a2b7e05810f61b5360 Mon Sep 17 00:00:00 2001 From: kingchenc Date: Thu, 11 Jun 2026 15:22:29 +0200 Subject: [PATCH] test(golden): language-neutral fixtures + per-binding parity runners (#255) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Task 5 — golden-fixture parity for the C-ABI bindings.** Lifts the C#/Go/Java/R tests from *one indicator per archetype* toward reference-value parity, catching FFI wiring bugs (swapped params, wrong multi-output field) the math-only core tests cannot see. ## What's here - **`examples/rust/src/bin/gen_golden.rs`** + **`testdata/golden/*.csv`** — a Rust generator computing a deterministic OHLCV series plus the core's reference outputs for a curated archetype-spanning set: scalar (`Sma`/`Ema`/`Rsi`), candle (`Atr`), scalar multi-output (`MACD`), candle multi-output (`ADX`), pairwise (`Beta`). `nan` marks warmup. Regenerate with `cargo run -p wickra-examples --bin gen_golden`. - **Parity runners** replaying the identical fixtures through each FFI (rel-tol 1e-6), each a standard test in the binding's existing suite (no `ci.yml` change — rides `dotnet test` / `go test` / `mvn install` / `R CMD`+testthat). A walk-up search locates `testdata/golden` regardless of run dir. - **C#** (`bindings/csharp/.../GoldenTests.cs`) — ✅ validated locally, 7/7 pass. - **Go** (`bindings/go/golden_test.go`) — ✅ validated locally, pass. - **Java** (`bindings/java/.../GoldenTests.java`) — modeled on the archetype API; validated by CI (no local mvn). - **R** (`bindings/r/tests/testthat/test-golden.R`) — modeled on the archetype API; validated by CI (no local Rscript). ## Notes - The curated set spans every marshalling archetype; extending the indicator list is mechanical (add to the generator + regenerate). Bars/profile archetypes can be added next. - No new CI jobs. --- bindings/csharp/Wickra.Tests/GoldenTests.cs | 162 +++++++++++++++ bindings/go/golden_test.go | 191 ++++++++++++++++++ .../src/test/java/org/wickra/GoldenTests.java | 160 +++++++++++++++ bindings/r/tests/testthat/test-golden.R | 98 +++++++++ examples/rust/src/bin/gen_golden.rs | 138 +++++++++++++ testdata/golden/adx.csv | 81 ++++++++ testdata/golden/atr.csv | 81 ++++++++ testdata/golden/beta.csv | 81 ++++++++ testdata/golden/ema.csv | 81 ++++++++ testdata/golden/input.csv | 81 ++++++++ testdata/golden/macd.csv | 81 ++++++++ testdata/golden/rsi.csv | 81 ++++++++ testdata/golden/sma.csv | 81 ++++++++ 13 files changed, 1397 insertions(+) create mode 100644 bindings/csharp/Wickra.Tests/GoldenTests.cs create mode 100644 bindings/go/golden_test.go create mode 100644 bindings/java/src/test/java/org/wickra/GoldenTests.java create mode 100644 bindings/r/tests/testthat/test-golden.R create mode 100644 examples/rust/src/bin/gen_golden.rs create mode 100644 testdata/golden/adx.csv create mode 100644 testdata/golden/atr.csv create mode 100644 testdata/golden/beta.csv create mode 100644 testdata/golden/ema.csv create mode 100644 testdata/golden/input.csv create mode 100644 testdata/golden/macd.csv create mode 100644 testdata/golden/rsi.csv create mode 100644 testdata/golden/sma.csv diff --git a/bindings/csharp/Wickra.Tests/GoldenTests.cs b/bindings/csharp/Wickra.Tests/GoldenTests.cs new file mode 100644 index 00000000..e4360e6f --- /dev/null +++ b/bindings/csharp/Wickra.Tests/GoldenTests.cs @@ -0,0 +1,162 @@ +using System.Globalization; +using System.Runtime.CompilerServices; +using Wickra; +using Xunit; + +namespace Wickra.Tests; + +/// +/// Golden-fixture parity: replay the shared testdata/golden input series +/// through the C# FFI and assert every value matches the Rust reference output. +/// Where the archetype tests only check finiteness, this pins exact values, so a +/// wiring bug (swapped parameter, wrong multi-output field) is caught. +/// Fixtures are generated by cargo run -p wickra-examples --bin gen_golden. +/// +public class GoldenTests +{ + private const double Tol = 1e-6; + + private static string GoldenDir([CallerFilePath] string file = "") => + Path.GetFullPath(Path.Combine(Path.GetDirectoryName(file)!, "..", "..", "..", "testdata", "golden")); + + private static List ReadCsv(string name) + { + var path = Path.Combine(GoldenDir(), name + ".csv"); + return File.ReadAllLines(path) + .Skip(1) // header + .Where(l => l.Length > 0) + .Select(l => l.Split(',')) + .ToList(); + } + + private static double[][] Input() + { + return ReadCsv("input") + .Select(r => r.Select(c => double.Parse(c, CultureInfo.InvariantCulture)).ToArray()) + .ToArray(); + } + + private static double Cell(string s) => + s == "nan" ? double.NaN : double.Parse(s, CultureInfo.InvariantCulture); + + private static void AssertClose(double got, double want, int row, string field) + { + if (double.IsNaN(want)) + { + Assert.True(double.IsNaN(got), $"row {row} {field}: expected warmup/NaN, got {got}"); + return; + } + var tol = Tol * Math.Max(1.0, Math.Abs(want)); + Assert.True(Math.Abs(got - want) <= tol, $"row {row} {field}: got {got}, want {want}"); + } + + // --- scalar (close-driven) ------------------------------------------------ + + [Theory] + [InlineData("sma")] + [InlineData("ema")] + [InlineData("rsi")] + public void Scalar_MatchesGolden(string name) + { + var input = Input(); + var expected = ReadCsv(name); + using var ind = (IDisposable)(name switch + { + "sma" => new Sma(14), + "ema" => new Ema(14), + "rsi" => new Rsi(14), + _ => throw new ArgumentOutOfRangeException(nameof(name)), + }); + for (var i = 0; i < input.Length; i++) + { + var close = input[i][3]; + double got = ind switch + { + Sma s => s.Update(close), + Ema e => e.Update(close), + Rsi r => r.Update(close), + _ => double.NaN, + }; + AssertClose(got, Cell(expected[i][0]), i, name); + } + } + + // --- candle, single output ------------------------------------------------ + + [Fact] + public void Candle_Atr_MatchesGolden() + { + var input = Input(); + var expected = ReadCsv("atr"); + using var atr = new Atr(14); + for (var i = 0; i < input.Length; i++) + { + var (o, h, l, c, v) = (input[i][0], input[i][1], input[i][2], input[i][3], input[i][4]); + AssertClose(atr.Update(o, h, l, c, v, i), Cell(expected[i][0]), i, "atr"); + } + } + + // --- pairwise ------------------------------------------------------------- + + [Fact] + public void Pairwise_Beta_MatchesGolden() + { + var input = Input(); + var expected = ReadCsv("beta"); + using var beta = new Beta(20); + for (var i = 0; i < input.Length; i++) + { + // generator fed (close, open) + AssertClose(beta.Update(input[i][3], input[i][0]), Cell(expected[i][0]), i, "beta"); + } + } + + // --- scalar multi-output: MACD ------------------------------------------- + + [Fact] + public void MultiOutput_Macd_MatchesGolden() + { + var input = Input(); + var expected = ReadCsv("macd"); + using var macd = new MacdIndicator(12, 26, 9); + for (var i = 0; i < input.Length; i++) + { + MacdOutput? got = macd.Update(input[i][3]); + var e = expected[i]; + if (e[0] == "nan") + { + Assert.Null(got); + continue; + } + Assert.NotNull(got); + AssertClose(got!.Value.Macd, Cell(e[0]), i, "macd.macd"); + AssertClose(got.Value.Signal, Cell(e[1]), i, "macd.signal"); + AssertClose(got.Value.Histogram, Cell(e[2]), i, "macd.histogram"); + } + } + + // --- candle multi-output: ADX -------------------------------------------- + + [Fact] + public void MultiOutput_Adx_MatchesGolden() + { + var input = Input(); + var expected = ReadCsv("adx"); + using var adx = new Adx(14); + for (var i = 0; i < input.Length; i++) + { + var (o, h, l, c, v) = (input[i][0], input[i][1], input[i][2], input[i][3], input[i][4]); + AdxOutput? got = adx.Update(o, h, l, c, v, i); + var e = expected[i]; + if (e[0] == "nan") + { + Assert.Null(got); + continue; + } + Assert.NotNull(got); + AssertClose(got!.Value.PlusDi, Cell(e[0]), i, "adx.plus_di"); + AssertClose(got.Value.MinusDi, Cell(e[1]), i, "adx.minus_di"); + AssertClose(got.Value.Adx, Cell(e[2]), i, "adx.adx"); + } + } +} diff --git a/bindings/go/golden_test.go b/bindings/go/golden_test.go new file mode 100644 index 00000000..b5ed3b84 --- /dev/null +++ b/bindings/go/golden_test.go @@ -0,0 +1,191 @@ +package wickra + +import ( + "bufio" + "math" + "os" + "strconv" + "strings" + "testing" +) + +// Golden-fixture parity: replay the shared testdata/golden input series through +// the Go FFI and assert every value matches the Rust reference output. Where the +// archetype test only checks finiteness, this pins exact values, catching wiring +// bugs (swapped params, wrong multi-output field). Fixtures are generated by +// `cargo run -p wickra-examples --bin gen_golden`. + +const goldenTol = 1e-6 + +func readGolden(t *testing.T, name string) [][]string { + t.Helper() + f, err := os.Open("../../testdata/golden/" + name + ".csv") + if err != nil { + t.Fatalf("open %s: %v", name, err) + } + defer f.Close() + var rows [][]string + sc := bufio.NewScanner(f) + first := true + for sc.Scan() { + line := sc.Text() + if first { + first = false + continue + } + if line == "" { + continue + } + rows = append(rows, strings.Split(line, ",")) + } + return rows +} + +func goldenCell(s string) float64 { + if s == "nan" { + return math.NaN() + } + v, _ := strconv.ParseFloat(s, 64) + return v +} + +func goldenInput(t *testing.T) [][]float64 { + rows := readGolden(t, "input") + out := make([][]float64, len(rows)) + for i, r := range rows { + vals := make([]float64, len(r)) + for j, c := range r { + vals[j] = goldenCell(c) + } + out[i] = vals + } + return out +} + +func assertGoldenClose(t *testing.T, got, want float64, row int, field string) { + t.Helper() + if math.IsNaN(want) { + if !math.IsNaN(got) { + t.Errorf("row %d %s: expected warmup/NaN, got %v", row, field, got) + } + return + } + tol := goldenTol * math.Max(1.0, math.Abs(want)) + if math.Abs(got-want) > tol { + t.Errorf("row %d %s: got %v want %v", row, field, got, want) + } +} + +func TestGoldenScalar(t *testing.T) { + input := goldenInput(t) + sma, err := NewSma(14) + if err != nil { + t.Fatal(err) + } + defer sma.Close() + ema, err := NewEma(14) + if err != nil { + t.Fatal(err) + } + defer ema.Close() + rsi, err := NewRsi(14) + if err != nil { + t.Fatal(err) + } + defer rsi.Close() + + cases := []struct { + name string + upd func(close float64) float64 + }{ + {"sma", sma.Update}, + {"ema", ema.Update}, + {"rsi", rsi.Update}, + } + for _, tc := range cases { + exp := readGolden(t, tc.name) + for i := range input { + assertGoldenClose(t, tc.upd(input[i][3]), goldenCell(exp[i][0]), i, tc.name) + } + } +} + +func TestGoldenAtr(t *testing.T) { + input := goldenInput(t) + exp := readGolden(t, "atr") + atr, err := NewAtr(14) + if err != nil { + t.Fatal(err) + } + defer atr.Close() + for i := range input { + got := atr.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i)) + assertGoldenClose(t, got, goldenCell(exp[i][0]), i, "atr") + } +} + +func TestGoldenBeta(t *testing.T) { + input := goldenInput(t) + exp := readGolden(t, "beta") + beta, err := NewBeta(20) + if err != nil { + t.Fatal(err) + } + defer beta.Close() + for i := range input { + // generator fed (close, open) + assertGoldenClose(t, beta.Update(input[i][3], input[i][0]), goldenCell(exp[i][0]), i, "beta") + } +} + +func TestGoldenMacd(t *testing.T) { + input := goldenInput(t) + exp := readGolden(t, "macd") + macd, err := NewMacdIndicator(12, 26, 9) + if err != nil { + t.Fatal(err) + } + defer macd.Close() + for i := range input { + out, ok := macd.Update(input[i][3]) + if exp[i][0] == "nan" { + if ok { + t.Errorf("row %d macd: expected warmup, got %+v", i, out) + } + continue + } + if !ok { + t.Errorf("row %d macd: expected value, got warmup", i) + continue + } + assertGoldenClose(t, out.Macd, goldenCell(exp[i][0]), i, "macd.macd") + assertGoldenClose(t, out.Signal, goldenCell(exp[i][1]), i, "macd.signal") + assertGoldenClose(t, out.Histogram, goldenCell(exp[i][2]), i, "macd.histogram") + } +} + +func TestGoldenAdx(t *testing.T) { + input := goldenInput(t) + exp := readGolden(t, "adx") + adx, err := NewAdx(14) + if err != nil { + t.Fatal(err) + } + defer adx.Close() + for i := range input { + out, ok := adx.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i)) + if exp[i][0] == "nan" { + if ok { + t.Errorf("row %d adx: expected warmup, got %+v", i, out) + } + continue + } + if !ok { + t.Errorf("row %d adx: expected value, got warmup", i) + continue + } + assertGoldenClose(t, out.PlusDi, goldenCell(exp[i][0]), i, "adx.plus_di") + assertGoldenClose(t, out.MinusDi, goldenCell(exp[i][1]), i, "adx.minus_di") + assertGoldenClose(t, out.Adx, goldenCell(exp[i][2]), i, "adx.adx") + } +} diff --git a/bindings/java/src/test/java/org/wickra/GoldenTests.java b/bindings/java/src/test/java/org/wickra/GoldenTests.java new file mode 100644 index 00000000..def1d374 --- /dev/null +++ b/bindings/java/src/test/java/org/wickra/GoldenTests.java @@ -0,0 +1,160 @@ +package org.wickra; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +/** + * Golden-fixture parity: replay the shared {@code testdata/golden} input series + * through the Java FFI and assert every value matches the Rust reference output. + * Where the archetype tests only check finiteness, this pins exact values, so a + * wiring bug (swapped parameter, wrong multi-output field) is caught. Fixtures + * are generated by {@code cargo run -p wickra-examples --bin gen_golden}. + */ +class GoldenTests { + private static final double TOL = 1e-6; + + private static Path goldenDir() { + File d = new File("").getAbsoluteFile(); + while (d != null) { + File g = new File(d, "testdata/golden"); + if (g.isDirectory()) { + return g.toPath(); + } + d = d.getParentFile(); + } + throw new IllegalStateException("testdata/golden not found from " + new File("").getAbsolutePath()); + } + + private static List readCsv(String name) throws Exception { + List lines = Files.readAllLines(goldenDir().resolve(name + ".csv")); + List rows = new ArrayList<>(); + for (int i = 1; i < lines.size(); i++) { // skip header + if (!lines.get(i).isEmpty()) { + rows.add(lines.get(i).split(",")); + } + } + return rows; + } + + private static double cell(String s) { + return s.equals("nan") ? Double.NaN : Double.parseDouble(s); + } + + private static double[][] input() throws Exception { + List rows = readCsv("input"); + double[][] out = new double[rows.size()][]; + for (int i = 0; i < rows.size(); i++) { + String[] r = rows.get(i); + double[] v = new double[r.length]; + for (int j = 0; j < r.length; j++) { + v[j] = Double.parseDouble(r[j]); + } + out[i] = v; + } + return out; + } + + private static void close(double got, double want, int row, String field) { + if (Double.isNaN(want)) { + assertTrue(Double.isNaN(got), "row " + row + " " + field + ": expected warmup/NaN, got " + got); + return; + } + double tol = TOL * Math.max(1.0, Math.abs(want)); + assertTrue(Math.abs(got - want) <= tol, "row " + row + " " + field + ": got " + got + " want " + want); + } + + @Test + void scalarMatchesGolden() throws Exception { + double[][] in = input(); + try (Sma sma = new Sma(14)) { + List e = readCsv("sma"); + for (int i = 0; i < in.length; i++) { + close(sma.update(in[i][3]), cell(e.get(i)[0]), i, "sma"); + } + } + try (Ema ema = new Ema(14)) { + List e = readCsv("ema"); + for (int i = 0; i < in.length; i++) { + close(ema.update(in[i][3]), cell(e.get(i)[0]), i, "ema"); + } + } + try (Rsi rsi = new Rsi(14)) { + List e = readCsv("rsi"); + for (int i = 0; i < in.length; i++) { + close(rsi.update(in[i][3]), cell(e.get(i)[0]), i, "rsi"); + } + } + } + + @Test + void candleAtrMatchesGolden() throws Exception { + double[][] in = input(); + List e = readCsv("atr"); + try (Atr atr = new Atr(14)) { + for (int i = 0; i < in.length; i++) { + close(atr.update(in[i][0], in[i][1], in[i][2], in[i][3], in[i][4], i), cell(e.get(i)[0]), i, "atr"); + } + } + } + + @Test + void pairwiseBetaMatchesGolden() throws Exception { + double[][] in = input(); + List e = readCsv("beta"); + try (Beta beta = new Beta(20)) { + for (int i = 0; i < in.length; i++) { + // generator fed (close, open) + close(beta.update(in[i][3], in[i][0]), cell(e.get(i)[0]), i, "beta"); + } + } + } + + @Test + void macdMatchesGolden() throws Exception { + double[][] in = input(); + List e = readCsv("macd"); + try (MacdIndicator macd = new MacdIndicator(12, 26, 9)) { + for (int i = 0; i < in.length; i++) { + MacdOutput o = macd.update(in[i][3]); + String[] row = e.get(i); + if (row[0].equals("nan")) { + assertNull(o, "row " + i + " macd: expected warmup"); + continue; + } + assertNotNull(o, "row " + i + " macd: expected value"); + close(o.macd(), cell(row[0]), i, "macd.macd"); + close(o.signal(), cell(row[1]), i, "macd.signal"); + close(o.histogram(), cell(row[2]), i, "macd.histogram"); + } + } + } + + @Test + void adxMatchesGolden() throws Exception { + double[][] in = input(); + List e = readCsv("adx"); + try (Adx adx = new Adx(14)) { + for (int i = 0; i < in.length; i++) { + AdxOutput o = adx.update(in[i][0], in[i][1], in[i][2], in[i][3], in[i][4], i); + String[] row = e.get(i); + if (row[0].equals("nan")) { + assertNull(o, "row " + i + " adx: expected warmup"); + continue; + } + assertNotNull(o, "row " + i + " adx: expected value"); + close(o.plusDi(), cell(row[0]), i, "adx.plus_di"); + close(o.minusDi(), cell(row[1]), i, "adx.minus_di"); + close(o.adx(), cell(row[2]), i, "adx.adx"); + } + } + } +} diff --git a/bindings/r/tests/testthat/test-golden.R b/bindings/r/tests/testthat/test-golden.R new file mode 100644 index 00000000..055d5b7c --- /dev/null +++ b/bindings/r/tests/testthat/test-golden.R @@ -0,0 +1,98 @@ +# Golden-fixture parity: replay the shared testdata/golden input series through +# the R FFI and assert every value matches the Rust reference output. Where the +# archetype test only checks finiteness, this pins exact values, catching wiring +# bugs (swapped params, wrong multi-output field). Fixtures are generated by +# `cargo run -p wickra-examples --bin gen_golden`. + +golden_dir <- local({ + d <- normalizePath(getwd(), winslash = "/", mustWork = FALSE) + repeat { + g <- file.path(d, "testdata", "golden") + if (dir.exists(g)) return(g) + parent <- dirname(d) + if (identical(parent, d)) stop("testdata/golden not found from ", getwd()) + d <- parent + } +}) + +read_golden <- function(name) { + read.csv(file.path(golden_dir, paste0(name, ".csv")), + colClasses = "character", check.names = FALSE) +} + +gcell <- function(s) if (identical(s, "nan")) NA_real_ else as.numeric(s) + +expect_close <- function(got, want, row, field) { + if (is.na(want)) { + expect_true(is.na(got), info = paste("row", row, field, "expected warmup/NA")) + } else { + tol <- 1e-6 * max(1, abs(want)) + expect_lte(abs(got - want), tol, label = paste("row", row, field, "got", got, "want", want)) + } +} + +golden_input <- read.csv(file.path(golden_dir, "input.csv")) + +test_that("scalar indicators match golden", { + specs <- list(c("sma", 14), c("ema", 14), c("rsi", 14)) + for (spec in specs) { + name <- spec[[1]] + ind <- switch(name, sma = Sma(14), ema = Ema(14), rsi = Rsi(14)) + exp <- read_golden(name) + for (i in seq_len(nrow(golden_input))) { + got <- update(ind, golden_input$close[i]) + expect_close(got, gcell(exp[i, 1]), i, name) + } + } +}) + +test_that("candle Atr matches golden", { + atr <- Atr(14) + exp <- read_golden("atr") + for (i in seq_len(nrow(golden_input))) { + got <- update(atr, golden_input$open[i], golden_input$high[i], golden_input$low[i], + golden_input$close[i], golden_input$volume[i], i - 1) + expect_close(got, gcell(exp[i, 1]), i, "atr") + } +}) + +test_that("pairwise Beta matches golden", { + beta <- Beta(20) + exp <- read_golden("beta") + for (i in seq_len(nrow(golden_input))) { + # generator fed (close, open) + got <- update(beta, golden_input$close[i], golden_input$open[i]) + expect_close(got, gcell(exp[i, 1]), i, "beta") + } +}) + +test_that("multi-output MACD matches golden", { + macd <- MacdIndicator(12, 26, 9) + exp <- read_golden("macd") + for (i in seq_len(nrow(golden_input))) { + out <- update(macd, golden_input$close[i]) + if (identical(exp[i, "macd"], "nan")) { + expect_true(all(is.na(out)), info = paste("row", i, "macd warmup")) + } else { + expect_close(out[["macd"]], gcell(exp[i, "macd"]), i, "macd.macd") + expect_close(out[["signal"]], gcell(exp[i, "signal"]), i, "macd.signal") + expect_close(out[["histogram"]], gcell(exp[i, "histogram"]), i, "macd.histogram") + } + } +}) + +test_that("multi-output ADX matches golden", { + adx <- Adx(14) + exp <- read_golden("adx") + for (i in seq_len(nrow(golden_input))) { + out <- update(adx, golden_input$open[i], golden_input$high[i], golden_input$low[i], + golden_input$close[i], golden_input$volume[i], i - 1) + if (identical(exp[i, "plus_di"], "nan")) { + expect_true(all(is.na(out)), info = paste("row", i, "adx warmup")) + } else { + expect_close(out[["plus_di"]], gcell(exp[i, "plus_di"]), i, "adx.plus_di") + expect_close(out[["minus_di"]], gcell(exp[i, "minus_di"]), i, "adx.minus_di") + expect_close(out[["adx"]], gcell(exp[i, "adx"]), i, "adx.adx") + } + } +}) diff --git a/examples/rust/src/bin/gen_golden.rs b/examples/rust/src/bin/gen_golden.rs new file mode 100644 index 00000000..ccfb2b65 --- /dev/null +++ b/examples/rust/src/bin/gen_golden.rs @@ -0,0 +1,138 @@ +//! Generate the language-neutral golden fixtures consumed by the per-binding +//! parity runners (`bindings/{csharp,go,java,r}`). +//! +//! Run from the repo root: `cargo run -p wickra-examples --bin gen_golden`. +//! It writes a deterministic OHLCV input series plus the reference outputs of a +//! curated set of indicators spanning the FFI archetypes (scalar, candle, +//! scalar multi-output, candle multi-output, pairwise), computed by the Rust +//! core. Each binding runner replays the same input through its own FFI and +//! checks it matches these values — catching wiring bugs (swapped params, +//! wrong multi-output index) that the math-only core tests cannot see. +//! +//! `nan` marks a warmup slot where the indicator returned `None`. + +use std::fmt::Write as _; +use std::fs; +use std::path::Path; + +use wickra::{Adx, Atr, Beta, Candle, Ema, Indicator, MacdIndicator, Rsi, Sma}; + +const N: usize = 80; + +/// Deterministic OHLCV bar `i`: a varied, non-degenerate path so every +/// indicator gets real movement (no constant returns, no zero ranges). +fn bar(i: usize) -> (f64, f64, f64, f64, f64) { + let t = i as f64; + let close = 100.0 + 10.0 * (t * 0.3).sin() + 0.5 * t; + let open = close - (t * 0.5).cos(); + let span = 1.0 + 0.5 * (t * 0.7).sin().abs(); + let high = close.max(open) + span; + let low = close.min(open) - span; + let volume = 1000.0 + 100.0 * (t * 0.2).sin().abs(); + (open, high, low, close, volume) +} + +fn cell(v: Option) -> String { + match v { + Some(x) => format!("{x}"), + None => "nan".to_owned(), + } +} + +fn write_csv(dir: &Path, name: &str, header: &str, rows: &[String]) { + let mut out = String::new(); + let _ = writeln!(out, "{header}"); + for r in rows { + let _ = writeln!(out, "{r}"); + } + let path = dir.join(format!("{name}.csv")); + fs::write(&path, out).expect("write fixture"); + println!("wrote {}", path.display()); +} + +fn main() { + let dir = Path::new("testdata/golden"); + fs::create_dir_all(dir).expect("create testdata/golden"); + + let candles: Vec = (0..N) + .map(|i| { + let (o, h, l, c, v) = bar(i); + Candle::new(o, h, l, c, v, i as i64).expect("valid candle") + }) + .collect(); + let closes: Vec = candles.iter().map(|c| c.close).collect(); + + // input.csv — the single shared series every runner reads. + let mut input = vec![String::from("open,high,low,close,volume")]; + for c in &candles { + input.push(format!( + "{},{},{},{},{}", + c.open, c.high, c.low, c.close, c.volume + )); + } + let input_header = input.remove(0); + write_csv(dir, "input", &input_header, &input); + + // scalar (close-driven), single output. Each is a distinct type, so write + // them out separately rather than from a heterogeneous collection. + { + let mut sma = Sma::new(14).unwrap(); + let rows: Vec = closes.iter().map(|&c| cell(sma.update(c))).collect(); + write_csv(dir, "sma", "sma", &rows); + } + { + let mut ema = Ema::new(14).unwrap(); + let rows: Vec = closes.iter().map(|&c| cell(ema.update(c))).collect(); + write_csv(dir, "ema", "ema", &rows); + } + { + let mut rsi = Rsi::new(14).unwrap(); + let rows: Vec = closes.iter().map(|&c| cell(rsi.update(c))).collect(); + write_csv(dir, "rsi", "rsi", &rows); + } + + // candle, single output. + { + let mut atr = Atr::new(14).unwrap(); + let rows: Vec = candles.iter().map(|&c| cell(atr.update(c))).collect(); + write_csv(dir, "atr", "atr", &rows); + } + + // scalar multi-output: MACD(12,26,9). + { + let mut macd = MacdIndicator::new(12, 26, 9).unwrap(); + let rows: Vec = closes + .iter() + .map(|&c| match macd.update(c) { + Some(o) => format!("{},{},{}", o.macd, o.signal, o.histogram), + None => "nan,nan,nan".to_owned(), + }) + .collect(); + write_csv(dir, "macd", "macd,signal,histogram", &rows); + } + + // candle multi-output: ADX(14). + { + let mut adx = Adx::new(14).unwrap(); + let rows: Vec = candles + .iter() + .map(|&c| match adx.update(c) { + Some(o) => format!("{},{},{}", o.plus_di, o.minus_di, o.adx), + None => "nan,nan,nan".to_owned(), + }) + .collect(); + write_csv(dir, "adx", "plus_di,minus_di,adx", &rows); + } + + // pairwise: Beta(20) over (close, open). + { + let mut beta = Beta::new(20).unwrap(); + let rows: Vec = candles + .iter() + .map(|&c| cell(beta.update((c.close, c.open)))) + .collect(); + write_csv(dir, "beta", "beta", &rows); + } + + println!("golden fixtures written to {}", dir.display()); +} diff --git a/testdata/golden/adx.csv b/testdata/golden/adx.csv new file mode 100644 index 00000000..35cc0959 --- /dev/null +++ b/testdata/golden/adx.csv @@ -0,0 +1,81 @@ +plus_di,minus_di,adx +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +39.5757019682593,10.51553170206367,30.912276792040817 +37.536261624936586,10.906948307963743,32.63069750889788 +35.215165462446194,12.883060473369223,33.61637662813429 +32.86410166086221,15.606658308025656,33.758337048896145 +30.70333902684003,18.50628500756431,33.11744953242373 +28.583754140794692,21.990805693589266,31.683067176177246 +26.55934622279741,25.269437743189002,29.597761510222966 +24.849750313093512,27.34663879718962,27.82532441495794 +23.505186308008465,28.419972295720065,26.513882262566277 +22.24831894567969,28.255382095825222,25.469626589930424 +21.991214452481188,26.24217101526044,24.279889509686143 +23.0167314852192,24.206077112686202,22.725510472977113 +25.270075861089534,22.49633154108633,21.51703781909767 +28.167925277844184,20.797978949255484,21.055190823958306 +32.199512135355,19.002819811263755,21.39222118044018 +36.67556284467224,17.08189161927664,22.467654445091764 +39.66767810504092,15.312333278446436,24.027003737037383 +41.25710485757308,13.864063299710923,25.86050681733179 +41.90255145462447,12.778588478409224,27.8177163877859 +42.395628716412624,11.937994453509367,29.834786399099126 +41.39315313749411,11.291691494482587,31.784798356703554 +38.98369312390027,10.942263455121635,33.52631517717672 +36.80520205991526,11.790866997951634,34.808291852450445 +34.33471861125097,14.165199105275695,35.29246448522293 +31.575896425438806,17.056390539549085,34.90412360474046 +29.011655429985023,19.499990165743316,33.81146999165209 +26.941362744011517,21.41861333571675,32.21208520671275 +25.293388618220902,23.528371409730532,30.16945244226847 +23.70108159294569,26.085794270270323,28.356623123061247 +21.9818825371455,26.753559469925122,27.030505691607747 +20.4556018343849,25.575479039846915,25.894230469505597 +19.315305653157523,24.149775639951073,24.839117763267886 +20.15346427198841,22.836532180755725,23.510691119817217 +22.621623373749483,21.279561359099215,22.049713637827605 +26.102172398824052,19.564946484191104,21.497230627213543 +29.5794011460886,17.914862734831026,21.71599192763469 +33.103914522491884,16.421886804841026,22.570814571368153 +36.988461875248944,14.998303176938437,23.980008725527608 +39.75301194225335,13.75986552582622,25.73669586768207 +41.37315229671887,12.777991517838071,27.67023180773477 +42.48135161688847,11.981093863498268,29.693955639647385 +42.62294720176041,11.257736731480932,31.730983563876975 +40.47684671425082,10.451870979556956,33.67554918447296 +37.62379013468778,11.104415435913893,35.1575133843429 +35.445073236866044,12.840589537363048,35.99012453099969 +33.361147221464314,15.58241905716853,36.014040954862004 +30.784559395666363,20.10967847345435,34.9397977135121 +28.101022653590128,23.891333482064333,33.02243689823779 +25.724805235133402,26.22784634752717,30.732853434649453 +23.898483996930594,27.16675703876092,28.994806129209827 +22.507592062957475,27.59047661188655,27.648453495608493 +21.169522519356338,26.35196828602853,26.452526680749255 +21.224977830554703,24.775436644747092,25.11436900225873 diff --git a/testdata/golden/atr.csv b/testdata/golden/atr.csv new file mode 100644 index 00000000..1b38d50d --- /dev/null +++ b/testdata/golden/atr.csv @@ -0,0 +1,81 @@ +atr +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +3.917642936098542 +3.871756865753318 +3.8256550237571303 +3.775585219414474 +3.7359112048078584 +3.751806385408204 +3.844730678370346 +3.9663267478375337 +4.065925146592011 +4.103990051025662 +4.122277884333844 +4.120405500552875 +4.1099068422738645 +4.067219182386907 +3.965656623010464 +3.883731209815142 +3.8454426000158177 +3.827649810828608 +3.8056867488416217 +3.7971798302715882 +3.795930185925992 +3.768312827006575 +3.700099728786484 +3.6306300896424184 +3.63084862338228 +3.6560272497167245 +3.6536783654804816 +3.670527021756155 +3.731163563648225 +3.855191519830406 +3.994403105040647 +4.097289247845801 +4.128368657741341 +4.103844480606131 +4.0291518193306395 +3.972922779511551 +3.9077919971718877 +3.8900842871217334 +3.9281940607734454 +3.970355102352287 +3.970344885666312 +3.9271768651715058 +3.891868629198634 +3.896746818914544 +3.888594808948336 +3.8241554941261624 +3.755340593345057 +3.742402894117046 +3.779811441067645 +3.833276428397188 +3.8832337640008086 +3.9482678364884123 +3.9963590513965954 +3.9961613679725096 +3.957618048219805 +3.9111378825809897 +3.9118754285284236 +3.9079875306228926 +3.8519602816708205 +3.8003043443211753 +3.824279210324508 +3.89030736805505 +3.946176568396809 +3.944383618517461 +3.889018639116889 +3.8395240928112067 +3.792174559624793 diff --git a/testdata/golden/beta.csv b/testdata/golden/beta.csv new file mode 100644 index 00000000..b93714a9 --- /dev/null +++ b/testdata/golden/beta.csv @@ -0,0 +1,81 @@ +beta +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +0.9195261318785413 +0.922546057952995 +0.9270670303012646 +0.9360823119678988 +0.9520494101604974 +0.9734365033467753 +0.995093627699459 +1.0111583960294632 +1.018359059410051 +1.0174553154888744 +1.0121204526718954 +1.0067713468887387 +1.0049777031298115 +1.0087588074727343 +1.0182494903866188 +1.031286972171161 +1.0431058848104742 +1.0473159800278413 +1.0395185963505182 +1.0218520432290943 +1.0027356441997792 +0.989852175809267 +0.9830850966851594 +0.9757086459066595 +0.9639743320260056 +0.9518863451049362 +0.945167696468917 +0.9459710447944064 +0.9530453598334451 +0.9634788824656162 +0.973948405670025 +0.9815754255825168 +0.9846435781849213 +0.9830670028068416 +0.9783981708129912 +0.9733920844663336 +0.9713725848572005 +0.9755741087472364 +0.988273819307749 +1.0091885908656193 +1.0332355593672604 +1.0510340548483572 +1.057262107302167 +1.058580145340488 +1.0645933328050556 +1.0735088672694193 +1.0743308164692502 +1.0612002349944065 +1.0392533178013483 +1.0174419588972756 +1.0016230577141623 +0.9934855658174216 +0.9919410932606791 +0.9943630990130676 +0.9973488461009995 +0.9974248103764315 +0.9919840212101835 +0.9802751136245323 +0.9638604974880948 +0.9461133999713321 +0.9309659374652403 diff --git a/testdata/golden/ema.csv b/testdata/golden/ema.csv new file mode 100644 index 00000000..27287680 --- /dev/null +++ b/testdata/golden/ema.csv @@ -0,0 +1,81 @@ +ema +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +107.082865308575 +105.90971557088022 +104.81838000454273 +103.91437652548925 +103.29137341232037 +103.02217097393634 +103.15163412061464 +103.6921955736008 +104.62232136443316 +105.88806700052659 +107.40757775297398 +109.07812453770966 +110.78504123504803 +112.4117601975411 +113.85004525232907 +115.00950442946956 +115.8255294789832 +116.26495019544112 +116.32889606739172 +116.05260755010886 +115.50221201839392 +114.76875083248335 +113.95998970819002 +113.19074277367604 +112.57257361958058 +112.2037924360836 +112.16064199938042 +112.49045917546245 +113.20742223024638 +114.29126332917505 +115.68906080413531 +117.3199507165269 +119.08233652305883 +120.86295254590979 +122.54697139145239 +124.02825224132444 +125.2188143840051 +126.05668958634725 +126.51145173900844 +126.58692971020658 +126.32086097368011 +125.78151686199148 +125.06160082390045 +124.26996466927172 +123.5218827065933 +122.92875250508669 +122.58814223802023 +122.57507361058863 +122.93531900967452 +123.68131159273575 +124.79103363078006 +126.20998238510259 +127.85603789338172 +129.62679782427364 +131.408724185901 +133.0872848279711 +134.5571838169111 +135.7317668260377 +136.550761377433 +136.98566051598186 +137.04226900211742 +136.7601845743594 +136.20926061449748 +135.48336619117217 +134.6920008752563 +133.95051334772793 +133.369797540168 diff --git a/testdata/golden/input.csv b/testdata/golden/input.csv new file mode 100644 index 00000000..78765d19 --- /dev/null +++ b/testdata/golden/input.csv @@ -0,0 +1,81 @@ +open,high,low,close,volume +99,101,98,100,1000 +102.57761950472302,104.77731091023225,101.25551066110417,103.4552020666134,1019.8669330795061 +106.10612242808222,108.13914959894458,104.61339756308799,106.64642473395035,1038.941834230865 +109.26253189460714,110.76487377959927,107.8309272112827,109.33326909627483,1056.4642473395036 +111.7365376962194,112.90403177129735,110.1528967845943,111.32039085967226,1071.7356090899523 +113.27609348158748,114.45148509543229,111.29955825219574,112.47494986604055,1084.1470984807897 +113.7284688053824,115.1642566915892,111.30268842257516,112.73847630878196,1093.2039085967226 +113.06855035377954,114.55977666009171,110.64086736017657,112.13209366648874,1098.544972998846 +111.40827542637513,112.72390874531129,109.43899848657534,110.75463180551151,1099.9573603041506 +108.98459460176909,109.99300155201126,107.76539185209613,108.7737988023383,1097.3847630878195 +106.12753789513545,107.73969337995807,104.79904459577605,106.41120008059868,1090.929742682568 +103.21387328427625,105.41662717550601,101.71978916733775,103.92254305856751,1080.849640381959 +100.61462528040111,103.00209502109563,99.18732582635697,101.57479556705148,1067.546318055115 +98.64575078243223,100.78188758933494,97.48620160125755,99.62233840816026,1051.5501371821465 +97.53034002152081,99.46748184049008,96.34710045689485,98.28424227586412,1033.4988150155905 +97.378063505514,99.16454670333486,95.93821562552817,97.72469882334903,1014.1120008059867 +98.18385394545021,99.67344281002588,96.54876504706593,98.0383539116416,1005.837414342758 +99.8438650794075,101.15293363552601,97.93278462060415,99.24185317672267,1025.5541102026832 +102.1834853863248,103.20029690993537,100.25554360082955,101.27235512444012,1044.2520443294852 +104.99031673022002,106.32510161131832,102.65835969292533,103.99314457402363,1061.185789094272 +108.04491654708718,109.54022022493461,105.7105413401633,107.20584501801073,1075.680249530793 +111.14367593283949,112.56654934841096,109.24526558927202,110.6681390048435,1087.157577241359 +114.11098793714572,115.26697281350663,112.95942875877287,114.11541363513378,1095.1602073889517 +116.80109288512898,118.47543335247398,115.61005717653698,117.28439764388199,1099.3691003633464 +119.09282467975903,121.38046215528227,117.64904116296829,119.93667863849153,1099.616460883584 +120.88220148856881,123.36781277048146,119.39438848583472,121.87999976774739,1095.8924274663138 +122.07798667229586,124.2878498649492,120.77557026109272,122.98543345374605,1088.3454655720152 +122.60397744514097,124.22410945235427,121.57876610123756,123.19889810845086,1077.2764487555987 +122.40925186267498,123.88697089091687,121.06827005264091,122.5459890808828,1063.126663787232 +121.48461656761053,122.98099977052848,119.63330909790388,121.12969230082183,1046.4602179413757 +119.8808727652764,121.29920058454442,117.70285703314954,119.12118485241757,1027.9415498198925 +117.72299769788953,118.8665240235534,115.60101790940676,116.74454423507063,1008.3089402817496 +115.21439166809358,116.4131695096543,113.05795434620948,114.2567321877702,1011.6549204850494 +112.6270381197495,114.07463180365934,110.47704737833695,111.92464106224679,1031.154136351338 +110.27641646211617,111.76231568498811,108.51535390119264,110.00125312406458,1049.4113351138608 +108.48360243707184,109.99872116521585,107.18792367213928,108.7030424002833,1065.6986598718788 +107.530320991091,109.22424173559783,106.49671695482826,108.19063769933508,1079.3667863849153 +107.61494922421186,109.90155645208623,106.26786689008574,108.55447411796011,1089.8708095811626 +108.81801012516657,111.30403713029216,107.32068773822765,109.80671474335324,1096.7919672031487 +111.08434919099572,113.29382811110736,109.67068524069803,111.88016416080967,1099.8543345374605 +114.22618875818226,115.76972371414958,113.09073586402833,114.63427081999565,1098.9358246623383 +117.94724565362051,119.15370942824079,116.6612183117217,117.86768208634197,1094.0730556679773 +121.88395973243564,123.33523703654073,119.88495316810628,121.33623047221137,1085.459890808828 +125.65653611891825,127.14038418498515,123.29089632531003,124.77474439137693,1073.4397097874114 +128.92069597346688,130.20955349568973,126.63187762484937,127.92073514707224,1058.4917192891762 +131.41114890560974,132.45313613345562,129.49585703767033,130.5378442655162,1041.2118485241756 +132.96978971477444,134.3228744433646,131.08387196585088,132.43695669444105,1022.2889914100246 +133.5549991814736,135.053120145851,131.99497292310176,133.49309388747918,1002.4775425453357 +133.2323987581558,135.065460892756,131.8235156308926,133.65657776549278,1017.4326781222982 +132.1510588175171,134.08480947785137,131.0237176510951,132.95746831142935,1036.647912925193 +130.5116755897077,132.71696973631924,129.29758425495962,131.50287840157117,1054.402111088937 +128.53409061924222,130.9222390672229,127.07925728332546,129.46740573130614,1069.9874687593544 +126.4306172006658,128.5591966352313,124.94895708842891,127.07753652299444,1082.7826469085653 +124.38927906587094,125.87336879788326,123.1073244542458,124.59141418625812,1092.2775421612807 +122.56791894474918,123.61827749324543,121.2254215875191,122.27578013601534,1098.093623006649 +121.09703354508844,122.45602084147426,119.02315927992295,120.38214657630877,1099.9990206550704 +120.08693553049852,121.58571423995242,117.62555095473105,119.12432966418496,1097.9177729151318 +119.63399522490424,121.03798192673773,117.25536324735009,118.65934994918358,1091.9328525664675 +119.82146372498268,120.94065716085713,117.95421275941922,119.07340619529367,1082.2828594968707 +120.7124947130592,121.93415307848436,119.152517136663,120.37417550208815,1069.3525084777123 +122.33587608239566,123.94838830624106,120.87761530843784,122.49012753228324,1053.6572918000436 +124.66785812762632,126.75625008370868,123.18852214765032,125.27691410373268,1035.8229282236828 +127.61552102482923,129.80523536741393,126.34054904004905,128.53026338263376,1016.5604175448309 +131.00775897051096,133.06294250948164,129.94904333909741,132.0042268780681,1003.3623047221139 +134.59892592769242,136.79793765689536,133.234137558996,135.43314928819893,1023.1509825101539 +138.08766685694894,140.0546941334019,136.58837142074304,138.555398697196,1042.016703682664 +141.1500141222941,142.5489901806554,139.73776131670974,141.13673737507105,1059.2073514707224 +143.48228026040917,144.5932922665057,141.8802335303824,142.99124553647894,1074.037588995245 +144.84649927621132,146.0756620034572,142.76876627418082,143.9979290014267,1085.9161814856498 +145.1098734724699,146.57143212166807,142.649968595823,144.11152724502116,1094.3695669444105 +144.27024859045207,145.7471249168318,141.88968005898082,143.36655638536055,1099.060735569487 +142.46200877506277,143.72992039092952,140.60631434563564,141.8742259615024,1099.8026652716362 +139.9404686061768,141.00752471999962,138.7454488027266,139.81250491654941,1096.5657776549278 +137.04703875358243,138.78071046739933,135.67655244818158,137.41022416199849,1089.4791172140503 +134.16122174198705,136.42630679247307,132.66155074344638,134.9266357939324,1078.8252067375315 +131.6480122345848,134.02210748888697,130.25415962109273,132.6282548753949,1065.0287840157116 +129.80997879551026,131.86785162975886,128.70717960530897,130.76505243955756,1048.6398688853799 +128.85205701193928,130.78472862032595,127.6154547134165,129.54812632180318,1030.3118356745701 +128.8642014864336,130.59557044803222,127.39947545719488,129.13084441879352,1010.7753652299442 +129.82321172551153,131.29749357237068,128.12086294416926,129.59514479102842,1009.1906850227682 diff --git a/testdata/golden/macd.csv b/testdata/golden/macd.csv new file mode 100644 index 00000000..7ad5a00c --- /dev/null +++ b/testdata/golden/macd.csv @@ -0,0 +1,81 @@ +macd,signal,histogram +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +nan,nan,nan +3.553825374871522,4.7567646581597245,-1.2029392832882024 +2.8208654161048656,4.369584809748753,-1.5487193936438874 +2.1109014543067843,3.9178481386603594,-1.806946684353575 +1.4897304373886868,3.4322245984060253,-1.9424941610173385 +1.0151049910054155,2.9488006769259036,-1.9336956859204881 +0.7315731385597957,2.5053551692526823,-1.7737820306928866 +0.6664991548332893,2.137583966368804,-1.4710848115355146 +0.8276206486027746,1.8755913028155982,-1.0479706542128235 +1.2023598043004569,1.74094500311257,-0.5385851988121131 +1.7589500704087442,1.744546016571805,0.014404053836939212 +2.449277456832604,1.885492304623965,0.563785152208639 +3.2131824784813148,2.1510303393954353,1.0621521390858795 +3.9838383370451567,2.5175919389253796,1.466246398119777 +4.693724820711168,2.9528185152825377,1.7409063054286302 +5.280664208546995,3.418387653935429,1.8622765546115656 +5.693379944743711,3.8733861120970854,1.8199938326466256 +5.896081491642136,4.277925188006096,1.6181563036360398 +5.871665770714699,4.596673304547816,1.274992466166883 +5.623249186369037,4.801988480912061,0.821260705456976 +5.17389335937969,4.876369456605587,0.29752390277410345 +4.564549053715282,4.814005376027526,-0.2494563223122439 +3.8504019492156516,4.621284690665152,-0.7708827414495003 +3.0959466751243525,4.316217087556993,-1.2202704124326402 +2.369229123514458,3.926819494748486,-1.5575903712340278 +1.735771359392487,3.488609867677286,-1.7528385082847988 +1.252721799050505,3.0414322539519296,-1.7887104549014246 +0.9637532073126067,2.6258964446240656,-1.6621432373114589 +0.8951642651140048,2.2797500087220537,-1.3845857436080489 +1.0535329484060725,2.0345065966588574,-0.9809736482527849 +1.4251313413431745,1.9126315455957208,-0.48750020425254625 +1.9771541634642489,1.9255360691694265,0.051618094294822336 +2.6606512771767967,2.072559110770901,0.5880921664058958 +3.414902230493496,2.34102773471542,1.073874495778076 +4.17284207727586,2.707390603227508,1.4654514740483515 +4.867053809684606,3.139323244518928,1.727730565165678 +5.4357921236225195,3.5986170203396464,1.8371751032828731 +5.828500438890558,4.044593704049829,1.7839067348407287 +6.010328361604678,4.437740635560799,1.5725877260438788 +5.965246063689932,4.743241721186626,1.2220043425033058 +5.697477387222705,4.934088854393842,0.7633885328288628 +5.231123664416998,4.9934958163984735,0.23762784801852455 +4.608011861752942,4.916399025469367,-0.30838716371642505 +3.883959272298,4.709911074835094,-0.8259518025370935 +3.123788425025225,4.39268654487312,-1.2688981198478952 +2.395537519050208,3.993256739708538,-1.59771922065833 +1.764383551803462,3.547482102127523,-1.7830985503240608 +1.286820974089835,3.0953498765199856,-1.8085289024301505 diff --git a/testdata/golden/rsi.csv b/testdata/golden/rsi.csv new file mode 100644 index 00000000..de46fdff --- /dev/null +++ b/testdata/golden/rsi.csv @@ -0,0 +1,81 @@ +rsi +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +46.84518809898699 +45.82961265424322 +46.5293990131247 +49.23903870201344 +53.518768674122796 +58.56047460456064 +63.58342577996474 +68.07459111891804 +71.80327260156207 +74.72559789038102 +76.88488357198254 +78.34471541432595 +79.15131540353094 +79.31156608926268 +77.35312513119456 +73.13439349750769 +67.51119167055117 +61.48673113238304 +55.86675382432369 +51.14733221714866 +47.57753664027756 +45.28036590056008 +44.36975889174273 +45.212282377402985 +48.12434665867438 +52.61537254620642 +57.83678169397702 +62.992663568874654 +67.57346293490359 +71.3583982125327 +74.31265622918312 +76.4855340725887 +77.9435594091973 +78.73329989554925 +78.85948376472163 +76.76197022138767 +72.44443440532005 +66.78373698621982 +60.77877328002407 +55.21662487911296 +50.57413251002351 +47.087563552178636 +44.87458874458458 +44.050409895373384 +45.01878876311954 +48.0601943425273 +52.64880490307121 +57.92140340103244 +63.088714334306665 +67.65616719708338 +71.41589642993111 +74.34074717674663 +76.4832369420561 +77.91026866443379 +78.66702552588708 +78.75547116040536 +76.51507736151031 +72.09088140590961 +66.3803507173797 +60.378854393296976 +54.856911707280055 +50.27461841837877 +46.857548898064 +44.719625963574536 +43.978649539628535 +45.06927522550655 diff --git a/testdata/golden/sma.csv b/testdata/golden/sma.csv new file mode 100644 index 00000000..340eb8bd --- /dev/null +++ b/testdata/golden/sma.csv @@ -0,0 +1,81 @@ +sma +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +nan +107.082865308575 +106.96031118542241 +106.55098952518925 +105.93612732359576 +105.21531190077062 +104.49759506253974 +103.89175182739567 +103.49656387805487 +103.39199568793734 +103.63205153291037 +104.23995145016349 +105.20605706144154 +106.48873254066868 +108.01806381828972 +109.7021037968819 +111.4350857115261 +113.10687095991703 +114.61278745568676 +115.86297967414018 +116.79043517866376 +117.35697064210828 +117.55664264968355 +117.41627860650068 +116.99308032537222 +116.36951435923494 +115.64594550958222 +114.93167153765809 +114.33515992096163 +113.9543587765253 +113.86794744733447 +114.12830831094557 +114.75684761770661 +115.742083334167 +117.04067079892921 +118.58127457216008 +120.2709406179764 +122.00339961162967 +123.66855966178939 +125.16234049131411 +126.39597061843133 +127.30391704882051 +127.84973914282192 +128.0293437492481 +127.87135083438908 +127.43453694566097 +126.80258487767678 +126.07660853743191 +125.36612074013492 +124.7792507555816 +124.41308544252024 +124.34499676870873 +124.62573040346784 +125.27487275817427 +126.27910139046546 +127.59337505833837 +129.14495712015346 +130.83991288220435 +132.57150050514548 +134.2297059345968 +135.71107012897224 +136.92793074753726 +137.81625276622648 +138.34134853563094 +138.50097632541122 +138.3255405713923 +137.87537593786334 +137.23535780170852