├── .eslintignore ├── .npmignore ├── .babelrc ├── .travis.yml ├── src ├── common.js ├── sma.js ├── ema.js ├── sd.js ├── boll.js ├── macd.js ├── index.js ├── ma.js ├── hhv-llv.js ├── wma.js └── dma.js ├── .eslintrc.js ├── test ├── sd.test.js ├── lib │ └── runner.js ├── boll.test.js ├── hhv-llv.test.js ├── macd.test.js └── ma.test.js ├── .gitignore ├── LICENSE ├── package.json └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | /* 2 | !/test 3 | !/src 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .travis.yml 2 | .babelrc 3 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env"] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "12" 5 | after_success: 6 | - npm run report-cov 7 | -------------------------------------------------------------------------------- /src/common.js: -------------------------------------------------------------------------------- 1 | export const isNumber = subject => typeof subject === 'number' 2 | 3 | export const {isArray} = Array 4 | -------------------------------------------------------------------------------- /src/sma.js: -------------------------------------------------------------------------------- 1 | // Smoothed moving average 2 | 3 | import dma from './dma' 4 | 5 | export default (data, size, times = 1) => dma(data, times / size, 1) 6 | -------------------------------------------------------------------------------- /src/ema.js: -------------------------------------------------------------------------------- 1 | // Exponential moving average with 86% total weight 2 | 3 | import dma from './dma' 4 | 5 | export default (data, size) => dma(data, 2 / (size + 1)) 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | sourceType: 'module' 4 | }, 5 | extends: require.resolve('@ostai/eslint-config'), 6 | rules: { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/sd.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import {sd} from '../src' 3 | 4 | [ 5 | [ 6 | [1, 2, 3, 4, 5], 2, 7 | [, 0.5, 0.5, 0.5, 0.5] 8 | ], 9 | 10 | [ 11 | [1, 2, 4, 8], 2, 12 | [, 0.5, 1, 2] 13 | ] 14 | ].forEach(([datum, size, expected]) => { 15 | test(`datum: ${JSON.stringify(datum)}, size: ${size}`, t => { 16 | const result = sd(datum, size) 17 | t.deepEqual(result, expected) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /src/sd.js: -------------------------------------------------------------------------------- 1 | import ma from './ma' 2 | 3 | export default (data, size) => { 4 | const {length} = data 5 | const avg = ma(data, size) 6 | const ret = [] 7 | 8 | let i = size - 1 9 | let j 10 | let sum 11 | 12 | for (; i < length; i ++) { 13 | sum = 0 14 | j = i - size + 1 15 | 16 | for (; j <= i; j ++) { 17 | sum += (data[j] - avg[i]) ** 2 18 | } 19 | 20 | ret[i] = Math.sqrt(sum / size) 21 | } 22 | 23 | return ret 24 | } 25 | -------------------------------------------------------------------------------- /src/boll.js: -------------------------------------------------------------------------------- 1 | import { 2 | add, 3 | sub, 4 | mul 5 | } from 'math-array' 6 | 7 | import deviation from './sd' 8 | import ma from './ma' 9 | 10 | export default (datum, size = 20, times = 2, { 11 | ma: avg, 12 | sd 13 | } = {}) => { 14 | avg = avg || ma(datum, size) 15 | sd = sd || deviation(datum, size) 16 | 17 | const timesSd = mul(sd, times) 18 | 19 | return { 20 | upper: add(avg, timesSd), 21 | mid: avg, 22 | lower: sub(avg, timesSd) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/macd.js: -------------------------------------------------------------------------------- 1 | import { 2 | sub, 3 | mul 4 | } from 'math-array' 5 | 6 | import ema from './ema' 7 | 8 | export default ( 9 | data, 10 | slowPeriods = 26, 11 | fastPeriods = 12, 12 | signalPeriods = 9 13 | ) => { 14 | const MACD = sub( 15 | ema(data, fastPeriods), 16 | ema(data, slowPeriods), 17 | 1 18 | ) 19 | 20 | const signal = ema(MACD, signalPeriods) 21 | const histogram = mul(2, sub(MACD, signal), 1) 22 | 23 | return { 24 | MACD, 25 | signal, 26 | histogram 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | add, 3 | sub, 4 | mul, 5 | div 6 | } from 'math-array' 7 | 8 | import dma from './dma' 9 | import sma from './sma' 10 | import ema from './ema' 11 | import ma from './ma' 12 | import wma from './wma' 13 | import sd from './sd' 14 | import boll from './boll' 15 | import macd from './macd' 16 | import { 17 | hhv, 18 | llv 19 | } from './hhv-llv' 20 | 21 | export { 22 | dma, 23 | sma, 24 | ema, 25 | ma, 26 | wma, 27 | sd, 28 | boll, 29 | macd, 30 | hhv, 31 | llv, 32 | add, 33 | sub, 34 | mul, 35 | div 36 | } 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # yarn 2 | .lock 3 | /lib 4 | benchmark 5 | 6 | # Numerous always-ignore extensions 7 | *.bak 8 | *.patch 9 | *.diff 10 | *.err 11 | *.orig 12 | *.log 13 | *.rej 14 | *.swo 15 | *.swp 16 | *.zip 17 | *.vi 18 | *~ 19 | *.sass-cache 20 | package-lock.json 21 | .nyc_output/ 22 | *.tgz 23 | 24 | # OS or Editor folders 25 | .DS_Store 26 | ._* 27 | .cache 28 | .project 29 | .settings 30 | .tmproj 31 | *.esproj 32 | *.sublime-project 33 | *.sublime-workspace 34 | nbproject 35 | thumbs.db 36 | 37 | # Folders to ignore 38 | .hg 39 | .svn 40 | .CVS 41 | .idea 42 | node_modules 43 | old/ 44 | *-old/ 45 | *-notrack/ 46 | build/ 47 | combo/ 48 | reference/ 49 | jscoverage_lib/ 50 | temp/ 51 | tmp/ 52 | 53 | # codecov.io 54 | coverage/ 55 | -------------------------------------------------------------------------------- /src/ma.js: -------------------------------------------------------------------------------- 1 | // simple moving average 2 | 3 | import { 4 | isNumber 5 | } from './common' 6 | 7 | 8 | export default (data, size) => { 9 | const {length} = data 10 | 11 | if (size <= 1) { 12 | return data.slice() 13 | } 14 | 15 | if (size > length) { 16 | return Array(length) 17 | } 18 | 19 | const prepare = size - 1 20 | const ret = [] 21 | let sum = 0 22 | let i = 0 23 | let counter = 0 24 | let datum 25 | 26 | for (; i < length && counter < prepare; i ++) { 27 | datum = data[i] 28 | 29 | if (isNumber(datum)) { 30 | sum += datum 31 | counter ++ 32 | } 33 | } 34 | 35 | for (; i < length; i ++) { 36 | datum = data[i] 37 | 38 | if (isNumber(datum)) { 39 | sum += datum 40 | } 41 | 42 | if (isNumber(data[i - size])) { 43 | sum -= data[i - size] 44 | } 45 | 46 | ret[i] = sum / size 47 | } 48 | 49 | return ret 50 | } 51 | -------------------------------------------------------------------------------- /test/lib/runner.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import { 4 | dma, 5 | ma, 6 | sma, 7 | ema, 8 | wma 9 | } from '../../src' 10 | 11 | const methods = { 12 | dma, 13 | ma, 14 | sma, 15 | ema, 16 | wma 17 | } 18 | 19 | 20 | export const only = true 21 | 22 | const to_fixed_10 = n => n.toFixed(10) 23 | 24 | export const get_test = o => o ? test.only : test 25 | 26 | export const type = t => obj => { 27 | obj.type = t 28 | return obj 29 | } 30 | 31 | export function runner (c) { 32 | const {type: tt} = c 33 | const [ 34 | args, 35 | result, 36 | o 37 | ] = c 38 | 39 | const d_args = JSON.stringify(args) 40 | const d = `${tt}(${d_args})` 41 | 42 | get_test(o)(d, t => { 43 | const r = methods[tt](...args) 44 | 45 | if (Array.isArray(result)) { 46 | t.deepEqual(r.map(to_fixed_10), 47 | result.map(to_fixed_10)) 48 | return 49 | } 50 | 51 | t.is(to_fixed_10(r), to_fixed_10(result)) 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /src/hhv-llv.js: -------------------------------------------------------------------------------- 1 | // @param {Number} start index to start [ 2 | // @param {Number} end index less than ) 3 | // -> [start, end) 4 | const reduce = (array, start, end, reducer) => { 5 | let prev 6 | let i = start 7 | 8 | for (; i < end; i ++) { 9 | if (i in array) { 10 | if (prev === undefined) { 11 | prev = array[i] 12 | continue 13 | } 14 | 15 | prev = reducer(prev, array[i]) 16 | } 17 | } 18 | 19 | return prev 20 | } 21 | 22 | const compare = (data, size, comparer) => { 23 | const {length} = data 24 | 25 | if (size > length) { 26 | return Array(length) 27 | } 28 | 29 | if (size <= 1) { 30 | return data.slice() 31 | } 32 | 33 | let i = size - 1 34 | const ret = [] 35 | 36 | for (; i < length; i ++) { 37 | ret[i] = reduce(data, i - size + 1, i + 1, comparer) 38 | } 39 | 40 | return ret 41 | } 42 | 43 | export const hhv = (data, size) => 44 | compare(data, size, (a, b) => Math.max(a, b)) 45 | 46 | export const llv = (data, size) => 47 | compare(data, size, (a, b) => Math.min(a, b)) 48 | -------------------------------------------------------------------------------- /test/boll.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import {sd, ma, boll} from '../src' 4 | 5 | const data = [1, 2, 4, 8] 6 | const size = 2 7 | // const times = 2 8 | 9 | const expected = { 10 | upper: [, 2.5, 5, 10], 11 | mid: [, 1.5, 3, 6], 12 | lower: [, 0.5, 1, 2] 13 | } 14 | 15 | const CASES = [ 16 | { 17 | d: 'normal, default times, no options', 18 | data, 19 | size, 20 | expect: expected 21 | }, 22 | 23 | { 24 | d: 'options.ma', 25 | data, 26 | size: 2, 27 | expect: expected, 28 | options: { 29 | ma: ma(data, size) 30 | } 31 | }, 32 | 33 | { 34 | d: 'options.sd', 35 | data, 36 | size: 2, 37 | expect: expected, 38 | options: { 39 | sd: sd(data, size) 40 | } 41 | } 42 | ] 43 | 44 | CASES.forEach(({ 45 | d, 46 | data: dd, 47 | size: s, 48 | times, 49 | expect, 50 | options 51 | }) => { 52 | test(d, t => { 53 | const result = options 54 | ? boll(dd, s, times, options) 55 | : boll(dd, s, times) 56 | 57 | t.deepEqual(result, expect) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /src/wma.js: -------------------------------------------------------------------------------- 1 | // Weighted moving average 2 | 3 | import { 4 | isNumber 5 | } from './common' 6 | 7 | 8 | export default (data, size) => { 9 | const {length} = data 10 | 11 | if (size <= 1) { 12 | return data.slice() 13 | } 14 | 15 | if (size > length) { 16 | return Array(length) 17 | } 18 | 19 | const ret = [] 20 | const denominator = size * (size + 1) / 2 21 | const prepare = size - 1 22 | let sum = 0 23 | let numerator = 0 24 | let datum = 0 25 | let i = 0 26 | let real = - 1 27 | 28 | 29 | for (; i < prepare; i ++) { 30 | datum = data[i] 31 | 32 | if (isNumber(datum)) { 33 | sum += datum 34 | numerator += (i + 1) * datum 35 | } 36 | } 37 | 38 | for (; i < length; i ++, real ++) { 39 | datum = data[i] 40 | 41 | if (isNumber(datum)) { 42 | sum += datum 43 | numerator += size * datum 44 | } 45 | 46 | if (real >= 0 && isNumber(data[real])) { 47 | sum -= data[real] 48 | } 49 | 50 | ret[i] = numerator / denominator 51 | numerator -= sum 52 | } 53 | 54 | return ret 55 | } 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 kaelzhang , contributors 2 | http://kael.me/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /test/hhv-llv.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { 3 | hhv, 4 | llv 5 | } from '../src' 6 | 7 | const methods = { 8 | hhv, 9 | llv 10 | } 11 | 12 | const a = [1, 2, 4, 1] 13 | 14 | const CASES = [ 15 | [ 16 | 'hhv', 17 | [a, 2], 18 | [, 2, 4, 4] 19 | ], 20 | [ 21 | 'hhv', 22 | [a, 1], 23 | a 24 | ], 25 | [ 26 | 'hhv', 27 | [a, 0.5], 28 | a 29 | ], 30 | // [ 31 | // 'hhv', 32 | // [a], 33 | // 4 34 | // ], 35 | [ 36 | 'hhv', 37 | [a, 5], 38 | Array(4) 39 | ], 40 | [ 41 | 'llv', 42 | [a, 2], 43 | [, 1, 2, 1] 44 | ], 45 | [ 46 | 'llv', 47 | [a, 1], 48 | a 49 | ], 50 | [ 51 | 'llv', 52 | [a, 0.5], 53 | a 54 | ], 55 | // [ 56 | // 'llv', 57 | // [a], 58 | // 1 59 | // ], 60 | [ 61 | 'llv', 62 | [a, 5], 63 | Array(4) 64 | ] 65 | ] 66 | 67 | CASES.forEach(([type, args, expected]) => { 68 | const d = `${type}: ${JSON.stringify(args)}` 69 | 70 | test(d, t => { 71 | const result = methods[type](...args) 72 | 73 | if (Object(expected) === expected) { 74 | t.deepEqual(result, expected) 75 | return 76 | } 77 | 78 | t.is(result, expected) 79 | }) 80 | }) 81 | -------------------------------------------------------------------------------- /test/macd.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import {macd} from '../src' 3 | 4 | const CASES = [ 5 | [ 6 | 'normal', 7 | [ 8 | [1, 2, 7, 13, 5, 6, 7, 8, 9, 10, 11, 12] 9 | ], 10 | 11 | { 12 | MACD: [ 13 | 0, 14 | 0.07977207977207978, 15 | 0.5402228878012356, 16 | 1.373451037820645, 17 | 1.3724366136156738, 18 | 1.4357736132999195, 19 | 1.5488066060633616, 20 | 1.6994870806664757, 21 | 1.877946289194084, 22 | 2.0761357398730453, 23 | 2.2875247882412006, 24 | 2.5068464097462666 25 | ], 26 | signal: [ 27 | 0, 28 | 0.01595441595441596, 29 | 0.1208081103237799, 30 | 0.371336695823153, 31 | 0.5715566793816571, 32 | 0.7444000661653096, 33 | 0.9052813741449202, 34 | 1.0641225154492313, 35 | 1.2268872701982019, 36 | 1.3967369641331706, 37 | 1.5748945289547767, 38 | 1.7612849051130746 39 | ], 40 | histogram: [ 41 | 0, 42 | 0.12763532763532764, 43 | 0.8388295549549114, 44 | 2.004228683994984, 45 | 1.6017598684680334, 46 | 1.3827470942692197, 47 | 1.287050463836883, 48 | 1.2707291304344888, 49 | 1.302118037991764, 50 | 1.3587975514797495, 51 | 1.425260518572848, 52 | 1.491123009266384 53 | ] 54 | } 55 | ] 56 | ] 57 | 58 | CASES.forEach(([d, args, expected]) => { 59 | test(d, t => { 60 | const result = macd(...args) 61 | t.deepEqual(result, expected) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /src/dma.js: -------------------------------------------------------------------------------- 1 | // Dynamic Weighted Moving Average 2 | 3 | import { 4 | isNumber, 5 | isArray 6 | } from './common' 7 | 8 | 9 | // @param {Number|Array.} alpha 10 | export default (data, alpha, noHead) => { 11 | const {length} = data 12 | 13 | if (alpha > 1) { 14 | return Array(length) 15 | } 16 | 17 | if (alpha === 1) { 18 | return data.slice() 19 | } 20 | 21 | const isArrayWeight = isArray(alpha) 22 | const ret = [] 23 | 24 | let datum 25 | 26 | // period `i` 27 | let i = 0 28 | 29 | // `s` is the value of the DWMA at any time period `i` 30 | let s = 0 31 | 32 | // Handles head 33 | for (; i < length; i ++) { 34 | datum = data[i] 35 | 36 | if ( 37 | isNumber(datum) 38 | && ( 39 | !isArrayWeight 40 | || isNumber(datum) 41 | ) 42 | ) { 43 | ret[i] = noHead 44 | ? 0 45 | : datum 46 | 47 | s = datum 48 | i ++ 49 | 50 | break 51 | } 52 | } 53 | 54 | // Dynamic weights: an array of weights 55 | // Ref: 56 | // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average 57 | // with a dynamic alpha 58 | if (isArrayWeight) { 59 | for (; i < length; i ++) { 60 | datum = data[i] 61 | 62 | isNumber(datum) && isNumber(alpha[i]) 63 | ? s = ret[i] = alpha[i] * datum + (1 - alpha[i]) * s 64 | : ret[i] = ret[i - 1] 65 | } 66 | 67 | return ret 68 | } 69 | 70 | const o = 1 - alpha 71 | 72 | // Fixed alpha 73 | for (; i < length; i ++) { 74 | datum = data[i] 75 | 76 | isNumber(datum) 77 | ? s = ret[i] = alpha * datum + o * s 78 | : ret[i] = ret[i - 1] 79 | } 80 | 81 | return ret 82 | } 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "finmath", 3 | "version": "2.0.1", 4 | "description": "The FinTech utility collections of simple, cumulative, and exponential moving averages.", 5 | "main": "lib/index.js", 6 | "module": "src/index.js", 7 | "files": [ 8 | "lib/", 9 | "src/" 10 | ], 11 | "sideEffects": false, 12 | "scripts": { 13 | "build": "babel src --out-dir lib", 14 | "test:no-cov": "ava --timeout=10s --verbose", 15 | "test": "NODE_DEBUG=dark-hole nyc ava --timeout=10s --verbose", 16 | "test:dev": "NODE_DEBUG=dark-hole nyc ava --timeout=10s --verbose && npm run report:dev", 17 | "lint": "eslint .", 18 | "fix": "eslint . --fix", 19 | "posttest": "npm run report", 20 | "report": "nyc report --reporter=text-lcov > coverage.lcov && codecov", 21 | "report:dev": "nyc report --reporter=html && npm run report:open", 22 | "report:open": "open coverage/index.html", 23 | "prepublishOnly": "npm run build" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git://github.com/kaelzhang/finmath.git" 28 | }, 29 | "keywords": [ 30 | "finmath", 31 | "fintech", 32 | "math", 33 | "moving average", 34 | "moving-average", 35 | "moving-averages", 36 | "simple moving average", 37 | "exponential moving average", 38 | "weighted moving average", 39 | "smoothed moving average", 40 | "dynamic weighted moving average", 41 | "sma", 42 | "ema", 43 | "wma", 44 | "sma", 45 | "dma", 46 | "macd", 47 | "convergence", 48 | "divergence", 49 | "hhv", 50 | "llv", 51 | "highest", 52 | "lowest", 53 | "boll", 54 | "bollinger-bands", 55 | "array", 56 | "sub", 57 | "add" 58 | ], 59 | "ava": { 60 | "require": [ 61 | "@babel/register" 62 | ], 63 | "testOptions": { 64 | "babelrc": true 65 | }, 66 | "files": [ 67 | "test/*.test.js" 68 | ] 69 | }, 70 | "engines": { 71 | "node": ">=4.0.0" 72 | }, 73 | "author": "kaelzhang", 74 | "license": "MIT", 75 | "bugs": { 76 | "url": "https://github.com/kaelzhang/finmath/issues" 77 | }, 78 | "devDependencies": { 79 | "@babel/cli": "^7.7.0", 80 | "@babel/core": "^7.7.2", 81 | "@babel/preset-env": "^7.7.1", 82 | "@babel/register": "^7.7.0", 83 | "@ostai/eslint-config": "^3.5.0", 84 | "ava": "^2.4.0", 85 | "codecov": "^3.6.1", 86 | "eslint": "^6.4.0", 87 | "eslint-plugin-import": "^2.18.2", 88 | "nyc": "^14.1.1" 89 | }, 90 | "dependencies": { 91 | "math-array": "^1.1.2" 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/ma.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | runner, 3 | type 4 | } from './lib/runner' 5 | 6 | 7 | const a = [1, 2, 3, 4, 5] 8 | const dmaa50 = [0, 1.5, 2.25, 3.125, 4.0625] 9 | const dmaa51 = [1, 1.5, 2.25, 3.125, 4.0625] 10 | 11 | const CASES = {} 12 | 13 | CASES.dma = [ 14 | [ 15 | [a, 1, 1], 16 | a 17 | ], 18 | [ 19 | [a, 2, 1], 20 | Array(5) 21 | ], 22 | [ 23 | [a, 0.5, 1], 24 | dmaa50 25 | ], 26 | [ 27 | [a, 0.5], 28 | dmaa51 29 | ], 30 | [ 31 | [[1, 2, 3, 4, 5], [0.1, 0.2, 0.1, 0.1, 0.05]], 32 | [ 33 | 1, 34 | 1.2, 35 | 1.38, 36 | 1.642, 37 | 1.8099 38 | ] 39 | ], 40 | [ 41 | [[, 2, 3, 4, 5], [0.1, 0.2, 0.1, 0.1, 0.05]], 42 | [, 2, 2.1, 2.29, 2.4255] 43 | ], 44 | [ 45 | [[, 2, 3, 4,, 5], [0.1, 0.2, 0.1, 0.1, 0.1, 0.05]], 46 | [, 2, 2.1, 2.29, 2.29, 2.4255] 47 | ], 48 | [ 49 | [[1, 2,, 3, 4, 5], 0.1, 1], 50 | [0, 1.1, 1.1, 1.29, 1.561, 1.9049] 51 | ] 52 | ] 53 | 54 | CASES.ma = [ 55 | [ 56 | [a, 1], 57 | a 58 | ], 59 | [ 60 | [a, 0.5], 61 | a 62 | ], 63 | // [ 64 | // [a], 65 | // 3 66 | // ], 67 | [ 68 | [a, 6], 69 | Array(5) 70 | ], 71 | [ 72 | [a, 2], 73 | [, 1.5, 2.5, 3.5, 4.5] 74 | ], 75 | [ 76 | [[1,, 2,, 3, 4, 5], 2], 77 | [, 0.5, 1, 1, 1.5, 3.5, 4.5] 78 | ], 79 | [ 80 | [[, 1,, 2,, 3, 4, 5], 2], 81 | [,, 0.5, 1, 1, 1.5, 3.5, 4.5] 82 | ] 83 | ] 84 | 85 | 86 | CASES.wma = [ 87 | [ 88 | [[, 1,, 2,, 3, 4, 5], 2], 89 | [, 90 | 0.6666666666666666, 91 | 0.3333333333333333, 92 | 1.3333333333333333, 93 | 0.6666666666666666, 94 | 2, 95 | 3.6666666666666665, 96 | 4.666666666666667 97 | ] 98 | ], 99 | [ 100 | [[1,, 2,, 3, 4, 5], 2], 101 | [, 102 | 0.3333333333333333, 103 | 1.3333333333333333, 104 | 0.6666666666666666, 105 | 2, 106 | 3.6666666666666665, 107 | 4.666666666666667 108 | ] 109 | ], 110 | [ 111 | [a, 2], 112 | [, 113 | 1.6666666666666667, 114 | 2.6666666666666665, 115 | 3.6666666666666665, 116 | 4.666666666666667 117 | ] 118 | ], 119 | [ 120 | [a, 0], 121 | a 122 | ], 123 | [ 124 | [a, 6], 125 | Array(5) 126 | ] 127 | ] 128 | 129 | CASES.sma = [ 130 | [ 131 | [a, 2], 132 | dmaa50 133 | ] 134 | ] 135 | 136 | CASES.ema = [ 137 | [ 138 | [a, 3], 139 | dmaa51 140 | ] 141 | ] 142 | 143 | Object.keys(CASES).forEach(t => { 144 | CASES[t].map(type(t)).forEach(runner) 145 | }) 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/kaelzhang/finmath.svg?branch=master)](https://travis-ci.org/kaelzhang/finmath) 2 | [![Coverage](https://codecov.io/gh/kaelzhang/finmath/branch/master/graph/badge.svg)](https://codecov.io/gh/kaelzhang/finmath) 3 | 6 | 9 | 12 | 13 | # MAINTENANCE WARNING 14 | 15 | This module is lack of maintenance. 16 | 17 | If you are familiar with python programming maybe you could check [**stock-pandas**](https://github.com/kaelzhang/stock-pandas) which provides powerful statistic indicators support, and is backed by [`numpy`](https://numpy.org/) and [`pandas`](https://pandas.pydata.org/). 18 | 19 | The performance of [**stock-pandas**](https://github.com/kaelzhang/stock-pandas) is many times higher than JavaScript libraries, and can be directly used by machine learning programs. 20 | 21 | **** 22 | 23 | # finmath 24 | 25 | The complete collection of mathematical utility methods for [FinTech](https://en.wikipedia.org/wiki/Financial_technology) , including: 26 | 27 | - [Moving averages](https://en.wikipedia.org/wiki/Moving_average) 28 | - [MACD](https://en.wikipedia.org/wiki/MACD) 29 | - [Bollinger bands](https://en.wikipedia.org/wiki/Bollinger_Bands) 30 | - [Standard deviations](https://en.wikipedia.org/wiki/Standard_deviation) 31 | - Highest high values / Lowest low values 32 | 33 | And all finmath methods also handle empty values. 34 | 35 | # Table of Contents 36 | 37 | - [simple Moving Average (MA)](#simple-moving-average-madata-size) 38 | - [Dynamic weighted Moving Average (DMA)](#dynamic-weighted-moving-average-dmadata-alpha-nohead) 39 | - [Exponential Moving Average (EMA)](#exponential-moving-average-emadata-size) 40 | - [Smoothed Moving Average (SMA)](#smoothed-moving-average-smadata-size-times) 41 | - [Weighted Moving Average (WMA)](#weighted-moving-average-wmadata-size) 42 | - [MACD](#MACD-macddata-slowPeriods-fastPeriods-signalPeriods) 43 | - [BOLLinger bands (BOLL)](#bollinger-bands-bolldata-size-times-options) 44 | - [Standard Deviations (SD)](#standard-deviations-sddata-size) 45 | - [Highest High Values (HHV)](#highest-high-values-hhvdata-periods) 46 | - [Lowest Low Values (LLV)](#lowest-low-values-llvdata-periods) 47 | 48 | ## install 49 | 50 | ```sh 51 | $ npm i finmath 52 | ``` 53 | 54 | ## usage 55 | 56 | ```js 57 | import { 58 | ma, dma, ema, sma, wma, 59 | macd, 60 | boll, 61 | sd, 62 | hhv, llv, 63 | add, sub, mul, div 64 | } from 'finmath' 65 | 66 | ma([1, 2, 3, 4, 5], 2) 67 | // [<1 empty item>, 1.5, 2.5, 3.5, 4.5] 68 | ``` 69 | 70 | ## Simple Moving Average: `ma(data, size)` 71 | 72 | ```ts 73 | type Data = EmptyableArray 74 | ``` 75 | 76 | - **data** `Data` the collection of data inside which empty values are allowed. Empty values are useful if a stock is suspended. 77 | - **size** `number` the size of the periods. 78 | 79 | Returns `Data` 80 | 81 | Type `Array` represents an array of numbers or empty items. And every method of `finmath` does **NOT** accepts items that are not numbers. 82 | 83 | ```js 84 | [1,, 2, 3] // OK ✅ 85 | 86 | [1, undefined, 2, 3] // NOT OK ❌ 87 | 88 | [1, null, 2, 3] // NOT OK ❌ 89 | ``` 90 | 91 | #### Special Cases 92 | 93 | ```js 94 | // If the size is less than `1` 95 | ma([1, 2, 3], 0.5) // [1, 2, 3] 96 | 97 | // If the size is larger than data length 98 | ma([1, 2, 3], 5) // [<3 empty items>] 99 | 100 | ma([, 1,, 3, 4, 5], 2) 101 | // [<2 empty items>, 0.5, 1.5, 3.5, 4.5] 102 | ``` 103 | 104 | And all of the other moving average methods have similar mechanism. 105 | 106 | ## Dynamic Weighted Moving Average: `dma(data, alpha, noHead)` 107 | 108 | - **data** 109 | - **alpha** `Data` the coefficient or list of coefficients `alpha` represents the degree of weighting decrease for each datum. 110 | - If `alpha` is a number, then the weighting decrease for each datum is the same. 111 | - If `alpha` larger than `1` is invalid, then the return value will be an empty array of the same length of the original data. 112 | - If `alpha` is an array, then it could provide different decreasing degree for each datum. 113 | - **noHead** `Boolean=` whether we should abandon the first DMA. 114 | 115 | Returns `Data` 116 | 117 | ```js 118 | dma([1, 2, 3], 2) // [<3 empty items>] 119 | 120 | dma([1, 2, 3], 0.5) // [1, 1.5, 2.25] 121 | 122 | dma([1, 2, 3, 4, 5], [0.1, 0.2, 0.1]) 123 | // [1, 1.2, 1.38] 124 | ``` 125 | 126 | ## Exponential Moving Average: `ema(data, size)` 127 | 128 | Calulates the most frequent used exponential average which covers about 86% of the total weight (when `alpha = 2 / (N + 1)`). 129 | 130 | - **data** 131 | - **size** `Number` the size of the periods. 132 | 133 | Returns `Data` 134 | 135 | ## Smoothed Moving Average: `sma(data, size, times)` 136 | 137 | Also known as the modified moving average or running moving average, with `alpha = times / size`. 138 | 139 | - **data** 140 | - **size** 141 | - **times?** `Number=1` 142 | 143 | Returns `Data` 144 | 145 | ## Weighted Moving Average: `wma(data, size)` 146 | 147 | Calculates convolution of the datum points with a fixed weighting function. 148 | 149 | Returns `Data` 150 | 151 | ## MACD: macd(data, slowPeriods?, fastPeriods?, signalPeriods?) 152 | 153 | **MACD**, short for Moving Average Convergence / Divergence, is a trading indicator used in technical analysis of stock prices, created by Gerald Appel in the late 1970s. 154 | 155 | - **data** `Data` the collection of prices 156 | - **slowPeriods?** `number=26` the size of slow periods. Defaults to `26` 157 | - **fastPeriods?** `number=12` the size of fast periods. Defaults to `12` 158 | - **signalPeriods?** `number=9` the size of periods to calculate the MACD signal line. 159 | 160 | Returns `MACDGraph` 161 | 162 | ```js 163 | macd(data) 164 | 165 | // which returns: 166 | // { 167 | // MACD: , 168 | // signal: , 169 | // histogram: 170 | // } 171 | ``` 172 | 173 | ### struct `MACDGraph` 174 | 175 | - **MACD** `Data` the difference between [EMAs](https://www.npmjs.com/package/moving-averages#exponential-moving-average-emadata-size) of the fast periods and EMAs of the slow periods. 176 | - **signal** `Data` the EMAs of the `MACD` 177 | - **histogram** `Data` `MACD` minus `signal` 178 | 179 | In some countries, such as China, the three series above are commonly known as: 180 | 181 | ```sh 182 | MACD -> DIF 183 | signal -> DEA 184 | histogram -> MACD 185 | ``` 186 | 187 | ## Bollinger Bands: boll(data, size?, times?, options?) 188 | 189 | ```js 190 | boll([1, 2, 4, 8], 2, 2) 191 | // { 192 | // upper: [, 2.5, 5, 10], 193 | // mid : [, 1.5, 3, 6], 194 | // lower: [, 0.5, 1, 2] 195 | // } 196 | ``` 197 | 198 | - **data** `Data` the collection of data 199 | - **size?** `Number=20` the period size, defaults to `20` 200 | - **times?** `Number=2` the times of standard deviation between the upper band and the moving average. 201 | - **options?** `Object=` optional options 202 | - **ma?** `Data=` the moving averages of the provided `datum` and period `size`. This option is used to prevent duplicate calculation of moving average. 203 | - **sd?** `Data=` the standard average of the provided `datum` and period `size` 204 | 205 | Returns `Array` the array of the `Band` object. 206 | 207 | ```ts 208 | interface Band { 209 | // the value of the upper band 210 | upper: number 211 | // the value middle band (simple moving average) 212 | mid: number 213 | // the value of the lower band 214 | lower: number 215 | } 216 | ``` 217 | 218 | ## Standard deviations: sd(data, size) 219 | 220 | - **data** `Data` the collection of data 221 | - **size** `number` the sample size of 222 | 223 | Returns `Data` the array of standard deviations. 224 | 225 | ```js 226 | sd([1, 2, 4, 8], 2) // [<1 empty item>, 0.5, 1, 2] 227 | 228 | sd([1, 2, 3, 4, 5, 6], 4) 229 | // [ 230 | // <3 empty items>, 231 | // 1.118033988749895, 232 | // 1.118033988749895, 233 | // 1.118033988749895 234 | // ] 235 | ``` 236 | 237 | ## Highest High Values: hhv(data, periods) 238 | 239 | - **data** `Data` the array of closing prices. 240 | - **periods** `number` the size of periods 241 | 242 | Returns `Data` the highest high values of closing prices over the preceding `periods` periods (periods includes the current time). 243 | 244 | ```js 245 | const array = [1, 2, 4, 1] 246 | 247 | hhv(array, 2) // [, 2, 4, 4] 248 | hhv(array) // 4 249 | hhv(array, 5) // [<4 empty items>] 250 | hhv(array, 1) // [1, 2, 4, 1] 251 | 252 | hhv(array, 2) // [, 1, 2, 2] 253 | ``` 254 | 255 | ## Lowest Low Values: llv(data, periods) 256 | 257 | Instead, returns `Data` the lowest low values. 258 | 259 | ## License 260 | 261 | [MIT](LICENSE) 262 | --------------------------------------------------------------------------------