Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d0061c73b8 | |||
| 23d636fd97 |
@@ -10,3 +10,9 @@ bindings/c/include/wickra.h text eol=lf
|
||||
# files stay stable regardless of the committer's autocrlf setting.
|
||||
*.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
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ assignees: ""
|
||||
## Environment
|
||||
|
||||
- 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:
|
||||
- Rust / Python / Node / .NET version (If relevant):
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ assignees: ""
|
||||
- [ ] Should be exposed in the WASM binding
|
||||
- [ ] Should be exposed in the C ABI
|
||||
- [ ] Should be exposed in the C# / .NET binding
|
||||
- [ ] Should be exposed in the Go binding
|
||||
|
||||
## Additional context
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ assignees: []
|
||||
## Affected code path
|
||||
|
||||
- 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?
|
||||
|
||||
## Versions compared
|
||||
|
||||
@@ -33,4 +33,4 @@ import wickra as ta
|
||||
## Environment (Only if relevant)
|
||||
|
||||
- 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`
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
- [ ] `cargo clippy --workspace --all-targets -- -D warnings` is clean.
|
||||
- [ ] `cargo test --workspace` passes.
|
||||
- [ ] 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).
|
||||
- [ ] The relevant page on the [documentation site](https://docs.wickra.org)
|
||||
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`)
|
||||
- [ ] C ABI (`bindings/c`)
|
||||
- [ ] C# / .NET binding (`bindings/csharp`)
|
||||
- [ ] Go binding (`bindings/go`)
|
||||
- [ ] Examples / docs
|
||||
|
||||
## Linked issues
|
||||
|
||||
@@ -727,6 +727,105 @@ jobs:
|
||||
dotnet run --project "examples/csharp/$d" -c Release --no-build
|
||||
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
|
||||
# (.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
|
||||
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
# actually live (Cloudflare Pages, P8.1); merging this PR is therefore
|
||||
# gated on the domain resolving, otherwise the About link would 404.
|
||||
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
|
||||
# corrects the stale kingchenc URL and self-heals any future drift.
|
||||
# Same Administration-write permission as --description (no extra scope).
|
||||
|
||||
+2
-1
@@ -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
|
||||
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`,
|
||||
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 |
|
||||
|---|---|---|---|
|
||||
|
||||
+11
-1
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [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
|
||||
### Added
|
||||
- **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.
|
||||
- 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.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
|
||||
|
||||
@@ -23,6 +23,7 @@ licensed as above, without any additional terms or conditions.
|
||||
| `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/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. |
|
||||
| `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
|
||||
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.
|
||||
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
|
||||
[documentation site](https://docs.wickra.org) and the
|
||||
`README.md` when behaviour or the public API changes. The docs live in
|
||||
|
||||
Generated
+9
-9
@@ -1944,7 +1944,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra"
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"criterion",
|
||||
@@ -1955,7 +1955,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-bench"
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
"kand",
|
||||
@@ -1967,14 +1967,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-c"
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"wickra-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wickra-core"
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"proptest",
|
||||
@@ -1984,7 +1984,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-data"
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"csv",
|
||||
@@ -2001,7 +2001,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-examples"
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"tokio",
|
||||
@@ -2011,7 +2011,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-node"
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"napi",
|
||||
"napi-build",
|
||||
@@ -2021,7 +2021,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-python"
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"numpy",
|
||||
"pyo3",
|
||||
@@ -2030,7 +2030,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-wasm"
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"js-sys",
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ members = [
|
||||
exclude = ["fuzz"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
authors = ["kingchenc <support@wickra.org>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.86"
|
||||
@@ -26,7 +26,7 @@ keywords = ["finance", "trading", "indicators", "technical-analysis", "ta"]
|
||||
categories = ["finance", "mathematics", "science"]
|
||||
|
||||
[workspace.dependencies]
|
||||
wickra-core = { path = "crates/wickra-core", version = "0.7.6" }
|
||||
wickra-core = { path = "crates/wickra-core", version = "0.7.7" }
|
||||
|
||||
thiserror = "2"
|
||||
rayon = "1.10"
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
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++,
|
||||
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
|
||||
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),
|
||||
[WASM](https://docs.wickra.org/Quickstart-WASM),
|
||||
[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
|
||||
every one of the 514 indicators; start at the
|
||||
[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
|
||||
none of them stream.
|
||||
- **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
|
||||
GIL bottleneck.
|
||||
- **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 |
|
||||
|------------------|-------------|-------------|-----------------------------|-----------:|--------|
|
||||
| **★ Wickra**| **clean** | **yes, O(1)** | **Rust · Python · Node · WASM** | **514** | **yes** |
|
||||
| | | | **C · C#** | | |
|
||||
| | | | **C · C# · Go** | | |
|
||||
| kand | clean | yes | Python · WASM · Rust | ~60 | yes |
|
||||
| ta-rs | clean | yes | Rust only | ~30 | stale |
|
||||
| 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.
|
||||
|
||||
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 —
|
||||
regenerate from the core).
|
||||
inherits it automatically (the C ABI — and the C# and Go bindings generated from
|
||||
it — regenerate from the core).
|
||||
|
||||
## Languages
|
||||
|
||||
@@ -186,6 +187,7 @@ regenerate from the core).
|
||||
| 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# / .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);
|
||||
[`examples/README.md`](examples/README.md) is the full cross-language index.
|
||||
@@ -256,7 +258,8 @@ wickra/
|
||||
│ ├── node/ napi-rs (publishes on npm)
|
||||
│ ├── wasm/ wasm-bindgen (browsers, bundlers, Node)
|
||||
│ ├── 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
|
||||
│ ├── data/ real BTCUSDT OHLCV datasets, one per timeframe
|
||||
│ ├── rust/ Rust workspace member (`wickra-examples`)
|
||||
@@ -264,7 +267,8 @@ wickra/
|
||||
│ ├── node/ streaming, backtest, live trading (load `wickra`)
|
||||
│ ├── wasm/ browser demo for `wickra-wasm`
|
||||
│ ├── 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
|
||||
```
|
||||
|
||||
@@ -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)
|
||||
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
|
||||
@@ -319,6 +327,8 @@ Every layer is covered; run the suites with the commands in
|
||||
values across all indicators.
|
||||
- `bindings/wasm`: `wasm-bindgen-test` cases for constructors, equivalence,
|
||||
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
|
||||
|
||||
|
||||
+1
-1
@@ -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;
|
||||
investigate further allocation and cache improvements.
|
||||
- **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.
|
||||
- **Documentation.** Maintain a deep-dive page per indicator on
|
||||
<https://docs.wickra.org>, plus quickstarts and cookbook material.
|
||||
|
||||
+1
-1
@@ -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:
|
||||
|
||||
- **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.
|
||||
- **README:** <https://github.com/wickra-lib/wickra#readme> — installation and a
|
||||
quick overview.
|
||||
|
||||
+1
-1
@@ -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
|
||||
[`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
|
||||
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
|
||||
small.
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
library plus a generated `wickra.h` — no system dependencies.**
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
prebuilt native library, no system dependencies.**
|
||||
|
||||
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)
|
||||
streaming state machine, so live trading bots and historical backtests share
|
||||
the exact same implementation. This package is the .NET binding; it consumes the
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<!-- NuGet package metadata -->
|
||||
<PackageId>Wickra</PackageId>
|
||||
<Version>0.7.6</Version>
|
||||
<Version>0.7.7</Version>
|
||||
<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>
|
||||
<PackageLicenseExpression>MIT OR Apache-2.0</PackageLicenseExpression>
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# Wickra — Go
|
||||
|
||||
[](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
|
||||
[](https://codecov.io/gh/wickra-lib/wickra)
|
||||
[](https://pkg.go.dev/github.com/wickra-lib/wickra/bindings/go)
|
||||
[](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.
|
||||
@@ -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
@@ -0,0 +1,7 @@
|
||||
# Prebuilt Wickra C ABI libraries are provisioned locally / in CI, not committed.
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
*.a
|
||||
*.lib
|
||||
*.exp
|
||||
@@ -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")
|
||||
@@ -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
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
prebuilt native binary, no system dependencies.**
|
||||
|
||||
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)
|
||||
streaming state machine, so live trading bots and historical backtests share
|
||||
the exact same implementation. This package is the Node.js binding (napi-rs);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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.",
|
||||
"main": "wickra.darwin-arm64.node",
|
||||
"files": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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.",
|
||||
"main": "wickra.darwin-x64.node",
|
||||
"files": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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.",
|
||||
"main": "wickra.linux-arm64-gnu.node",
|
||||
"files": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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.",
|
||||
"main": "wickra.linux-x64-gnu.node",
|
||||
"files": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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.",
|
||||
"main": "wickra.win32-arm64-msvc.node",
|
||||
"files": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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.",
|
||||
"main": "wickra.win32-x64-msvc.node",
|
||||
"files": [
|
||||
|
||||
Generated
+20
-20
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "wickra",
|
||||
"version": "0.7.6",
|
||||
"version": "0.7.7",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wickra",
|
||||
"version": "0.7.6",
|
||||
"version": "0.7.7",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.18.0"
|
||||
@@ -15,12 +15,12 @@
|
||||
"node": ">= 18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"wickra-darwin-arm64": "0.7.6",
|
||||
"wickra-darwin-x64": "0.7.6",
|
||||
"wickra-linux-arm64-gnu": "0.7.6",
|
||||
"wickra-linux-x64-gnu": "0.7.6",
|
||||
"wickra-win32-arm64-msvc": "0.7.6",
|
||||
"wickra-win32-x64-msvc": "0.7.6"
|
||||
"wickra-darwin-arm64": "0.7.7",
|
||||
"wickra-darwin-x64": "0.7.7",
|
||||
"wickra-linux-arm64-gnu": "0.7.7",
|
||||
"wickra-linux-x64-gnu": "0.7.7",
|
||||
"wickra-win32-arm64-msvc": "0.7.7",
|
||||
"wickra-win32-x64-msvc": "0.7.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/cli": {
|
||||
@@ -41,8 +41,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wickra-darwin-arm64": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/wickra-darwin-arm64/-/wickra-darwin-arm64-0.7.6.tgz",
|
||||
"version": "0.7.7",
|
||||
"resolved": "https://registry.npmjs.org/wickra-darwin-arm64/-/wickra-darwin-arm64-0.7.7.tgz",
|
||||
"integrity": "sha512-4eZiBR/yGUdr4nzhEUFy2i69XgNx64iI2ax/LPamsThgylC0KpHOZKK19QzJ2d9KbK4C8nMjME5FLuR+4GNEwQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
@@ -57,8 +57,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wickra-darwin-x64": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/wickra-darwin-x64/-/wickra-darwin-x64-0.7.6.tgz",
|
||||
"version": "0.7.7",
|
||||
"resolved": "https://registry.npmjs.org/wickra-darwin-x64/-/wickra-darwin-x64-0.7.7.tgz",
|
||||
"integrity": "sha512-6hf8zI3QPjTFp4zCpmgUwDvNtu6jHqNUHKD5e55POo0CgA52HkpyxSPtVm8TGTIZDI7kPjlbOdBM8CJ76mmXwA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
@@ -73,8 +73,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wickra-linux-arm64-gnu": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/wickra-linux-arm64-gnu/-/wickra-linux-arm64-gnu-0.7.6.tgz",
|
||||
"version": "0.7.7",
|
||||
"resolved": "https://registry.npmjs.org/wickra-linux-arm64-gnu/-/wickra-linux-arm64-gnu-0.7.7.tgz",
|
||||
"integrity": "sha512-kSe6y0xBMSiqdPLXNjwop5WZdHtvdBNKSEBCwZ4hFq33p4apW25/wrlzv9/oDuyD4kuPabJEhCCnFOplh58CUg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
@@ -89,8 +89,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wickra-linux-x64-gnu": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/wickra-linux-x64-gnu/-/wickra-linux-x64-gnu-0.7.6.tgz",
|
||||
"version": "0.7.7",
|
||||
"resolved": "https://registry.npmjs.org/wickra-linux-x64-gnu/-/wickra-linux-x64-gnu-0.7.7.tgz",
|
||||
"integrity": "sha512-tWBWS4qz7hxM4xnpFb59bhf6TaLwXq0Z3jEa/2l7r8PiHA94g8r8S53NRMiT+4yiL5hSWe/nUiC/YXdRrhEZ4g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
@@ -105,8 +105,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wickra-win32-arm64-msvc": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/wickra-win32-arm64-msvc/-/wickra-win32-arm64-msvc-0.7.6.tgz",
|
||||
"version": "0.7.7",
|
||||
"resolved": "https://registry.npmjs.org/wickra-win32-arm64-msvc/-/wickra-win32-arm64-msvc-0.7.7.tgz",
|
||||
"integrity": "sha512-EXIckHxAtF75PUGDKRzXyqMe9ldP0JjSdu68WFN6iJfp+McYrGu6h40TEJlQ/oUEIoPqiZB/xhVyo/el5Lg7zw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
@@ -121,8 +121,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wickra-win32-x64-msvc": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/wickra-win32-x64-msvc/-/wickra-win32-x64-msvc-0.7.6.tgz",
|
||||
"version": "0.7.7",
|
||||
"resolved": "https://registry.npmjs.org/wickra-win32-x64-msvc/-/wickra-win32-x64-msvc-0.7.7.tgz",
|
||||
"integrity": "sha512-Yfsqq1Xwp6hdxMyLze411vNdo7BDwI6+lPSe7A9XdqyPecNDbtKwYLpsal2r8EHbNzqM+R8XnuRtUaEQS5VlUQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wickra",
|
||||
"version": "0.7.6",
|
||||
"version": "0.7.7",
|
||||
"description": "Streaming-first technical indicators: incremental, fast, install-free. Node bindings powered by Rust.",
|
||||
"author": "kingchenc <support@wickra.org>",
|
||||
"main": "index.js",
|
||||
@@ -47,12 +47,12 @@
|
||||
"node": ">= 18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"wickra-linux-x64-gnu": "0.7.6",
|
||||
"wickra-linux-arm64-gnu": "0.7.6",
|
||||
"wickra-darwin-x64": "0.7.6",
|
||||
"wickra-darwin-arm64": "0.7.6",
|
||||
"wickra-win32-x64-msvc": "0.7.6",
|
||||
"wickra-win32-arm64-msvc": "0.7.6"
|
||||
"wickra-linux-x64-gnu": "0.7.7",
|
||||
"wickra-linux-arm64-gnu": "0.7.7",
|
||||
"wickra-darwin-x64": "0.7.7",
|
||||
"wickra-darwin-arm64": "0.7.7",
|
||||
"wickra-win32-x64-msvc": "0.7.7",
|
||||
"wickra-win32-arm64-msvc": "0.7.7"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "napi build --platform --release",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
system dependencies, no C build tooling.**
|
||||
|
||||
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)
|
||||
streaming state machine, so live trading bots and historical backtests share
|
||||
the exact same implementation. This package is the Python binding (PyO3); it
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "wickra"
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
description = "Streaming-first technical indicators: incremental, fast, install-free."
|
||||
readme = "README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
wickra-wasm` — pure WebAssembly, runs anywhere a modern JS engine does.**
|
||||
|
||||
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)
|
||||
streaming state machine, so live trading dashboards and historical backtests
|
||||
share the exact same implementation. This package is the WebAssembly binding
|
||||
|
||||
+3
-2
@@ -8,8 +8,9 @@ That includes:
|
||||
[Python](https://docs.wickra.org/Quickstart-Python),
|
||||
[Node](https://docs.wickra.org/Quickstart-Node),
|
||||
[WASM](https://docs.wickra.org/Quickstart-WASM),
|
||||
[C](https://docs.wickra.org/Quickstart-C), and
|
||||
[C#](https://docs.wickra.org/Quickstart-CSharp).
|
||||
[C](https://docs.wickra.org/Quickstart-C),
|
||||
[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
|
||||
the sixteen families (Moving Averages, Momentum Oscillators, Trend &
|
||||
Directional, Price Oscillators, Volatility & Bands, Bands & Channels,
|
||||
|
||||
@@ -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
|
||||
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/`
|
||||
|
||||
| Example | What it does | Run |
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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=
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Generated
+7
-7
@@ -17,7 +17,7 @@
|
||||
},
|
||||
"../../bindings/node": {
|
||||
"name": "wickra",
|
||||
"version": "0.7.6",
|
||||
"version": "0.7.7",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.18.0"
|
||||
@@ -26,12 +26,12 @@
|
||||
"node": ">= 18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"wickra-darwin-arm64": "0.7.6",
|
||||
"wickra-darwin-x64": "0.7.6",
|
||||
"wickra-linux-arm64-gnu": "0.7.6",
|
||||
"wickra-linux-x64-gnu": "0.7.6",
|
||||
"wickra-win32-arm64-msvc": "0.7.6",
|
||||
"wickra-win32-x64-msvc": "0.7.6"
|
||||
"wickra-darwin-arm64": "0.7.7",
|
||||
"wickra-darwin-x64": "0.7.7",
|
||||
"wickra-linux-arm64-gnu": "0.7.7",
|
||||
"wickra-linux-x64-gnu": "0.7.7",
|
||||
"wickra-win32-arm64-msvc": "0.7.7",
|
||||
"wickra-win32-x64-msvc": "0.7.7"
|
||||
}
|
||||
},
|
||||
"node_modules/wickra": {
|
||||
|
||||
Reference in New Issue
Block a user