From 7a5812fbe1adf01242d1a4a05d58d69e0e09fbc9 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Thu, 19 Jul 2018 16:43:51 +0100 Subject: [PATCH 1/2] feat(transducers-stats): add @thi.ng/transducers-stats package --- packages/transducers-stats/.npmignore | 10 + packages/transducers-stats/LICENSE | 201 ++++++++++++++++++ packages/transducers-stats/README.md | 60 ++++++ packages/transducers-stats/package.json | 41 ++++ packages/transducers-stats/src/donchian.ts | 31 +++ packages/transducers-stats/src/dot.ts | 14 ++ packages/transducers-stats/src/ema.ts | 41 ++++ packages/transducers-stats/src/index.ts | 10 + packages/transducers-stats/src/momentum.ts | 33 +++ packages/transducers-stats/src/mse.ts | 13 ++ packages/transducers-stats/src/roc.ts | 34 +++ packages/transducers-stats/src/sma.ts | 34 +++ packages/transducers-stats/src/trix.ts | 22 ++ packages/transducers-stats/src/wma.ts | 33 +++ packages/transducers-stats/test/index.ts | 6 + packages/transducers-stats/test/tsconfig.json | 10 + packages/transducers-stats/tsconfig.json | 9 + 17 files changed, 602 insertions(+) create mode 100644 packages/transducers-stats/.npmignore create mode 100644 packages/transducers-stats/LICENSE create mode 100644 packages/transducers-stats/README.md create mode 100644 packages/transducers-stats/package.json create mode 100644 packages/transducers-stats/src/donchian.ts create mode 100644 packages/transducers-stats/src/dot.ts create mode 100644 packages/transducers-stats/src/ema.ts create mode 100644 packages/transducers-stats/src/index.ts create mode 100644 packages/transducers-stats/src/momentum.ts create mode 100644 packages/transducers-stats/src/mse.ts create mode 100644 packages/transducers-stats/src/roc.ts create mode 100644 packages/transducers-stats/src/sma.ts create mode 100644 packages/transducers-stats/src/trix.ts create mode 100644 packages/transducers-stats/src/wma.ts create mode 100644 packages/transducers-stats/test/index.ts create mode 100644 packages/transducers-stats/test/tsconfig.json create mode 100644 packages/transducers-stats/tsconfig.json diff --git a/packages/transducers-stats/.npmignore b/packages/transducers-stats/.npmignore new file mode 100644 index 0000000000..d703bda97a --- /dev/null +++ b/packages/transducers-stats/.npmignore @@ -0,0 +1,10 @@ +build +coverage +dev +doc +src* +test +.nyc_output +tsconfig.json +*.tgz +*.html diff --git a/packages/transducers-stats/LICENSE b/packages/transducers-stats/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/transducers-stats/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/transducers-stats/README.md b/packages/transducers-stats/README.md new file mode 100644 index 0000000000..c7338ef9c3 --- /dev/null +++ b/packages/transducers-stats/README.md @@ -0,0 +1,60 @@ +# @thi.ng/transducers-stats + +[![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/transducers-stats.svg)](https://www.npmjs.com/package/@thi.ng/transducers-stats) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + + + +- [About](#about) +- [Supported indicators](#supported-indicators) +- [Installation](#installation) +- [Usage examples](#usage-examples) +- [Authors](#authors) +- [License](#license) + + + +## About + +This package provides a set of +[transducers](https://github.com/thi-ng/umbrella/tree/master/packages/transducers) +for [technical +(financial)](https://en.wikipedia.org/wiki/Technical_indicator) and +statistical analysis and replaces the older +[@thi.ng/indicators](https://github.com/thi-ng/indicators). + +## Supported indicators + +- [Bollinger Bands](https://github.com/thi-ng/indicators/blob/master/src/bollinger.ts) +- [Donchian Channel](https://github.com/thi-ng/indicators/blob/master/src/donchian.ts) +- [EMA (Exponential Moving Average)](https://github.com/thi-ng/indicators/blob/master/src/ema.ts) +- [HMA (Hull Moving Average)](https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/hull-moving-average) +- [Momentum](https://github.com/thi-ng/indicators/blob/master/src/momentum.ts) +- [ROC (Rate of change)](https://github.com/thi-ng/indicators/blob/master/src/roc.ts) +- [RSI (Relative Strength Index)](https://github.com/thi-ng/indicators/blob/master/src/rsi.ts) +- [SD (Standard Deviation)](https://github.com/thi-ng/indicators/blob/master/src/sd.ts) +- [SMA (Simple Moving Average)](https://github.com/thi-ng/indicators/blob/master/src/sma.ts) +- [TRIX (Triple smoothed EMA)](https://github.com/thi-ng/indicators/blob/master/src/trix.ts) +- [WMA (Weighted Moving Average)](https://github.com/thi-ng/indicators/blob/master/src/wma.ts) + +## Installation + +```bash +yarn add @thi.ng/transducers-stats +``` + +## Usage examples + +```ts +import * as stats from "@thi.ng/transducers-stats"; +``` + +## Authors + +- Karsten Schmidt + +## License + +© 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/transducers-stats/package.json b/packages/transducers-stats/package.json new file mode 100644 index 0000000000..aef75243f4 --- /dev/null +++ b/packages/transducers-stats/package.json @@ -0,0 +1,41 @@ +{ + "name": "@thi.ng/transducers-stats", + "version": "0.0.1", + "description": "Transducers for statistical / technical analysis", + "main": "./index.js", + "typings": "./index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/transducers-stats", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn run clean && tsc --declaration", + "clean": "rm -rf *.js *.d.ts .nyc_output build coverage doc", + "cover": "yarn test && nyc report --reporter=lcov", + "doc": "node_modules/.bin/typedoc --mode modules --out doc src", + "pub": "yarn run build && yarn publish --access public", + "test": "rm -rf build && tsc -p test && nyc mocha build/test/*.js" + }, + "devDependencies": { + "@types/mocha": "^5.2.0", + "@types/node": "^10.0.6", + "mocha": "^5.1.1", + "nyc": "^11.7.1", + "typedoc": "^0.11.1", + "typescript": "^2.8.3" + }, + "dependencies": { + "@thi.ng/dcons": "^1.0.5", + "@thi.ng/transducers": "^1.14.0" + }, + "keywords": [ + "ES6", + "typescript" + ], + "publishConfig": { + "access": "public" + } +} \ No newline at end of file diff --git a/packages/transducers-stats/src/donchian.ts b/packages/transducers-stats/src/donchian.ts new file mode 100644 index 0000000000..907ab0cb32 --- /dev/null +++ b/packages/transducers-stats/src/donchian.ts @@ -0,0 +1,31 @@ +import { Transducer } from "@thi.ng/transducers/api"; +import { comp } from "@thi.ng/transducers/func/comp"; +import { partition } from "@thi.ng/transducers/xform/partition"; +import { map } from "@thi.ng/transducers/xform/map"; + +/** + * Computes min/max values for sliding window. + * + * https://en.wikipedia.org/wiki/Donchian_channel + * + * Note: the number of results will be `period-1` less than the + * number of processed inputs and no outputs will be produced if there + * were less than `period` input values. + * + * @param period + */ +export function donchian(period: number): Transducer { + return comp( + partition(period, 1), + map((window) => { + let min = window[0]; + let max = min; + for (let i = 1; i < period; i++) { + const v = window[i]; + min = Math.min(min, v); + max = Math.max(max, v); + } + return [min, max]; + }) + ); +}; diff --git a/packages/transducers-stats/src/dot.ts b/packages/transducers-stats/src/dot.ts new file mode 100644 index 0000000000..6d095ef241 --- /dev/null +++ b/packages/transducers-stats/src/dot.ts @@ -0,0 +1,14 @@ +/** + * Computes dot product of 2 arrays. Assumes `a` and `b` are equal + * sized, but only considers length of `a`. + * + * @param a + * @param b + */ +export const dot = (a: number[], b: number[]) => { + let sum = 0; + for (let i = a.length - 1; i >= 0; i--) { + sum += a[i] * b[i]; + } + return sum; +}; \ No newline at end of file diff --git a/packages/transducers-stats/src/ema.ts b/packages/transducers-stats/src/ema.ts new file mode 100644 index 0000000000..a7c76661a5 --- /dev/null +++ b/packages/transducers-stats/src/ema.ts @@ -0,0 +1,41 @@ +import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; +import { Reducer, Transducer } from "@thi.ng/transducers/api"; +import { compR } from "@thi.ng/transducers/func/compr"; + +/** + * https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average + * + * Note: the number of results will be `period-1` less than the number + * of processed inputs and no outputs will be produced if there were + * less than `period` input values. + * + * @param period + */ +export function ema(period: number): Transducer { + period |= 0; + period < 2 && illegalArgs("period must be >= 2"); + const k = 2 / (period + 1); + return (rfn: Reducer) => { + const reduce = rfn[2]; + let window = []; + let sum = 0; + let ema: number; + return compR( + rfn, + (acc, x) => { + if (ema != null) { + ema += (x - ema) * k; + return rfn[2](acc, ema); + } else { + window.push(x); + sum += x; + if (window.length == period) { + ema = sum / period; + window = null; + return reduce(acc, ema); + } + return acc; + } + }); + }; +}; diff --git a/packages/transducers-stats/src/index.ts b/packages/transducers-stats/src/index.ts new file mode 100644 index 0000000000..2e7ba808db --- /dev/null +++ b/packages/transducers-stats/src/index.ts @@ -0,0 +1,10 @@ +export * from "./donchian"; +export * from "./ema"; +export * from "./momentum"; +export * from "./roc"; +export * from "./sma"; +export * from "./trix"; +export * from "./wma"; + +export * from "./dot"; +export * from "./mse"; diff --git a/packages/transducers-stats/src/momentum.ts b/packages/transducers-stats/src/momentum.ts new file mode 100644 index 0000000000..5668ecf937 --- /dev/null +++ b/packages/transducers-stats/src/momentum.ts @@ -0,0 +1,33 @@ +import { DCons } from "@thi.ng/dcons"; +import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; +import { Reducer, Transducer } from "@thi.ng/transducers/api"; +import { compR } from "@thi.ng/transducers/func/compr"; + +/** + * https://en.wikipedia.org/wiki/Momentum_(technical_analysis) + * + * Note: the number of results will be `period-1` less than the number + * of processed inputs and no outputs will be produced if there were + * less than `period` input values. + * + * @param period + */ +export function momentum(period: number): Transducer { + period |= 0; + period < 1 && illegalArgs("period must be >= 1"); + return (rfn: Reducer) => { + const reduce = rfn[2]; + const window = new DCons(); + return compR( + rfn, + (acc, x) => { + window.push(x); + if (window.length <= period) { + return acc; + } + const prev = window.drop(); + return reduce(acc, x - prev); + } + ); + } +}; diff --git a/packages/transducers-stats/src/mse.ts b/packages/transducers-stats/src/mse.ts new file mode 100644 index 0000000000..4a204a242a --- /dev/null +++ b/packages/transducers-stats/src/mse.ts @@ -0,0 +1,13 @@ +/** + * Computes mean square error of given array. + * + * @param window + * @param mean + */ +export const mse = (window: number[], mean: number) => { + let sum = 0; + for (let i = window.length - 1; i >= 0; i--) { + sum += Math.pow(window[i] - mean, 2); + } + return sum; +}; diff --git a/packages/transducers-stats/src/roc.ts b/packages/transducers-stats/src/roc.ts new file mode 100644 index 0000000000..a674f5d6a4 --- /dev/null +++ b/packages/transducers-stats/src/roc.ts @@ -0,0 +1,34 @@ +import { DCons } from "@thi.ng/dcons"; +import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; +import { Reducer, Transducer } from "@thi.ng/transducers/api"; +import { compR } from "@thi.ng/transducers/func/compr"; + +/** + * Rate of change. + * + * https://en.wikipedia.org/wiki/Momentum_(technical_analysis) + * + * Note: the number of results will be `period-1` less than the number + * of processed inputs and no outputs will be produced if there were + * less than `period` input values. + * + * @param period + */ +export function roc(period: number): Transducer { + period < 1 && illegalArgs("period must be >= 1"); + return (rfn: Reducer) => { + const reduce = rfn[2]; + const window = new DCons(); + return compR( + rfn, + (acc, x) => { + window.push(x); + if (window.length <= period) { + return acc; + } + const prev = window.drop(); + return reduce(acc, (x - prev) / prev); + } + ); + } +}; diff --git a/packages/transducers-stats/src/sma.ts b/packages/transducers-stats/src/sma.ts new file mode 100644 index 0000000000..757f9fe0a8 --- /dev/null +++ b/packages/transducers-stats/src/sma.ts @@ -0,0 +1,34 @@ +import { DCons } from "@thi.ng/dcons"; +import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; +import { Reducer, Transducer } from "@thi.ng/transducers/api"; +import { compR } from "@thi.ng/transducers/func/compr"; + +/** + * Like @thi.ng/transducers `movingAverage`, but using more efficient + * linked list as sliding window buffer. + * + * Note: the number of results will be `period-1` less than the number + * of processed inputs and no outputs will be produced if there were + * less than `period` input values. + * + * @param period + */ +export function sma(period: number): Transducer { + period |= 0; + period < 2 && illegalArgs("period must be >= 2"); + return (rfn: Reducer) => { + const reduce = rfn[2]; + const window = new DCons(); + let sum = 0; + return compR( + rfn, + (acc, x) => { + window.push(x); + const n = window.length; + sum += x; + n > period && (sum -= window.drop()); + return n >= period ? reduce(acc, sum / period) : acc; + } + ); + } +}; diff --git a/packages/transducers-stats/src/trix.ts b/packages/transducers-stats/src/trix.ts new file mode 100644 index 0000000000..6b157c0865 --- /dev/null +++ b/packages/transducers-stats/src/trix.ts @@ -0,0 +1,22 @@ +import { Transducer } from "@thi.ng/transducers/api"; +import { comp } from "@thi.ng/transducers/func/comp"; +import { ema } from "./ema"; +import { roc } from "./roc"; + +/** + * https://en.wikipedia.org/wiki/Trix_(technical_analysis) + * + * Note: the number of results will be `3 * (period - 1) + 1` less than the + * number of processed inputs and no outputs will be produced if there + * were less than `period` input values. + * + * @param period + */ +export function trix(period: number): Transducer { + return comp( + ema(period), + ema(period), + ema(period), + roc(1), + ); +}; diff --git a/packages/transducers-stats/src/wma.ts b/packages/transducers-stats/src/wma.ts new file mode 100644 index 0000000000..2f36e45213 --- /dev/null +++ b/packages/transducers-stats/src/wma.ts @@ -0,0 +1,33 @@ +import { isNumber } from "@thi.ng/checks/is-number"; +import { Transducer } from "@thi.ng/transducers/api"; +import { comp } from "@thi.ng/transducers/func/comp"; +import { range } from "@thi.ng/transducers/iter/range"; +import { map } from "@thi.ng/transducers/xform/map"; +import { partition } from "@thi.ng/transducers/xform/partition"; + +import { dot } from "./dot"; + +/** + * https://en.wikipedia.org/wiki/Moving_average#Weighted_moving_average + * + * Note: the number of results will be `period-1` less than the number + * of processed inputs and no outputs will be produced if there were + * less than `period` input values. + * + * @param weights period or array of weights + */ +export function wma(weights: number | number[]): Transducer { + let period, wsum; + if (isNumber(weights)) { + period = weights | 0; + weights = [...range(1, period + 1)]; + wsum = (period * (period + 1)) / 2; + } else { + period = weights.length; + wsum = weights.reduce((acc, x) => acc + x, 0); + } + return comp( + partition(period, 1), + map((window) => dot(window, weights) / wsum) + ); +}; diff --git a/packages/transducers-stats/test/index.ts b/packages/transducers-stats/test/index.ts new file mode 100644 index 0000000000..d3ab007784 --- /dev/null +++ b/packages/transducers-stats/test/index.ts @@ -0,0 +1,6 @@ +// import * as assert from "assert"; +// import * as transducers-stats from "../src/index"; + +describe("transducers-stats", () => { + it("tests pending"); +}); diff --git a/packages/transducers-stats/test/tsconfig.json b/packages/transducers-stats/test/tsconfig.json new file mode 100644 index 0000000000..bcf29ace54 --- /dev/null +++ b/packages/transducers-stats/test/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/transducers-stats/tsconfig.json b/packages/transducers-stats/tsconfig.json new file mode 100644 index 0000000000..bd6481a5a6 --- /dev/null +++ b/packages/transducers-stats/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "." + }, + "include": [ + "./src/**/*.ts" + ] +} From 7df3ce04d5d851b1bbe31c8fb240586c175f8ad7 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Thu, 19 Jul 2018 21:12:55 +0100 Subject: [PATCH 2/2] feat(transducers-stats): add other xforms - bollinger() - donchian() - hma() - rsi() - sd() --- packages/transducers-stats/src/bollinger.ts | 34 +++++++++++++++++++++ packages/transducers-stats/src/donchian.ts | 2 +- packages/transducers-stats/src/hma.ts | 27 ++++++++++++++++ packages/transducers-stats/src/index.ts | 4 +++ packages/transducers-stats/src/rsi.ts | 28 +++++++++++++++++ packages/transducers-stats/src/sd.ts | 29 ++++++++++++++++++ 6 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 packages/transducers-stats/src/bollinger.ts create mode 100644 packages/transducers-stats/src/hma.ts create mode 100644 packages/transducers-stats/src/rsi.ts create mode 100644 packages/transducers-stats/src/sd.ts diff --git a/packages/transducers-stats/src/bollinger.ts b/packages/transducers-stats/src/bollinger.ts new file mode 100644 index 0000000000..b5ef187b19 --- /dev/null +++ b/packages/transducers-stats/src/bollinger.ts @@ -0,0 +1,34 @@ +import { Transducer } from "@thi.ng/transducers/api"; +import { comp } from "@thi.ng/transducers/func/comp"; +import { drop } from "@thi.ng/transducers/xform/drop"; +import { map } from "@thi.ng/transducers/xform/map"; +import { multiplex } from "@thi.ng/transducers/xform/multiplex"; +import { partition } from "@thi.ng/transducers/xform/partition"; + +import { mse } from "./mse"; +import { sma } from "./sma"; + +/** + * Computes Bollinger bands using sliding window. + * + * https://en.wikipedia.org/wiki/Bollinger_Bands + * + * Note: the number of results will be `period-1` less than the + * number of processed inputs and no outputs will be produced if there + * were less than `period` input values. + * + * @param period + */ +export function bollinger(period = 20, sd = 2): Transducer { + return comp( + multiplex(partition(period, 1), sma(period)), + drop(period - 1), + map(([window, mean]) => { + const std = Math.sqrt(mse(window, mean) / period) * sd; + const min = mean - std; + const max = mean + std; + const pb = (window[period - 1] - min) / (max - min); + return { min, max, mean, pb }; + }) + ); +}; diff --git a/packages/transducers-stats/src/donchian.ts b/packages/transducers-stats/src/donchian.ts index 907ab0cb32..8004429f7d 100644 --- a/packages/transducers-stats/src/donchian.ts +++ b/packages/transducers-stats/src/donchian.ts @@ -4,7 +4,7 @@ import { partition } from "@thi.ng/transducers/xform/partition"; import { map } from "@thi.ng/transducers/xform/map"; /** - * Computes min/max values for sliding window. + * Computes Donchian channel, i.e. min/max values for sliding window. * * https://en.wikipedia.org/wiki/Donchian_channel * diff --git a/packages/transducers-stats/src/hma.ts b/packages/transducers-stats/src/hma.ts new file mode 100644 index 0000000000..4d468d0293 --- /dev/null +++ b/packages/transducers-stats/src/hma.ts @@ -0,0 +1,27 @@ +import { Transducer } from "@thi.ng/transducers/api"; +import { comp } from "@thi.ng/transducers/func/comp"; +import { drop } from "@thi.ng/transducers/xform/drop"; +import { map } from "@thi.ng/transducers/xform/map"; +import { multiplex } from "@thi.ng/transducers/xform/multiplex"; + +import { wma } from "./wma"; + +/** + * https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/hull-moving-average + * + * Note: the number of results will be: + * + * `period + floor(sqrt(period)) - 2` + * + * less than the number of processed inputs. + * + * @param weights period or array of weights + */ +export function hma(period: number): Transducer { + return comp( + multiplex(wma(period / 2 | 0), wma(period)), + drop(period - 1), + map((w) => 2 * w[0] - w[1]), + wma(Math.sqrt(period)) + ); +}; diff --git a/packages/transducers-stats/src/index.ts b/packages/transducers-stats/src/index.ts index 2e7ba808db..af012662cb 100644 --- a/packages/transducers-stats/src/index.ts +++ b/packages/transducers-stats/src/index.ts @@ -1,7 +1,11 @@ +export * from "./bollinger"; export * from "./donchian"; export * from "./ema"; +export * from "./hma"; export * from "./momentum"; export * from "./roc"; +export * from "./rsi"; +export * from "./sd"; export * from "./sma"; export * from "./trix"; export * from "./wma"; diff --git a/packages/transducers-stats/src/rsi.ts b/packages/transducers-stats/src/rsi.ts new file mode 100644 index 0000000000..7956f3e852 --- /dev/null +++ b/packages/transducers-stats/src/rsi.ts @@ -0,0 +1,28 @@ +import { Transducer } from "@thi.ng/transducers/api"; +import { comp } from "@thi.ng/transducers/func/comp"; +import { drop } from "@thi.ng/transducers/xform/drop"; +import { map } from "@thi.ng/transducers/xform/map"; +import { multiplex } from "@thi.ng/transducers/xform/multiplex"; +import { momentum } from "./momentum"; +import { sma } from "./sma"; + +/** + * https://en.wikipedia.org/wiki/Relative_strength_index + * + * Note: the number of results will be `period` less than the + * number of processed inputs and no outputs will be produced if there + * were less than `period` input values. + * + * @param period + */ +export function rsi(period: number): Transducer { + return comp( + momentum(1), + multiplex( + comp(map((x) => x > 0 ? x : 0), sma(period)), + comp(map((x) => x < 0 ? -x : 0), sma(period)), + ), + drop(period - 1), + map((hl) => 100 - 100 / (1 + hl[0] / Math.max(1e-6, hl[1]))) + ); +}; diff --git a/packages/transducers-stats/src/sd.ts b/packages/transducers-stats/src/sd.ts new file mode 100644 index 0000000000..1b7ad8c934 --- /dev/null +++ b/packages/transducers-stats/src/sd.ts @@ -0,0 +1,29 @@ +import { Transducer } from "@thi.ng/transducers/api"; +import { comp } from "@thi.ng/transducers/func/comp"; +import { drop } from "@thi.ng/transducers/xform/drop"; +import { map } from "@thi.ng/transducers/xform/map"; +import { multiplex } from "@thi.ng/transducers/xform/multiplex"; +import { partition } from "@thi.ng/transducers/xform/partition"; + +import { mse } from "./mse"; +import { sma } from "./sma"; + +/** + * Moving standard deviation, calculates mean square error to SMA and + * yields sequence of `sqrt(error / period)`. + * + * https://en.wikipedia.org/wiki/Bollinger_Bands + * + * Note: the number of results will be `period-1` less than the number + * of processed inputs and no outputs will be produced if there were + * less than `period` input values. + * + * @param period + */ +export function sd(period = 20, scale = 1): Transducer { + return comp( + multiplex(partition(period, 1), sma(period)), + drop(period - 1), + map(([window, mean]) => Math.sqrt(mse(window, mean) / period) * scale) + ); +};