diff --git a/README.md b/README.md index 15441de..dfb2dc0 100644 --- a/README.md +++ b/README.md @@ -73,12 +73,21 @@ ws.on("message", (tick) => { ## Examples Real-world entry points live in `examples/`: -- `examples/rsi-strategy.ts` -- `examples/vwap-session.ts` -- `examples/funding-arbitrage.ts` +- `examples/rsi-strategy.mjs` +- `examples/vwap-session.mjs` +- `examples/funding-arbitrage.mjs` +- `examples/rsi-technicalindicators-check.mjs` (external reference check) If you're using ta-crypto in production or experiments, feel free to add your example here. +Run examples: + +```bash +npm run example:all +``` + +Detailed commands and expected outputs: `examples/README.md` + ## Compatibility `ta-crypto` now ships golden tests (`test/fixtures/golden.json`) to lock behavior across releases. @@ -295,6 +304,10 @@ npm run version:patch npm run release ``` +## Trust and Verification + +For release integrity, compatibility policy, and verification workflow, see `docs/trust.md`. + ## License MIT diff --git a/docs/trust.md b/docs/trust.md new file mode 100644 index 0000000..d4313bf --- /dev/null +++ b/docs/trust.md @@ -0,0 +1,58 @@ +# Trust and Verification + +This page summarizes how to verify `ta-crypto` behavior and release integrity. + +## What is verified before release + +- Build and tests across Node versions (CI matrix). +- Golden parity checks for core indicators. +- Compatibility checks against external references. +- Typed API contracts and input validation behavior. + +Primary workflow: +- `.github/workflows/ci.yml` + +## Compatibility trust model + +Policy source: +- `scripts/compat-policy.json` + +Rules: +- Reference series are aligned to equal length before comparison. +- Indicator-specific burn-in is applied before measuring differences. +- Blocking references: `TA-Lib`, `technicalindicators`. +- Non-blocking telemetry: `pandas-ta` (environment dependent). + +Related scripts: +- `scripts/export-compat-vectors.js` +- `scripts/compare-technicalindicators.js` +- `scripts/compare-python-refs.py` + +## How to verify locally + +```bash +npm ci +npm run build +npm test +npm run test:golden +npm run test:compat:technicalindicators +# python deps: pip install -r scripts/requirements-compat.txt +npm run test:compat:python +``` + +## Release traceability + +- Changelog history: `CHANGELOG.md` +- Git tags and releases: GitHub Releases page +- Current stable line: `v0.3.0` + +Recommended verification checks: +1. Confirm release tag exists and matches expected commit. +2. Confirm CI run on `main` is green for the release commit. +3. Confirm compatibility scripts pass in CI logs. + +## Limitations and transparency + +- Some reference libraries differ in warmup semantics; comparisons use overlapping non-null windows. +- Python reference checks depend on environment packages (`TA-Lib`, `pandas-ta`). +- Crypto orderflow proxies are candle-derived and not equivalent to L2/L3 order book signals. diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..a401554 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,33 @@ +# Examples + +All examples are runnable from the repository root and use local `dist/` outputs. + +## Run commands + +```bash +npm run example:rsi +npm run example:vwap +npm run example:funding +npm run example:rsi:compat +npm run example:all +``` + +## Expected outputs + +- `example:rsi` + - Prints a table with recent rows containing `index`, `close`, `rsi`, and `signal`. + - Warmup rows appear first as `signal: "warmup"`. +- `example:vwap` + - Prints `Batch VWAP session` and `Streaming VWAP session`. + - Both arrays should match index by index. +- `example:funding` + - Prints funding series, cumulative values, and annualized APR values. + - Cumulative is running sum; APR is scaled by `periodsPerYear * 100`. +- `example:rsi:compat` + - Compares `rsi(14)` against `technicalindicators` RSI reference. + - Prints max diff, tolerance, and `PASS`/`FAIL`. + +## Notes + +- These examples are smoke-entry points for contributors and docs users. +- For reproducibility, run `npm ci` once before running examples. diff --git a/examples/funding-arbitrage.mjs b/examples/funding-arbitrage.mjs new file mode 100644 index 0000000..4f7a83b --- /dev/null +++ b/examples/funding-arbitrage.mjs @@ -0,0 +1,12 @@ +import { fundingRateAPR, fundingRateCumulative } from "../dist/index.js"; + +const funding = [0.0001, 0.0002, -0.00005, 0.00015, 0.00008, -0.00002]; +const periodsPerYear = 1095; + +const cumulative = fundingRateCumulative(funding); +const apr = fundingRateAPR(funding, periodsPerYear); + +console.log("Funding series:", funding); +console.log("Cumulative funding:", cumulative); +console.log("Annualized funding APR (%):", apr); +console.log("Expected: cumulative is running sum; APR scales each point by periodsPerYear * 100."); diff --git a/examples/rsi-strategy.mjs b/examples/rsi-strategy.mjs new file mode 100644 index 0000000..cabd890 --- /dev/null +++ b/examples/rsi-strategy.mjs @@ -0,0 +1,17 @@ +import { rsi } from "../dist/index.js"; + +const close = [ + 100, 101, 102, 100, 98, 97, 99, 101, 103, 104, 103, 102, 101, 99, 98, 97, 99, 102, 104, 105 +]; + +const rsi14 = rsi(close, 14); + +const signals = rsi14.map((value, index) => { + if (value === null) return { index, close: close[index], rsi: null, signal: "warmup" }; + if (value < 30) return { index, close: close[index], rsi: value, signal: "buy" }; + if (value > 70) return { index, close: close[index], rsi: value, signal: "sell" }; + return { index, close: close[index], rsi: value, signal: "hold" }; +}); + +console.table(signals.slice(-8)); +console.log("Expected: warmup rows first, then hold/buy/sell decisions based on RSI thresholds."); diff --git a/examples/rsi-technicalindicators-check.mjs b/examples/rsi-technicalindicators-check.mjs new file mode 100644 index 0000000..4b04b53 --- /dev/null +++ b/examples/rsi-technicalindicators-check.mjs @@ -0,0 +1,26 @@ +import { RSI } from "technicalindicators"; +import { rsi } from "../dist/index.js"; + +const close = Array.from({ length: 240 }, (_, i) => 100 + i * 0.04 + Math.sin(i / 9) * 1.2 + Math.cos(i / 17) * 0.7); +const ours = rsi(close, 14); + +const padFront = (values, totalLength) => + Array.from({ length: totalLength - values.length }, () => null).concat(values); + +const ref = padFront(RSI.calculate({ period: 14, values: close }), close.length); + +let maxDiff = 0; +for (let i = 28; i < ours.length; i++) { + if (ours[i] === null || ref[i] === null) continue; + const diff = Math.abs(ours[i] - ref[i]); + if (diff > maxDiff) maxDiff = diff; +} + +const tolerance = 5e-2; +const ok = maxDiff <= tolerance; + +console.log(`RSI(14) maxDiff vs technicalindicators: ${maxDiff}`); +console.log(`Tolerance: ${tolerance}`); +console.log(`Result: ${ok ? "PASS" : "FAIL"}`); + +if (!ok) process.exit(1); diff --git a/examples/vwap-session.mjs b/examples/vwap-session.mjs new file mode 100644 index 0000000..8f510d1 --- /dev/null +++ b/examples/vwap-session.mjs @@ -0,0 +1,24 @@ +import { createVWAPSession, vwapSession } from "../dist/index.js"; + +const high = [101, 103, 104, 102, 99, 101, 103, 105]; +const low = [99, 100, 101, 99, 96, 98, 100, 102]; +const close = [100, 102, 103, 100, 98, 100, 102, 104]; +const volume = [10, 15, 13, 20, 11, 14, 16, 19]; +const session = ["asia", "asia", "asia", "asia", "eu", "eu", "eu", "eu"]; + +const batch = vwapSession(high, low, close, volume, session); + +const stateful = createVWAPSession(); +const streaming = close.map((_, i) => + stateful.next({ + high: high[i], + low: low[i], + close: close[i], + volume: volume[i], + sessionId: session[i] + }) +); + +console.log("Batch VWAP session:", batch); +console.log("Streaming VWAP session:", streaming); +console.log("Expected: batch and streaming arrays must match exactly for each index."); diff --git a/package.json b/package.json index f0258d2..da211fb 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,12 @@ "test:golden": "npm run build && node --test test/golden.test.mjs", "test:compat:technicalindicators": "npm run generate:compat && node ./scripts/compare-technicalindicators.js", "test:compat:python": "npm run generate:compat && python ./scripts/compare-python-refs.py", - "test:compat": "npm run test:compat:technicalindicators && npm run test:compat:python" + "test:compat": "npm run test:compat:technicalindicators && npm run test:compat:python", + "example:rsi": "npm run build && node ./examples/rsi-strategy.mjs", + "example:vwap": "npm run build && node ./examples/vwap-session.mjs", + "example:funding": "npm run build && node ./examples/funding-arbitrage.mjs", + "example:rsi:compat": "npm run build && node ./examples/rsi-technicalindicators-check.mjs", + "example:all": "npm run example:rsi && npm run example:vwap && npm run example:funding && npm run example:rsi:compat" }, "devDependencies": { "rimraf": "^5.0.5",