Compare commits

..
2 Commits
Author SHA1 Message Date
kingchencandGitHub d0061c73b8 release: bump 0.7.6 -> 0.7.7 (#229)
Bumps the workspace to 0.7.7 to ship the Go binding.
2026-06-09 17:35:25 +02:00
kingchencandGitHub 23d636fd97 Add the Go binding over the C ABI hub (#228)
Adds a Go binding (`bindings/go`) over the C ABI hub — the second language stecker after C#.

## What's here
- **`bindings/go`** — a cgo binding exposing all 514 indicators as idiomatic Go types with `New<Indicator>` constructors and `Update`/`Batch`/`Reset`/`Close` methods. The wrappers in `indicators_gen.go` are generated from `bindings/c/include/wickra.h` (same archetype taxonomy as the C# generator: scalar/batch, multi-output, bars, profile, profile-values, array-input). Opaque handles are freed by `Close()` with a `runtime.SetFinalizer` backstop; pointer arguments are caller-owned, panics never cross the boundary.
- **`examples/go`** — the full example suite mirroring C/C#: streaming, backtest, multi_timeframe, parallel_assets (goroutine fan-out), three strategies, and `fetch_btcusdt`/`live_binance`.
- **CI** — a `go` job builds the C ABI library, stages it, and runs `gofmt`/`go vet`/`go test` plus the offline examples on Linux, macOS and Windows.
- **Docs** — Go added to the README languages table, project layout, building/testing, CONTRIBUTING binding table + regenerate note, ARCHITECTURE, examples index, issue/PR templates, the About-description template, and the other binding READMEs.

## Linking / distribution
The binding links the prebuilt C ABI library via cgo (`libwickra.so`/`.dylib`/`wickra.dll` staged under `bindings/go/lib`, gitignored). The native libraries are already shipped per target triple by the existing `c-abi-build` release job; distribution is via the subdirectory module tag `bindings/go/vX.Y.Z` (gated), so `release.yml` needs no new publish job.

No Rust crate or `Cargo.toml` change — the Go module is standalone and additive.

Not for merge yet (gated, per request).
2026-06-09 17:33:37 +02:00
56 changed files with 32758 additions and 78 deletions
+6
View File
@@ -10,3 +10,9 @@ bindings/c/include/wickra.h text eol=lf
# files stay stable regardless of the committer's autocrlf setting. # files stay stable regardless of the committer's autocrlf setting.
*.cs text eol=lf *.cs text eol=lf
# Go sources (including the generated binding) are pinned to LF so gofmt's CI
# check never trips on a CRLF checkout on Windows.
*.go text eol=lf
go.mod text eol=lf
go.sum text eol=lf
+1 -1
View File
@@ -30,7 +30,7 @@ assignees: ""
## Environment ## Environment
- Wickra version: - Wickra version:
- Language / binding: <!-- Rust crate / Python / Node / WASM / C ABI / C# (.NET) --> - Language / binding: <!-- Rust crate / Python / Node / WASM / C ABI / C# (.NET) / Go -->
- OS and architecture: - OS and architecture:
- Rust / Python / Node / .NET version (If relevant): - Rust / Python / Node / .NET version (If relevant):
@@ -27,6 +27,7 @@ assignees: ""
- [ ] Should be exposed in the WASM binding - [ ] Should be exposed in the WASM binding
- [ ] Should be exposed in the C ABI - [ ] Should be exposed in the C ABI
- [ ] Should be exposed in the C# / .NET binding - [ ] Should be exposed in the C# / .NET binding
- [ ] Should be exposed in the Go binding
## Additional context ## Additional context
@@ -13,7 +13,7 @@ assignees: []
## Affected code path ## Affected code path
- Indicator / API: `e.g. EMA.update` - Indicator / API: `e.g. EMA.update`
- Binding: `Rust / Python / Node / Wasm / C ABI / C# (.NET)` - Binding: `Rust / Python / Node / Wasm / C ABI / C# (.NET) / Go`
- Hot loop or one-shot call? - Hot loop or one-shot call?
## Versions compared ## Versions compared
+1 -1
View File
@@ -33,4 +33,4 @@ import wickra as ta
## Environment (Only if relevant) ## Environment (Only if relevant)
- Wickra version: `e.g. 0.4.2` - Wickra version: `e.g. 0.4.2`
- Binding: `Rust / Python / Node / Wasm / C ABI / C# (.NET)` - Binding: `Rust / Python / Node / Wasm / C ABI / C# (.NET) / Go`
+1 -1
View File
@@ -22,7 +22,7 @@
- [ ] `cargo clippy --workspace --all-targets -- -D warnings` is clean. - [ ] `cargo clippy --workspace --all-targets -- -D warnings` is clean.
- [ ] `cargo test --workspace` passes. - [ ] `cargo test --workspace` passes.
- [ ] New behaviour has tests; bug fixes have a regression test. - [ ] New behaviour has tests; bug fixes have a regression test.
- [ ] Public API changes are mirrored in the Python / Node / WASM bindings, and the C ABI + C# binding are regenerated - [ ] Public API changes are mirrored in the Python / Node / WASM bindings, and the C ABI + C# + Go bindings are regenerated
and their type stubs (If applicable). and their type stubs (If applicable).
- [ ] The relevant page on the [documentation site](https://docs.wickra.org) - [ ] The relevant page on the [documentation site](https://docs.wickra.org)
and the `README.md` are updated (If applicable). Docs edits go to a and the `README.md` are updated (If applicable). Docs edits go to a
@@ -25,6 +25,7 @@ Please fill in the sections below. Delete any that don't apply.
- [ ] WebAssembly binding (`bindings/wasm`) - [ ] WebAssembly binding (`bindings/wasm`)
- [ ] C ABI (`bindings/c`) - [ ] C ABI (`bindings/c`)
- [ ] C# / .NET binding (`bindings/csharp`) - [ ] C# / .NET binding (`bindings/csharp`)
- [ ] Go binding (`bindings/go`)
- [ ] Examples / docs - [ ] Examples / docs
## Linked issues ## Linked issues
+99
View File
@@ -727,6 +727,105 @@ jobs:
dotnet run --project "examples/csharp/$d" -c Release --no-build dotnet run --project "examples/csharp/$d" -c Release --no-build
done done
go:
name: Go on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# Go is not reliably on PATH on every runner image, so install it
# explicitly. cache: false — the cargo build dominates and the modules
# have no external Go deps worth caching.
- name: Set up Go
id: setup-go
continue-on-error: true
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: "stable"
cache: false
- name: Retry Go setup (CDN flake)
if: steps.setup-go.outcome == 'failure'
shell: bash
run: |
echo "::warning::setup-go failed (likely CDN flake), waiting 30s before retry..."
sleep 30
- name: Set up Go (retry)
if: steps.setup-go.outcome == 'failure'
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: "stable"
cache: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
- name: Cache cargo
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
timeout-minutes: 6
# The binding links against the C ABI hub via cgo; build it first and stage
# the platform library under bindings/go/lib so cgo's LDFLAGS find it. Go is
# preinstalled on the GitHub runners, so no setup-go step is needed.
- name: Build the C ABI library
run: cargo build -p wickra-c --release
- name: Stage the native library
shell: bash
run: |
mkdir -p bindings/go/lib
case "$RUNNER_OS" in
Linux) cp target/release/libwickra.so bindings/go/lib/ ;;
macOS) cp target/release/libwickra.dylib bindings/go/lib/ ;;
Windows) cp target/release/wickra.dll bindings/go/lib/ ;;
esac
- name: Go info
run: go version
- name: Check gofmt
shell: bash
run: |
unformatted="$(gofmt -l bindings/go examples/go)"
if [ -n "$unformatted" ]; then
echo "gofmt needed on:"; echo "$unformatted"; exit 1
fi
- name: Vet and test the Go binding
shell: bash
# On Windows there is no rpath; the loader resolves wickra.dll via PATH.
run: |
export PATH="$PWD/bindings/go/lib:$PATH"
cd bindings/go
go vet ./...
go test ./...
- name: Build the Go examples
shell: bash
run: |
export PATH="$PWD/bindings/go/lib:$PATH"
cd examples/go
go build ./...
# Run only the offline examples (fetch_btcusdt / live_binance need network).
- name: Run the offline Go examples
shell: bash
run: |
export PATH="$PWD/bindings/go/lib:$PATH"
cd examples/go
for d in streaming backtest multi_timeframe parallel_assets \
strategy_rsi_mean_reversion strategy_macd_adx strategy_bollinger_squeeze; do
go run "./$d"
done
# The cross-library benchmark has moved to a dedicated scheduled workflow # The cross-library benchmark has moved to a dedicated scheduled workflow
# (.github/workflows/bench.yml) — see audit finding R10. It runs nightly # (.github/workflows/bench.yml) — see audit finding R10. It runs nightly
# at 03:00 UTC and on-demand via `workflow_dispatch`, and is no longer on # at 03:00 UTC and on-demand via `workflow_dispatch`, and is no longer on
+1 -1
View File
@@ -149,7 +149,7 @@ jobs:
# actually live (Cloudflare Pages, P8.1); merging this PR is therefore # actually live (Cloudflare Pages, P8.1); merging this PR is therefore
# gated on the domain resolving, otherwise the About link would 404. # gated on the domain resolving, otherwise the About link would 404.
homepage="https://docs.wickra.org" homepage="https://docs.wickra.org"
desc="Streaming-first technical indicators with a Rust core and Python, Node.js, WebAssembly, C ABI, and .NET bindings. ${n} indicators, O(1) per-tick updates, no system dependencies. Drop-in TA-Lib replacement." desc="Streaming-first technical indicators with a Rust core and Python, Node.js, WebAssembly, C ABI, .NET, and Go bindings. ${n} indicators, O(1) per-tick updates, no system dependencies. Drop-in TA-Lib replacement."
# Enforce the homepage unconditionally — it is a constant, so this both # Enforce the homepage unconditionally — it is a constant, so this both
# corrects the stale kingchenc URL and self-heals any future drift. # corrects the stale kingchenc URL and self-heals any future drift.
# Same Administration-write permission as --description (no extra scope). # Same Administration-write permission as --description (no extra scope).
+2 -1
View File
@@ -46,7 +46,8 @@ wasm-bindgen). The C ABI is the *hub* every other C-capable language links
against: it builds to a `cdylib`/`staticlib` plus a generated `wickra.h`, and against: it builds to a `cdylib`/`staticlib` plus a generated `wickra.h`, and
downstream languages link that one artifact rather than each re-wrapping the downstream languages link that one artifact rather than each re-wrapping the
core. C and C++ link it directly; the **C# / .NET** binding (`bindings/csharp`, core. C and C++ link it directly; the **C# / .NET** binding (`bindings/csharp`,
on NuGet) is generated from `wickra.h`, with Go / Java / R planned the same way. on NuGet) and the **Go** binding (`bindings/go`, cgo) are generated from
`wickra.h`, with Java / R planned the same way.
| Crate | Path | What it owns | Public deps | | Crate | Path | What it owns | Public deps |
|---|---|---|---| |---|---|---|---|
+11 -1
View File
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.7.7] - 2026-06-09
### Added
- **Go binding (`bindings/go`)** — a cgo binding over the C ABI hub exposing all
514 indicators as idiomatic types with `New<Indicator>` constructors and
`Update`/`Batch`/`Reset`/`Close` methods, generated from `wickra.h`. Handles are
freed by `Close()` with a `runtime.SetFinalizer` backstop. Ships a full example
suite mirroring the C and C# examples; distributed as a subdirectory module
(`go get github.com/wickra-lib/wickra/bindings/go`).
## [0.7.6] - 2026-06-09 ## [0.7.6] - 2026-06-09
### Added ### Added
- **C# / .NET binding (`bindings/csharp`)** — the first language stecker on the - **C# / .NET binding (`bindings/csharp`)** — the first language stecker on the
@@ -1434,7 +1443,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
optional Binance live feed. optional Binance live feed.
- Bindings for Python, Node.js, and WebAssembly. - Bindings for Python, Node.js, and WebAssembly.
[Unreleased]: https://github.com/wickra-lib/wickra/compare/v0.7.6...HEAD [Unreleased]: https://github.com/wickra-lib/wickra/compare/v0.7.7...HEAD
[0.7.7]: https://github.com/wickra-lib/wickra/compare/v0.7.6...v0.7.7
[0.7.6]: https://github.com/wickra-lib/wickra/compare/v0.7.5...v0.7.6 [0.7.6]: https://github.com/wickra-lib/wickra/compare/v0.7.5...v0.7.6
[0.7.5]: https://github.com/wickra-lib/wickra/compare/v0.7.4...v0.7.5 [0.7.5]: https://github.com/wickra-lib/wickra/compare/v0.7.4...v0.7.5
[0.7.4]: https://github.com/wickra-lib/wickra/compare/v0.7.3...v0.7.4 [0.7.4]: https://github.com/wickra-lib/wickra/compare/v0.7.3...v0.7.4
+3
View File
@@ -23,6 +23,7 @@ licensed as above, without any additional terms or conditions.
| `bindings/wasm` | wasm-bindgen bindings (`wickra-wasm` on npm). | | `bindings/wasm` | wasm-bindgen bindings (`wickra-wasm` on npm). |
| `bindings/c` | C ABI — `cdylib` + `staticlib` + generated `include/wickra.h`. The hub for C / C++ and any C-capable language. | | `bindings/c` | C ABI — `cdylib` + `staticlib` + generated `include/wickra.h`. The hub for C / C++ and any C-capable language. |
| `bindings/csharp` | .NET binding over the C ABI (`Wickra` on NuGet) — `[LibraryImport]` P/Invoke generated from `wickra.h`. | | `bindings/csharp` | .NET binding over the C ABI (`Wickra` on NuGet) — `[LibraryImport]` P/Invoke generated from `wickra.h`. |
| `bindings/go` | Go binding over the C ABI via cgo (module tag `bindings/go/vX.Y.Z`) — wrappers generated from `wickra.h`. |
| `examples/` | Runnable examples. | | `examples/` | Runnable examples. |
| `docs/` | Pointer to the documentation site (docs.wickra.org); the docs live in the `wickra-lib/wickra-docs` repo. | | `docs/` | Pointer to the documentation site (docs.wickra.org); the docs live in the `wickra-lib/wickra-docs` repo. |
@@ -108,6 +109,8 @@ installed. Dependabot also keeps the `.github/requirements` pins current.
(`bindings/c`) is generated from the core, so regenerate it from the core and (`bindings/c`) is generated from the core, so regenerate it from the core and
commit `src/lib.rs` + `include/wickra.h`. The C# binding (`bindings/csharp`) is commit `src/lib.rs` + `include/wickra.h`. The C# binding (`bindings/csharp`) is
generated from `wickra.h`, so regenerate and commit its `Generated/*.g.cs` too. generated from `wickra.h`, so regenerate and commit its `Generated/*.g.cs` too.
The Go binding (`bindings/go`) is likewise generated from `wickra.h`, so
regenerate and commit `indicators_gen.go` (`gofmt`-clean).
- **Docs.** Update the relevant page on the - **Docs.** Update the relevant page on the
[documentation site](https://docs.wickra.org) and the [documentation site](https://docs.wickra.org) and the
`README.md` when behaviour or the public API changes. The docs live in `README.md` when behaviour or the public API changes. The docs live in
Generated
+9 -9
View File
@@ -1944,7 +1944,7 @@ dependencies = [
[[package]] [[package]]
name = "wickra" name = "wickra"
version = "0.7.6" version = "0.7.7"
dependencies = [ dependencies = [
"approx", "approx",
"criterion", "criterion",
@@ -1955,7 +1955,7 @@ dependencies = [
[[package]] [[package]]
name = "wickra-bench" name = "wickra-bench"
version = "0.7.6" version = "0.7.7"
dependencies = [ dependencies = [
"criterion", "criterion",
"kand", "kand",
@@ -1967,14 +1967,14 @@ dependencies = [
[[package]] [[package]]
name = "wickra-c" name = "wickra-c"
version = "0.7.6" version = "0.7.7"
dependencies = [ dependencies = [
"wickra-core", "wickra-core",
] ]
[[package]] [[package]]
name = "wickra-core" name = "wickra-core"
version = "0.7.6" version = "0.7.7"
dependencies = [ dependencies = [
"approx", "approx",
"proptest", "proptest",
@@ -1984,7 +1984,7 @@ dependencies = [
[[package]] [[package]]
name = "wickra-data" name = "wickra-data"
version = "0.7.6" version = "0.7.7"
dependencies = [ dependencies = [
"approx", "approx",
"csv", "csv",
@@ -2001,7 +2001,7 @@ dependencies = [
[[package]] [[package]]
name = "wickra-examples" name = "wickra-examples"
version = "0.7.6" version = "0.7.7"
dependencies = [ dependencies = [
"serde_json", "serde_json",
"tokio", "tokio",
@@ -2011,7 +2011,7 @@ dependencies = [
[[package]] [[package]]
name = "wickra-node" name = "wickra-node"
version = "0.7.6" version = "0.7.7"
dependencies = [ dependencies = [
"napi", "napi",
"napi-build", "napi-build",
@@ -2021,7 +2021,7 @@ dependencies = [
[[package]] [[package]]
name = "wickra-python" name = "wickra-python"
version = "0.7.6" version = "0.7.7"
dependencies = [ dependencies = [
"numpy", "numpy",
"pyo3", "pyo3",
@@ -2030,7 +2030,7 @@ dependencies = [
[[package]] [[package]]
name = "wickra-wasm" name = "wickra-wasm"
version = "0.7.6" version = "0.7.7"
dependencies = [ dependencies = [
"console_error_panic_hook", "console_error_panic_hook",
"js-sys", "js-sys",
+2 -2
View File
@@ -14,7 +14,7 @@ members = [
exclude = ["fuzz"] exclude = ["fuzz"]
[workspace.package] [workspace.package]
version = "0.7.6" version = "0.7.7"
authors = ["kingchenc <support@wickra.org>"] authors = ["kingchenc <support@wickra.org>"]
edition = "2021" edition = "2021"
rust-version = "1.86" rust-version = "1.86"
@@ -26,7 +26,7 @@ keywords = ["finance", "trading", "indicators", "technical-analysis", "ta"]
categories = ["finance", "mathematics", "science"] categories = ["finance", "mathematics", "science"]
[workspace.dependencies] [workspace.dependencies]
wickra-core = { path = "crates/wickra-core", version = "0.7.6" } wickra-core = { path = "crates/wickra-core", version = "0.7.7" }
thiserror = "2" thiserror = "2"
rayon = "1.10" rayon = "1.10"
+18 -8
View File
@@ -20,7 +20,7 @@
Wickra is a multi-language technical-analysis library with a Rust core and Wickra is a multi-language technical-analysis library with a Rust core and
native bindings for Python, Node.js and WebAssembly, plus a C ABI that C, C++, native bindings for Python, Node.js and WebAssembly, plus a C ABI that C, C++,
C# / .NET and any other C-capable language links against. Every indicator is a C# / .NET, Go and any other C-capable language links against. Every indicator is a
state machine that updates in O(1) per new data point, so live trading bots and state machine that updates in O(1) per new data point, so live trading bots and
historical backtests share the exact same implementation. historical backtests share the exact same implementation.
@@ -50,7 +50,8 @@ Full documentation lives at **[docs.wickra.org](https://docs.wickra.org)**:
[Node](https://docs.wickra.org/Quickstart-Node), [Node](https://docs.wickra.org/Quickstart-Node),
[WASM](https://docs.wickra.org/Quickstart-WASM), [WASM](https://docs.wickra.org/Quickstart-WASM),
[C](https://docs.wickra.org/Quickstart-C), [C](https://docs.wickra.org/Quickstart-C),
[C#](https://docs.wickra.org/Quickstart-CSharp). [C#](https://docs.wickra.org/Quickstart-CSharp),
[Go](https://docs.wickra.org/Quickstart-Go).
- **Indicators** — a per-indicator deep dive (formula, parameters, warmup) for - **Indicators** — a per-indicator deep dive (formula, parameters, warmup) for
every one of the 514 indicators; start at the every one of the 514 indicators; start at the
[indicators overview](https://docs.wickra.org/Indicators-Overview). [indicators overview](https://docs.wickra.org/Indicators-Overview).
@@ -76,7 +77,7 @@ times to get there.
metrics — every single one updating in **O(1) per tick**. TA-Lib ships ~150 and metrics — every single one updating in **O(1) per tick**. TA-Lib ships ~150 and
none of them stream. none of them stream.
- **One Rust core, five first-class targets.** Native **Python · Node.js · - **One Rust core, five first-class targets.** Native **Python · Node.js ·
WebAssembly · Rust** plus a **C ABI** for C / C++, C# / .NET and any other C-capable language — WebAssembly · Rust** plus a **C ABI** for C / C++, C# / .NET, Go and any other C-capable language —
identical math, identical results, zero per-language reimplementation and zero identical math, identical results, zero per-language reimplementation and zero
GIL bottleneck. GIL bottleneck.
- **Correct by construction, not by hope.** Every `update` validates its input, - **Correct by construction, not by hope.** Every `update` validates its input,
@@ -101,7 +102,7 @@ Every other library forces one of those compromises. Wickra doesn't:
| Library | Install | Streaming | Languages | Indicators | Active | | Library | Install | Streaming | Languages | Indicators | Active |
|------------------|-------------|-------------|-----------------------------|-----------:|--------| |------------------|-------------|-------------|-----------------------------|-----------:|--------|
| **★&nbsp;Wickra**| **clean** | **yes, O(1)** | **Rust · Python · Node · WASM** | **514** | **yes** | | **★&nbsp;Wickra**| **clean** | **yes, O(1)** | **Rust · Python · Node · WASM** | **514** | **yes** |
| | | | **C · C#** | | | | | | | **C · C# · Go** | | |
| kand | clean | yes | Python · WASM · Rust | ~60 | yes | | kand | clean | yes | Python · WASM · Rust | ~60 | yes |
| ta-rs | clean | yes | Rust only | ~30 | stale | | ta-rs | clean | yes | Rust only | ~30 | stale |
| yata | clean | partial | Rust only | ~35 | yes | | yata | clean | partial | Rust only | ~35 | yes |
@@ -173,8 +174,8 @@ construct it in signed mode (`Doji::new().signed()`, `Doji(signed=True)`,
`new Doji(true)`) for a dragonfly / gravestone `±1` reading. `new Doji(true)`) for a dragonfly / gravestone `±1` reading.
Adding a new indicator means implementing one trait in Rust; every binding Adding a new indicator means implementing one trait in Rust; every binding
inherits it automatically (the C ABI — and the C# binding generated from it — inherits it automatically (the C ABI — and the C# and Go bindings generated from
regenerate from the core). it — regenerate from the core).
## Languages ## Languages
@@ -186,6 +187,7 @@ regenerate from the core).
| Rust | `cargo add wickra` | `examples/rust/src/bin/backtest.rs` | | Rust | `cargo add wickra` | `examples/rust/src/bin/backtest.rs` |
| C / C++ (C ABI) | header + library, see [`bindings/c`](bindings/c) | `examples/c/streaming.c` | | C / C++ (C ABI) | header + library, see [`bindings/c`](bindings/c) | `examples/c/streaming.c` |
| C# / .NET (C ABI) | `dotnet add package Wickra`, see [`bindings/csharp`](bindings/csharp) | `examples/csharp/streaming` | | C# / .NET (C ABI) | `dotnet add package Wickra`, see [`bindings/csharp`](bindings/csharp) | `examples/csharp/streaming` |
| Go (cgo, C ABI) | `go get github.com/wickra-lib/wickra/bindings/go`, see [`bindings/go`](bindings/go) | `examples/go/streaming` |
Each binding ships several runnable examples (streaming, backtest, live feed); Each binding ships several runnable examples (streaming, backtest, live feed);
[`examples/README.md`](examples/README.md) is the full cross-language index. [`examples/README.md`](examples/README.md) is the full cross-language index.
@@ -256,7 +258,8 @@ wickra/
│ ├── node/ napi-rs (publishes on npm) │ ├── node/ napi-rs (publishes on npm)
│ ├── wasm/ wasm-bindgen (browsers, bundlers, Node) │ ├── wasm/ wasm-bindgen (browsers, bundlers, Node)
│ ├── c/ C ABI (cdylib + staticlib) + generated include/wickra.h │ ├── c/ C ABI (cdylib + staticlib) + generated include/wickra.h
── csharp/ .NET binding over the C ABI (publishes on NuGet) ── csharp/ .NET binding over the C ABI (publishes on NuGet)
│ └── go/ Go binding over the C ABI via cgo (module tag)
├── examples/ examples/README.md indexes every language ├── examples/ examples/README.md indexes every language
│ ├── data/ real BTCUSDT OHLCV datasets, one per timeframe │ ├── data/ real BTCUSDT OHLCV datasets, one per timeframe
│ ├── rust/ Rust workspace member (`wickra-examples`) │ ├── rust/ Rust workspace member (`wickra-examples`)
@@ -264,7 +267,8 @@ wickra/
│ ├── node/ streaming, backtest, live trading (load `wickra`) │ ├── node/ streaming, backtest, live trading (load `wickra`)
│ ├── wasm/ browser demo for `wickra-wasm` │ ├── wasm/ browser demo for `wickra-wasm`
│ ├── c/ C smoke + streaming, C++ RAII wrapper │ ├── c/ C smoke + streaming, C++ RAII wrapper
── csharp/ streaming, backtest, strategies (load `Wickra`) ── csharp/ streaming, backtest, strategies (load `Wickra`)
│ └── go/ streaming, backtest, strategies (cgo binding)
└── .github/workflows/ CI and release pipelines └── .github/workflows/ CI and release pipelines
``` ```
@@ -300,6 +304,10 @@ cmake --build examples/c/build && ctest --test-dir examples/c/build --output-on-
# C# / .NET binding (requires the .NET 8 SDK; links the C ABI above) # C# / .NET binding (requires the .NET 8 SDK; links the C ABI above)
dotnet test bindings/csharp/Wickra.Tests/Wickra.Tests.csproj dotnet test bindings/csharp/Wickra.Tests/Wickra.Tests.csproj
# Go binding (requires a C compiler for cgo; links the C ABI above)
cp target/release/libwickra.so bindings/go/lib/ # .dylib on macOS, wickra.dll on Windows
cd bindings/go && go test ./...
``` ```
## Testing ## Testing
@@ -319,6 +327,8 @@ Every layer is covered; run the suites with the commands in
values across all indicators. values across all indicators.
- `bindings/wasm`: `wasm-bindgen-test` cases for constructors, equivalence, - `bindings/wasm`: `wasm-bindgen-test` cases for constructors, equivalence,
and reference values. and reference values.
- `bindings/go`: `go test` cases covering one indicator per FFI archetype
(scalar/batch, multi-output, bars, profile, array input), reset, and lifecycle.
## Contributing ## Contributing
+1 -1
View File
@@ -22,7 +22,7 @@ minor releases; breaking changes are called out in the changelog.
- **Performance.** Keep per-tick updates O(1) and maintain the benchmark suite; - **Performance.** Keep per-tick updates O(1) and maintain the benchmark suite;
investigate further allocation and cache improvements. investigate further allocation and cache improvements.
- **Bindings parity.** Keep the Python, Node.js and WebAssembly bindings — plus - **Bindings parity.** Keep the Python, Node.js and WebAssembly bindings — plus
the C ABI and the C# / .NET binding generated from it — in lockstep with the the C ABI and the C# / .NET and Go bindings generated from it — in lockstep with the
Rust core, including type stubs and platform coverage. Rust core, including type stubs and platform coverage.
- **Documentation.** Maintain a deep-dive page per indicator on - **Documentation.** Maintain a deep-dive page per indicator on
<https://docs.wickra.org>, plus quickstarts and cookbook material. <https://docs.wickra.org>, plus quickstarts and cookbook material.
+1 -1
View File
@@ -7,7 +7,7 @@ Thanks for using Wickra! Here is where to get help, depending on what you need.
Most questions are answered in the documentation: Most questions are answered in the documentation:
- **Docs site:** <https://docs.wickra.org> — quickstarts for Rust, Python, - **Docs site:** <https://docs.wickra.org> — quickstarts for Rust, Python,
Node.js, WebAssembly, C and C#, a per-indicator reference, warmup periods, the Node.js, WebAssembly, C, C# and Go, a per-indicator reference, warmup periods, the
data layer, and an FAQ. data layer, and an FAQ.
- **README:** <https://github.com/wickra-lib/wickra#readme> — installation and a - **README:** <https://github.com/wickra-lib/wickra#readme> — installation and a
quick overview. quick overview.
+1 -1
View File
@@ -4,7 +4,7 @@ This document describes Wickra's attack surface and the threats considered,
together with their mitigations. It complements the security assurance case in together with their mitigations. It complements the security assurance case in
[`SECURITY.md`](SECURITY.md). Wickra is a computational technical-analysis [`SECURITY.md`](SECURITY.md). Wickra is a computational technical-analysis
library (a Rust core with Python, Node.js and WebAssembly bindings plus a C ABI library (a Rust core with Python, Node.js and WebAssembly bindings plus a C ABI
and the .NET binding built on it), and the .NET and Go bindings built on it),
not a network service or trading system; the attack surface is correspondingly not a network service or trading system; the attack surface is correspondingly
small. small.
+1 -1
View File
@@ -9,7 +9,7 @@
library plus a generated `wickra.h` — no system dependencies.** library plus a generated `wickra.h` — no system dependencies.**
Wickra is a multi-language technical-analysis library with a Rust core and Wickra is a multi-language technical-analysis library with a Rust core and
bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C# and any bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C#, Go and any
other C-capable language. Every indicator is an O(1) other C-capable language. Every indicator is an O(1)
streaming state machine, so live trading bots and historical backtests share streaming state machine, so live trading bots and historical backtests share
the exact same implementation. This package is the **C ABI hub**: it compiles the the exact same implementation. This package is the **C ABI hub**: it compiles the
+1 -1
View File
@@ -9,7 +9,7 @@
prebuilt native library, no system dependencies.** prebuilt native library, no system dependencies.**
Wickra is a multi-language technical-analysis library with a Rust core and Wickra is a multi-language technical-analysis library with a Rust core and
bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C# and any bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C#, Go and any
other C-capable language. Every indicator is an O(1) other C-capable language. Every indicator is an O(1)
streaming state machine, so live trading bots and historical backtests share streaming state machine, so live trading bots and historical backtests share
the exact same implementation. This package is the .NET binding; it consumes the the exact same implementation. This package is the .NET binding; it consumes the
+1 -1
View File
@@ -11,7 +11,7 @@
<!-- NuGet package metadata --> <!-- NuGet package metadata -->
<PackageId>Wickra</PackageId> <PackageId>Wickra</PackageId>
<Version>0.7.6</Version> <Version>0.7.7</Version>
<Authors>kingchenc</Authors> <Authors>kingchenc</Authors>
<Description>High-performance streaming technical-analysis indicators (514 indicators) for .NET, backed by the native Rust core via the Wickra C ABI.</Description> <Description>High-performance streaming technical-analysis indicators (514 indicators) for .NET, backed by the native Rust core via the Wickra C ABI.</Description>
<PackageLicenseExpression>MIT OR Apache-2.0</PackageLicenseExpression> <PackageLicenseExpression>MIT OR Apache-2.0</PackageLicenseExpression>
+100
View File
@@ -0,0 +1,100 @@
# Wickra — Go
[![CI](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml/badge.svg)](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/wickra-lib/wickra/branch/main/graph/badge.svg)](https://codecov.io/gh/wickra-lib/wickra)
[![Go Reference](https://pkg.go.dev/badge/github.com/wickra-lib/wickra/bindings/go.svg)](https://pkg.go.dev/github.com/wickra-lib/wickra/bindings/go)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT_OR_Apache--2.0-blue)](https://github.com/wickra-lib/wickra#license)
**Streaming-first technical indicators for Go, over the Wickra C ABI hub via cgo.**
Wickra is a multi-language technical-analysis library with a Rust core and
bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C#, Go and
any other C-capable language. Every indicator is an O(1) streaming state machine,
so live trading bots and historical backtests share the exact same
implementation. This package is the Go binding; it consumes the C ABI hub through
cgo and exposes all 514 streaming-first indicators as idiomatic types.
## Install
```bash
go get github.com/wickra-lib/wickra/bindings/go
```
The binding uses cgo, so a C compiler is required, and it links against the
prebuilt Wickra C ABI library. Build that library from the workspace and stage
it under this package's `lib/` directory:
```bash
cargo build -p wickra-c --release
cp target/release/libwickra.so bindings/go/lib/ # Linux
cp target/release/libwickra.dylib bindings/go/lib/ # macOS
cp target/release/wickra.dll bindings/go/lib/ # Windows (also on PATH at run time)
```
On Linux and macOS the library path is baked in via rpath; on Windows the DLL
must be discoverable at run time (next to the executable or on `PATH`).
## Quick start
```go
package main
import (
"fmt"
wickra "github.com/wickra-lib/wickra/bindings/go"
)
func main() {
// Batch: run an indicator over a whole series (NaN at warmup positions).
prices := make([]float64, 1000)
for i := range prices {
prices[i] = 100.0 + float64(i)*0.1
}
sma, _ := wickra.NewSma(20)
defer sma.Close()
values := sma.Batch(prices)
// Streaming: the same indicator, fed tick by tick in O(1).
rsi, _ := wickra.NewRsi(14)
defer rsi.Close()
for _, price := range prices {
value := rsi.Update(price) // NaN during warmup, no recomputation
if value > 70 {
fmt.Println("overbought")
}
}
_ = values
}
```
`Batch(prices)` and feeding the same prices through `Update()` produce identical
values — the equivalence is enforced by the test suite. Multi-output indicators
(MACD, Bollinger, ADX, …) return `(Output, bool)`, with `false` while warming up.
Every indicator owns a native handle freed by `Close()`; a finalizer is wired as
a backstop, but call `Close()` (e.g. with `defer`) to release memory promptly.
## Documentation
The full indicator catalogue, guides, quickstarts, and API reference live in the
main repository and documentation site:
- **Repository & full indicator list:** <https://github.com/wickra-lib/wickra>
- **Docs** (quickstarts, cookbook, TA-Lib migration): <https://docs.wickra.org>
- **Runnable examples:** [`examples/go/`](https://github.com/wickra-lib/wickra/tree/main/examples/go)
Wickra ships native bindings for Python, Node.js, WebAssembly and Rust, plus a
C ABI hub that any C-capable language (C, C++, C#, Go, Java, R) links against —
all exposing the same indicators from the shared, `unsafe`-forbidden Rust core.
## Disclaimer
Wickra is an indicator toolkit, not a trading system. The values it computes are
deterministic transforms of the input data — they are not financial advice and
do not predict the market. Any use in a live trading context is at your own risk.
The library is provided **as is**, without warranty of any kind.
## License
Licensed under either of [Apache-2.0](https://github.com/wickra-lib/wickra/blob/main/LICENSE-APACHE)
or [MIT](https://github.com/wickra-lib/wickra/blob/main/LICENSE-MIT) at your option.
+3
View File
@@ -0,0 +1,3 @@
module github.com/wickra-lib/wickra/bindings/go
go 1.23
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
# Prebuilt Wickra C ABI libraries are provisioned locally / in CI, not committed.
*.so
*.dylib
*.dll
*.a
*.lib
*.exp
+25
View File
@@ -0,0 +1,25 @@
// Package wickra provides idiomatic Go bindings for the Wickra
// technical-analysis library over its C ABI hub.
//
// Each indicator is an opaque-handle type with a New<Indicator> constructor and
// Update/Batch/Reset/Close methods. Handles are freed by Close and, as a
// backstop, by a finalizer; call Close explicitly to release native memory
// promptly. The binding links against the prebuilt Wickra C ABI library
// (libwickra.so/.dylib or wickra.dll) staged under ./lib — see the package
// README for how to provision it.
package wickra
/*
#cgo CFLAGS: -I${SRCDIR}/../c/include
#cgo linux LDFLAGS: -L${SRCDIR}/lib -lwickra -Wl,-rpath,${SRCDIR}/lib
#cgo darwin LDFLAGS: -L${SRCDIR}/lib -lwickra -Wl,-rpath,${SRCDIR}/lib
#cgo windows LDFLAGS: -L${SRCDIR}/lib -l:wickra.dll
#include "wickra.h"
*/
import "C"
import "errors"
// ErrInvalidParams is returned by a New<Indicator> constructor when the native
// constructor rejects the supplied parameters (for example a zero period).
var ErrInvalidParams = errors.New("wickra: invalid indicator parameters")
+151
View File
@@ -0,0 +1,151 @@
package wickra
import (
"math"
"testing"
)
// One indicator per FFI archetype, exercising the full New/Update/Batch/Reset/
// Close surface against the real native library.
func TestScalarKnownValue(t *testing.T) {
s, err := NewSma(3)
if err != nil {
t.Fatalf("NewSma: %v", err)
}
defer s.Close()
var last float64
for _, v := range []float64{1, 2, 3, 4, 5} {
last = s.Update(v)
}
if math.Abs(last-4.0) > 1e-9 {
t.Fatalf("sma(3) last = %v, want 4.0", last)
}
}
func TestScalarBatchMatchesStreaming(t *testing.T) {
input := []float64{1, 2, 3, 4, 5, 6, 7, 8}
stream, _ := NewSma(3)
defer stream.Close()
want := make([]float64, len(input))
for i, v := range input {
want[i] = stream.Update(v)
}
batchInd, _ := NewSma(3)
defer batchInd.Close()
got := batchInd.Batch(input)
if len(got) != len(want) {
t.Fatalf("batch len = %d, want %d", len(got), len(want))
}
for i := range want {
if math.IsNaN(want[i]) && math.IsNaN(got[i]) {
continue
}
if math.Abs(got[i]-want[i]) > 1e-9 {
t.Fatalf("batch[%d] = %v, streaming = %v", i, got[i], want[i])
}
}
}
func TestMultiOutput(t *testing.T) {
m, err := NewMacdIndicator(3, 6, 3)
if err != nil {
t.Fatalf("NewMacdIndicator: %v", err)
}
defer m.Close()
var ok bool
var out MacdOutput
for i := 0; i < 30; i++ {
out, ok = m.Update(100 + float64(i))
}
if !ok {
t.Fatal("macd never produced a value after warmup")
}
if math.IsNaN(out.Macd) {
t.Fatal("macd value is NaN after warmup")
}
}
func TestBars(t *testing.T) {
rb, err := NewRangeBars(2.0)
if err != nil {
t.Fatalf("NewRangeBars: %v", err)
}
defer rb.Close()
total := 0
for _, p := range []float64{100, 101, 103, 104, 99, 96, 102, 108, 95, 110} {
bars := rb.Update(p, p, p, p, 1, 0)
total += len(bars)
}
if total == 0 {
t.Fatal("range bars produced no bars over a 15-point move")
}
}
func TestProfile(t *testing.T) {
vp, err := NewVolumeProfile(10, 24)
if err != nil {
t.Fatalf("NewVolumeProfile: %v", err)
}
defer vp.Close()
var ok bool
var snap VolumeProfileOutputScalars
for i := 0; i < 50; i++ {
price := 100 + 5*math.Sin(float64(i)*0.3)
snap, ok = vp.Update(price, price+1, price-1, price, 1000, int64(i))
}
if !ok {
t.Fatal("volume profile never produced a snapshot")
}
if len(snap.Values) == 0 {
t.Fatal("volume profile returned an empty values buffer")
}
}
func TestArrayInput(t *testing.T) {
ob, err := NewOrderBookImbalanceFull()
if err != nil {
t.Fatalf("NewOrderBookImbalanceFull: %v", err)
}
defer ob.Close()
bidPrice := []float64{99.9, 99.8, 99.7}
bidSize := []float64{5, 3, 2}
askPrice := []float64{100.1, 100.2, 100.3}
askSize := []float64{1, 1, 1}
v := ob.Update(bidPrice, bidSize, askPrice, askSize)
if math.IsNaN(v) {
t.Fatal("order-book imbalance is NaN on a populated book")
}
}
func TestResetReturnsToWarmup(t *testing.T) {
s, _ := NewSma(3)
defer s.Close()
for _, v := range []float64{1, 2, 3} {
s.Update(v)
}
s.Reset()
if got := s.Update(10); !math.IsNaN(got) {
t.Fatalf("after reset first update = %v, want NaN (warmup)", got)
}
}
func TestInvalidParams(t *testing.T) {
if _, err := NewSma(0); err == nil {
t.Fatal("NewSma(0) should return ErrInvalidParams")
}
}
func TestCloseIsIdempotent(t *testing.T) {
s, _ := NewSma(3)
s.Close()
s.Close() // must not panic or double-free
}
+1 -1
View File
@@ -9,7 +9,7 @@
prebuilt native binary, no system dependencies.** prebuilt native binary, no system dependencies.**
Wickra is a multi-language technical-analysis library with a Rust core and Wickra is a multi-language technical-analysis library with a Rust core and
bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C# and any bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C#, Go and any
other C-capable language. Every indicator is an O(1) other C-capable language. Every indicator is an O(1)
streaming state machine, so live trading bots and historical backtests share streaming state machine, so live trading bots and historical backtests share
the exact same implementation. This package is the Node.js binding (napi-rs); the exact same implementation. This package is the Node.js binding (napi-rs);
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "wickra-darwin-arm64", "name": "wickra-darwin-arm64",
"version": "0.7.6", "version": "0.7.7",
"description": "Native binding for wickra (macOS Apple Silicon). Installed automatically as an optional dependency of wickra on matching platforms.", "description": "Native binding for wickra (macOS Apple Silicon). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.darwin-arm64.node", "main": "wickra.darwin-arm64.node",
"files": [ "files": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "wickra-darwin-x64", "name": "wickra-darwin-x64",
"version": "0.7.6", "version": "0.7.7",
"description": "Native binding for wickra (macOS Intel). Installed automatically as an optional dependency of wickra on matching platforms.", "description": "Native binding for wickra (macOS Intel). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.darwin-x64.node", "main": "wickra.darwin-x64.node",
"files": [ "files": [
@@ -1,6 +1,6 @@
{ {
"name": "wickra-linux-arm64-gnu", "name": "wickra-linux-arm64-gnu",
"version": "0.7.6", "version": "0.7.7",
"description": "Native binding for wickra (linux arm64 GNU). Installed automatically as an optional dependency of wickra on matching platforms.", "description": "Native binding for wickra (linux arm64 GNU). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.linux-arm64-gnu.node", "main": "wickra.linux-arm64-gnu.node",
"files": [ "files": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "wickra-linux-x64-gnu", "name": "wickra-linux-x64-gnu",
"version": "0.7.6", "version": "0.7.7",
"description": "Native binding for wickra (linux x64 GNU). Installed automatically as an optional dependency of wickra on matching platforms.", "description": "Native binding for wickra (linux x64 GNU). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.linux-x64-gnu.node", "main": "wickra.linux-x64-gnu.node",
"files": [ "files": [
@@ -1,6 +1,6 @@
{ {
"name": "wickra-win32-arm64-msvc", "name": "wickra-win32-arm64-msvc",
"version": "0.7.6", "version": "0.7.7",
"description": "Native binding for wickra (Windows arm64 MSVC). Installed automatically as an optional dependency of wickra on matching platforms.", "description": "Native binding for wickra (Windows arm64 MSVC). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.win32-arm64-msvc.node", "main": "wickra.win32-arm64-msvc.node",
"files": [ "files": [
@@ -1,6 +1,6 @@
{ {
"name": "wickra-win32-x64-msvc", "name": "wickra-win32-x64-msvc",
"version": "0.7.6", "version": "0.7.7",
"description": "Native binding for wickra (Windows x64 MSVC). Installed automatically as an optional dependency of wickra on matching platforms.", "description": "Native binding for wickra (Windows x64 MSVC). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.win32-x64-msvc.node", "main": "wickra.win32-x64-msvc.node",
"files": [ "files": [
+20 -20
View File
@@ -1,12 +1,12 @@
{ {
"name": "wickra", "name": "wickra",
"version": "0.7.6", "version": "0.7.7",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "wickra", "name": "wickra",
"version": "0.7.6", "version": "0.7.7",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"devDependencies": { "devDependencies": {
"@napi-rs/cli": "^2.18.0" "@napi-rs/cli": "^2.18.0"
@@ -15,12 +15,12 @@
"node": ">= 18" "node": ">= 18"
}, },
"optionalDependencies": { "optionalDependencies": {
"wickra-darwin-arm64": "0.7.6", "wickra-darwin-arm64": "0.7.7",
"wickra-darwin-x64": "0.7.6", "wickra-darwin-x64": "0.7.7",
"wickra-linux-arm64-gnu": "0.7.6", "wickra-linux-arm64-gnu": "0.7.7",
"wickra-linux-x64-gnu": "0.7.6", "wickra-linux-x64-gnu": "0.7.7",
"wickra-win32-arm64-msvc": "0.7.6", "wickra-win32-arm64-msvc": "0.7.7",
"wickra-win32-x64-msvc": "0.7.6" "wickra-win32-x64-msvc": "0.7.7"
} }
}, },
"node_modules/@napi-rs/cli": { "node_modules/@napi-rs/cli": {
@@ -41,8 +41,8 @@
} }
}, },
"node_modules/wickra-darwin-arm64": { "node_modules/wickra-darwin-arm64": {
"version": "0.7.6", "version": "0.7.7",
"resolved": "https://registry.npmjs.org/wickra-darwin-arm64/-/wickra-darwin-arm64-0.7.6.tgz", "resolved": "https://registry.npmjs.org/wickra-darwin-arm64/-/wickra-darwin-arm64-0.7.7.tgz",
"integrity": "sha512-4eZiBR/yGUdr4nzhEUFy2i69XgNx64iI2ax/LPamsThgylC0KpHOZKK19QzJ2d9KbK4C8nMjME5FLuR+4GNEwQ==", "integrity": "sha512-4eZiBR/yGUdr4nzhEUFy2i69XgNx64iI2ax/LPamsThgylC0KpHOZKK19QzJ2d9KbK4C8nMjME5FLuR+4GNEwQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
@@ -57,8 +57,8 @@
} }
}, },
"node_modules/wickra-darwin-x64": { "node_modules/wickra-darwin-x64": {
"version": "0.7.6", "version": "0.7.7",
"resolved": "https://registry.npmjs.org/wickra-darwin-x64/-/wickra-darwin-x64-0.7.6.tgz", "resolved": "https://registry.npmjs.org/wickra-darwin-x64/-/wickra-darwin-x64-0.7.7.tgz",
"integrity": "sha512-6hf8zI3QPjTFp4zCpmgUwDvNtu6jHqNUHKD5e55POo0CgA52HkpyxSPtVm8TGTIZDI7kPjlbOdBM8CJ76mmXwA==", "integrity": "sha512-6hf8zI3QPjTFp4zCpmgUwDvNtu6jHqNUHKD5e55POo0CgA52HkpyxSPtVm8TGTIZDI7kPjlbOdBM8CJ76mmXwA==",
"cpu": [ "cpu": [
"x64" "x64"
@@ -73,8 +73,8 @@
} }
}, },
"node_modules/wickra-linux-arm64-gnu": { "node_modules/wickra-linux-arm64-gnu": {
"version": "0.7.6", "version": "0.7.7",
"resolved": "https://registry.npmjs.org/wickra-linux-arm64-gnu/-/wickra-linux-arm64-gnu-0.7.6.tgz", "resolved": "https://registry.npmjs.org/wickra-linux-arm64-gnu/-/wickra-linux-arm64-gnu-0.7.7.tgz",
"integrity": "sha512-kSe6y0xBMSiqdPLXNjwop5WZdHtvdBNKSEBCwZ4hFq33p4apW25/wrlzv9/oDuyD4kuPabJEhCCnFOplh58CUg==", "integrity": "sha512-kSe6y0xBMSiqdPLXNjwop5WZdHtvdBNKSEBCwZ4hFq33p4apW25/wrlzv9/oDuyD4kuPabJEhCCnFOplh58CUg==",
"cpu": [ "cpu": [
"arm64" "arm64"
@@ -89,8 +89,8 @@
} }
}, },
"node_modules/wickra-linux-x64-gnu": { "node_modules/wickra-linux-x64-gnu": {
"version": "0.7.6", "version": "0.7.7",
"resolved": "https://registry.npmjs.org/wickra-linux-x64-gnu/-/wickra-linux-x64-gnu-0.7.6.tgz", "resolved": "https://registry.npmjs.org/wickra-linux-x64-gnu/-/wickra-linux-x64-gnu-0.7.7.tgz",
"integrity": "sha512-tWBWS4qz7hxM4xnpFb59bhf6TaLwXq0Z3jEa/2l7r8PiHA94g8r8S53NRMiT+4yiL5hSWe/nUiC/YXdRrhEZ4g==", "integrity": "sha512-tWBWS4qz7hxM4xnpFb59bhf6TaLwXq0Z3jEa/2l7r8PiHA94g8r8S53NRMiT+4yiL5hSWe/nUiC/YXdRrhEZ4g==",
"cpu": [ "cpu": [
"x64" "x64"
@@ -105,8 +105,8 @@
} }
}, },
"node_modules/wickra-win32-arm64-msvc": { "node_modules/wickra-win32-arm64-msvc": {
"version": "0.7.6", "version": "0.7.7",
"resolved": "https://registry.npmjs.org/wickra-win32-arm64-msvc/-/wickra-win32-arm64-msvc-0.7.6.tgz", "resolved": "https://registry.npmjs.org/wickra-win32-arm64-msvc/-/wickra-win32-arm64-msvc-0.7.7.tgz",
"integrity": "sha512-EXIckHxAtF75PUGDKRzXyqMe9ldP0JjSdu68WFN6iJfp+McYrGu6h40TEJlQ/oUEIoPqiZB/xhVyo/el5Lg7zw==", "integrity": "sha512-EXIckHxAtF75PUGDKRzXyqMe9ldP0JjSdu68WFN6iJfp+McYrGu6h40TEJlQ/oUEIoPqiZB/xhVyo/el5Lg7zw==",
"cpu": [ "cpu": [
"arm64" "arm64"
@@ -121,8 +121,8 @@
} }
}, },
"node_modules/wickra-win32-x64-msvc": { "node_modules/wickra-win32-x64-msvc": {
"version": "0.7.6", "version": "0.7.7",
"resolved": "https://registry.npmjs.org/wickra-win32-x64-msvc/-/wickra-win32-x64-msvc-0.7.6.tgz", "resolved": "https://registry.npmjs.org/wickra-win32-x64-msvc/-/wickra-win32-x64-msvc-0.7.7.tgz",
"integrity": "sha512-Yfsqq1Xwp6hdxMyLze411vNdo7BDwI6+lPSe7A9XdqyPecNDbtKwYLpsal2r8EHbNzqM+R8XnuRtUaEQS5VlUQ==", "integrity": "sha512-Yfsqq1Xwp6hdxMyLze411vNdo7BDwI6+lPSe7A9XdqyPecNDbtKwYLpsal2r8EHbNzqM+R8XnuRtUaEQS5VlUQ==",
"cpu": [ "cpu": [
"x64" "x64"
+7 -7
View File
@@ -1,6 +1,6 @@
{ {
"name": "wickra", "name": "wickra",
"version": "0.7.6", "version": "0.7.7",
"description": "Streaming-first technical indicators: incremental, fast, install-free. Node bindings powered by Rust.", "description": "Streaming-first technical indicators: incremental, fast, install-free. Node bindings powered by Rust.",
"author": "kingchenc <support@wickra.org>", "author": "kingchenc <support@wickra.org>",
"main": "index.js", "main": "index.js",
@@ -47,12 +47,12 @@
"node": ">= 18" "node": ">= 18"
}, },
"optionalDependencies": { "optionalDependencies": {
"wickra-linux-x64-gnu": "0.7.6", "wickra-linux-x64-gnu": "0.7.7",
"wickra-linux-arm64-gnu": "0.7.6", "wickra-linux-arm64-gnu": "0.7.7",
"wickra-darwin-x64": "0.7.6", "wickra-darwin-x64": "0.7.7",
"wickra-darwin-arm64": "0.7.6", "wickra-darwin-arm64": "0.7.7",
"wickra-win32-x64-msvc": "0.7.6", "wickra-win32-x64-msvc": "0.7.7",
"wickra-win32-arm64-msvc": "0.7.6" "wickra-win32-arm64-msvc": "0.7.7"
}, },
"scripts": { "scripts": {
"build": "napi build --platform --release", "build": "napi build --platform --release",
+1 -1
View File
@@ -9,7 +9,7 @@
system dependencies, no C build tooling.** system dependencies, no C build tooling.**
Wickra is a multi-language technical-analysis library with a Rust core and Wickra is a multi-language technical-analysis library with a Rust core and
bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C# and any bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C#, Go and any
other C-capable language. Every indicator is an O(1) other C-capable language. Every indicator is an O(1)
streaming state machine, so live trading bots and historical backtests share streaming state machine, so live trading bots and historical backtests share
the exact same implementation. This package is the Python binding (PyO3); it the exact same implementation. This package is the Python binding (PyO3); it
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "maturin"
[project] [project]
name = "wickra" name = "wickra"
version = "0.7.6" version = "0.7.7"
description = "Streaming-first technical indicators: incremental, fast, install-free." description = "Streaming-first technical indicators: incremental, fast, install-free."
readme = "README.md" readme = "README.md"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
+1 -1
View File
@@ -9,7 +9,7 @@
wickra-wasm` — pure WebAssembly, runs anywhere a modern JS engine does.** wickra-wasm` — pure WebAssembly, runs anywhere a modern JS engine does.**
Wickra is a multi-language technical-analysis library with a Rust core and Wickra is a multi-language technical-analysis library with a Rust core and
bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C# and any bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C#, Go and any
other C-capable language. Every indicator is an O(1) other C-capable language. Every indicator is an O(1)
streaming state machine, so live trading dashboards and historical backtests streaming state machine, so live trading dashboards and historical backtests
share the exact same implementation. This package is the WebAssembly binding share the exact same implementation. This package is the WebAssembly binding
+3 -2
View File
@@ -8,8 +8,9 @@ That includes:
[Python](https://docs.wickra.org/Quickstart-Python), [Python](https://docs.wickra.org/Quickstart-Python),
[Node](https://docs.wickra.org/Quickstart-Node), [Node](https://docs.wickra.org/Quickstart-Node),
[WASM](https://docs.wickra.org/Quickstart-WASM), [WASM](https://docs.wickra.org/Quickstart-WASM),
[C](https://docs.wickra.org/Quickstart-C), and [C](https://docs.wickra.org/Quickstart-C),
[C#](https://docs.wickra.org/Quickstart-CSharp). [C#](https://docs.wickra.org/Quickstart-CSharp), and
[Go](https://docs.wickra.org/Quickstart-Go).
- A per-indicator deep dive for every one of the **514 indicators** across - A per-indicator deep dive for every one of the **514 indicators** across
the sixteen families (Moving Averages, Momentum Oscillators, Trend & the sixteen families (Moving Averages, Momentum Oscillators, Trend &
Directional, Price Oscillators, Volatility & Bands, Bands & Channels, Directional, Price Oscillators, Volatility & Bands, Bands & Channels,
+22
View File
@@ -69,6 +69,28 @@ The offline examples run on deterministic synthetic data (and under CI on all
three OSes); `fetch_btcusdt` and `live_binance` reach the network and are built three OSes); `fetch_btcusdt` and `live_binance` reach the network and are built
but not run in CI. but not run in CI.
## Go — `examples/go/`
Build the C ABI library first (`cargo build -p wickra-c --release`) and stage it
under `bindings/go/lib/` (see the [Go binding README](../bindings/go)), then run
any example from the `examples/go` module.
| Example | What it does | Run |
| --- | --- | --- |
| `streaming` | Feed a synthetic price series through SMA / EMA / RSI / MACD tick by tick. | `go run ./streaming` |
| `backtest` | Basket of indicators over an OHLCV series (CSV arg or synthetic). | `go run ./backtest <ohlcv.csv>` |
| `multi_timeframe` | Resample a 1-minute series to 5m / 15m and print an indicator per timeframe. | `go run ./multi_timeframe` |
| `parallel_assets` | SMA(20) batch over a panel, serial vs goroutine fan-out, with speedup. | `go run ./parallel_assets 200 5000` |
| `strategy_rsi_mean_reversion` | RSI(14) mean-reversion with PnL / Sharpe / max-DD summary. | `go run ./strategy_rsi_mean_reversion` |
| `strategy_macd_adx` | Trend-follower: MACD crossover entries gated by ADX(14) > 20. | `go run ./strategy_macd_adx` |
| `strategy_bollinger_squeeze` | Bollinger-squeeze breakout with an ATR(14) trailing stop. | `go run ./strategy_bollinger_squeeze` |
| `fetch_btcusdt` | Download real BTCUSDT klines from the Binance REST API into a CSV. | `go run ./fetch_btcusdt` |
| `live_binance` | Stream live Binance klines through EMA(20) over a WebSocket. | `go run ./live_binance` |
The offline examples run on deterministic synthetic data (and under CI on all
three OSes); `fetch_btcusdt` and `live_binance` reach the network and are built
but not run in CI.
## Python — `examples/python/` ## Python — `examples/python/`
| Example | What it does | Run | | Example | What it does | Run |
+14
View File
@@ -0,0 +1,14 @@
# Data fetched at runtime by fetch_btcusdt
**/data/
# Compiled example binaries
/streaming/streaming
/backtest/backtest
/multi_timeframe/multi_timeframe
/parallel_assets/parallel_assets
/strategy_rsi_mean_reversion/strategy_rsi_mean_reversion
/strategy_macd_adx/strategy_macd_adx
/strategy_bollinger_squeeze/strategy_bollinger_squeeze
/fetch_btcusdt/fetch_btcusdt
/live_binance/live_binance
*.exe
+38
View File
@@ -0,0 +1,38 @@
# Wickra examples — Go
Runnable Go examples for the [Wickra Go binding](../../bindings/go). Each example
is a small `main` program in its own directory; they share the deterministic
synthetic data, CSV loader, and equity summary in
[`internal/market`](internal/market).
The binding links against the prebuilt Wickra C ABI library, so build and stage
it once before running anything:
```bash
cargo build -p wickra-c --release
cp target/release/libwickra.so bindings/go/lib/ # Linux
cp target/release/libwickra.dylib bindings/go/lib/ # macOS
cp target/release/wickra.dll bindings/go/lib/ # Windows (also put it on PATH)
```
Then run any example from the `examples/go` module:
```bash
cd examples/go
go run ./streaming
```
| Example | What it does | Run |
| --- | --- | --- |
| `streaming` | Feed a synthetic price series through SMA / EMA / RSI / MACD tick by tick. | `go run ./streaming` |
| `backtest` | Compute a basket of indicators over an OHLCV series and print a summary. | `go run ./backtest <ohlcv.csv>` |
| `multi_timeframe` | Resample a 1-minute series into 5m / 15m and print an indicator per timeframe. | `go run ./multi_timeframe` |
| `parallel_assets` | SMA(20) batch over a panel of assets, serial vs goroutine fan-out, with speedup. | `go run ./parallel_assets 200 5000` |
| `strategy_rsi_mean_reversion` | RSI(14) mean-reversion with a PnL / Sharpe / max-DD summary. | `go run ./strategy_rsi_mean_reversion` |
| `strategy_macd_adx` | MACD crossover entries gated by ADX(14) > 20. | `go run ./strategy_macd_adx` |
| `strategy_bollinger_squeeze` | Bollinger-squeeze breakout with an ATR(14) trailing stop. | `go run ./strategy_bollinger_squeeze` |
| `fetch_btcusdt` | Download real BTCUSDT klines from the Binance REST API into a CSV. | `go run ./fetch_btcusdt` |
| `live_binance` | Stream live Binance klines through EMA(20) over a WebSocket. | `go run ./live_binance` |
`fetch_btcusdt` and `live_binance` require network access; the rest run offline
on deterministic synthetic data.
+56
View File
@@ -0,0 +1,56 @@
// Compute a basket of indicators over an OHLCV series and print a summary.
// Pass a CSV path (timestamp,open,high,low,close,volume) or run on synthetic data.
package main
import (
"fmt"
"log"
"math"
"os"
wickra "github.com/wickra-lib/wickra/bindings/go"
"github.com/wickra-lib/wickra/examples/go/internal/market"
)
func main() {
source := "synthetic"
var bars []market.Bar
if len(os.Args) > 1 {
source = os.Args[1]
loaded, err := market.LoadOhlcvCsv(os.Args[1])
if err != nil {
log.Fatalf("load csv: %v", err)
}
bars = loaded
} else {
bars = market.SyntheticCandles(1000)
}
fmt.Printf("Backtest over %d bars (%s):\n", len(bars), source)
sma, _ := wickra.NewSma(20)
defer sma.Close()
ema, _ := wickra.NewEma(50)
defer ema.Close()
rsi, _ := wickra.NewRsi(14)
defer rsi.Close()
atr, _ := wickra.NewAtr(14)
defer atr.Close()
var lastSma, lastEma, lastRsi, lastAtr float64
oversold := 0
for _, b := range bars {
lastSma = sma.Update(b.Close)
lastEma = ema.Update(b.Close)
lastRsi = rsi.Update(b.Close)
lastAtr = atr.Update(b.Open, b.High, b.Low, b.Close, b.Volume, b.Timestamp)
if !math.IsNaN(lastRsi) && lastRsi < 30.0 {
oversold++
}
}
fmt.Printf(" SMA(20) last = %.4f\n", lastSma)
fmt.Printf(" EMA(50) last = %.4f\n", lastEma)
fmt.Printf(" RSI(14) last = %.4f (%d oversold bars)\n", lastRsi, oversold)
fmt.Printf(" ATR(14) last = %.4f\n", lastAtr)
}
+58
View File
@@ -0,0 +1,58 @@
// Download real BTCUSDT hourly klines from the Binance REST API into a CSV that the
// other examples can consume. Requires network access (build-only in CI).
package main
import (
"bufio"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
)
func main() {
const url = "https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=1h&limit=500"
fmt.Printf("Fetching %s\n", url)
resp, err := http.Get(url)
if err != nil {
log.Fatalf("request: %v", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("read body: %v", err)
}
// Binance kline array: [openTime, open, high, low, close, volume, ...].
var klines [][]any
if err := json.Unmarshal(body, &klines); err != nil {
log.Fatalf("parse json: %v", err)
}
dir := "data"
if err := os.MkdirAll(dir, 0o755); err != nil {
log.Fatalf("mkdir: %v", err)
}
path := filepath.Join(dir, "btcusdt_1h.csv")
file, err := os.Create(path)
if err != nil {
log.Fatalf("create: %v", err)
}
defer file.Close()
writer := bufio.NewWriter(file)
defer writer.Flush()
fmt.Fprintln(writer, "timestamp,open,high,low,close,volume")
count := 0
for _, k := range klines {
ts := int64(k[0].(float64))
fmt.Fprintf(writer, "%d,%s,%s,%s,%s,%s\n", ts, k[1], k[2], k[3], k[4], k[5])
count++
}
fmt.Printf("Wrote %d klines to %s\n", count, path)
}
+9
View File
@@ -0,0 +1,9 @@
module github.com/wickra-lib/wickra/examples/go
go 1.23
require github.com/wickra-lib/wickra/bindings/go v0.0.0
require github.com/coder/websocket v1.8.14
replace github.com/wickra-lib/wickra/bindings/go => ../../bindings/go
+2
View File
@@ -0,0 +1,2 @@
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
+156
View File
@@ -0,0 +1,156 @@
// Package market provides deterministic synthetic market data, a small OHLCV
// CSV loader, and an equity-curve summary shared by the offline Go examples so
// they run without network access. It mirrors the helpers used by the Python,
// C, and C# example suites.
package market
import (
"bufio"
"fmt"
"math"
"os"
"strconv"
"strings"
)
// Bar is one OHLCV bar with a millisecond timestamp.
type Bar struct {
Open float64
High float64
Low float64
Close float64
Volume float64
Timestamp int64
}
// SyntheticPrices returns a reproducible price path (trend + two cycles), with
// no randomness, starting at 100.
func SyntheticPrices(count int) []float64 {
return SyntheticPricesFrom(count, 100.0)
}
// SyntheticPricesFrom is SyntheticPrices with an explicit starting level.
func SyntheticPricesFrom(count int, start float64) []float64 {
prices := make([]float64, count)
for i := range prices {
fi := float64(i)
prices[i] = start + 12.0*math.Sin(fi*0.05) + 5.0*math.Sin(fi*0.013) + fi*0.01
}
return prices
}
// SyntheticCandles returns a reproducible OHLCV series derived from
// SyntheticPrices, one bar per hour.
func SyntheticCandles(count int) []Bar {
return SyntheticCandlesStep(count, 0, 3_600_000)
}
// SyntheticCandlesStep is SyntheticCandles with an explicit start timestamp and
// per-bar step in milliseconds.
func SyntheticCandlesStep(count int, startTimestamp, stepMs int64) []Bar {
prices := SyntheticPrices(count + 1)
bars := make([]Bar, count)
for i := 0; i < count; i++ {
fi := float64(i)
op := prices[i]
cl := prices[i+1]
high := math.Max(op, cl) + 0.5 + math.Abs(math.Sin(fi*0.7))
low := math.Min(op, cl) - 0.5 - math.Abs(math.Cos(fi*0.7))
volume := 1000.0 + 500.0*(1.0+math.Sin(fi*0.1))
bars[i] = Bar{op, high, low, cl, volume, startTimestamp + int64(i)*stepMs}
}
return bars
}
// LoadOhlcvCsv loads an OHLCV CSV. It accepts rows of
// timestamp,open,high,low,close,volume or open,high,low,close,volume; a
// non-numeric first row is treated as a header and skipped.
func LoadOhlcvCsv(path string) ([]Bar, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
var bars []Bar
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
cols := strings.Split(line, ",")
if _, err := strconv.ParseFloat(cols[0], 64); err != nil {
continue // header row
}
f := func(i int) float64 {
v, _ := strconv.ParseFloat(strings.TrimSpace(cols[i]), 64)
return v
}
if len(cols) >= 6 {
ts, _ := strconv.ParseInt(strings.TrimSpace(cols[0]), 10, 64)
bars = append(bars, Bar{f(1), f(2), f(3), f(4), f(5), ts})
} else {
bars = append(bars, Bar{f(0), f(1), f(2), f(3), f(4), int64(len(bars))})
}
}
return bars, scanner.Err()
}
// EquityResult holds summary statistics for a long-only equity curve.
type EquityResult struct {
TotalReturnPct float64
Sharpe float64
MaxDrawdownPct float64
Trades int
FinalEquity float64
}
// Summarize turns a stream of per-bar fractional returns (0.01 == +1%) into a
// PnL / Sharpe / max-drawdown summary, annualised by periodsPerYear.
func Summarize(periodReturns []float64, trades int, periodsPerYear float64) EquityResult {
equity, peak, maxDrawdown := 1.0, 1.0, 0.0
for _, r := range periodReturns {
equity *= 1.0 + r
peak = math.Max(peak, equity)
if peak > 0 {
maxDrawdown = math.Max(maxDrawdown, (peak-equity)/peak)
}
}
mean := 0.0
if len(periodReturns) > 0 {
var sum float64
for _, r := range periodReturns {
sum += r
}
mean = sum / float64(len(periodReturns))
}
variance := 0.0
if len(periodReturns) > 1 {
var ss float64
for _, r := range periodReturns {
ss += (r - mean) * (r - mean)
}
variance = ss / float64(len(periodReturns)-1)
}
stdDev := math.Sqrt(variance)
sharpe := 0.0
if stdDev > 1e-12 {
sharpe = mean / stdDev * math.Sqrt(periodsPerYear)
}
return EquityResult{
TotalReturnPct: (equity - 1.0) * 100.0,
Sharpe: sharpe,
MaxDrawdownPct: maxDrawdown * 100.0,
Trades: trades,
FinalEquity: equity,
}
}
// Print writes a one-line summary of an equity result.
func Print(name string, r EquityResult) {
fmt.Printf("%-26s return=%8.2f%% sharpe=%6.2f maxDD=%6.2f%% trades=%d\n",
name, r.TotalReturnPct, r.Sharpe, r.MaxDrawdownPct, r.Trades)
}
+54
View File
@@ -0,0 +1,54 @@
// Stream live BTCUSDT 1-minute klines from Binance and feed each close through EMA(20).
// Requires network access (build-only in CI). Runs for up to 60 seconds.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"strconv"
"time"
"github.com/coder/websocket"
wickra "github.com/wickra-lib/wickra/bindings/go"
)
func main() {
const url = "wss://stream.binance.com:9443/ws/btcusdt@kline_1m"
fmt.Printf("Connecting to %s (up to 60s)...\n", url)
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
conn, _, err := websocket.Dial(ctx, url, nil)
if err != nil {
log.Fatalf("dial: %v", err)
}
defer conn.CloseNow()
ema, _ := wickra.NewEma(20)
defer ema.Close()
for {
_, data, err := conn.Read(ctx)
if err != nil {
fmt.Println("Done (time limit reached).")
return
}
var msg struct {
K struct {
Close string `json:"c"`
} `json:"k"`
}
if err := json.Unmarshal(data, &msg); err != nil || msg.K.Close == "" {
continue
}
closePx, err := strconv.ParseFloat(msg.K.Close, 64)
if err != nil {
continue
}
fmt.Printf("close=%.2f EMA(20)=%.2f\n", closePx, ema.Update(closePx))
}
}
+54
View File
@@ -0,0 +1,54 @@
// Resample a 1-minute series into higher timeframes and run an indicator per timeframe.
package main
import (
"fmt"
"math"
wickra "github.com/wickra-lib/wickra/bindings/go"
"github.com/wickra-lib/wickra/examples/go/internal/market"
)
func main() {
oneMinute := market.SyntheticCandlesStep(1200, 0, 60_000)
fmt.Println("EMA(20) of close across timeframes (resampled from 1-minute bars):")
for _, factor := range []int{1, 5, 15} {
bars := resample(oneMinute, factor)
ema, _ := wickra.NewEma(20)
var last float64
for _, b := range bars {
last = ema.Update(b.Close)
}
ema.Close()
fmt.Printf(" %2dm: %5d bars EMA(20) last = %.4f\n", factor, len(bars), last)
}
}
func resample(source []market.Bar, factor int) []market.Bar {
if factor <= 1 {
return source
}
var out []market.Bar
for i := 0; i < len(source); i += factor {
end := i + factor
if end > len(source) {
end = len(source)
}
high, low, volume := math.Inf(-1), math.Inf(1), 0.0
for j := i; j < end; j++ {
high = math.Max(high, source[j].High)
low = math.Min(low, source[j].Low)
volume += source[j].Volume
}
out = append(out, market.Bar{
Open: source[i].Open,
High: high,
Low: low,
Close: source[end-1].Close,
Volume: volume,
Timestamp: source[i].Timestamp,
})
}
return out
}
+80
View File
@@ -0,0 +1,80 @@
// Run SMA(20) batch over a panel of assets, serial vs goroutine fan-out, and
// report the speedup.
package main
import (
"fmt"
"os"
"runtime"
"strconv"
"sync"
"time"
wickra "github.com/wickra-lib/wickra/bindings/go"
"github.com/wickra-lib/wickra/examples/go/internal/market"
)
func main() {
assets := argInt(1, 500)
bars := argInt(2, 20_000)
panel := make([][]float64, assets)
for a := 0; a < assets; a++ {
panel[a] = market.SyntheticPricesFrom(bars, 50.0+float64(a)*0.1)
}
// Warm up so the comparison is fair.
if warm, err := wickra.NewSma(20); err == nil {
warm.Batch(panel[0])
warm.Close()
}
sink := 0.0
start := time.Now()
for a := 0; a < assets; a++ {
sma, _ := wickra.NewSma(20)
result := sma.Batch(panel[a])
sma.Close()
sink += result[len(result)-1]
}
serial := time.Since(start)
lasts := make([]float64, assets)
start = time.Now()
var wg sync.WaitGroup
work := make(chan int, assets)
for w := 0; w < runtime.GOMAXPROCS(0); w++ {
wg.Add(1)
go func() {
defer wg.Done()
for a := range work {
sma, _ := wickra.NewSma(20)
result := sma.Batch(panel[a])
sma.Close()
lasts[a] = result[len(result)-1]
}
}()
}
for a := 0; a < assets; a++ {
work <- a
}
close(work)
wg.Wait()
parallel := time.Since(start)
serialMs := float64(serial.Microseconds()) / 1000.0
parallelMs := float64(parallel.Microseconds()) / 1000.0
fmt.Printf("%d assets x %d bars, SMA(20) batch:\n", assets, bars)
fmt.Printf(" serial %8.1f ms\n", serialMs)
fmt.Printf(" parallel %8.1f ms (%.1fx speedup)\n", parallelMs, serialMs/max(parallelMs, 1e-9))
_ = sink
}
func argInt(i, def int) int {
if len(os.Args) > i {
if v, err := strconv.Atoi(os.Args[i]); err == nil {
return v
}
}
return def
}
@@ -0,0 +1,66 @@
// Breakout: when Bollinger bandwidth is tight (a "squeeze") and price closes above
// the upper band, go long with an ATR(14) trailing stop.
package main
import (
"log"
"math"
"os"
wickra "github.com/wickra-lib/wickra/bindings/go"
"github.com/wickra-lib/wickra/examples/go/internal/market"
)
func main() {
bars := loadBars()
bollinger, _ := wickra.NewBollingerBands(20, 2.0)
defer bollinger.Close()
atr, _ := wickra.NewAtr(14)
defer atr.Close()
var returns []float64
trades := 0
inPosition := false
entry := 0.0
stop := 0.0
for _, b := range bars {
band, okBand := bollinger.Update(b.Close)
atrValue := atr.Update(b.Open, b.High, b.Low, b.Close, b.Volume, b.Timestamp)
if !okBand || math.IsNaN(atrValue) {
continue
}
bandwidth := math.MaxFloat64
if band.Middle != 0.0 {
bandwidth = (band.Upper - band.Lower) / band.Middle
}
if !inPosition && bandwidth < 0.06 && b.Close > band.Upper {
inPosition = true
entry = b.Close
stop = b.Close - 2.0*atrValue
trades++
} else if inPosition {
stop = math.Max(stop, b.Close-2.0*atrValue) // trail the stop up
if b.Close < stop {
returns = append(returns, (b.Close-entry)/entry)
inPosition = false
}
}
}
market.Print("Bollinger squeeze", market.Summarize(returns, trades, 252.0))
}
func loadBars() []market.Bar {
if len(os.Args) > 1 {
bars, err := market.LoadOhlcvCsv(os.Args[1])
if err != nil {
log.Fatalf("load csv: %v", err)
}
return bars
}
return market.SyntheticCandles(2000)
}
+59
View File
@@ -0,0 +1,59 @@
// Trend follower: enter long on a MACD histogram cross up, but only when ADX(14) > 20
// confirms a trend; exit when the histogram crosses back below zero.
package main
import (
"log"
"math"
"os"
wickra "github.com/wickra-lib/wickra/bindings/go"
"github.com/wickra-lib/wickra/examples/go/internal/market"
)
func main() {
bars := loadBars()
macd, _ := wickra.NewMacdIndicator(12, 26, 9)
defer macd.Close()
adx, _ := wickra.NewAdx(14)
defer adx.Close()
var returns []float64
trades := 0
inPosition := false
entry := 0.0
prevHistogram := math.NaN()
for _, b := range bars {
m, okMacd := macd.Update(b.Close)
a, okAdx := adx.Update(b.Open, b.High, b.Low, b.Close, b.Volume, b.Timestamp)
if !okMacd || !okAdx {
continue
}
trending := a.Adx > 20.0
if !inPosition && trending && !math.IsNaN(prevHistogram) && prevHistogram <= 0.0 && m.Histogram > 0.0 {
inPosition = true
entry = b.Close
trades++
} else if inPosition && m.Histogram < 0.0 {
returns = append(returns, (b.Close-entry)/entry)
inPosition = false
}
prevHistogram = m.Histogram
}
market.Print("MACD + ADX trend", market.Summarize(returns, trades, 252.0))
}
func loadBars() []market.Bar {
if len(os.Args) > 1 {
bars, err := market.LoadOhlcvCsv(os.Args[1])
if err != nil {
log.Fatalf("load csv: %v", err)
}
return bars
}
return market.SyntheticCandles(2000)
}
@@ -0,0 +1,51 @@
// Mean reversion: go long when RSI(14) drops below 30, exit when it recovers above 50.
package main
import (
"log"
"math"
"os"
wickra "github.com/wickra-lib/wickra/bindings/go"
"github.com/wickra-lib/wickra/examples/go/internal/market"
)
func main() {
bars := loadBars()
rsi, _ := wickra.NewRsi(14)
defer rsi.Close()
var returns []float64
trades := 0
inPosition := false
entry := 0.0
for _, b := range bars {
value := rsi.Update(b.Close)
if math.IsNaN(value) {
continue
}
if !inPosition && value < 30.0 {
inPosition = true
entry = b.Close
trades++
} else if inPosition && value > 50.0 {
returns = append(returns, (b.Close-entry)/entry)
inPosition = false
}
}
market.Print("RSI mean-reversion", market.Summarize(returns, trades, 252.0))
}
func loadBars() []market.Bar {
if len(os.Args) > 1 {
bars, err := market.LoadOhlcvCsv(os.Args[1])
if err != nil {
log.Fatalf("load csv: %v", err)
}
return bars
}
return market.SyntheticCandles(2000)
}
+40
View File
@@ -0,0 +1,40 @@
// Feed a synthetic price series through several indicators tick by tick (O(1) each).
package main
import (
"fmt"
wickra "github.com/wickra-lib/wickra/bindings/go"
"github.com/wickra-lib/wickra/examples/go/internal/market"
)
func main() {
prices := market.SyntheticPrices(500)
sma, _ := wickra.NewSma(20)
defer sma.Close()
ema, _ := wickra.NewEma(20)
defer ema.Close()
rsi, _ := wickra.NewRsi(14)
defer rsi.Close()
macd, _ := wickra.NewMacdIndicator(12, 26, 9)
defer macd.Close()
var lastSma, lastEma, lastRsi float64
var lastMacd wickra.MacdOutput
var haveMacd bool
for _, price := range prices {
lastSma = sma.Update(price)
lastEma = ema.Update(price)
lastRsi = rsi.Update(price)
lastMacd, haveMacd = macd.Update(price)
}
fmt.Printf("Streamed %d prices through SMA(20), EMA(20), RSI(14), MACD(12,26,9):\n", len(prices))
fmt.Printf(" SMA = %.4f\n", lastSma)
fmt.Printf(" EMA = %.4f\n", lastEma)
fmt.Printf(" RSI = %.4f\n", lastRsi)
if haveMacd {
fmt.Printf(" MACD = %.4f signal=%.4f hist=%.4f\n", lastMacd.Macd, lastMacd.Signal, lastMacd.Histogram)
}
}
+7 -7
View File
@@ -17,7 +17,7 @@
}, },
"../../bindings/node": { "../../bindings/node": {
"name": "wickra", "name": "wickra",
"version": "0.7.6", "version": "0.7.7",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"devDependencies": { "devDependencies": {
"@napi-rs/cli": "^2.18.0" "@napi-rs/cli": "^2.18.0"
@@ -26,12 +26,12 @@
"node": ">= 18" "node": ">= 18"
}, },
"optionalDependencies": { "optionalDependencies": {
"wickra-darwin-arm64": "0.7.6", "wickra-darwin-arm64": "0.7.7",
"wickra-darwin-x64": "0.7.6", "wickra-darwin-x64": "0.7.7",
"wickra-linux-arm64-gnu": "0.7.6", "wickra-linux-arm64-gnu": "0.7.7",
"wickra-linux-x64-gnu": "0.7.6", "wickra-linux-x64-gnu": "0.7.7",
"wickra-win32-arm64-msvc": "0.7.6", "wickra-win32-arm64-msvc": "0.7.7",
"wickra-win32-x64-msvc": "0.7.6" "wickra-win32-x64-msvc": "0.7.7"
} }
}, },
"node_modules/wickra": { "node_modules/wickra": {