├── docs ├── AwesomeOscillator.md ├── .github │ └── unilevel.png ├── SuperTrend.md ├── ConnorsRSI.md ├── StochasticRsi.md ├── SmoothedMovingAverage.md ├── WeightedMovingAverage.md ├── WellesWildersSmoothingAverage.md ├── SimpleMovingAverage.md ├── WildersSmoothedMovingAverage.md ├── ExponentialWeightedMovingAverage.md ├── ParabolicStopAndReverse.md ├── RateofChange.md ├── RelativeStrengthIndex.md ├── ChaikinOscillator.md ├── LinearlyWeightedMovingAverage.md ├── StochasticOscillator.md ├── AverageDirectionalIndex.md ├── ExponentialMovingAverage.md ├── MovingAverageConvergenceDivergence.md ├── DonchianChannels.md ├── CommodityChannelIndex.md ├── BollingerBands.md ├── PivotPointLevels.md ├── MoneyFlowIndex.md ├── UniLevel.md ├── AverageTrueRange.md ├── AcceleratorOscillator.md └── AdaptiveMovingAverage.md ├── .gitignore ├── tests ├── .DS_Store ├── ema │ ├── EMA.xlsx │ ├── ema.spec.ts │ └── excel-data.ts ├── dc │ ├── Donchian.xls │ └── dc.spec.ts ├── macd │ ├── MACD.xlsx │ ├── macd.spec.ts │ └── excel-data.ts ├── roc │ ├── cs-roc.xls │ ├── excel-data.ts │ └── roc.spec.ts ├── heikenashi │ ├── ha-chart.xlsx │ ├── ha.spec.ts │ └── excel-data.ts ├── mfi │ ├── MoneyFlowIndex.xlsm │ └── mfi.spec.ts ├── psar │ ├── Parabolic-SAR.xlsm │ ├── psar.spec.ts │ └── excel-data.ts ├── rsi │ ├── RSI-calculation.xlsx │ ├── issue-16.spec.ts │ └── rsi.spec.ts ├── sma │ ├── moving-average.xlsx │ └── sma.spec.ts ├── williams │ ├── cs-percentr.xls │ ├── williams.spec.ts │ └── data.ts ├── atr │ ├── Average-True-Range.xlsx │ ├── issue-25.spec.ts │ └── atr.spec.ts ├── bbands │ ├── Bollinger-Bands.xlsx │ └── bbands.spec.ts ├── supertrend │ ├── SuperTrend.xlsx │ ├── supertrend.spec.ts │ └── data.ts ├── ama │ ├── AMA_AUDCAD_TF5_15-2-30.xlsx │ └── ama.spec.ts ├── cci │ ├── Commoditive Channel Index.xls │ └── cci.spec.ts ├── wma │ ├── 11-Weighted-Moving-Average.xlsx │ └── wma.spec.ts ├── stochastic │ ├── StochasticOscillator.xlsm │ └── stochastic.spec.ts ├── ac │ ├── awesome-and-accelerator-oscillators.xlsx │ ├── ac.spec.ts │ └── excel-data.ts ├── ao │ ├── awesome-and-accelerator-oscillators.xlsx │ ├── ao.spec.ts │ └── excel-data.ts ├── providers │ ├── percent-rank.ts │ ├── circular-buffer.spec.ts │ ├── sampler.spec.ts │ └── standard-deviation.spec.ts ├── pivot │ └── pivot.ts ├── wema │ └── wema.spec.ts ├── volume-profile │ └── volume-profile.spec.ts ├── adx │ ├── adx.spec.ts │ └── data.ts ├── stoch-rsi │ └── stoch-rsi.spec.ts ├── truerange │ ├── truerange.spec.ts │ └── data.ts ├── rma │ ├── rma.spec.ts │ └── data.ts ├── ewma │ ├── ewma.spec.ts │ └── data.ts └── crsi │ └── crsi.ts ├── jest.config.js ├── src ├── trendlines │ ├── sample2.jpg │ ├── sample3.png │ ├── sample4.png │ ├── types.ts │ ├── readme.md │ └── lines.model.ts ├── providers │ ├── true-range.ts │ ├── standard-deviation.ts │ ├── min-value.ts │ ├── max-value.ts │ ├── percent-rank.ts │ ├── gain.ts │ ├── correlation.ts │ ├── mean-deviation.ts │ ├── sampler.ts │ ├── extremum.ts │ ├── circular-buffer.ts │ └── levels.ts ├── move.ts ├── utils.ts ├── williams.ts ├── dc.ts ├── smma.ts ├── wema.ts ├── rma.ts ├── sma.ts ├── cci.ts ├── ewma.ts ├── wws.ts ├── lwma.ts ├── bands.ts ├── roc.ts ├── ac.ts ├── ema.ts ├── ao.ts ├── rsi.ts ├── macd.ts ├── chaikin.ts ├── wma.ts ├── heiken-ashi.ts ├── atr.ts ├── stochastic.ts ├── ama.ts ├── stochastic-rsi.ts ├── supertrend.ts ├── wave.ts ├── mfi.ts ├── crsi.ts ├── adx.ts ├── ichimoku.ts ├── pivot.ts └── volume-profile.ts ├── .prettierrc ├── .npmignore ├── .vscode ├── settings.json └── launch.json ├── .editorconfig ├── tsconfig.json ├── .github └── FUNDING.yml ├── .eslintrc ├── index.ts ├── rollup.config.js └── package.json /docs/AwesomeOscillator.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | npm-debug.log 4 | lib 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /tests/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/.DS_Store -------------------------------------------------------------------------------- /tests/ema/EMA.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/ema/EMA.xlsx -------------------------------------------------------------------------------- /tests/dc/Donchian.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/dc/Donchian.xls -------------------------------------------------------------------------------- /tests/macd/MACD.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/macd/MACD.xlsx -------------------------------------------------------------------------------- /tests/roc/cs-roc.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/roc/cs-roc.xls -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | }; 5 | -------------------------------------------------------------------------------- /docs/.github/unilevel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/docs/.github/unilevel.png -------------------------------------------------------------------------------- /src/trendlines/sample2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/src/trendlines/sample2.jpg -------------------------------------------------------------------------------- /src/trendlines/sample3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/src/trendlines/sample3.png -------------------------------------------------------------------------------- /src/trendlines/sample4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/src/trendlines/sample4.png -------------------------------------------------------------------------------- /tests/heikenashi/ha-chart.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/heikenashi/ha-chart.xlsx -------------------------------------------------------------------------------- /tests/mfi/MoneyFlowIndex.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/mfi/MoneyFlowIndex.xlsm -------------------------------------------------------------------------------- /tests/psar/Parabolic-SAR.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/psar/Parabolic-SAR.xlsm -------------------------------------------------------------------------------- /tests/rsi/RSI-calculation.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/rsi/RSI-calculation.xlsx -------------------------------------------------------------------------------- /tests/sma/moving-average.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/sma/moving-average.xlsx -------------------------------------------------------------------------------- /tests/williams/cs-percentr.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/williams/cs-percentr.xls -------------------------------------------------------------------------------- /tests/atr/Average-True-Range.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/atr/Average-True-Range.xlsx -------------------------------------------------------------------------------- /tests/bbands/Bollinger-Bands.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/bbands/Bollinger-Bands.xlsx -------------------------------------------------------------------------------- /tests/supertrend/SuperTrend.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/supertrend/SuperTrend.xlsx -------------------------------------------------------------------------------- /tests/ama/AMA_AUDCAD_TF5_15-2-30.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/ama/AMA_AUDCAD_TF5_15-2-30.xlsx -------------------------------------------------------------------------------- /tests/cci/Commoditive Channel Index.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/cci/Commoditive Channel Index.xls -------------------------------------------------------------------------------- /tests/wma/11-Weighted-Moving-Average.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/wma/11-Weighted-Moving-Average.xlsx -------------------------------------------------------------------------------- /tests/stochastic/StochasticOscillator.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/stochastic/StochasticOscillator.xlsm -------------------------------------------------------------------------------- /tests/ac/awesome-and-accelerator-oscillators.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/ac/awesome-and-accelerator-oscillators.xlsx -------------------------------------------------------------------------------- /tests/ao/awesome-and-accelerator-oscillators.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/debut-indicators/master/tests/ao/awesome-and-accelerator-oscillators.xlsx -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "printWidth": 120, 6 | "tabWidth": 4, 7 | "endOfLine": "lf" 8 | } 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .nyc_output 3 | coverage 4 | .editorconfig 5 | .eslintrc.js 6 | .prettierrc.js 7 | README.md 8 | rollup.config.js 9 | *.xlsx 10 | *.xls 11 | *.xlsm 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "extremum", 4 | "kdiff", 5 | "Multuplicator", 6 | "tline" 7 | ], 8 | "cSpell.language": "en,ru,ru-RU,en-US" 9 | } 10 | -------------------------------------------------------------------------------- /tests/williams/williams.spec.ts: -------------------------------------------------------------------------------- 1 | import { Williams } from '../../src/williams'; 2 | import { data, expected } from './data'; 3 | 4 | describe.skip('Williams R%', () => { 5 | it('Excel data test match', () => {}); 6 | }); 7 | -------------------------------------------------------------------------------- /docs/SuperTrend.md: -------------------------------------------------------------------------------- 1 | # SuperTrend MTF (ST MTF) 2 | The SuperTrend MTF is a trend-following indicator that is designed to help traders identify the direction of a trend. It is calculated using the following formula: 3 | 4 | ``` 5 | ST MTF = (High + Low) / 2 6 | ``` 7 | -------------------------------------------------------------------------------- /docs/ConnorsRSI.md: -------------------------------------------------------------------------------- 1 | # Connor's RSI (CRSI) 2 | Connor's RSI is a variation of the Relative Strength Index (RSI) that attempts to reduce the lag effect of the original RSI. It is calculated using the following formula: 3 | 4 | ``` 5 | CRSI = (RSI(n) + RSI(2n)) / 2 6 | ``` -------------------------------------------------------------------------------- /docs/StochasticRsi.md: -------------------------------------------------------------------------------- 1 | # Stochastic RSI (KD) 2 | The Stochastic RSI is a variation of the Stochastic Oscillator that is applied to the RSI. It is calculated using the following formula: 3 | 4 | ``` 5 | Stochastic RSI = (RSI - Lowest RSI) / (Highest RSI - Lowest RSI) * 100 6 | ``` -------------------------------------------------------------------------------- /docs/SmoothedMovingAverage.md: -------------------------------------------------------------------------------- 1 | # Smoothed Moving Average (SMMA) 2 | The Smoothed Moving Average (SMMA) is a type of moving average that assigns more weight to recent data points. It is calculated using the following formula: 3 | 4 | ``` 5 | SMMA = (Close + (n - 1) * Previous SMMA) / n 6 | ``` -------------------------------------------------------------------------------- /docs/WeightedMovingAverage.md: -------------------------------------------------------------------------------- 1 | # Weighted Moving Average (WMA) 2 | The Weighted Moving Average (WMA) is a type of moving average that assigns more weight to recent data points. It is calculated using the following formula: 3 | 4 | ``` 5 | WMA = (Sum of (Close * Weight)) / (Sum of Weights) 6 | ``` -------------------------------------------------------------------------------- /docs/WellesWildersSmoothingAverage.md: -------------------------------------------------------------------------------- 1 | # Welles Wilder's Smoothing Average (WWS) 2 | Welles Wilder's Smoothing Average (WWS) is a type of moving average that assigns more weight to recent data points. It is calculated using the following formula: 3 | 4 | ``` 5 | WWS = (Close + (n - 1) * Previous WWS) / n 6 | ``` -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 4 13 | -------------------------------------------------------------------------------- /docs/SimpleMovingAverage.md: -------------------------------------------------------------------------------- 1 | # Simple Moving Average (SMA) 2 | The Simple Moving Average (SMA) is a calculation that takes the arithmetic mean of a given set of prices over a specific number of days. It is calculated using the following formula: 3 | 4 | ``` 5 | SMA = (Sum of Close prices) / Number of periods 6 | ``` -------------------------------------------------------------------------------- /docs/WildersSmoothedMovingAverage.md: -------------------------------------------------------------------------------- 1 | # Wilder's Smoothed Moving Average (WEMA) 2 | Wilder's Smoothed Moving Average (WEMA) is a type of moving average that assigns more weight to recent data points. It is calculated using the following formula: 3 | 4 | ``` 5 | WEMA = (Close + (n - 1) * Previous WEMA) / n 6 | ``` 7 | -------------------------------------------------------------------------------- /tests/providers/percent-rank.ts: -------------------------------------------------------------------------------- 1 | import { PercentRank } from '../../src/providers/percent-rank'; 2 | 3 | const percentRank = new PercentRank(10); 4 | 5 | [100, 5, 20, 50, 55, 60, 70, 80, 90, 1].forEach((v) => { 6 | percentRank.nextValue(v); 7 | }); 8 | 9 | console.log(percentRank.nextValue(66)); 10 | -------------------------------------------------------------------------------- /tests/pivot/pivot.ts: -------------------------------------------------------------------------------- 1 | import { Pivot } from '../../src/pivot'; 2 | // High 1000 3 | // Low 950 4 | // Close 975 5 | 6 | // R3 1050 7 | // R2 1025 8 | // R1 1000 9 | // PP 975 10 | // S1 950 11 | // S2 925 12 | // S3 900 13 | 14 | const pivot = new Pivot(); 15 | 16 | console.log(pivot.nextValue(1000, 950, 975)); 17 | -------------------------------------------------------------------------------- /docs/ExponentialWeightedMovingAverage.md: -------------------------------------------------------------------------------- 1 | # Exponential Weighted Moving Average (EWMA) 2 | The Exponential Weighted Moving Average (EWMA) is a type of moving average that gives more weight to recent data points. It is calculated using the following formula: 3 | 4 | ``` 5 | EWMA = (Close - Previous EWMA) * (2 / (n + 1)) + Previous EWMA 6 | ``` -------------------------------------------------------------------------------- /docs/ParabolicStopAndReverse.md: -------------------------------------------------------------------------------- 1 | # Parabolic Stop And Reverse (PSAR) 2 | The Parabolic Stop And Reverse (PSAR) is a trend-following indicator that is designed to help traders identify the direction of a trend. It is calculated using the following formula: 3 | 4 | ``` 5 | PSAR = PSAR(previous bar) + (PSAR(previous bar) - PSAR(two bars ago)) 6 | ``` -------------------------------------------------------------------------------- /docs/RateofChange.md: -------------------------------------------------------------------------------- 1 | # Rate of Change (ROC) 2 | The Rate of Change (ROC) is a momentum oscillator that measures the percentage change in price between the current price and the price a certain number of periods ago. It is calculated using the following formula: 3 | 4 | ``` 5 | ROC = [(Close - Close n periods ago) / (Close n periods ago)] * 100 6 | ``` -------------------------------------------------------------------------------- /docs/RelativeStrengthIndex.md: -------------------------------------------------------------------------------- 1 | # Relative Strength Index (RSI) 2 | The Relative Strength Index (RSI) is a momentum oscillator that measures the speed and change of price movements. It is used to identify overbought or oversold conditions in a market. The RSI is calculated using the following formula: 3 | 4 | ``` 5 | RSI = 100 - (100 / (1 + RS)) 6 | ``` -------------------------------------------------------------------------------- /docs/ChaikinOscillator.md: -------------------------------------------------------------------------------- 1 | # Chaikin Oscillator 2 | The Chaikin Oscillator is a momentum indicator that measures the accumulation-distribution line, comparing it to a moving average of the accumulation-distribution line over a certain period. It is calculated using the following formula: 3 | 4 | ``` 5 | Chaikin Oscillator = (ADL - ADL(n)) / ADL(n) 6 | ``` -------------------------------------------------------------------------------- /docs/LinearlyWeightedMovingAverage.md: -------------------------------------------------------------------------------- 1 | # Linearly Weighted Moving Average (LWMA) 2 | The Linearly Weighted Moving Average (LWMA) is a type of moving average that assigns weights to data points based on their distance from the current point. It is calculated using the following formula: 3 | 4 | ``` 5 | LWMA = (Sum of (Close * Weight)) / (Sum of Weights) 6 | ``` -------------------------------------------------------------------------------- /docs/StochasticOscillator.md: -------------------------------------------------------------------------------- 1 | # Stochastic Oscillator (KD) 2 | The Stochastic Oscillator is a momentum indicator comparing a particular closing price of a security to a range of its prices over a certain period. It is calculated using the following formula: 3 | 4 | ``` 5 | %K = (Current Close - Lowest Low) / (Highest High - Lowest Low) * 100 6 | %D = 3-period SMA of %K 7 | ``` -------------------------------------------------------------------------------- /docs/AverageDirectionalIndex.md: -------------------------------------------------------------------------------- 1 | # Average Directional Index (ADX) 2 | The Average Directional Index (ADX) is a technical analysis indicator used to quantify the strength of a trend. It is a smoothed moving average of the Directional Movement Index (DMI), which is a measure of the strength of a trend. The ADX is calculated using the following formula: 3 | 4 | ``` 5 | ADX = (DMI + DMI(n)) / 2 6 | ``` -------------------------------------------------------------------------------- /docs/ExponentialMovingAverage.md: -------------------------------------------------------------------------------- 1 | # Exponential Moving Average (EMA) 2 | The Exponential Moving Average (EMA) is a type of moving average that places a greater weight and significance on the most recent data points. It is calculated using the following formula: 3 | 4 | ``` 5 | EMA = (Close - Previous EMA) * (2 / (n + 1)) + Previous EMA 6 | ``` 7 | 8 | ## How to calculate Exponential Moving Average (EMA) in excel spreadsheet? 9 | 10 | [Download excel sample](../tests/ema/EMA.xlsx) -------------------------------------------------------------------------------- /docs/MovingAverageConvergenceDivergence.md: -------------------------------------------------------------------------------- 1 | # Moving Average Convergence Divergence (MACD) 2 | The Moving Average Convergence Divergence (MACD) is a trend-following momentum indicator that shows the relationship between two moving averages of a security's price. The MACD is calculated using the following formula: 3 | 4 | ``` 5 | MACD = 12-period EMA - 26-period EMA 6 | ``` 7 | 8 | ## How to calculate Moving Average Convergence Divergence (MACD) in excel spreadsheet? 9 | 10 | [Download excel sample](../tests/macd/MACD.xlsx) -------------------------------------------------------------------------------- /docs/DonchianChannels.md: -------------------------------------------------------------------------------- 1 | # Donchian Channels (DC) 2 | Donchian Channels are a type of volatility indicator that consists of two lines, the upper and lower channels, which are plotted at the highest and lowest points of a specified period. The channels are calculated using the following formula: 3 | 4 | ``` 5 | Upper Channel = Highest High over the period 6 | Lower Channel = Lowest Low over the period 7 | ``` 8 | 9 | ## How to calculate Donchian Channels (DC) in excel spreadsheet? 10 | 11 | [Download excel sample](../tests/dc/Donchian.xls) -------------------------------------------------------------------------------- /tests/roc/excel-data.ts: -------------------------------------------------------------------------------- 1 | export const closes = [ 2 | 81.59, 81.06, 82.87, 83.0, 83.61, 83.15, 82.84, 83.99, 84.55, 84.36, 85.53, 86.54, 86.89, 87.77, 87.29, 3 | ]; 4 | 5 | export const rocValues = [ 6 | undefined, 7 | undefined, 8 | undefined, 9 | undefined, 10 | undefined, 11 | 0.01911999019, 12 | 0.02195904268, 13 | 0.0135151442, 14 | 0.01867469879, 15 | 0.00897021887, 16 | 0.02862297053, 17 | 0.04466441332, 18 | 0.03452791999, 19 | 0.03808397397, 20 | 0.03473210052, 21 | ]; 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "moduleResolution": "node", 5 | "lib": ["ESNext"], 6 | "skipLibCheck": true, 7 | "module": "commonjs", 8 | "allowJs": true, 9 | "outDir": "./lib", 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "sourceMap": true, 13 | "isolatedModules": true, 14 | "allowSyntheticDefaultImports": true, 15 | "declaration": true, 16 | }, 17 | "include": [ 18 | "src/**/*.ts" 19 | ], 20 | "exclude": ["node_modules"], 21 | } 22 | -------------------------------------------------------------------------------- /docs/CommodityChannelIndex.md: -------------------------------------------------------------------------------- 1 | # Commodity Channel Index (CCI) 2 | The Commodity Channel Index (CCI) is a versatile indicator that can be used to identify a new trend or warn of extreme conditions. It is primarily used to identify cyclical trends in commodities, but it can also be used in stocks. The CCI is calculated using the following formula: 3 | 4 | ``` 5 | CCI = (Typical Price - 20-period SMA of TP) / (0.015 x Mean Deviation) 6 | ``` 7 | 8 | ## How to calculate Commodity Channel Index (CCI) in excel spreadsheet? 9 | 10 | [Download excel sample](../tests/cci/Commoditive%20Channel%20Index.xls) -------------------------------------------------------------------------------- /tests/providers/circular-buffer.spec.ts: -------------------------------------------------------------------------------- 1 | import { CircularBuffer } from '../../src/providers/circular-buffer'; 2 | 3 | describe('Circular buffer', () => { 4 | it('for each', () => { 5 | const buffer = new CircularBuffer(6); 6 | const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; 7 | const result = [8, 9, 10, 11, 12, 13]; 8 | 9 | data.forEach((item) => buffer.push(item)); 10 | 11 | buffer.forEach((item, idx) => { 12 | // console.log(item, idx); 13 | expect(item).toEqual(result[idx]); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /tests/ama/ama.spec.ts: -------------------------------------------------------------------------------- 1 | import { AMA } from '../../src/ama'; 2 | import { data } from './data'; 3 | 4 | describe('Adaptive Moving Average', () => { 5 | const ama = new AMA(15, 2, 30); 6 | const EPSILON = 0.001; 7 | 8 | it('Excel validate', () => { 9 | data.forEach((tick, idx) => { 10 | const amaValue = ama.nextValue(tick.c); 11 | const expected = Number(tick.ama); 12 | 13 | if (amaValue && expected) { 14 | expect(Math.abs(amaValue - expected)).toBeLessThan(EPSILON); 15 | } 16 | 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/ac/ac.spec.ts: -------------------------------------------------------------------------------- 1 | import { AC } from '../../src/ac'; 2 | import { aoValues, lows, highs } from './excel-data'; 3 | 4 | describe('AC', () => { 5 | it('Excel Validate', () => { 6 | const ac = new AC(); 7 | 8 | lows.forEach((l, idx) => { 9 | const calculated = ac.nextValue(highs[idx], l); 10 | const excel = aoValues[idx]; 11 | 12 | if (!excel && !calculated) { 13 | expect(excel).toEqual(undefined); 14 | } else { 15 | expect(Math.abs(calculated - excel)).toBeLessThan(0.0001); 16 | } 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/supertrend/supertrend.spec.ts: -------------------------------------------------------------------------------- 1 | import { ohlc, stExcelCalculated } from './data'; 2 | import { SuperTrend } from '../../src/supertrend'; 3 | 4 | describe('Super Trend', () => { 5 | it('Excel validation test', () => { 6 | const st = new SuperTrend(10, 2, 'SMA'); 7 | 8 | ohlc.forEach((tick, idx) => { 9 | const value = st.nextValue(tick.h, tick.l, tick.c); 10 | const excel = stExcelCalculated[idx]; 11 | 12 | if (value?.upper && excel) { 13 | const diff = Math.abs(value.upper - excel.upper); 14 | 15 | expect(diff).toBeLessThan(0.00001); 16 | } 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/wema/wema.spec.ts: -------------------------------------------------------------------------------- 1 | import { WEMA as WEMA1 } from 'technicalindicators'; 2 | import { WEMA as WEMA2 } from '../../src/wema'; 3 | import { ohlc } from './data'; 4 | 5 | describe("Wilder's Smoothed Moving Average", () => { 6 | it('Cross validate', () => { 7 | const cross = new WEMA1({ period: 6, values: [] }); 8 | const local = new WEMA2(6); 9 | 10 | ohlc.forEach((tick, idx) => { 11 | const calcValue = cross.nextValue(tick.c); 12 | const crossValue = local.nextValue(tick.c); 13 | 14 | if (idx > 6) { 15 | expect(calcValue).toEqual(crossValue); 16 | } 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /docs/BollingerBands.md: -------------------------------------------------------------------------------- 1 | # Bollinger Bands (BB) 2 | Bollinger Bands are a type of statistical chart characterizing the prices and volatility over time of a financial instrument or commodity, using a formulaic method propounded by John Bollinger in the 1980s. The bands are plotted at standard deviation levels above and below a moving average, which is typically the simple moving average (SMA). The bands are calculated using the following formula: 3 | 4 | ``` 5 | Upper Band = SMA + (n * Standard Deviation) 6 | Lower Band = SMA - (n * Standard Deviation) 7 | ``` 8 | 9 | ## How to calculate Bollinger Bands (BB) in excel spreadsheet? 10 | 11 | [Download excel sample](../tests/bbands/Bollinger-Bands.xlsx) -------------------------------------------------------------------------------- /src/providers/true-range.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * True range calculation 3 | */ 4 | export function getTrueRange(high: number, low: number, prevClose: number) { 5 | if (prevClose) { 6 | // Linear conditions without max min and abs 7 | // Perormance reason 8 | const hl = high - low; 9 | const hc = high > prevClose ? high - prevClose : prevClose - high; 10 | const lc = low > prevClose ? low - prevClose : prevClose - low; 11 | 12 | if (hl >= hc && hl >= lc) { 13 | return hl; 14 | } 15 | 16 | if (hc >= hl && hc >= lc) { 17 | return hc; 18 | } 19 | 20 | return lc; 21 | } 22 | 23 | return; 24 | } 25 | -------------------------------------------------------------------------------- /docs/PivotPointLevels.md: -------------------------------------------------------------------------------- 1 | # Pivot Point Levels (Classic / Woodie / Camarilla / Fibonacci) 2 | Pivot Point Levels are used to determine the overall trend of the market. The classic method uses the high, low, and close prices of the previous trading period to calculate the pivot point. The Woodie method adds the high and low prices of the previous trading period to the close price. The Camarilla method uses the high, low, and close prices of the previous trading period, as well as the high and low prices of the current trading period. The Fibonacci method uses the high, low, and close prices of the previous trading period, as well as the high and low prices of the current trading period, and applies Fibonacci retracement levels. -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "command": "npm start ./tests/trendlines/index.ts", 9 | "name": "Run npm start", 10 | "request": "launch", 11 | "type": "node-terminal" 12 | }, 13 | { 14 | "command": "npm run test", 15 | "name": "Run npm test", 16 | "request": "launch", 17 | "type": "node-terminal" 18 | } 19 | 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tests/wma/wma.spec.ts: -------------------------------------------------------------------------------- 1 | import { WMA as WMA1 } from 'technicalindicators'; 2 | import { WMA as WMA2 } from '../../src/wma'; 3 | import { ohlc } from './data'; 4 | 5 | describe("Wilder's Moving Average", () => { 6 | it('Cross validate', () => { 7 | const cross = new WMA1({ period: 6, values: [] }); 8 | const local = new WMA2(6); 9 | 10 | ohlc.forEach((tick) => { 11 | const crossValue = cross.nextValue(tick.c); 12 | const localMoment = local.momentValue(tick.c); 13 | const localValue = local.nextValue(tick.c); 14 | 15 | // console.log(calcValue, crossValue); 16 | expect(crossValue).toEqual(localValue); 17 | expect(localMoment).toEqual(localValue); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/providers/standard-deviation.ts: -------------------------------------------------------------------------------- 1 | import { CircularBuffer } from './circular-buffer'; 2 | export class StandardDeviation { 3 | private values: CircularBuffer; 4 | 5 | constructor(private period: number) { 6 | this.values = new CircularBuffer(period); 7 | } 8 | 9 | nextValue(value: number, mean?: number) { 10 | this.values.push(value); 11 | 12 | return Math.sqrt(this.values.toArray().reduce((acc, item) => acc + (item - mean) ** 2, 0) / this.period); 13 | } 14 | 15 | momentValue(value: number, mean?: number) { 16 | const rm = this.values.push(value); 17 | const result = Math.sqrt( 18 | this.values.toArray().reduce((acc, item) => acc + (item - mean) ** 2, 0) / this.period, 19 | ); 20 | this.values.pushback(rm); 21 | 22 | return result; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/dc/dc.spec.ts: -------------------------------------------------------------------------------- 1 | import { DC } from '../../src/dc'; 2 | import { dcValues, ohlc } from './excel-data'; 3 | 4 | describe.only('Donchian Channels', () => { 5 | it('Excel Validate', () => { 6 | const dc = new DC(21); 7 | const EPSILON = 0.001; 8 | 9 | ohlc.forEach((tick, idx) => { 10 | const calculated = dc.nextValue(tick.h, tick.l); 11 | const excel = dcValues[idx]; 12 | 13 | // console.log(calculated.upper, excel.upper) 14 | // console.log(calculated.lower, excel.lower) 15 | 16 | if (!calculated) { 17 | return; 18 | } 19 | 20 | expect(Math.abs(calculated.upper - excel.upper)).toBeLessThan(EPSILON); 21 | expect(Math.abs(calculated.lower - excel.lower)).toBeLessThan(EPSILON); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: #businessduck # Replace with a single Patreon username 5 | open_collective: debut-js # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /tests/atr/issue-25.spec.ts: -------------------------------------------------------------------------------- 1 | import { ATR } from '../../src/atr'; 2 | import { ohlc } from './excel-data'; 3 | 4 | let closes = [ 5 | 51.59, 51.65, 51.65, 51.65, 51.65, 51.65, 51.65, 51.65, 51.65, 51.65, 51.65, 51.65, 51.65, 51.65, 51.65, 51.6, 51.6, 6 | 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 7 | 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 51.6, 8 | ]; 9 | 10 | describe('ATR', () => { 11 | it('issue-25: ATR sometimes return undefined value', () => { 12 | const atr = new ATR(); 13 | 14 | ohlc.forEach((candle, index) => { 15 | atr.nextValue(candle.h, candle.l, candle.c); 16 | }); 17 | 18 | closes.forEach((close) => { 19 | const value = atr.nextValue(close, close, close); 20 | 21 | expect(value).toBeDefined(); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /docs/MoneyFlowIndex.md: -------------------------------------------------------------------------------- 1 | # Money Flow Index (MFI) 2 | Money Flow Index (MFI) is a movement indicator used in technical analysis that looks at time and price to measure the trading pressure — buying or selling. It is also called volume-weighted Relative Strength Index (RSI), as it includes volume, unlike RSI, which only incorporates price. 3 | 4 | ## Key features 5 | 6 | - The Money Flow Index (MFI) is a technical indicator that generates overbought or oversold signals using both prices and volume data. 7 | 8 | - An MFI reading above 80 is considered overbought and an MFI reading below 20 is considered oversold, although levels of 90 and 10 are also used as thresholds. 9 | 10 | - A divergence between the indicator and price is noteworthy. For example, if the indicator is rising while the price is falling or flat, the price could start rising. 11 | 12 | ## How to calculate Money Flow Index (MFI) in excel spreadsheet? 13 | 14 | [Download excel sample](../tests/mfi/MoneyFlowIndex.xlsm) -------------------------------------------------------------------------------- /tests/rsi/issue-16.spec.ts: -------------------------------------------------------------------------------- 1 | import { RSI } from '../../src/rsi'; 2 | import { RSI as RSI2 } from 'technicalindicators'; 3 | 4 | let closes = [ 5 | 1.59, 1.65, 1.65, 1.65, 1.65, 1.65, 1.65, 1.65, 1.65, 1.65, 1.65, 1.65, 1.65, 1.65, 1.65, 1.6, 1.6, 1.6, 1.6, 1.6, 6 | 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 7 | 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 8 | ]; 9 | 10 | const EPSILON = 0.003; 11 | 12 | describe('RSI', () => { 13 | it('issue-16: Invalid rsi return when avg is zero', () => { 14 | let rsi = new RSI(14); 15 | const rsi2 = new RSI2({ period: 14, values: [] }); 16 | 17 | closes.forEach((c) => { 18 | const calculated = rsi.nextValue(c); 19 | const vendor = rsi2.nextValue(c); 20 | 21 | if (calculated && vendor) { 22 | expect(Math.abs(calculated - vendor)).toBeLessThan(EPSILON); 23 | } 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/move.ts: -------------------------------------------------------------------------------- 1 | import { percentChange } from './utils'; 2 | import { CircularBuffer } from './providers/circular-buffer'; 3 | 4 | export class Move { 5 | private changes: CircularBuffer; 6 | private prevPrice: number; 7 | private value = 0; 8 | 9 | /** 10 | * Конструктор 11 | * @param period - целочисленное значение от 1 до 12 12 | * @param period - период 13 | */ 14 | constructor(private period: number) { 15 | this.changes = new CircularBuffer(period); 16 | } 17 | 18 | nextValue(close: number) { 19 | if (this.prevPrice) { 20 | const change = percentChange(close, this.prevPrice); 21 | this.calculate(change); 22 | this.prevPrice = close; 23 | 24 | return this.value; 25 | } 26 | 27 | this.prevPrice = close; 28 | } 29 | 30 | calculate(change: number) { 31 | this.value += change; 32 | this.value -= this.changes.push(change); 33 | 34 | return this.value; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/providers/min-value.ts: -------------------------------------------------------------------------------- 1 | import { CircularBuffer } from './circular-buffer'; 2 | import { getMin } from '../utils'; 3 | 4 | /** 5 | * Provider for fast detection highs and lows in period 6 | */ 7 | export class MinProvider { 8 | private lowest: CircularBuffer; 9 | private min = Infinity; 10 | 11 | public constructor(period: number) { 12 | this.lowest = new CircularBuffer(period); 13 | } 14 | 15 | filled() { 16 | return this.lowest.filled; 17 | } 18 | 19 | nextValue(low: number) { 20 | if (this.min > low) { 21 | this.min = low; 22 | } 23 | 24 | const rmMin = this.lowest.push(low); 25 | 26 | // Most perf degrade case 27 | if (rmMin === this.min && low !== this.min) { 28 | this.min = getMin(this.lowest.toArray()); 29 | // console.count('degrade_min'); 30 | } 31 | 32 | return this.min; 33 | } 34 | 35 | momentValue(low: number) { 36 | return this.min < low ? this.min : low; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/ema/ema.spec.ts: -------------------------------------------------------------------------------- 1 | import { EMA } from '../../src/ema'; 2 | import { EMA as EMA2 } from 'technicalindicators'; 3 | import { emaValues, closes } from './excel-data'; 4 | 5 | describe('EMA', () => { 6 | it('Excel Validate', () => { 7 | const ema = new EMA(10); 8 | 9 | closes.forEach((c, idx) => { 10 | const calculated = ema.nextValue(c); 11 | const excel = emaValues[idx]; 12 | 13 | if (!excel && !calculated) { 14 | expect(excel).toEqual(calculated); 15 | } else { 16 | expect(Math.abs(calculated - excel)).toBeLessThan(0.0001); 17 | } 18 | }); 19 | }); 20 | 21 | it('Cross sdk validate', () => { 22 | const ema = new EMA(14); 23 | const ema2 = new EMA2({ period: 14, values: [] }); 24 | 25 | closes.forEach((c) => { 26 | const local = ema.nextValue(c); 27 | const cross = ema2.nextValue(c); 28 | 29 | expect(local).toEqual(cross); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/trendlines/types.ts: -------------------------------------------------------------------------------- 1 | export type LineId = string; 2 | export enum LineEvent { 3 | 'BREAKDOWN', 4 | 'WAIT_SMOOTH', 5 | 'SMOOTH', 6 | 'BOUNCE', 7 | } 8 | 9 | export class Point { 10 | y: number; 11 | x: number; 12 | } 13 | 14 | export interface LineDirective { 15 | condition: 'lt' | 'gt' | 'lgt'; 16 | value: number; 17 | action: string; 18 | lineIndex: number; 19 | } 20 | 21 | export interface Env { 22 | step?: number; // time step in minutes 23 | minLength: number; 24 | minRightLeg?: number; 25 | maxForks?: number; 26 | minLog?: number; 27 | maxLog?: number; 28 | minIsSizeOnRollback?: number; // Минимальный размер тренда при котором вводится максимальный откат 29 | rollbackLength: number; // Устойчивый откат после пробоя линии тренда 30 | forkDurationMin: number; // Лимитное значение минимальной длительности волны 31 | forkDurationMax: number; // Лимитное значение минимальной длительности волны 32 | deltaModel: 1 | 2; // Модели отсчета. 2 - FTM/USDT, 1 - другие 33 | } 34 | -------------------------------------------------------------------------------- /src/providers/max-value.ts: -------------------------------------------------------------------------------- 1 | import { CircularBuffer } from './circular-buffer'; 2 | import { getMax } from '../utils'; 3 | 4 | /** 5 | * Provider for fast detection highs and lows in period 6 | */ 7 | export class MaxProvider { 8 | private highest: CircularBuffer; 9 | private max = -Infinity; 10 | 11 | public constructor(period: number) { 12 | this.highest = new CircularBuffer(period); 13 | } 14 | 15 | filled() { 16 | return this.highest.filled; 17 | } 18 | 19 | nextValue(high: number) { 20 | if (this.max < high) { 21 | this.max = high; 22 | } 23 | 24 | const rmMax = this.highest.push(high); 25 | 26 | // Most perf degrade case 27 | if (rmMax === this.max && high !== this.max) { 28 | // console.count('degrade_max'); 29 | 30 | this.max = getMax(this.highest.toArray()); 31 | } 32 | 33 | return this.max; 34 | } 35 | 36 | momentValue(high: number) { 37 | return this.max > high ? this.max : high; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export function sum(arr: number[]) { 2 | let sum = 0; 3 | let i = arr.length; 4 | 5 | while (i > 0) { 6 | sum += arr[--i]; 7 | } 8 | 9 | return sum; 10 | } 11 | 12 | export const percentChange = (current: number, prev: number) => ((current - prev) / prev) * 100; 13 | export const avg = (arr: number[], period = arr.length) => sum(arr) / period || 0; 14 | export const getMax = (arr: number[]) => { 15 | let max = -Infinity; 16 | let idx = 0; 17 | 18 | for (let i = arr.length - 1; i >= 0; i--) { 19 | const item = arr[i]; 20 | 21 | if (max < item) { 22 | idx = i; 23 | max = item; 24 | } 25 | } 26 | 27 | return max; 28 | }; 29 | export const getMin = (arr: number[]) => { 30 | let min = Infinity; 31 | let idx = 0; 32 | 33 | for (let i = arr.length - 1; i >= 0; i--) { 34 | const item = arr[i]; 35 | 36 | if (min > item) { 37 | idx = i; 38 | min = item; 39 | } 40 | } 41 | 42 | return min; 43 | }; 44 | -------------------------------------------------------------------------------- /tests/roc/roc.spec.ts: -------------------------------------------------------------------------------- 1 | import { ROC } from '../../src/roc'; 2 | import { ROC as ROC2 } from 'technicalindicators'; 3 | import { rocValues, closes } from './excel-data'; 4 | 5 | describe('ROC', () => { 6 | it('Excel Validate', () => { 7 | const ema = new ROC(5); 8 | 9 | closes.forEach((c, idx) => { 10 | const calculated = ema.nextValue(c); 11 | const excel = rocValues[idx]; 12 | 13 | if (!excel && !calculated) { 14 | expect(excel).toEqual(calculated); 15 | } else { 16 | expect(Math.abs(calculated - excel * 100)).toBeLessThan(0.0001); 17 | } 18 | }); 19 | }); 20 | 21 | it('Cross sdk validate', () => { 22 | const roc = new ROC(5); 23 | const roc2 = new ROC2({ period: 5, values: [] }); 24 | 25 | closes.forEach((c) => { 26 | const local = roc.nextValue(c); 27 | const cross = roc2.nextValue(c); 28 | 29 | // console.log(local, cross); 30 | expect(local).toEqual(cross); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/psar/psar.spec.ts: -------------------------------------------------------------------------------- 1 | import { PSAR } from '../../src/psar'; 2 | import { PSAR as PSAR2 } from 'technicalindicators'; 3 | import { lows, highs, closes } from './excel-data'; 4 | 5 | describe('PSAR', () => { 6 | it('Cross sdk validate', () => { 7 | const psar = new PSAR(); 8 | 9 | const local = []; 10 | const cross = PSAR2.calculate({ high: highs, low: lows, step: 0.02, max: 0.2 }); 11 | 12 | for (let i = 0; i < lows.length; i++) { 13 | local.push(psar.nextValue(highs[i], lows[i], closes[i])); 14 | } 15 | 16 | expect(local).toEqual(cross); 17 | }); 18 | it('Cross sdk moment value validate', () => { 19 | const psar = new PSAR(); 20 | 21 | const localMoment = []; 22 | const cross = PSAR2.calculate({ high: highs, low: lows, step: 0.02, max: 0.2 }); 23 | 24 | for (let i = 0; i < lows.length; i++) { 25 | localMoment.push(psar.momentValue(highs[i], lows[i])); 26 | psar.nextValue(highs[i], lows[i], closes[i]); 27 | } 28 | 29 | expect(localMoment).toEqual(cross); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", // Specifies the ESLint parser 3 | "extends": [ 4 | "plugin:prettier/recommended", // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 5 | "prettier" 6 | ], 7 | "parserOptions": { 8 | "ecmaVersion": 2018, // Allows for the parsing of modern ECMAScript features 9 | "sourceType": "module" // Allows for the use of imports 10 | }, 11 | 12 | "rules": { 13 | // note you must disable the base rule as it can report incorrect errors 14 | "indent": ["error", 4, { "SwitchCase": 1 }], 15 | "no-use-before-define": "off", 16 | "no-empty-function": "off", 17 | "@typescript-eslint/no-use-before-define": "off", 18 | "@typescript-eslint/explicit-module-boundary-types": "off", 19 | "@typescript-eslint/explicit-function-return-type": "off", 20 | "@typescript-eslint/no-empty-function": "off", 21 | "rules": { "prettier/prettier": ["error", { "endOfLine": "auto" }] } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/cci/cci.spec.ts: -------------------------------------------------------------------------------- 1 | import { CCI } from '../../src/cci'; 2 | import { CCI as CCI2 } from 'technicalindicators'; 3 | import { cciValues, ohlc } from './excel-data'; 4 | 5 | describe('CCI', () => { 6 | it('Excel Validate', () => { 7 | const cci = new CCI(20); 8 | ohlc.forEach((tick, idx) => { 9 | const calculated = cci.nextValue(tick.h, tick.l, tick.c); 10 | const excel = cciValues[idx]; 11 | 12 | if (!excel && !calculated) { 13 | expect(excel).toEqual(calculated); 14 | } else { 15 | expect(Math.abs(calculated - excel)).toBeLessThan(0.007); 16 | } 17 | }); 18 | }); 19 | 20 | it('Cross sdk validate', () => { 21 | const atr = new CCI(14); 22 | const atr2 = new CCI2({ period: 14, high: [], low: [], close: [] }); 23 | 24 | ohlc.forEach((tick) => { 25 | const local = atr.nextValue(tick.h, tick.l, tick.c); 26 | const cross = atr2.nextValue({ high: tick.h, low: tick.l, close: tick.c }); 27 | 28 | expect(local).toEqual(cross); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /tests/providers/sampler.spec.ts: -------------------------------------------------------------------------------- 1 | import { Sampler } from '../../src/providers/sampler'; 2 | import { SMA } from '../../src/sma'; 3 | 4 | describe('Sampler', () => { 5 | const PERIOD = 6; 6 | 7 | it('SMA samples', () => { 8 | let data = [ 9 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 10 | 30, 11 | ]; 12 | data = data.concat(data.reverse()); 13 | 14 | const sampler = new Sampler(SMA, 3); 15 | const sma1 = new SMA(PERIOD); 16 | const sma2 = new SMA(PERIOD); 17 | const sma3 = new SMA(PERIOD); 18 | 19 | sampler.create(PERIOD); 20 | 21 | data.forEach((item) => { 22 | const sample1 = sma1.nextValue(item); 23 | const sample2 = sample1 !== undefined && sma2.nextValue(sample1); 24 | const sample3 = sample2 !== undefined && sma3.nextValue(sample2); 25 | const samplerValue = sampler.nextValue(item); 26 | 27 | if (sample3 && samplerValue) { 28 | expect(samplerValue).toBe(sample3); 29 | } 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /tests/volume-profile/volume-profile.spec.ts: -------------------------------------------------------------------------------- 1 | import { VolumeProfile } from '../../src/volume-profile'; 2 | import data from './data'; 3 | 4 | describe.skip('Volume Profile', () => { 5 | it('Base working test', () => { 6 | const actual: Record = {}; 7 | const expected = { 8 | '1.4589921875': '===', 9 | '1.464': '===', 10 | '1.471': '==', 11 | '1.477': '===', 12 | '1.487': '==', 13 | '1.49': '===', 14 | }; 15 | 16 | function drawBar(count: number) { 17 | const length = Math.round(count / 100_000); 18 | 19 | return new Array(length).fill('=').join(''); 20 | } 21 | 22 | const vp = new VolumeProfile(4); 23 | 24 | for (const candle of data) { 25 | vp.nextValue(candle); 26 | } 27 | 28 | const vpSession = vp.getSession(data[data.length - 1]); 29 | 30 | vpSession.forEach((volume, price) => { 31 | actual[price] = drawBar(volume); 32 | }); 33 | 34 | console.log(actual); 35 | 36 | expect(actual).toEqual(expected); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /tests/mfi/mfi.spec.ts: -------------------------------------------------------------------------------- 1 | import { MFI } from '../../src/mfi'; 2 | import { MFI as MFICross } from 'technicalindicators'; 3 | import { data, excelData } from './data'; 4 | 5 | describe('MFI', () => { 6 | it('Cross Validate', () => { 7 | const mfi = new MFI(14); 8 | const mfiCross = new MFICross({ period: 14, high: [], low: [], close: [], volume: [] }); 9 | 10 | data.forEach(({ high, low, close, volume }) => { 11 | const calculated = mfi.nextValue(high, low, close, volume); 12 | const sdk = mfiCross.nextValue({ high, low, close, volume }); 13 | 14 | if (sdk && calculated) { 15 | expect(Math.abs(calculated - sdk)).toBeLessThan(0.005); 16 | } 17 | }); 18 | }); 19 | 20 | it('Excel validate', () => { 21 | const mfi = new MFI(14); 22 | 23 | excelData.forEach(({ high, low, close, volume, Mfi }) => { 24 | const calculated = mfi.nextValue(high, low, close, volume); 25 | 26 | if (Mfi && calculated) { 27 | expect(Math.abs(calculated - Mfi)).toBeLessThan(0.00001); 28 | } 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /tests/adx/adx.spec.ts: -------------------------------------------------------------------------------- 1 | import { ADX } from '../../src/adx'; 2 | import { ADX as ADX2 } from 'technicalindicators'; 3 | import { ohlc } from './data'; 4 | 5 | describe('ADX', () => { 6 | it('Cross sdk validate', () => { 7 | const period = 14; 8 | const adx = new ADX(period); 9 | const adx2 = new ADX2({ period, high: [], low: [], close: [] }); 10 | 11 | ohlc.forEach((tick) => { 12 | const localMoment = adx.momentValue(tick.h, tick.l, tick.c); 13 | const local = adx.nextValue(tick.h, tick.l, tick.c); 14 | // @ts-ignore typing error? 15 | const cross = adx2.nextValue({ high: tick.h, low: tick.l, close: tick.c }); 16 | 17 | if (local && cross && localMoment?.adx) { 18 | expect(local.adx).toEqual(cross.adx); 19 | expect(local.mdi).toEqual(cross.mdi); 20 | expect(local.pdi).toEqual(cross.pdi); 21 | expect(localMoment.adx).toEqual(local.adx); 22 | expect(localMoment.mdi).toEqual(local.mdi); 23 | expect(localMoment.pdi).toEqual(local.pdi); 24 | } 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/williams.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Developed by Larry Williams, Williams %R is a momentum indicator that is the inverse of the Fast Stochastic Oscillator. 3 | * Also referred to as %R, Williams %R reflects the level of the close relative to the highest high for the look-back period. 4 | * In contrast, the Stochastic Oscillator reflects the level of the close relative to the lowest low. 5 | * %R corrects for the inversion by multiplying the raw value by -100. As a result, 6 | * the Fast Stochastic Oscillator and Williams %R produce the exact same lines, 7 | * but with different scaling. Williams %R oscillates from 0 to -100; readings from 0 to -20 are considered overbought, 8 | * while readings from -80 to -100 are considered oversold. Unsurprisingly, 9 | * signals derived from the Stochastic Oscillator are also applicable to Williams %R. 10 | */ 11 | export class Williams { 12 | private higherH = 0; 13 | private lowerL = 0; 14 | private filled = false; 15 | 16 | constructor(private period: number) {} 17 | 18 | // formula 19 | // %R = (Highest High - Close)/(Highest High - Lowest Low) * -100 20 | 21 | public nextValue(h: number, l: number, c: number) {} 22 | } 23 | -------------------------------------------------------------------------------- /tests/atr/atr.spec.ts: -------------------------------------------------------------------------------- 1 | import { ATR } from '../../src/atr'; 2 | import { ATR as ATR2 } from 'technicalindicators'; 3 | import { atrValues, ohlc } from './excel-data'; 4 | 5 | describe('ATR', () => { 6 | it('Excel Validate', () => { 7 | const period = 14; 8 | const atr = new ATR(period, 'SMA'); 9 | 10 | ohlc.forEach((tick, idx) => { 11 | const calculated = atr.nextValue(tick.h, tick.l, tick.c); 12 | const excel = atrValues[idx]; 13 | 14 | if (idx > period) { 15 | expect(Math.abs(calculated - excel)).toBeLessThan(0.007); 16 | } 17 | }); 18 | }); 19 | 20 | it('Cross sdk validate', () => { 21 | const period = 14; 22 | const atr = new ATR(period); 23 | const atr2 = new ATR2({ period, high: [], low: [], close: [] }); 24 | 25 | const local = []; 26 | const cross = []; 27 | 28 | ohlc.forEach((tick) => { 29 | local.push(atr.nextValue(tick.h, tick.l, tick.c)); 30 | cross.push(atr2.nextValue({ high: tick.h, low: tick.l, close: tick.c })); 31 | }); 32 | 33 | expect(local).toEqual(cross); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/dc.ts: -------------------------------------------------------------------------------- 1 | import { MaxProvider } from './providers/max-value'; 2 | import { MinProvider } from './providers/min-value'; 3 | 4 | /** 5 | * Donchian channels were developed by Richard Donchian, a pioneer of mechanical trend following systems. 6 | * The two outer bands are plotted as the highest high and lowest low for a set period, 7 | * originally 20 days, with the optional middle band calculated as the average of the two. 8 | */ 9 | export class DC { 10 | private maxProvider: MaxProvider; 11 | private minProvider: MinProvider; 12 | 13 | constructor(period = 20) { 14 | this.maxProvider = new MaxProvider(period); 15 | this.minProvider = new MinProvider(period); 16 | } 17 | 18 | nextValue(high: number, low: number) { 19 | const upper = this.maxProvider.nextValue(high); 20 | const lower = this.minProvider.nextValue(low); 21 | 22 | return { upper, middle: (upper + lower) / 2, lower }; 23 | } 24 | 25 | momentValue(high: number, low: number) { 26 | const upper = this.maxProvider.momentValue(high); 27 | const lower = this.minProvider.momentValue(low); 28 | 29 | return { upper, middle: (upper + lower) / 2, lower }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/stoch-rsi/stoch-rsi.spec.ts: -------------------------------------------------------------------------------- 1 | import { ohlc } from './excel-data'; 2 | import { StochasticRSI } from '../../src/stochastic-rsi'; 3 | import { StochasticRSI as StochasticRSITI } from 'technicalindicators'; 4 | 5 | describe('StochRSI', () => { 6 | it('Cross sdk validate', () => { 7 | const configTI = { stochasticPeriod: 14, rsiPeriod: 14, kPeriod: 3, dPeriod: 3, values: [] as number[] }; 8 | const stochRsi = new StochasticRSI(); 9 | const stochRsi2 = new StochasticRSITI(configTI); 10 | 11 | ohlc.forEach((tick) => { 12 | configTI.values.push(tick.c); 13 | const local = stochRsi.nextValue(tick.c); 14 | //@ts-expect-error incorrect typings 15 | const cross = stochRsi2.nextValue(tick.c); 16 | 17 | if (local?.k && cross?.k) { 18 | //precision issues with technicalindicators setConfig('precision') is not work as expected 19 | expect(Math.abs(local.k - cross.k)).toBeLessThan(0.05); 20 | expect(Math.abs(local.d - cross.d)).toBeLessThan(0.05); 21 | expect(Math.abs(local.stochRsi - cross.stochRSI)).toBeLessThan(0.05); 22 | } 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/smma.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * SMMA (Smoothed Moving Average) is another popular and widely used moving average indicator. 3 | * As all the other moving average indicators, to achieve the goals, the indicator filters 4 | * out the market fluctuations (noises) by averaging the price values of the periods, over which it is calculated. 5 | */ 6 | export class SMMA { 7 | private sum = 0; 8 | private avg = 0; 9 | private filled = false; 10 | private fill = 0; 11 | 12 | constructor(private period: number) {} 13 | 14 | nextValue(value: number) { 15 | if (this.filled) { 16 | this.nextValue = (value: number) => (this.avg = (this.avg * (this.period - 1) + value) / this.period); 17 | return this.nextValue(value); 18 | } 19 | 20 | this.sum += value; 21 | this.fill++; 22 | 23 | if (this.fill === this.period) { 24 | this.filled = true; 25 | this.avg = this.sum / this.period; 26 | 27 | return this.avg; 28 | } 29 | } 30 | 31 | momentValue(value: number) { 32 | if (!this.filled) { 33 | return; 34 | } 35 | 36 | return (this.avg * (this.period - 1) + value) / this.period; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/rsi/rsi.spec.ts: -------------------------------------------------------------------------------- 1 | import { ohlc, rsiValues } from './excel-data'; 2 | import { RSI } from '../../src/rsi'; 3 | import { RSI as RSI2 } from 'technicalindicators'; 4 | 5 | describe('RSI', () => { 6 | it('Excel Validate', () => { 7 | const bb = new RSI(); 8 | const EPSILON = 0.001; 9 | 10 | ohlc.forEach((tick, idx) => { 11 | const calculated = bb.nextValue(tick.c); 12 | const excel = rsiValues[idx]; 13 | 14 | if (!excel && !calculated) { 15 | expect(excel).toEqual(calculated); 16 | } else { 17 | expect(Math.abs(calculated - excel)).toBeLessThan(EPSILON); 18 | } 19 | }); 20 | }); 21 | 22 | it('Cross sdk validate', () => { 23 | const rsi = new RSI(); 24 | const rsi2 = new RSI2({ period: 14, values: [] }); 25 | 26 | ohlc.forEach((tick) => { 27 | const local = rsi.nextValue(tick.c); 28 | const cross = rsi2.nextValue(tick.c); 29 | 30 | if (!local || !cross) { 31 | expect(local).toEqual(cross); 32 | } else { 33 | expect(Math.abs(local - cross)).toBeLessThan(0.01); 34 | } 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /tests/williams/data.ts: -------------------------------------------------------------------------------- 1 | export const data = [ 2 | { h: 127.01, l: 125.36 }, 3 | { h: 127.62, l: 126.16 }, 4 | { h: 126.59, l: 124.93 }, 5 | { h: 127.35, l: 126.09 }, 6 | { h: 128.17, l: 126.82 }, 7 | { h: 128.43, l: 126.48 }, 8 | { h: 127.37, l: 126.03 }, 9 | { h: 126.42, l: 124.83 }, 10 | { h: 126.9, l: 126.39 }, 11 | { h: 126.85, l: 125.72 }, 12 | { h: 125.65, l: 124.56 }, 13 | { h: 125.72, l: 124.57 }, 14 | { h: 127.16, l: 125.07 }, 15 | { h: 127.72, l: 126.86 }, 16 | { h: 127.69, l: 126.63 }, 17 | { h: 128.22, l: 126.8 }, 18 | { h: 128.27, l: 126.71 }, 19 | { h: 128.09, l: 126.8 }, 20 | { h: 128.27, l: 126.13 }, 21 | { h: 127.74, l: 125.92 }, 22 | { h: 128.77, l: 126.99 }, 23 | { h: 129.29, l: 127.81 }, 24 | { h: 130.06, l: 128.47 }, 25 | { h: 129.12, l: 128.06 }, 26 | { h: 129.29, l: 127.61 }, 27 | { h: 128.47, l: 127.6 }, 28 | { h: 128.09, l: 127.0 }, 29 | { h: 128.65, l: 126.9 }, 30 | { h: 129.14, l: 127.49 }, 31 | { h: 128.64, l: 127.4 }, 32 | ]; 33 | 34 | export const expected = [ 35 | -29.56, -32.39, -10.8, -34.19, -18.25, -35.48, -25.47, -1.42, -29.9, -26.94, -26.58, -38.77, -39.04, -59.61, -59.61, 36 | -33.17, -43.27, 37 | ]; 38 | -------------------------------------------------------------------------------- /src/wema.ts: -------------------------------------------------------------------------------- 1 | import { SMA } from './sma'; 2 | 3 | /** 4 | * The WEMA (Wilder's Smoothed Moving Average) is a powerful indicator based on the Simple Moving Average indicator. 5 | * The Simple Moving Average (SMA) indicator is useful to identify the start and reversal of a trend. 6 | */ 7 | export class WEMA { 8 | private smooth: number; 9 | private wema: number; 10 | private sma: SMA; 11 | 12 | constructor(private period: number) { 13 | this.smooth = 1 / this.period; 14 | this.sma = new SMA(this.period); 15 | } 16 | 17 | /** 18 | * Get next value for closed candle hlc 19 | * affect all next calculations 20 | */ 21 | nextValue(value: number) { 22 | if (!this.wema) { 23 | return (this.wema = this.sma.nextValue(value)); 24 | } 25 | 26 | return (this.wema = (value - this.wema) * this.smooth + this.wema); 27 | } 28 | 29 | /** 30 | * Get next value for non closed (tick) candle hlc 31 | * does not affect any next calculations 32 | */ 33 | momentValue(value: number) { 34 | if (!this.wema) { 35 | return this.sma.momentValue(value); 36 | } 37 | 38 | return (value - this.wema) * this.smooth + this.wema; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/truerange/truerange.spec.ts: -------------------------------------------------------------------------------- 1 | import { ohlc, trueRangeData, averageTrueRrange } from './data'; 2 | import { getTrueRange } from '../../src/providers/true-range'; 3 | import { ATR } from '../../src/atr'; 4 | 5 | describe('True Range', () => { 6 | it('Excel validation test', () => { 7 | let prevClose: number; 8 | 9 | ohlc.forEach((tick, idx) => { 10 | const calculated = getTrueRange(tick.h, tick.l, prevClose); 11 | const excel = trueRangeData[idx]; 12 | 13 | if (excel && calculated) { 14 | const diff = Math.abs(calculated - excel); 15 | 16 | expect(diff).toBeLessThan(0.00001); 17 | } 18 | 19 | prevClose = tick.c; 20 | }); 21 | }); 22 | 23 | it('ATR for true range validation test', () => { 24 | const atr = new ATR(10, 'SMA'); 25 | 26 | ohlc.forEach((tick, idx) => { 27 | const calculated = atr.nextValue(tick.h, tick.l, tick.c); 28 | const excel = averageTrueRrange[idx]; 29 | 30 | if (excel && calculated) { 31 | const diff = Math.abs(calculated - excel); 32 | 33 | expect(diff).toBeLessThan(0.00001); 34 | } 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/rma.ts: -------------------------------------------------------------------------------- 1 | import { SMA } from './sma'; 2 | 3 | /** 4 | * Relative Moving Average adds more weight to recent data (and gives less importance to older data). 5 | * This makes the RMA similar to the Exponential Moving Average, although it’s somewhat slower to respond than an EMA is. 6 | */ 7 | export class RMA { 8 | private prevValue: number; 9 | private alpha: number; 10 | private sma: SMA; 11 | 12 | constructor(private period: number = 14) { 13 | this.alpha = 1 / period; 14 | this.sma = new SMA(this.period); 15 | } 16 | 17 | nextValue(value: number) { 18 | if (!this.prevValue) { 19 | this.prevValue = this.sma.nextValue(value); 20 | } else { 21 | this.prevValue = this.alpha * value + (1 - this.alpha) * this.prevValue; 22 | 23 | this.nextValue = (value: number) => { 24 | this.prevValue = this.alpha * value + (1 - this.alpha) * this.prevValue; 25 | 26 | return this.prevValue; 27 | }; 28 | } 29 | 30 | return this.prevValue; 31 | } 32 | 33 | momentValue(value: number) { 34 | if (this.prevValue) { 35 | return this.alpha * value + (1 - this.alpha) * this.prevValue; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/providers/percent-rank.ts: -------------------------------------------------------------------------------- 1 | import { CircularBuffer } from './circular-buffer'; 2 | /** 3 | * Returns the percentile of a value. Returns the same values as the Excel PERCENTRANK and PERCENTRANK.INC functions. 4 | */ 5 | export class PercentRank { 6 | private values: CircularBuffer; 7 | private fill = 0; 8 | 9 | constructor(private period: number) { 10 | this.values = new CircularBuffer(period); 11 | } 12 | 13 | public nextValue(value: number) { 14 | this.values.push(value); 15 | this.fill++; 16 | 17 | if (this.fill === this.period) { 18 | this.nextValue = (value: number) => { 19 | const result = this.calc(value); 20 | 21 | this.values.push(value); 22 | 23 | return result; 24 | }; 25 | 26 | this.momentValue = this.calc; 27 | 28 | return this.calc(value); 29 | } 30 | } 31 | 32 | public momentValue(value: number): number { 33 | return; 34 | } 35 | 36 | private calc(value: number) { 37 | let count = 0; 38 | 39 | this.values.toArray().forEach((item) => { 40 | if (item <= value) count++; 41 | }); 42 | 43 | return (count / this.period) * 100; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/sma.ts: -------------------------------------------------------------------------------- 1 | import { CircularBuffer } from './providers/circular-buffer'; 2 | // console.log(sma([1, 2, 3, 4, 5, 6, 7, 8, 9], 4)); 3 | //=> [ '2.50', '3.50', '4.50', '5.50', '6.50', '7.50' ] 4 | //=> │ │ │ │ │ └─(6+7+8+9)/4 5 | //=> │ │ │ │ └─(5+6+7+8)/4 6 | //=> │ │ │ └─(4+5+6+7)/4 7 | //=> │ │ └─(3+4+5+6)/4 8 | //=> │ └─(2+3+4+5)/4 9 | //=> └─(1+2+3+4)/4 10 | 11 | export class SMA { 12 | private circular: CircularBuffer; 13 | private sum = 0; 14 | 15 | constructor(private period: number) { 16 | this.circular = new CircularBuffer(period); 17 | } 18 | 19 | nextValue(value: number) { 20 | this.circular.push(value); 21 | this.sum += value; 22 | 23 | if (!this.circular.filled) { 24 | return; 25 | } 26 | 27 | this.nextValue = (value: number) => { 28 | this.sum = this.sum - this.circular.push(value) + value; 29 | 30 | return this.sum / this.period; 31 | }; 32 | 33 | this.momentValue = (value: number) => { 34 | return (this.sum - this.circular.peek() + value) / this.period; 35 | }; 36 | 37 | return this.sum / this.period; 38 | } 39 | 40 | momentValue(value: number): number { 41 | return; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/cci.ts: -------------------------------------------------------------------------------- 1 | import { SMA } from './sma'; 2 | import { MeanDeviationProvider } from './providers/mean-deviation'; 3 | 4 | /** 5 | * The CCI, or Commodity Channel Index, was developed by Donald Lambert, 6 | * a technical analyst who originally published the indicator in Commodities magazine (now Futures) 7 | * in 1980.1 Despite its name, the CCI can be used in any market and is not just for commodities 8 | */ 9 | export class CCI { 10 | private md: MeanDeviationProvider; 11 | private sma: SMA; 12 | 13 | constructor(period = 20) { 14 | this.md = new MeanDeviationProvider(period); 15 | this.sma = new SMA(period); 16 | } 17 | 18 | nextValue(high: number, low: number, close: number) { 19 | const typicalPrice = (high + low + close) / 3; 20 | const average = this.sma.nextValue(typicalPrice); 21 | const meanDeviation = this.md.nextValue(typicalPrice, average); 22 | 23 | return meanDeviation && (typicalPrice - average) / (0.015 * meanDeviation); 24 | } 25 | 26 | momentValue(high: number, low: number, close: number) { 27 | const typicalPrice = (high + low + close) / 3; 28 | const average = this.sma.momentValue(typicalPrice); 29 | const meanDeviation = this.md.momentValue(typicalPrice, average); 30 | 31 | return meanDeviation && (typicalPrice - average) / (0.015 * meanDeviation); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/ao/ao.spec.ts: -------------------------------------------------------------------------------- 1 | import { AO } from '../../src/ao'; 2 | import { AwesomeOscillator as AO2 } from 'technicalindicators'; 3 | import { aoValues, lows, highs } from './excel-data'; 4 | 5 | describe('AO', () => { 6 | it('Excel Validate', () => { 7 | const ao = new AO(); 8 | 9 | lows.forEach((l, idx) => { 10 | const calculated = ao.nextValue(highs[idx], l); 11 | const excel = aoValues[idx]; 12 | 13 | if (!excel && !calculated) { 14 | expect(excel).toEqual(undefined); 15 | } else { 16 | expect(Math.abs(calculated - excel)).toBeLessThan(0.0001); 17 | } 18 | }); 19 | }); 20 | 21 | it('Cross sdk validate', () => { 22 | const ao = new AO(5, 34); 23 | const ao2 = new AO2({ fastPeriod: 5, high: [], low: [], slowPeriod: 34 }); 24 | 25 | lows.forEach((l, idx) => { 26 | const priceData = { 27 | high: highs[idx], 28 | low: l, 29 | }; 30 | 31 | const local = ao.nextValue(priceData.high, priceData.low); 32 | const cross = ao2.nextValue(priceData); 33 | 34 | if (!local || !cross) { 35 | expect(local).toEqual(cross); 36 | } else { 37 | expect(Math.abs(local - cross)).toBeLessThan(0.000001); 38 | } 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/providers/gain.ts: -------------------------------------------------------------------------------- 1 | import { SMMA } from '../smma'; 2 | 3 | export class AvgChangeProvider { 4 | private avgGain: SMMA; 5 | private avgLoss: SMMA; 6 | private prev: number; 7 | 8 | constructor(period: number) { 9 | this.avgGain = new SMMA(period); 10 | this.avgLoss = new SMMA(period); 11 | } 12 | 13 | nextValue(value: number) { 14 | const change = value - this.prev; 15 | 16 | if (!this.prev) { 17 | this.prev = value; 18 | return; 19 | } 20 | 21 | const isPositive = change > 0; 22 | const isNegative = change < 0; 23 | const localGain = isPositive ? change : 0; 24 | const localLoss = isNegative ? change : 0; 25 | const upAvg = this.avgGain.nextValue(localGain); 26 | const downAvg = this.avgLoss.nextValue(localLoss); 27 | 28 | this.prev = value; 29 | 30 | return { upAvg, downAvg }; 31 | } 32 | 33 | momentValue(value: number) { 34 | const change = value - this.prev; 35 | const isPositive = change > 0; 36 | const isNegative = change < 0; 37 | const localGain = isPositive ? change : 0; 38 | const localLoss = isNegative ? change : 0; 39 | const upAvg = this.avgGain.momentValue(localGain); 40 | const downAvg = this.avgLoss.momentValue(localLoss); 41 | 42 | return { upAvg, downAvg }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ewma.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The Exponentially Weighted Moving Average (EWMA) is a quantitative or statistical measure used to model or describe 3 | * a time series. The EWMA is widely used in finance, the main applications being technical analysis and volatility modeling. 4 | * The moving average is designed as such that older observations are given lower weights. 5 | * The weights fall exponentially as the data point gets older – hence the name exponentially weighted. 6 | * The only decision a user of the EWMA must make is the parameter alpha. 7 | * The parameter decides how important the current observation is in the calculation of the EWMA. 8 | * The higher the value of alpha, the more closely the EWMA tracks the original time series. 9 | * @param alpha must be from 0 to 1 10 | */ 11 | export class EWMA { 12 | private prevValue: number; 13 | private filled = false; 14 | 15 | constructor(private alpha = 0.2) {} 16 | 17 | nextValue(value: number) { 18 | const ewma = this.alpha * value + (1 - this.alpha) * (this.prevValue || 1); 19 | this.filled = this.filled || this.prevValue !== undefined; 20 | this.prevValue = ewma; 21 | 22 | if (this.filled) { 23 | return ewma; 24 | } 25 | } 26 | 27 | momentValue(value: number) { 28 | if (this.filled) { 29 | return this.alpha * value + (1 - this.alpha) * (this.prevValue || 1); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/wws.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The Welles Wilder's Smoothing Average (WWS) was developed by J. Welles Wilder, Jr. and is part of the Wilder's RSI indicator implementation. 3 | * This indicator smoothes price movements to help you identify and spot bullish and bearish trends. 4 | */ 5 | export class WWS { 6 | private prevValue: number = 0; 7 | private sumCount = 1; 8 | 9 | constructor(private period: number) {} 10 | 11 | /** 12 | * Get next value for closed candle hlc 13 | * affect all next calculations 14 | */ 15 | nextValue(value: number) { 16 | if (this.sumCount < this.period) { 17 | this.prevValue += value; 18 | this.sumCount++; 19 | 20 | return; 21 | } 22 | 23 | if (this.sumCount === this.period) { 24 | this.prevValue += value; 25 | this.sumCount++; 26 | 27 | this.nextValue = (value: number) => 28 | (this.prevValue = this.prevValue - this.prevValue / this.period + value); 29 | 30 | return this.prevValue; 31 | } 32 | } 33 | 34 | /** 35 | * Get next value for non closed (tick) candle hlc 36 | * does not affect any next calculations 37 | */ 38 | momentValue(value: number) { 39 | if (this.sumCount < this.period) { 40 | return; 41 | } 42 | 43 | return this.prevValue - this.prevValue / this.period + value; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/providers/correlation.ts: -------------------------------------------------------------------------------- 1 | import { SMA } from '../sma'; 2 | import { CircularBuffer } from './circular-buffer'; 3 | 4 | export class Correlation { 5 | private pricesX: CircularBuffer; 6 | private pricesY: CircularBuffer; 7 | private filled: boolean; 8 | private SMAx: SMA; 9 | private SMAy: SMA; 10 | private SMAxValue: number; 11 | private SMAyValue: number; 12 | 13 | constructor(public period: number) { 14 | this.SMAx = new SMA(this.period); 15 | this.SMAy = new SMA(this.period); 16 | this.pricesX = new CircularBuffer(this.period); 17 | this.pricesY = new CircularBuffer(this.period); 18 | } 19 | 20 | nextValue(priceX: number, priceY: number) { 21 | this.pricesX.push(priceX); 22 | this.pricesY.push(priceY); 23 | 24 | this.SMAxValue = this.SMAx.nextValue(priceX); 25 | this.SMAyValue = this.SMAy.nextValue(priceY); 26 | 27 | let SSxy = 0; 28 | let SSxx = 0; 29 | let SSyy = 0; 30 | 31 | for (let i = 0; i < this.period; i++) { 32 | const xPrice = this.pricesX.toArray()[i]; 33 | const yPrice = this.pricesY.toArray()[i]; 34 | 35 | SSxy += (xPrice - this.SMAxValue) * (yPrice - this.SMAyValue); 36 | SSxx += (xPrice - this.SMAxValue) ** 2; 37 | SSyy += (yPrice - this.SMAyValue) ** 2; 38 | } 39 | 40 | return SSxy / Math.sqrt(SSxx * SSyy); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/lwma.ts: -------------------------------------------------------------------------------- 1 | import { sum } from './utils'; 2 | 3 | /** 4 | * A linearly weighted moving average (LWMA) is a moving average calculation that more heavily weights recent price data. 5 | * The most recent price has the highest weighting, and each prior price has progressively less weight. 6 | * The weights drop in a linear fashion. 7 | * LWMAs are quicker to react to price changes than simple moving averages (SMA) and exponential moving averages (EMA). 8 | */ 9 | export class LWMA { 10 | // Circular buffer ned foreach or reduce for that case 11 | private arr: number[] = []; 12 | private filled = false; 13 | private devider = 0; 14 | private lastSum = 0; 15 | 16 | constructor(private period: number) { 17 | this.devider = sum(Array.from(Array(this.period).keys()).map((i) => i + 1)); 18 | } 19 | 20 | nextValue(value: number) { 21 | this.filled = this.filled || this.arr.length === this.period; 22 | this.arr.push(value); 23 | 24 | if (this.filled) { 25 | this.arr.shift(); 26 | return this.arr.reduce((sum, value, idx) => sum + value * (idx + 1), 0) / this.devider; 27 | } 28 | } 29 | 30 | momentValue(value: number) { 31 | if (this.filled) { 32 | const arr = this.arr.slice(1); 33 | 34 | arr.push(value); 35 | return arr.reduce((sum, value, idx) => sum + value * (idx + 1), 0) / this.devider; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/providers/mean-deviation.ts: -------------------------------------------------------------------------------- 1 | import { CircularBuffer } from './circular-buffer'; 2 | export class MeanDeviationProvider { 3 | private values: CircularBuffer; 4 | 5 | constructor(private period: number) { 6 | this.values = new CircularBuffer(period); 7 | } 8 | 9 | nextValue(typicalPrice: number, average?: number) { 10 | if (!average) { 11 | this.values.push(typicalPrice); 12 | return void 0; 13 | } 14 | 15 | this.nextValue = this.pureNextValue; 16 | this.momentValue = this.pureMomentValue; 17 | 18 | return this.pureNextValue(typicalPrice, average); 19 | } 20 | 21 | momentValue(typicalPrice: number, average?: number) { 22 | return void 0; 23 | } 24 | 25 | private pureNextValue(typicalPrice: number, average: number) { 26 | this.values.push(typicalPrice); 27 | 28 | return this.values.toArray().reduce((acc, value) => acc + this.positiveDelta(average, value), 0) / this.period; 29 | } 30 | 31 | private pureMomentValue(typicalPrice: number, average: number) { 32 | const rm = this.values.push(typicalPrice); 33 | const mean = this.values.toArray().reduce((acc, value) => acc + this.positiveDelta(average, value), 0); 34 | 35 | this.values.pushback(rm); 36 | 37 | return mean / this.period; 38 | } 39 | 40 | private positiveDelta(a: number, b: number) { 41 | return a > b ? a - b : b - a; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/macd/macd.spec.ts: -------------------------------------------------------------------------------- 1 | import { MACD as MACD2 } from 'technicalindicators'; 2 | import { macdValues, closes } from './excel-data'; 3 | import { MACD } from '../../src/macd'; 4 | 5 | describe('MACD', () => { 6 | it('Excel Validate', () => { 7 | const macd = new MACD(); 8 | const EPSILON = 0.001; 9 | 10 | closes.forEach((c, idx) => { 11 | const calculated = macd.nextValue(c); 12 | const excel = macdValues[idx]; 13 | 14 | expect(Math.abs((calculated?.macd || 0) - (excel?.macd || 0))).toBeLessThan(EPSILON); 15 | expect(Math.abs((calculated?.histogram || 0) - (excel?.histogram || 0))).toBeLessThan(EPSILON); 16 | expect(Math.abs((calculated?.signal || 0) - (excel?.signal || 0))).toBeLessThan(EPSILON); 17 | }); 18 | }); 19 | 20 | it('Cross sdk validate', () => { 21 | const macd = new MACD(); 22 | const macd2 = new MACD2({ 23 | values: [], 24 | SimpleMAOscillator: false, 25 | SimpleMASignal: false, 26 | fastPeriod: 12, 27 | slowPeriod: 26, 28 | signalPeriod: 9, 29 | }); 30 | 31 | closes.forEach((tick) => { 32 | const local = macd.nextValue(tick); 33 | const cross = macd2.nextValue(tick); 34 | 35 | expect(local?.macd).toEqual(cross?.MACD); 36 | expect(local?.histogram).toEqual(cross?.histogram); 37 | expect(local?.signal).toEqual(cross?.signal); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/bands.ts: -------------------------------------------------------------------------------- 1 | import { SMA } from './sma'; 2 | import { StandardDeviation } from './providers/standard-deviation'; 3 | export class BollingerBands { 4 | private sd: StandardDeviation; 5 | private sma: SMA; 6 | private fill = 0; 7 | 8 | constructor(private period = 20, private stdDev: number = 2) { 9 | this.sma = new SMA(period); 10 | this.sd = new StandardDeviation(period); 11 | } 12 | 13 | nextValue(close: number) { 14 | const middle = this.sma.nextValue(close); 15 | const sd = this.sd.nextValue(close, middle); 16 | 17 | this.fill++; 18 | 19 | if (this.fill !== this.period) { 20 | return; 21 | } 22 | 23 | const lower = middle - this.stdDev * sd; 24 | const upper = middle + this.stdDev * sd; 25 | 26 | this.nextValue = (close: number) => { 27 | const middle = this.sma.nextValue(close); 28 | const sd = this.sd.nextValue(close, middle); 29 | const lower = middle - this.stdDev * sd; 30 | const upper = middle + this.stdDev * sd; 31 | 32 | return { lower, middle, upper }; 33 | }; 34 | 35 | return { lower, middle, upper }; 36 | } 37 | 38 | momentValue(close: number) { 39 | const middle = this.sma.momentValue(close); 40 | const sd = this.sd.momentValue(close, middle); 41 | const lower = middle - this.stdDev * sd; 42 | const upper = middle + this.stdDev * sd; 43 | 44 | return { lower, middle, upper }; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/roc.ts: -------------------------------------------------------------------------------- 1 | import { CircularBuffer } from './providers/circular-buffer'; 2 | /** 3 | * The Rate-of-Change (ROC) indicator, which is also referred to as simply Momentum, 4 | * is a pure momentum oscillator that measures the percent change in price from one period to the next. 5 | * The ROC calculation compares the current price with the price “n” periods ago. 6 | * The plot forms an oscillator that fluctuates above and below the zero line as the Rate-of-Change moves from positive to negative. 7 | * As a momentum oscillator, ROC signals include centerline crossovers, divergences and overbought-oversold readings. 8 | * Divergences fail to foreshadow reversals more often than not, so this article will forgo a detailed discussion on them. 9 | * Even though centerline crossovers are prone to whipsaw, especially short-term, 10 | * these crossovers can be used to identify the overall trend. 11 | * Identifying overbought or oversold extremes comes naturally to the Rate-of-Change oscillator. 12 | **/ 13 | export class ROC { 14 | private values: CircularBuffer; 15 | 16 | constructor(period = 5) { 17 | this.values = new CircularBuffer(period); 18 | } 19 | 20 | nextValue(value: number) { 21 | const outed = this.values.push(value); 22 | 23 | if (outed) { 24 | return ((value - outed) / outed) * 100; 25 | } 26 | } 27 | 28 | momentValue(value: number) { 29 | const outed = this.values.peek(); 30 | 31 | if (outed) { 32 | return ((value - outed) / outed) * 100; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /docs/UniLevel.md: -------------------------------------------------------------------------------- 1 | # UniLevel (Universal Level) 2 | 3 | ![universal level indicator](https://github.com/debut-js/Indicators/blob/master/docs/.github/unilevel.png) 4 | 5 | Utility for universal construction of levels. It is used, for example, to plot the levels of the SCI, RSI, and other oscillators balanced relative to the zero point (from -N to +N). 6 | 7 | # Examples 8 | 9 | ## Streaming calculation using nextValue 10 | 11 | ```typescript 12 | import { EMA, UniLevel, CCI } from '@debut/indicators'; 13 | 14 | // Create rsi 15 | const cci = new CCI(20); 16 | 17 | // EMA smoothed universal level constructor 18 | const levels = new UniLevel(0.99, EMA, 3, 2, 0); 19 | const period = 12; 20 | 21 | // Create EMA with period `period` for working with levels 22 | levels.create(period); 23 | 24 | // ohlc - historical candle open/high/low/close/volume format {o: number, h: number, l: number, c: number, v: number, time: number } 25 | // get this from your broker history 26 | 27 | ohlc.forEach(({ h, l, c }) => { 28 | const cciValue = cci.nextValue(h, l, c); 29 | const [upper, lower ] = levels.nextValue(cciValue); 30 | 31 | console.log(upper, lower); // Upper and lower levels for cci oscillator 32 | }); 33 | ``` 34 | 35 | ## Immediate calculation using momentValue 36 | 37 | ```typescript 38 | // Getted new tick from stream 39 | const { h, l, c } = tick; 40 | const cciValue = cci.momentValue(h, l, c); 41 | const [upper, lower ] = levels.momentValue(cciValue); 42 | 43 | console.log(upper, lower); // Upper and lower levels for cci oscillator for current tick 44 | }); 45 | 46 | ``` 47 | -------------------------------------------------------------------------------- /src/ac.ts: -------------------------------------------------------------------------------- 1 | import { SMA } from './sma'; 2 | import { AO } from './ao'; 3 | 4 | /** 5 | The Accelerator Oscillator (AC indicator) is a technical analysis tool that helps 6 | to gauge the momentum in the market. It also helps to predict when the momentum will change. 7 | */ 8 | 9 | export class AC { 10 | private sma: SMA; 11 | private ao: AO; 12 | private smaValue: number; 13 | private aoValue: number; 14 | 15 | constructor() { 16 | this.sma = new SMA(5); 17 | this.ao = new AO(); 18 | } 19 | 20 | nextValue(high: number, low: number) { 21 | this.aoValue = this.ao.nextValue(high, low); 22 | 23 | if (this.aoValue === undefined) { 24 | return; 25 | } 26 | 27 | this.smaValue = this.sma.nextValue(this.aoValue); 28 | 29 | if (this.smaValue === undefined) { 30 | return; 31 | } 32 | 33 | // Performance hack with method overrides speed up +30_000 op/sec 34 | this.nextValue = (high: number, low: number) => { 35 | this.aoValue = this.ao.nextValue(high, low); 36 | this.smaValue = this.sma.nextValue(this.aoValue); 37 | 38 | return this.aoValue - this.smaValue; 39 | }; 40 | 41 | // Performance hack with method overrides 42 | this.momentValue = (high: number, low: number) => { 43 | return this.ao.momentValue(high, low) - this.sma.momentValue(this.aoValue); 44 | }; 45 | 46 | return this.aoValue - this.smaValue; 47 | } 48 | 49 | momentValue(high: number, low: number) { 50 | return; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/ema.ts: -------------------------------------------------------------------------------- 1 | import { SMA } from './sma'; 2 | 3 | /** 4 | * An exponential moving average (EMA) is a type of moving average (MA) 5 | * that places a greater weight and significance on the most recent data points. 6 | * The exponential moving average is also referred to as the exponentially weighted moving average. 7 | * An exponentially weighted moving average reacts more significantly to recent price changes 8 | * than a simple moving average (SMA), which applies an equal weight to all observations in the period. 9 | */ 10 | export class EMA { 11 | private smooth: number; 12 | private ema: number; 13 | private sma: SMA; 14 | 15 | constructor(private period: number) { 16 | this.smooth = 2 / (this.period + 1); 17 | this.sma = new SMA(this.period); 18 | } 19 | 20 | /** 21 | * Get next value for closed candle hlc 22 | * affect all next calculations 23 | */ 24 | nextValue(value: number) { 25 | const sma = this.sma.nextValue(value); 26 | 27 | if (sma) { 28 | this.ema = sma; 29 | this.nextValue = (value: number) => { 30 | return (this.ema = (value - this.ema) * this.smooth + this.ema); 31 | }; 32 | this.momentValue = (value: number) => { 33 | return (value - this.ema) * this.smooth + this.ema; 34 | }; 35 | } 36 | 37 | return sma; 38 | } 39 | 40 | /** 41 | * Get next value for non closed (tick) candle hlc 42 | * does not affect any next calculations 43 | */ 44 | momentValue(value: number): number { 45 | return; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/bbands/bbands.spec.ts: -------------------------------------------------------------------------------- 1 | import { BollingerBands } from '../../src/bands'; 2 | import { BollingerBands as BollingerBands2 } from 'technicalindicators'; 3 | import { bbandsValues, ohlc } from './excel-data'; 4 | 5 | describe('Bollinger Bands', () => { 6 | it('Excel Validate', () => { 7 | const bb = new BollingerBands(20); 8 | const EPSILON = 0.009; 9 | 10 | ohlc.forEach((tick, idx) => { 11 | const calculated = bb.nextValue(tick.c); 12 | const excel = bbandsValues[idx]; 13 | 14 | if (!excel && !calculated) { 15 | expect(excel).toEqual(calculated); 16 | } else { 17 | expect(Math.abs(calculated.upper - excel.upper)).toBeLessThan(EPSILON); 18 | expect(Math.abs(calculated.middle - excel.middle)).toBeLessThan(EPSILON); 19 | expect(Math.abs(calculated.lower - excel.lower)).toBeLessThan(EPSILON); 20 | } 21 | }); 22 | }); 23 | 24 | it('Cross sdk validate', () => { 25 | const bb1 = new BollingerBands(14); 26 | const bb2 = new BollingerBands2({ period: 14, stdDev: 2, values: [] }); 27 | 28 | ohlc.forEach((tick) => { 29 | const local = bb1.nextValue(tick.c); 30 | const cross = bb2.nextValue(tick.c); 31 | 32 | expect(Math.abs((local?.lower || 0) - (cross?.lower || 0))).toBeLessThan(0.000001); 33 | expect(Math.abs((local?.middle || 0) - (cross?.middle || 0))).toBeLessThan(0.000001); 34 | expect(Math.abs((local?.upper || 0) - (cross?.upper || 0))).toBeLessThan(0.000001); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/ao.ts: -------------------------------------------------------------------------------- 1 | import { SMA } from './sma'; 2 | 3 | /** 4 | Awesome Oscillator (AO) is an indicator that is non-limiting oscillator, 5 | providing insight into the weakness or the strength of a stock. 6 | The Awesome Oscillator is used to measure market momentum and 7 | to affirm trends or to anticipate possible reversals. 8 | */ 9 | 10 | export class AO { 11 | private smaSlow: SMA; 12 | private smaFast: SMA; 13 | private smaSlowValue: number; 14 | private smaFastValue: number; 15 | 16 | constructor(fastPeriod = 5, slowPeriod = 34) { 17 | this.smaSlow = new SMA(slowPeriod); 18 | this.smaFast = new SMA(fastPeriod); 19 | } 20 | 21 | nextValue(high: number, low: number) { 22 | this.smaSlowValue = this.smaSlow.nextValue((high + low) / 2); 23 | this.smaFastValue = this.smaFast.nextValue((high + low) / 2); 24 | 25 | if (this.smaSlowValue === undefined || this.smaFastValue === undefined) { 26 | return; 27 | } 28 | 29 | this.nextValue = (high: number, low: number) => { 30 | this.smaSlowValue = this.smaSlow.nextValue((high + low) / 2); 31 | this.smaFastValue = this.smaFast.nextValue((high + low) / 2); 32 | 33 | return this.smaFastValue - this.smaSlowValue; 34 | }; 35 | 36 | this.momentValue = (high: number, low: number) => { 37 | return this.smaFast.momentValue((high + low) / 2) - this.smaSlow.momentValue((high + low) / 2); 38 | }; 39 | 40 | return this.smaFastValue - this.smaSlowValue; 41 | } 42 | 43 | momentValue(high: number, low: number): number { 44 | return; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /docs/AverageTrueRange.md: -------------------------------------------------------------------------------- 1 | # Average True Range (ATR) 2 | The Average True Range (ATR) is a measure of market volatility. It calculates the average range between the high and low prices over a specified period. This indicator helps traders gauge the strength of a trend and the level of price volatility, which can be crucial for making informed trading decisions. 3 | 4 | # Examples 5 | 6 | ## Streaming calculation using nextValue 7 | 8 | 9 | ```javascript 10 | import { ATR } from '@debut/indicators'; 11 | 12 | const period = 5; 13 | const atr = new ATR(period, 'WEMA'); 14 | 15 | // ohlc - historical candle open/high/low/close/volume format {o: number, h: number, l: number, c: number, v: number, time: number } 16 | // get this from your broker history 17 | 18 | ohlc.forEach(({ o, h, l, c, v}) => { 19 | const atrValue = atr.nextValue(h, l, c); 20 | 21 | console.log(atrValue); // Average True Range calculated value for current candle 22 | }); 23 | ``` 24 | 25 | ## Immediate calculation using momentValue 26 | 27 | ```javascript 28 | import { ATR } from '@debut/indicators'; 29 | 30 | const period = 5; 31 | const atr = new ATR(period, 'WEMA'); 32 | 33 | // Last tick for now, getted from broker websocket ticks stream 34 | const lastTick = { o: 12, h: 14, l: 13, c: 14, v: 156, time: 1642694284515 }; 35 | const atrValue = atr.momentValue(h, l); 36 | console.log(atrValue); // Average True Range calculated value for current tick 37 | 38 | // When candle closed call nextValue for candle data 39 | atr.nextValue(...); 40 | ``` 41 | 42 | ## How to calculate Average True Range (ATR) in excel spreadsheet? 43 | 44 | [Download excel sample](../tests/atr/Average-True-Range.xlsx) -------------------------------------------------------------------------------- /tests/providers/standard-deviation.spec.ts: -------------------------------------------------------------------------------- 1 | import { StandardDeviation } from '../../src/providers/standard-deviation'; 2 | import { SMA } from '../../src/sma'; 3 | import { SD } from 'technicalindicators'; 4 | 5 | export const closes = [ 6 | 1975, 1984.65, 1983.3, 1986.55, 1987.8, 1986.75, 1989, 1990.1, 1992.7, 1997.35, 1995.6, 1987, 1986.1, 1982, 1979.95, 7 | 1983.95, 1985, 1983.15, 1983.5, 1983.95, 1980.95, 1984.1, 1984.9, 1985.85, 1985.95, 1985.95, 1985.25, 1984.35, 8 | 1985.2, 1983.95, 1984.5, 1984.75, 1985.25, 1984.6, 1985.5, 1985.2, 1986.4, 1987.9, 1987, 1987.85, 1987.9, 1988.35, 9 | 1988.25, 1988.4, 1988.35, 1987.95, 1987.7, 1988.25, 1990.35, 1990.9, 1991.45, 1991.45, 1987.75, 1990.35, 1990, 10 | 1987.3, 1989.7, 1989.25, 1989, 1988.95, 1987, 1987.7, 1984, 1981.2, 1984.2, 1986.9, 1984.5, 1984.85, 1983.05, 1985, 11 | 1983.5, 1983.85, 1983.45, 1981.05, 1978.05, 1986.95, 1993.45, 1991.05, 1990.2, 1988.95, 1987.35, 1989.65, 1989.85, 12 | 1985.05, 1986.65, 1984.8, 1988.5, 1987.2, 1986, 1985.9, 1985.95, 1988.6, 1987.65, 13 | ]; 14 | 15 | describe('StandardDeviation', () => { 16 | const PERIOD = 6; 17 | 18 | it('Basic usage', () => { 19 | const stDevOne = new StandardDeviation(PERIOD); 20 | const stDevThird = new SD({ period: PERIOD, values: [] }); 21 | const sma = new SMA(PERIOD); 22 | 23 | closes.forEach((item) => { 24 | const mean = sma.nextValue(item); 25 | 26 | const localValue = stDevOne.nextValue(item, mean); 27 | const externalValue = stDevThird.nextValue(item); 28 | 29 | if (localValue && externalValue) { 30 | expect(Math.abs(localValue - externalValue)).toBeLessThan(0.00001); 31 | } 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/rsi.ts: -------------------------------------------------------------------------------- 1 | import { AvgChangeProvider } from './providers/gain'; 2 | 3 | /** 4 | * The relative strength index (RSI) is a momentum indicator used in technical analysis 5 | * that measures the magnitude of recent price changes to evaluate overbought or oversold conditions 6 | * in the price of a stock or other asset. The RSI is displayed as an oscillator 7 | * (a line graph that moves between two extremes) and can have a reading from 0 to 100. 8 | * The indicator was originally developed by J. Welles Wilder Jr. and introduced in his seminal 1978 book, 9 | * "New Concepts in Technical Trading Systems." 10 | * 11 | * Traditional interpretation and usage of the RSI are that values of 70 or above indicate 12 | * that a security is becoming overbought or overvalued and may be primed 13 | * for a trend reversal or corrective pullback in price. 14 | * An RSI reading of 30 or below indicates an oversold or undervalued condition. 15 | */ 16 | export class RSI { 17 | private change: AvgChangeProvider; 18 | 19 | constructor(private period = 14) { 20 | this.change = new AvgChangeProvider(this.period); 21 | } 22 | 23 | nextValue(value: number) { 24 | const { downAvg, upAvg } = this.change.nextValue(value) || {}; 25 | 26 | if (upAvg === undefined || downAvg === undefined) { 27 | return; 28 | } 29 | 30 | const RS = upAvg / -downAvg; 31 | 32 | return 100 - 100 / (1 + RS); 33 | } 34 | 35 | momentValue(value: number) { 36 | const { downAvg, upAvg } = this.change.momentValue(value) || {}; 37 | 38 | if (upAvg === undefined || downAvg === undefined) { 39 | return; 40 | } 41 | 42 | const RS = upAvg / -downAvg; 43 | 44 | return 100 - 100 / (1 + RS); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/heikenashi/ha.spec.ts: -------------------------------------------------------------------------------- 1 | import { haValues, ohlc } from './excel-data'; 2 | import { HeikenAshi } from '../../src/heiken-ashi'; 3 | import { HeikinAshi as HeikenAshi2 } from 'technicalindicators'; 4 | 5 | describe('Heiken Ashi', () => { 6 | it('Excel Validate', () => { 7 | const ha = new HeikenAshi(); 8 | const EPSILON = 0.008; 9 | 10 | ohlc.forEach((tick, idx) => { 11 | const calculated = ha.nextValue(tick.o, tick.h, tick.l, tick.c); 12 | const excel = haValues[idx]; 13 | 14 | expect(Math.abs(calculated.o - excel.o)).toBeLessThan(EPSILON); 15 | expect(Math.abs(calculated.h - excel.h)).toBeLessThan(EPSILON); 16 | expect(Math.abs(calculated.l - excel.l)).toBeLessThan(EPSILON); 17 | expect(Math.abs(calculated.c - excel.c)).toBeLessThan(EPSILON); 18 | }); 19 | }); 20 | 21 | it('Cross SDK Validate', () => { 22 | const ha = new HeikenAshi(); 23 | const first = ohlc[0]; 24 | const ha2 = new HeikenAshi2({ open: [first.o], high: [first.h], low: [first.l], close: [first.c] }); 25 | const EPSILON = 0.000001; 26 | 27 | ohlc.forEach((tick, idx) => { 28 | const calculated = ha.nextValue(tick.o, tick.h, tick.l, tick.c); 29 | 30 | if (idx == 0) { 31 | return; 32 | } 33 | 34 | const cross = ha2.nextValue({ open: tick.o, high: tick.h, low: tick.l, close: tick.c }); 35 | 36 | expect(calculated.o - cross.open).toBeLessThan(EPSILON); 37 | expect(calculated.h - cross.high).toBeLessThan(EPSILON); 38 | expect(calculated.l - cross.low).toBeLessThan(EPSILON); 39 | expect(calculated.c - cross.close).toBeLessThan(EPSILON); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/macd.ts: -------------------------------------------------------------------------------- 1 | import { EMA } from './ema'; 2 | 3 | /* 4 | How work MACD? 5 | https://www.investopedia.com/terms/m/macd.asp#:~:text=The%20MACD%20is%20calculated%20by,for%20buy%20and%20sell%20signals. 6 | */ 7 | 8 | export class MACD { 9 | private emaFastIndicator: EMA; 10 | private emaSlowIndicator: EMA; 11 | private emaSignalIndicator: EMA; 12 | 13 | constructor(private periodEmaFast = 12, private periodEmaSlow = 26, private periodSignal = 9) { 14 | this.emaFastIndicator = new EMA(periodEmaFast); 15 | this.emaSlowIndicator = new EMA(periodEmaSlow); 16 | this.emaSignalIndicator = new EMA(periodSignal); 17 | } 18 | 19 | nextValue(value: number) { 20 | const emaFast = this.emaFastIndicator.nextValue(value); 21 | const emaSlow = this.emaSlowIndicator.nextValue(value); 22 | const macd = emaFast - emaSlow; 23 | const signal = (macd && this.emaSignalIndicator.nextValue(macd)) || undefined; 24 | const histogram = macd - signal || undefined; 25 | 26 | if (isNaN(macd)) { 27 | return; 28 | } 29 | 30 | return { 31 | macd, 32 | emaFast, 33 | emaSlow, 34 | signal, 35 | histogram, 36 | }; 37 | } 38 | 39 | momentValue(value: number) { 40 | const emaFast = this.emaFastIndicator.momentValue(value); 41 | const emaSlow = this.emaSlowIndicator.momentValue(value); 42 | const macd = emaFast - emaSlow; 43 | const signal = macd && this.emaSignalIndicator.momentValue(macd); 44 | const histogram = macd - signal; 45 | 46 | return { 47 | macd, 48 | emaFast, 49 | emaSlow, 50 | signal, 51 | histogram, 52 | }; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/providers/sampler.ts: -------------------------------------------------------------------------------- 1 | export interface IndicatorInstance { 2 | nextValue: (value: number) => number; 3 | momentValue: (value: number) => number; 4 | } 5 | 6 | export interface IndicatorConstructor { 7 | new (...args: any[]): IndicatorInstance; 8 | } 9 | 10 | /** 11 | * Sampler class for using with simple indicators like SMA, EMA, which nextValue arguments is number and 12 | * return type also number. Can be user for replace SMA(SMA(SMA(SMA))) calls (sma x4 sample) 13 | */ 14 | export class Sampler { 15 | private _indicators: IndicatorInstance[] = []; 16 | 17 | constructor(private indicator: T, private samples: number) {} 18 | 19 | /** 20 | * Create indicator instances for next usage, pass period and other 21 | * indicator constructor parameters 22 | */ 23 | create(...args: ConstructorParameters) { 24 | for (let i = 0; i < this.samples; i++) { 25 | this._indicators.push(new this.indicator(...args)); 26 | } 27 | } 28 | 29 | /** 30 | * Calculate next values to get all samples of current idicator 31 | */ 32 | nextValue(value: number): number { 33 | for (let i = 0; i < this.samples; i++) { 34 | value = this._indicators[i].nextValue(value); 35 | 36 | if (value === undefined) { 37 | return value; 38 | } 39 | } 40 | 41 | return value; 42 | } 43 | 44 | /** 45 | * Get immediate value 46 | */ 47 | momentValue(value: number): number { 48 | for (let i = 0; i < this.samples; i++) { 49 | value = this._indicators[i].momentValue(value); 50 | 51 | if (value === undefined || value === null) { 52 | return value; 53 | } 54 | } 55 | 56 | return value; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/ema/excel-data.ts: -------------------------------------------------------------------------------- 1 | export const closes = [ 2 | 7104.0, 7058.4, 7269.1, 7398.3, 7489.45, 7505.45, 7496.25, 7579.55, 7540.1, 7569.45, 7598.35, 7545.8, 7587.35, 3 | 7576.8, 7658.95, 7763.7, 7782.8, 7801.2, 7718.65, 7699.45, 7814.9, 7811.55, 7776.2, 7820.7, 7657.7, 7667.75, 7599.3, 4 | 7611.85, 7735.3, 7764.6, 7902.55, 7955.8, 7969.9, 7951.75, 7952.1, 7901.5, 8017.2, 8029.95, 7895.3, 7894.8, 7840.1, 5 | 7783.8, 7732.75, 7773.1, 7760.25, 7904.3, 7919.75, 7865.85, 7928.2, 7830.55, 7887.2, 7907.6, 6 | ]; 7 | 8 | export const emaValues = [ 9 | undefined, 10 | undefined, 11 | undefined, 12 | undefined, 13 | undefined, 14 | undefined, 15 | undefined, 16 | undefined, 17 | undefined, 18 | 7401.005, 19 | 7436.88590909091, 20 | 7456.68847107438, 21 | 7480.44511269722, 22 | 7497.96418311591, 23 | 7527.23433164029, 24 | 7570.22808952387, 25 | 7608.87752779226, 26 | 7643.84525001185, 27 | 7657.44611364606, 28 | 7665.08318389223, 29 | 7692.32260500273, 30 | 7714.00031318405, 31 | 7725.30934715059, 32 | 7742.65310221412, 33 | 7727.20708362973, 34 | 7716.39670478796, 35 | 7695.10639482651, 36 | 7679.96886849442, 37 | 7690.02907422271, 38 | 7703.58742436404, 39 | 7739.76243811603, 40 | 7779.04199482221, 41 | 7813.74345030908, 42 | 7838.83555025288, 43 | 7859.42908657054, 44 | 7867.07834355771, 45 | 7894.37319018358, 46 | 7919.02351924111, 47 | 7914.71015210636, 48 | 7911.09012445066, 49 | 7898.18282909599, 50 | 7877.38595107854, 51 | 7851.0885054279, 52 | 7836.90877716828, 53 | 7822.97081768314, 54 | 7837.75794174075, 55 | 7852.66558869698, 56 | 7855.06275438844, 57 | 7868.36043540872, 58 | 7861.48581078895, 59 | 7866.16111791823, 60 | 7873.69546011492, 61 | ]; 62 | -------------------------------------------------------------------------------- /src/chaikin.ts: -------------------------------------------------------------------------------- 1 | import { EMA } from './ema'; 2 | 3 | /** 4 | * Developed by Marc Chaikin, the Chaikin Oscillator measures the momentum of the Accumulation Distribution Line 5 | * using the MACD formula. (This makes it an indicator of an indicator.) The Chaikin Oscillator 6 | * is the difference between the 3-day and 10-day EMAs of the Accumulation Distribution Line. 7 | * Like other momentum indicators, this indicator is designed to anticipate directional changes 8 | * in the Accumulation Distribution Line by measuring the momentum behind the movements. 9 | * A momentum change is the first step to a trend change. 10 | * Anticipating trend changes in the Accumulation Distribution Line can help chartists anticipate 11 | * trend changes in the underlying security. The Chaikin Oscillator generates signals with 12 | * crosses above/below the zero line or with bullish/bearish divergences. 13 | */ 14 | export class ChaikinOscillator { 15 | private accDistribution = 0; 16 | private emaFast: EMA; 17 | private emaSlow: EMA; 18 | 19 | constructor(fastPeriod: number = 3, slowPeriod: number = 10) { 20 | this.emaFast = new EMA(fastPeriod); 21 | this.emaSlow = new EMA(slowPeriod); 22 | } 23 | 24 | nextValue(h: number, l: number, c: number, v: number) { 25 | this.accDistribution += (c === h && c === l) || h === l ? 0 : ((2 * c - l - h) / (h - l)) * v; 26 | 27 | return this.emaFast.nextValue(this.accDistribution) - this.emaSlow.nextValue(this.accDistribution); 28 | } 29 | 30 | momentValue(h: number, l: number, c: number, v: number) { 31 | const accDistribution = 32 | this.accDistribution + ((c === h && c === l) || h === l ? 0 : ((2 * c - l - h) / (h - l)) * v); 33 | 34 | return this.emaFast.momentValue(accDistribution) - this.emaSlow.momentValue(accDistribution); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/stochastic/stochastic.spec.ts: -------------------------------------------------------------------------------- 1 | import { Stochastic } from '../../src/stochastic'; 2 | import { Stochastic as Stochastic2 } from 'technicalindicators'; 3 | import { ohlc, stochValues } from './excel-data'; 4 | 5 | describe('Stochastic Oscillator', () => { 6 | it('Excel Validate', () => { 7 | const st = new Stochastic(); 8 | const EPSILON = 0.0001; 9 | 10 | ohlc.forEach((tick, idx) => { 11 | const calculated = st.nextValue(tick.h, tick.l, tick.c); 12 | const excel = stochValues[idx]; 13 | 14 | if (calculated?.k && excel?.k) { 15 | expect(Math.abs(calculated?.k - excel?.k)).toBeLessThan(EPSILON); 16 | } 17 | 18 | if (calculated?.d && excel?.d) { 19 | expect(Math.abs(calculated?.d - excel?.d)).toBeLessThan(EPSILON); 20 | } 21 | }); 22 | }); 23 | 24 | it('Cross Validate', () => { 25 | const st = new Stochastic(); 26 | const st2 = new Stochastic2({ 27 | period: 14, 28 | low: [], 29 | high: [], 30 | close: [], 31 | signalPeriod: 3, 32 | }); 33 | 34 | ohlc.forEach((tick) => { 35 | const calculated = st.nextValue(tick.h, tick.l, tick.c); 36 | const cross = st2.nextValue({ 37 | high: [tick.h], 38 | low: [tick.l], 39 | close: [tick.c], 40 | period: 14, 41 | signalPeriod: 3, 42 | }); 43 | 44 | if (calculated?.k && cross?.k) { 45 | expect(Math.abs(calculated.k - cross.k)).toBeLessThan(0.01); 46 | } 47 | 48 | if (calculated?.d && cross?.d) { 49 | expect(Math.abs(calculated.d - cross.d)).toBeLessThan(0.01); 50 | } 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/wma.ts: -------------------------------------------------------------------------------- 1 | import { CircularBuffer } from './providers/circular-buffer'; 2 | /** 3 | * Weighted moving average (WMA) assign a heavier weighting to more current data points since they are more relevant than data points 4 | * in the distant past. The sum of the weighting should add up to 1 (or 100%). 5 | * In the case of the simple moving average, the weightings are equally distributed, which is why they are not shown in the table above. 6 | */ 7 | export class WMA { 8 | private denominator: number; 9 | private buffer: CircularBuffer; 10 | private values: number[]; 11 | 12 | constructor(private period: number) { 13 | this.denominator = (period * (period + 1)) / 2; 14 | this.buffer = new CircularBuffer(period); 15 | this.values = []; 16 | } 17 | 18 | /** 19 | * Get next value for closed candle hlc 20 | * affect all next calculations 21 | */ 22 | nextValue(value: number) { 23 | this.buffer.push(value); 24 | 25 | if (!this.buffer.filled) { 26 | return; 27 | } 28 | 29 | let result = 0; 30 | 31 | this.buffer.forEach((v, idx) => { 32 | result += (v * (idx + 1)) / this.denominator; 33 | }); 34 | 35 | return result; 36 | } 37 | 38 | /** 39 | * Get next value for non closed (tick) candle hlc 40 | * does not affect any next calculations 41 | */ 42 | momentValue(value: number) { 43 | const removed = this.buffer.push(value); 44 | 45 | if (!this.buffer.filled) { 46 | this.buffer.pushback(removed); 47 | return; 48 | } 49 | 50 | let result = 0; 51 | 52 | this.buffer.forEach((v, idx) => { 53 | result += (v * (idx + 1)) / this.denominator; 54 | }); 55 | 56 | this.buffer.pushback(removed); 57 | 58 | return result; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import * as TrendLines from './src/trendlines'; 2 | 3 | export { Extremums } from './src/providers/extremum'; 4 | export { TrendLines }; 5 | export { Level, UniLevel } from './src/providers/levels'; 6 | export { Correlation } from './src/providers/correlation'; 7 | export { SMA } from './src/sma'; 8 | export { WEMA } from './src/wema'; 9 | export { WMA } from './src/wma'; 10 | export { EMA } from './src/ema'; 11 | export { EWMA } from './src/ewma'; 12 | export { SMMA } from './src/smma'; 13 | export { RMA } from './src/rma'; 14 | export { AO } from './src/ao'; 15 | export { AC } from './src/ac'; 16 | export { MFI } from './src/mfi'; 17 | export { Move } from './src/move'; 18 | export { Wave } from './src/wave'; 19 | export { Stochastic } from './src/stochastic'; 20 | export { StochasticRSI } from './src/stochastic-rsi'; 21 | export { RSI } from './src/rsi'; 22 | export { CCI } from './src/cci'; 23 | export { ATR } from './src/atr'; 24 | export { ROC } from './src/roc'; 25 | export { DC } from './src/dc'; 26 | export { cRSI } from './src/crsi'; 27 | export { BollingerBands } from './src/bands'; 28 | export { StandardDeviation } from './src/providers/standard-deviation'; 29 | export { MACD } from './src/macd'; 30 | export { HeikenAshi } from './src/heiken-ashi'; 31 | export { Pivot } from './src/pivot'; 32 | export { LWMA } from './src/lwma'; 33 | export { PSAR } from './src/psar'; 34 | export { ADX } from './src/adx'; 35 | export { WWS } from './src/wws'; 36 | export { SuperTrend } from './src/supertrend'; 37 | export { CircularBuffer } from './src/providers/circular-buffer'; 38 | export { Sampler } from './src/providers/sampler'; 39 | export { VolumeProfile } from './src/volume-profile'; /** BETA UNSTABLE */ 40 | export { ChaikinOscillator } from './src/chaikin'; 41 | export { AMA } from './src/ama'; 42 | export * from './src/ichimoku'; 43 | // export { OrderBlock } from './src/order-block'; 44 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | import commonjs from 'rollup-plugin-commonjs'; 3 | import { terser } from 'rollup-plugin-terser'; 4 | import pkg from './package.json'; 5 | import buble from 'rollup-plugin-buble'; 6 | import typescript from 'rollup-plugin-typescript2'; 7 | import nodeResolve from 'rollup-plugin-node-resolve'; 8 | 9 | export default [ 10 | // browser-friendly UMD build 11 | { 12 | input: 'index.ts', 13 | output: { 14 | name: 'indicators', 15 | file: pkg.browser, 16 | format: 'umd', 17 | }, 18 | plugins: [ 19 | nodeResolve(), 20 | resolve(), // so Rollup can find `ms` 21 | commonjs(), // so Rollup can convert `ms` to an ES module 22 | typescript({ 23 | tsconfigOverride: { 24 | compilerOptions: { 25 | target: 'ES5', 26 | module: 'ESNext', 27 | }, 28 | }, 29 | }), 30 | buble({ 31 | transforms: { forOf: false }, 32 | objectAssign: 'Object.assign', 33 | asyncAwait: false, 34 | }), 35 | terser(), // uglify 36 | ], 37 | }, 38 | { 39 | input: 'index.ts', 40 | external: [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {})], 41 | plugins: [ 42 | typescript({ 43 | tsconfigOverride: { 44 | compilerOptions: { 45 | target: 'ES2020', 46 | module: 'ESNext', 47 | }, 48 | }, 49 | }), 50 | ], 51 | output: [ 52 | { file: pkg.main, format: 'cjs' }, 53 | { file: pkg.module, format: 'es' }, 54 | ], 55 | }, 56 | ]; 57 | -------------------------------------------------------------------------------- /tests/ao/excel-data.ts: -------------------------------------------------------------------------------- 1 | export const highs = [ 2 | 7150, 7169, 7278.2, 7410, 7500, 7520, 7535, 7588, 7584.6, 7598, 7647, 7592, 7598, 7660.5, 7668.4, 7773.4, 7791.55, 3 | 7809.95, 7796.7, 7751.95, 7822, 7848.95, 7800, 7839.9, 7790.15, 7687.3, 7670.7, 7629.9, 7743.5, 7769, 7926.8, 7962, 4 | 7996.5, 8021.9, 7980, 7952, 8029, 8035.15, 8037, 7950, 7874.45, 7949.95, 7789, 7817.5, 7770.6, 7912.2, 7933.55, 5 | 7909.9, 7939.4, 7891.8, 7898.1, 7957.8, 6 | ]; 7 | 8 | export const lows = [ 9 | 7060, 6884.85, 7092.25, 7344.15, 7407, 7443, 7457.3, 7451.75, 7491.6, 7508, 7584.65, 7532.15, 7492, 7545.1, 7587.8, 10 | 7672.15, 7714, 7738.8, 7686.4, 7676.65, 7726.8, 7783.3, 7733.7, 7767.15, 7645, 7639, 7590.1, 7584.8, 7566, 7717.15, 11 | 7818.7, 7885.3, 7930.15, 7924, 7922, 7867.6, 7871.1, 7991, 7884, 7831.1, 7805.1, 7770.1, 7725, 7733, 7700.95, 7775, 12 | 7867.15, 7798.2, 7860.4, 7787.3, 7780.1, 7896, 13 | ]; 14 | 15 | export const aoValues = [ 16 | undefined, 17 | undefined, 18 | undefined, 19 | undefined, 20 | undefined, 21 | undefined, 22 | undefined, 23 | undefined, 24 | undefined, 25 | undefined, 26 | undefined, 27 | undefined, 28 | undefined, 29 | undefined, 30 | undefined, 31 | undefined, 32 | undefined, 33 | undefined, 34 | undefined, 35 | undefined, 36 | undefined, 37 | undefined, 38 | undefined, 39 | undefined, 40 | undefined, 41 | undefined, 42 | undefined, 43 | undefined, 44 | undefined, 45 | undefined, 46 | undefined, 47 | undefined, 48 | undefined, 49 | 268.5088, 50 | 285.2115, 51 | 266.6546, 52 | 249.4397, 53 | 240.6838, 54 | 223.2821, 55 | 199.1612, 56 | 175.0496, 57 | 147.0401, 58 | 89.3869, 59 | 45.8001, 60 | 11.3172, 61 | 3.8021, 62 | 1.4156, 63 | 13.4359, 64 | 30.3718, 65 | 47.6922, 66 | 44.2532, 67 | 45.0772, 68 | ]; 69 | -------------------------------------------------------------------------------- /src/heiken-ashi.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Heikin-Ashi Candlesticks are an offshoot from Japanese candlesticks. 3 | * Heikin-Ashi Candlesticks use the open-close data from the prior period 4 | * and the open-high-low-close data from the current period to create a combo candlestick. 5 | * The resulting candlestick filters out some noise in an effort to better capture the trend. 6 | * In Japanese, Heikin means “average” and Ashi means “pace” (EUDict.com). 7 | * Taken together, Heikin-Ashi represents the average pace of prices. 8 | * Heikin-Ashi Candlesticks are not used like normal candlesticks. 9 | * Dozens of bullish or bearish reversal patterns consisting of 1-3 candlesticks are not to be found. 10 | * Instead, these candlesticks can be used to identify trending periods, 11 | * potential reversal points and classic technical analysis patterns. 12 | */ 13 | export class HeikenAshi { 14 | private prevOpen = 0; 15 | private prevClose = 0; 16 | 17 | /** 18 | * Get next value for closed candle hlc 19 | * affect all next calculations 20 | */ 21 | nextValue(o: number, h: number, l: number, c: number) { 22 | const data = this.calculate(o, h, l, c); 23 | 24 | this.prevClose = data.c; 25 | this.prevOpen = data.o; 26 | 27 | return data; 28 | } 29 | 30 | /** 31 | * Get next value for non closed (tick) candle hlc 32 | * does not affect any next calculations 33 | */ 34 | momentValue(o: number, h: number, l: number, c: number) { 35 | return this.calculate(o, h, l, c); 36 | } 37 | 38 | /** 39 | * Heiken ashi formula 40 | */ 41 | calculate(o: number, h: number, l: number, c: number) { 42 | c = (o + h + l + c) / 4; 43 | 44 | if (this.prevOpen) { 45 | o = (this.prevOpen + this.prevClose) / 2; 46 | } 47 | 48 | h = Math.max(h, o, c); 49 | l = Math.min(l, o, c); 50 | 51 | return { o, h, l, c }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/ac/excel-data.ts: -------------------------------------------------------------------------------- 1 | export const highs = [ 2 | 7150, 7169, 7278.2, 7410, 7500, 7520, 7535, 7588, 7584.6, 7598, 7647, 7592, 7598, 7660.5, 7668.4, 7773.4, 7791.55, 3 | 7809.95, 7796.7, 7751.95, 7822, 7848.95, 7800, 7839.9, 7790.15, 7687.3, 7670.7, 7629.9, 7743.5, 7769, 7926.8, 7962, 4 | 7996.5, 8021.9, 7980, 7952, 8029, 8035.15, 8037, 7950, 7874.45, 7949.95, 7789, 7817.5, 7770.6, 7912.2, 7933.55, 5 | 7909.9, 7939.4, 7891.8, 7898.1, 7957.8, 6 | ]; 7 | 8 | export const lows = [ 9 | 7060, 6884.85, 7092.25, 7344.15, 7407, 7443, 7457.3, 7451.75, 7491.6, 7508, 7584.65, 7532.15, 7492, 7545.1, 7587.8, 10 | 7672.15, 7714, 7738.8, 7686.4, 7676.65, 7726.8, 7783.3, 7733.7, 7767.15, 7645, 7639, 7590.1, 7584.8, 7566, 7717.15, 11 | 7818.7, 7885.3, 7930.15, 7924, 7922, 7867.6, 7871.1, 7991, 7884, 7831.1, 7805.1, 7770.1, 7725, 7733, 7700.95, 7775, 12 | 7867.15, 7798.2, 7860.4, 7787.3, 7780.1, 7896, 13 | ]; 14 | 15 | export const aoValues = [ 16 | undefined, 17 | undefined, 18 | undefined, 19 | undefined, 20 | undefined, 21 | undefined, 22 | undefined, 23 | undefined, 24 | undefined, 25 | undefined, 26 | undefined, 27 | undefined, 28 | undefined, 29 | undefined, 30 | undefined, 31 | undefined, 32 | undefined, 33 | undefined, 34 | undefined, 35 | undefined, 36 | undefined, 37 | undefined, 38 | undefined, 39 | undefined, 40 | undefined, 41 | undefined, 42 | undefined, 43 | undefined, 44 | undefined, 45 | undefined, 46 | undefined, 47 | undefined, 48 | undefined, 49 | undefined, 50 | undefined, 51 | undefined, 52 | undefined, 53 | -21.41588, 54 | -29.77224, 55 | -36.68308, 56 | -42.47368, 57 | -50.00326, 58 | -77.39708, 59 | -85.48748, 60 | -82.40158, 61 | -55.66718, 62 | -28.92878, 63 | -1.71828, 64 | 18.30328, 65 | 28.34868, 66 | 16.81946, 67 | 8.91114, 68 | ]; 69 | -------------------------------------------------------------------------------- /tests/truerange/data.ts: -------------------------------------------------------------------------------- 1 | // Used super trend document for validation 2 | export { ohlc } from '../supertrend/data'; 3 | 4 | export const trueRangeData = [ 5 | undefined, 6 | 284.15, 7 | 219.8, 8 | 140.9, 9 | 101.7, 10 | 77, 11 | 77.7, 12 | 136.25, 13 | 93, 14 | 90, 15 | 77.55, 16 | 66.2, 17 | 106, 18 | 115.4, 19 | 91.6, 20 | 114.45, 21 | 77.55, 22 | 71.15, 23 | 114.8, 24 | 75.3, 25 | 122.55, 26 | 65.65, 27 | 77.85, 28 | 72.75, 29 | 175.7, 30 | 48.3, 31 | 80.6, 32 | 45.1, 33 | 177.5, 34 | 51.85, 35 | 162.2, 36 | 76.7, 37 | 66.35, 38 | 97.9, 39 | 58, 40 | 84.5, 41 | 157.9, 42 | 44.15, 43 | 153, 44 | 118.9, 45 | 89.7, 46 | 179.85, 47 | 64, 48 | 84.75, 49 | 72.15, 50 | 151.95, 51 | 66.4, 52 | 121.55, 53 | 79, 54 | 140.9, 55 | 118, 56 | 70.6, 57 | ]; 58 | 59 | export const averageTrueRrange = [ 60 | undefined, 61 | undefined, 62 | undefined, 63 | undefined, 64 | undefined, 65 | undefined, 66 | undefined, 67 | undefined, 68 | undefined, 69 | undefined, 70 | 129.805, 71 | 108.01, 72 | 96.63, 73 | 94.08, 74 | 93.07, 75 | 96.815, 76 | 96.8, 77 | 90.29, 78 | 92.47, 79 | 91, 80 | 95.5, 81 | 95.445, 82 | 92.63, 83 | 88.365, 84 | 96.775, 85 | 90.16, 86 | 90.465, 87 | 87.86, 88 | 94.13, 89 | 91.785, 90 | 95.75, 91 | 96.855, 92 | 95.705, 93 | 98.22, 94 | 86.45, 95 | 90.07, 96 | 97.8, 97 | 97.705, 98 | 95.255, 99 | 101.96, 100 | 94.71, 101 | 105.025, 102 | 104.79, 103 | 103.475, 104 | 104.89, 105 | 111.635, 106 | 102.485, 107 | 110.225, 108 | 102.825, 109 | 105.025, 110 | 107.855, 111 | 96.93, 112 | ]; 113 | -------------------------------------------------------------------------------- /tests/rma/rma.spec.ts: -------------------------------------------------------------------------------- 1 | import { RMA } from '../../src/rma'; 2 | import { ohlc } from './data'; 3 | 4 | describe('Relative Moving Average (RMA)', () => { 5 | it('Base test', () => { 6 | const solvedData = [ 7 | undefined, 8 | undefined, 9 | undefined, 10 | undefined, 11 | undefined, 12 | undefined, 13 | undefined, 14 | undefined, 15 | undefined, 16 | undefined, 17 | undefined, 18 | undefined, 19 | undefined, 20 | 1134.0892857142858, 21 | 1133.142193877551, 22 | 1132.6934657434404, 23 | 1132.2132181903376, 24 | 1131.9694168910278, 25 | 1131.5644585416687, 26 | 1129.881997217264, 27 | 1128.3582831303165, 28 | 1126.1491200495798, 29 | 1124.7970400460385, 30 | 1125.4293943284642, 31 | 1125.1244375907168, 32 | 1123.5541206199512, 33 | 1121.5852548613834, 34 | 1120.2513080855701, 35 | 1119.8355003651723, 36 | 1118.268678910517, 37 | 1117.2230589883375, 38 | 1116.9835547748849, 39 | 1117.6411580052504, 40 | 1119.3953610048752, 41 | 1120.6299780759555, 42 | 1122.01855107053, 43 | 1122.9100831369208, 44 | 1123.3379343414265, 45 | 1123.5387961741817, 46 | 1124.0874535903117, 47 | 1126.219778333861, 48 | 1129.0047941671567, 49 | 1132.353023155217, 50 | 1135.235664358416, 51 | 1137.4838311899578, 52 | 1139.0492718192465, 53 | ]; 54 | const local = new RMA(14); 55 | 56 | ohlc.forEach((tick, idx) => { 57 | const value = local.nextValue(tick.c); 58 | 59 | expect(value).toBe(solvedData[idx]); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /tests/ewma/ewma.spec.ts: -------------------------------------------------------------------------------- 1 | import { WEMA } from '../../src/wema'; 2 | import { ohlc } from './data'; 3 | 4 | describe('Exponentially Weighted Moving Average (EWMA)', () => { 5 | it('Base test', () => { 6 | const solvedData = [ 7 | undefined, 8 | undefined, 9 | undefined, 10 | undefined, 11 | undefined, 12 | 1129.6899999999998, 13 | 1130.0366666666664, 14 | 1131.347222222222, 15 | 1133.1743518518515, 16 | 1136.9136265432096, 17 | 1138.6080221193413, 18 | 1139.5650184327844, 19 | 1136.8091820273203, 20 | 1133.5176516894335, 21 | 1131.403043074528, 22 | 1130.6458692287733, 23 | 1129.8665576906444, 24 | 1129.688798075537, 25 | 1129.123998396281, 26 | 1125.6049986635676, 27 | 1122.7624988863063, 28 | 1118.5404157385885, 29 | 1116.653679782157, 30 | 1119.4863998184642, 31 | 1119.7653331820534, 32 | 1116.9944443183779, 33 | 1113.4937035986482, 34 | 1111.7297529988734, 35 | 1112.1797941657278, 36 | 1109.79982847144, 37 | 1108.7715237262, 38 | 1109.6212697718333, 39 | 1112.3827248098612, 40 | 1117.3522706748843, 41 | 1120.5735588957368, 42 | 1123.8229657464474, 43 | 1125.6024714553728, 44 | 1126.152059546144, 45 | 1126.1517162884534, 46 | 1126.9964302403778, 47 | 1131.4870252003147, 48 | 1137.1075210002623, 49 | 1143.569600833552, 50 | 1148.42633402796, 51 | 1151.4736116899667, 52 | 1152.7946764083056, 53 | ]; 54 | const local = new WEMA(6); 55 | 56 | ohlc.forEach((tick, idx) => { 57 | const value = local.nextValue(tick.c); 58 | 59 | expect(value).toBe(solvedData[idx]); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/atr.ts: -------------------------------------------------------------------------------- 1 | import { SMMA } from './smma'; 2 | import { EMA } from './ema'; 3 | import { WEMA } from './wema'; 4 | import { LWMA } from './lwma'; 5 | import { SMA } from './sma'; 6 | import { EWMA } from './ewma'; 7 | import { getTrueRange } from './providers/true-range'; 8 | import { RMA } from './rma'; 9 | 10 | export class ATR { 11 | private prevClose: number; 12 | private avg: EMA | SMMA | WEMA | LWMA | SMA | EWMA | RMA; 13 | 14 | /** 15 | * Конструктор 16 | * @param period - период по умолчанию 14 17 | */ 18 | constructor(period = 14, smoothing: 'SMA' | 'EMA' | 'SMMA' | 'WEMA' | 'LWMA' | 'EWMA' | 'RMA' = 'WEMA') { 19 | switch (smoothing) { 20 | case 'SMA': 21 | this.avg = new SMA(period); 22 | break; 23 | case 'EMA': 24 | this.avg = new EMA(period); 25 | break; 26 | case 'SMMA': 27 | this.avg = new SMMA(period); 28 | break; 29 | case 'WEMA': 30 | this.avg = new WEMA(period); 31 | break; 32 | case 'LWMA': 33 | this.avg = new LWMA(period); 34 | break; 35 | case 'EWMA': 36 | this.avg = new EWMA(0.2); 37 | break; 38 | case 'RMA': 39 | this.avg = new RMA(period); 40 | break; 41 | } 42 | 43 | this.prevClose = 0; 44 | } 45 | 46 | nextValue(high: number, low: number, close: number) { 47 | const trueRange = getTrueRange(high, low, this.prevClose); 48 | 49 | this.prevClose = close; 50 | 51 | if (trueRange === undefined) { 52 | return; 53 | } 54 | 55 | return this.avg.nextValue(trueRange); 56 | } 57 | 58 | momentValue(high: number, low: number) { 59 | const trueRange = getTrueRange(high, low, this.prevClose); 60 | 61 | if (trueRange === undefined) { 62 | return; 63 | } 64 | 65 | return this.avg.momentValue(trueRange); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/stochastic.ts: -------------------------------------------------------------------------------- 1 | import { SMA } from './sma'; 2 | import { MaxProvider } from './providers/max-value'; 3 | import { MinProvider } from './providers/min-value'; 4 | 5 | /** 6 | * A stochastic oscillator is a momentum indicator comparing a particular closing price 7 | * of a security to a range of its prices over a certain period of time. 8 | * The sensitivity of the oscillator to market movements is reducible by adjusting that 9 | * time period or by taking a moving average of the result. 10 | * It is used to generate overbought and oversold trading signals, 11 | * utilizing a 0-100 bounded range of values. 12 | */ 13 | export class Stochastic { 14 | private max: MaxProvider; 15 | private min: MinProvider; 16 | private sma: SMA; 17 | 18 | constructor(private period: number = 14, private smaPeriod: number = 3) { 19 | this.sma = new SMA(this.smaPeriod); 20 | this.max = new MaxProvider(period); 21 | this.min = new MinProvider(period); 22 | } 23 | 24 | /** 25 | * Get next value for closed candle hlc 26 | * affect all next calculations 27 | */ 28 | nextValue(high: number, low: number, close: number) { 29 | const max = this.max.nextValue(high); 30 | const min = this.min.nextValue(low); 31 | 32 | if (!this.max.filled()) { 33 | return; 34 | } 35 | 36 | const k: number = ((close - min) / (max - min)) * 100; 37 | const d: number = this.sma.nextValue(k); 38 | 39 | return { k, d }; 40 | } 41 | 42 | /** 43 | * Get next value for non closed (tick) candle hlc 44 | * does not affect any next calculations 45 | */ 46 | momentValue(high: number, low: number, close: number): { k: number; d: number } { 47 | if (!this.max.filled()) { 48 | return; 49 | } 50 | 51 | const max = this.max.momentValue(high); 52 | const min = this.min.momentValue(low); 53 | 54 | const k: number = ((close - min) / (max - min)) * 100; 55 | const d: number = this.sma.momentValue(k); 56 | 57 | return { k, d }; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/sma/sma.spec.ts: -------------------------------------------------------------------------------- 1 | import { SMA } from '../../src/sma'; 2 | import { SMA as SMA2 } from 'technicalindicators'; 3 | 4 | describe('Simple Moving Average', () => { 5 | const ticks = [120, 150, 240, 540, 210, 380, 120, 870, 250, 1100, 500, 950]; 6 | // Interval = 2 7 | const interval2 = [undefined, 135, 195, 390, 375, 295, 250, 495, 560, 675, 800, 725]; 8 | // Interval = 4 9 | const interval4 = [undefined, undefined, undefined, 262.5, 285, 342.5, 312.5, 395, 405, 585, 680, 700]; 10 | // Interval = 6 11 | const interval6 = [ 12 | undefined, 13 | undefined, 14 | undefined, 15 | undefined, 16 | undefined, 17 | 273.333333333333, 18 | 273.333333333333, 19 | 393.333333333333, 20 | 395, 21 | 488.333333333333, 22 | 536.666666666667, 23 | 631.666666666667, 24 | ]; 25 | 26 | const sma2 = new SMA(2); 27 | const sma4 = new SMA(4); 28 | const sma6 = new SMA(6); 29 | const EPSILON = 0.0001; 30 | 31 | it('Excel validate', () => { 32 | ticks.forEach((tick, idx) => { 33 | const sma2Calc = sma2.nextValue(tick); 34 | const sma4Calc = sma4.nextValue(tick); 35 | const sma6Calc = sma6.nextValue(tick); 36 | 37 | if (interval2[idx]) { 38 | expect(Math.abs(sma2Calc - interval2[idx])).toBeLessThan(EPSILON); 39 | } 40 | 41 | if (interval4[idx]) { 42 | expect(Math.abs(sma4Calc - interval4[idx])).toBeLessThan(EPSILON); 43 | } 44 | 45 | if (interval6[idx]) { 46 | expect(Math.abs(sma6Calc - interval6[idx])).toBeLessThan(EPSILON); 47 | } 48 | }); 49 | }); 50 | 51 | it('Cross validate', () => { 52 | const localSMA = new SMA(6); 53 | const crossSMA = new SMA2({ period: 6, values: [] }); 54 | 55 | ticks.forEach((tick) => { 56 | const calc = localSMA.nextValue(tick); 57 | const cross = crossSMA.nextValue(tick); 58 | 59 | expect(calc).toEqual(cross); 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /tests/crsi/crsi.ts: -------------------------------------------------------------------------------- 1 | import { cRSI } from '../../src/crsi'; 2 | 3 | // const ticks = [2393.57, 2402.3, 2398.44, 2407.94, 2435.13, 2430.2, 2448.91, 2428.39, 2415.45, 2434.87]; 4 | // right: 77.25 70.54 44.86 64.92 78.97 5 | 6 | const ticks = [ 7 | 444.4695, 444.7798, 448.5435, 446.977, 444.98, 444.3243, 443.4985, 452.1121, 449.6447, 452.007, 443.6937, 443.8639, 8 | 439.054, 439.5245, 438.6336, 438.3934, 443.9439, 444.4395, 438.4835, 436.6116, 433.3033, 427.2623, 428.3584, 9 | 434.5546, 436.4314, 438.4935, 441.4464, 449.4645, 444.8398, 506.2112, 502.1522, 504.004, 516.2212, 513.2883, 10 | 508.1081, 508.008, 518.6386, 515.7257, 515.8058, 514.0341, 513.5685, 511.2713, 511.8869, 504.4795, 508.5235, 11 | 505.8008, 506.3964, 516.7518, 518.1331, 517.2973, 516.2913, 513.1131, 511.6667, 517.5526, 516.4615, 523.4885, 12 | 529.7347, 532.0871, 530.3253, 527.7678, 527.1572, 529.6196, 529.1992, 535.4705, 539.6096, 542.8729, 539.1842, 13 | 535.5155, 530.9259, 537.027, 535.4655, 542.9179, 543.6536, 550.8608, 558.1081, 556.4765, 559.2893, 559.7598, 14 | 555.2853, 560.9159, 557.1171, 553.053, 559.2192, 570, 571.1862, 565.6857, 565.6556, 562.0521, 575.2753, 574.8849, 15 | 578.6887, 575.8408, 582.4324, 583.0931, 580.6306, 562.4775, 551.1661, 562.0671, 554.014, 568.2632, 591.0761, 16 | 567.2823, 569.6497, 572.1722, 580.5605, 589.3093, 587.0521, 595.6857, 593.939, 600.5505, 602.002, 606.046, 601.7718, 17 | 602.6577, 602.4975, 606.8619, 610.6106, 610.6957, 610.2152, 608.4334, 601.947, 608.063, 609.7397, 610.4154, 608.003, 18 | 606.3914, 600.5956, 604.2543, 595.1251, 586.987, 596.6467, 606.2362, 600.2252, 599.1792, 592.1121, 579.5446, 19 | ]; 20 | 21 | /** 22 | * 27.63 23 | 56.61 24 | 61.54 25 | 76.63 26 | 82.39 27 | 42.74 28 | 72.51 29 | 46.86 30 | 70.74 31 | 68.56 32 | 76.88 33 | 35.46 34 | 56.63 35 | 47.47 36 | 69.35 37 | 73.32 38 | 69.02 39 | 50.89 40 | 36.52 41 | 14.45 42 | 65.63 43 | */ 44 | 45 | const crsi = new cRSI(3, 2, 100); 46 | ticks.forEach((tick, index) => { 47 | if (tick === 567.2823) { 48 | console.log('------'); 49 | } 50 | console.log(index, ':', tick, crsi.momentValue(tick), crsi.nextValue(tick)); 51 | }); 52 | -------------------------------------------------------------------------------- /docs/AcceleratorOscillator.md: -------------------------------------------------------------------------------- 1 | # Accelerator Oscillator (AO). 2 | 3 | Accelerator Oscillator (AC) belongs to the group of oscillators, is an indicator of the deceleration or acceleration of the driving force of the traded asset. 4 | 5 | The creator of this indicator is a well-known trader and author of a number of books on stock trading Bill Williams. Accelerator Oscillator is based on the principle of increasing or decreasing the speed of price movement. When reversing the price movement, the speed will slow down, and the development of market movement increases and the acceleration of prices. Noticing the peculiarities of the change in the rate of price, Williams developed an indicator to recognize this change to use such features to determine the points of opening and closing of trading positions. 6 | 7 | # Examples 8 | 9 | ## Streaming calculation using nextValue 10 | 11 | ```javascript 12 | import { AO } from '@debut/indicators'; 13 | 14 | const fastPeriod = 5; 15 | const slowPeriod = 34 16 | const ao = new AO(fastPeriod, slowPeriod); 17 | 18 | // ohlc - historical candle open/high/low/close/volume format {o: number, h: number, l: number, c: number, v: number, time: number } 19 | // get this from your broker history 20 | 21 | ohlc.forEach(({ o, h, l, c, v}) => { 22 | const aoValue = ao.nextValue(h, l); 23 | 24 | console.log(aoValue); // Accelerator Oscillator calculated value for current candle 25 | }); 26 | ``` 27 | 28 | ## Immediate calculation using momentValue 29 | 30 | ```javascript 31 | import { AO } from '@debut/indicators'; 32 | 33 | const fastPeriod = 5; 34 | const slowPeriod = 34 35 | const ao = new AO(fastPeriod, slowPeriod); 36 | 37 | // Last tick for now, getted from broker websocket ticks stream 38 | const lastTick = { o: 12, h: 14, l: 13, c: 14, v: 156, time: 1642694284515 }; 39 | const aoValue = ao.momentValue(h, l); 40 | console.log(aoValue); // Accelerator Oscillator calculated value for current tick 41 | 42 | // When candle closed call nextValue for candle data 43 | ao.nextValue(...); 44 | ``` 45 | 46 | ## How to calculate Accelerator Oscillator (AC) in excel spreadsheet? 47 | 48 | [Download excel sample](../tests/ac/awesome-and-accelerator-oscillators.xlsx) 49 | -------------------------------------------------------------------------------- /tests/ewma/data.ts: -------------------------------------------------------------------------------- 1 | export const ohlc = [ 2 | { h: 1149, l: 1131.27, c: 1144.24 }, 3 | { h: 1160.42, l: 1144.77, c: 1159.96 }, 4 | { h: 1159.8, l: 1123.68, c: 1127.66 }, 5 | { h: 1128.96, l: 1107.93, c: 1111.44 }, 6 | { h: 1126.31, l: 1105.56, c: 1115.35 }, 7 | { h: 1123.77, l: 1095.79, c: 1119.49 }, 8 | { h: 1132.11, l: 1121.86, c: 1131.77 }, 9 | { h: 1141.11, l: 1127.05, c: 1137.9 }, 10 | { h: 1142.39, l: 1132.22, c: 1142.31 }, 11 | { h: 1157.87, l: 1143.36, c: 1155.61 }, 12 | { h: 1155.42, l: 1146.84, c: 1147.08 }, 13 | { h: 1152.82, l: 1136.43, c: 1144.35 }, 14 | { h: 1139.01, l: 1120.98, c: 1123.03 }, 15 | { h: 1131.67, l: 1102.26, c: 1117.06 }, 16 | { h: 1126.46, l: 1118.42, c: 1120.83 }, 17 | { h: 1126.96, l: 1109.37, c: 1126.86 }, 18 | { h: 1133.73, l: 1114.29, c: 1125.97 }, 19 | { h: 1137.8, l: 1126.39, c: 1128.8 }, 20 | { h: 1127.99, l: 1115.1, c: 1126.3 }, 21 | { h: 1124.05, l: 1108.01, c: 1108.01 }, 22 | { h: 1110.85, l: 1093.28, c: 1108.55 }, 23 | { h: 1119.79, l: 1095.51, c: 1097.43 }, 24 | { h: 1107.38, l: 1091.5, c: 1107.22 }, 25 | { h: 1136.95, l: 1111.69, c: 1133.65 }, 26 | { h: 1134.45, l: 1121.03, c: 1121.16 }, 27 | { h: 1120.03, l: 1102.06, c: 1103.14 }, 28 | { h: 1099.19, l: 1082.53, c: 1095.99 }, 29 | { h: 1102.91, l: 1088.56, c: 1102.91 }, 30 | { h: 1116.04, l: 1099.62, c: 1114.43 }, 31 | { h: 1112.08, l: 1092, c: 1097.9 }, 32 | { h: 1107.26, l: 1093.67, c: 1103.63 }, 33 | { h: 1116.61, l: 1105.15, c: 1113.87 }, 34 | { h: 1126.2, l: 1113.75, c: 1126.19 }, 35 | { h: 1144.1, l: 1132.43, c: 1142.2 }, 36 | { h: 1140.84, l: 1132.95, c: 1136.68 }, 37 | { h: 1141.35, l: 1134.78, c: 1140.07 }, 38 | { h: 1141.73, l: 1130.8, c: 1134.5 }, 39 | { h: 1136.82, l: 1121.05, c: 1128.9 }, 40 | { h: 1129.26, l: 1118.66, c: 1126.15 }, 41 | { h: 1131.75, l: 1119.86, c: 1131.22 }, 42 | { h: 1153.94, l: 1128.4, c: 1153.94 }, 43 | { h: 1167.54, l: 1158.39, c: 1165.21 }, 44 | { h: 1179.84, l: 1166.14, c: 1175.88 }, 45 | { h: 1173.84, l: 1166.9, c: 1172.71 }, 46 | { h: 1168.99, l: 1161.43, c: 1166.71 }, 47 | { h: 1165.37, l: 1156.07, c: 1159.4 }, 48 | ]; 49 | -------------------------------------------------------------------------------- /tests/rma/data.ts: -------------------------------------------------------------------------------- 1 | export const ohlc = [ 2 | { h: 1149, l: 1131.27, c: 1144.24 }, 3 | { h: 1160.42, l: 1144.77, c: 1159.96 }, 4 | { h: 1159.8, l: 1123.68, c: 1127.66 }, 5 | { h: 1128.96, l: 1107.93, c: 1111.44 }, 6 | { h: 1126.31, l: 1105.56, c: 1115.35 }, 7 | { h: 1123.77, l: 1095.79, c: 1119.49 }, 8 | { h: 1132.11, l: 1121.86, c: 1131.77 }, 9 | { h: 1141.11, l: 1127.05, c: 1137.9 }, 10 | { h: 1142.39, l: 1132.22, c: 1142.31 }, 11 | { h: 1157.87, l: 1143.36, c: 1155.61 }, 12 | { h: 1155.42, l: 1146.84, c: 1147.08 }, 13 | { h: 1152.82, l: 1136.43, c: 1144.35 }, 14 | { h: 1139.01, l: 1120.98, c: 1123.03 }, 15 | { h: 1131.67, l: 1102.26, c: 1117.06 }, 16 | { h: 1126.46, l: 1118.42, c: 1120.83 }, 17 | { h: 1126.96, l: 1109.37, c: 1126.86 }, 18 | { h: 1133.73, l: 1114.29, c: 1125.97 }, 19 | { h: 1137.8, l: 1126.39, c: 1128.8 }, 20 | { h: 1127.99, l: 1115.1, c: 1126.3 }, 21 | { h: 1124.05, l: 1108.01, c: 1108.01 }, 22 | { h: 1110.85, l: 1093.28, c: 1108.55 }, 23 | { h: 1119.79, l: 1095.51, c: 1097.43 }, 24 | { h: 1107.38, l: 1091.5, c: 1107.22 }, 25 | { h: 1136.95, l: 1111.69, c: 1133.65 }, 26 | { h: 1134.45, l: 1121.03, c: 1121.16 }, 27 | { h: 1120.03, l: 1102.06, c: 1103.14 }, 28 | { h: 1099.19, l: 1082.53, c: 1095.99 }, 29 | { h: 1102.91, l: 1088.56, c: 1102.91 }, 30 | { h: 1116.04, l: 1099.62, c: 1114.43 }, 31 | { h: 1112.08, l: 1092, c: 1097.9 }, 32 | { h: 1107.26, l: 1093.67, c: 1103.63 }, 33 | { h: 1116.61, l: 1105.15, c: 1113.87 }, 34 | { h: 1126.2, l: 1113.75, c: 1126.19 }, 35 | { h: 1144.1, l: 1132.43, c: 1142.2 }, 36 | { h: 1140.84, l: 1132.95, c: 1136.68 }, 37 | { h: 1141.35, l: 1134.78, c: 1140.07 }, 38 | { h: 1141.73, l: 1130.8, c: 1134.5 }, 39 | { h: 1136.82, l: 1121.05, c: 1128.9 }, 40 | { h: 1129.26, l: 1118.66, c: 1126.15 }, 41 | { h: 1131.75, l: 1119.86, c: 1131.22 }, 42 | { h: 1153.94, l: 1128.4, c: 1153.94 }, 43 | { h: 1167.54, l: 1158.39, c: 1165.21 }, 44 | { h: 1179.84, l: 1166.14, c: 1175.88 }, 45 | { h: 1173.84, l: 1166.9, c: 1172.71 }, 46 | { h: 1168.99, l: 1161.43, c: 1166.71 }, 47 | { h: 1165.37, l: 1156.07, c: 1159.4 }, 48 | ]; 49 | -------------------------------------------------------------------------------- /src/ama.ts: -------------------------------------------------------------------------------- 1 | import { CircularBuffer } from './providers/circular-buffer'; 2 | 3 | /** 4 | * Adaptive Moving Average (AMA) is a powerful tool that can significantly improve your trading strategy. 5 | * In this ultimate guide, I’ll walk you through everything you need to know about AMA – from its 6 | * basics to implementing it in your own trading approach. 7 | * Get ready to take your trading game to the next level! 8 | */ 9 | export class AMA { 10 | private circular: CircularBuffer; 11 | private sumNoise = 0; 12 | private prevPrice: number; 13 | private prevAMA: number; 14 | private nfastend: number; 15 | private nslowend: number; 16 | 17 | constructor(length: number, fastend: number = 4, slowend: number = 30) { 18 | this.circular = new CircularBuffer(length); 19 | this.nfastend = 2 / (fastend + 1); 20 | this.nslowend = 2 / (slowend + 1); 21 | this.prevPrice = 0; // Можно инициализировать значением, например, первым полученным значением цены 22 | this.prevAMA = 0; // Аналогично 23 | } 24 | 25 | nextValue(price: number) { 26 | // Add the new value to the circular buffer and get the noise 27 | const prevPrice = this.prevPrice; 28 | const priceChange = Math.abs(price - prevPrice); 29 | const prevChange = this.circular.push(priceChange) ?? 0; 30 | this.sumNoise += priceChange - (this.circular.filled ? prevChange : 0); 31 | 32 | if (!this.circular.filled) { 33 | // Not enough data to calculate yet, return undefined 34 | this.prevAMA = price; 35 | this.prevPrice = price; // Update prevPrice 36 | return; 37 | } 38 | 39 | // Efficiency Ratio (ER) 40 | const signal = Math.abs(price - prevPrice); 41 | const noiseSum = this.sumNoise; 42 | const er = noiseSum !== 0 ? signal / noiseSum : 0; 43 | 44 | // Smoothing Constant (SC) 45 | const smooth = Math.pow(er * (this.nfastend - this.nslowend) + this.nslowend, 2); 46 | 47 | // AMA Calculation 48 | const currentAMA = this.prevAMA + smooth * (price - this.prevAMA); 49 | 50 | this.prevAMA = currentAMA; 51 | this.prevPrice = price; // Update prevPrice 52 | return currentAMA; 53 | } 54 | 55 | momentValue(): number { 56 | // Return the last calculated AMA value 57 | return this.prevAMA; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/psar/excel-data.ts: -------------------------------------------------------------------------------- 1 | export const highs = [ 2 | 1988.4, 1985, 1988, 1991, 1988.9, 1989.85, 1990.5, 1992.9, 1997.7, 1997.9, 1995.95, 1987.6, 1987, 1982.75, 1984.55, 3 | 1985, 1985, 1983.8, 1985, 1984, 1984.6, 1984.9, 1986, 1987.85, 1987, 1987.35, 1985.5, 1985.35, 1986.15, 1985.55, 4 | 1985, 1986.9, 1986, 1985.65, 1986, 1986.5, 1988.4, 1987.95, 1988, 1989.4, 1989, 1989, 1988.5, 1988.4, 1988.35, 1989, 5 | 1989.8, 1992.7, 1992, 1993, 1992, 1991.65, 1990.75, 1991.5, 1990, 1989.7, 1989.95, 1989.45, 1989, 1989, 1988, 6 | 1987.7, 1985.85, 1984.5, 1987, 1987.95, 1985.2, 1984.95, 1985.2, 1986, 1985, 1984, 1984.4, 1981.4, 1979, 1995.9, 7 | 1994, 1993.35, 1993, 1990, 1989.85, 1991.45, 1990, 1988, 1987.3, 1989.5, 1988.95, 1987.9, 1987, 1987.2, 1988.75, 8 | 1989, 1987.75, 9 | ]; 10 | 11 | export const lows = [ 12 | 1975, 1980, 1980.4, 1985.05, 1985.05, 1985.95, 1986, 1989.05, 1991.3, 1991.9, 1983.1, 1985, 1980.05, 1976.55, 13 | 1978.4, 1983, 1978, 1980.75, 1982.6, 1980.55, 1980.5, 1983.4, 1983.7, 1985.15, 1985.1, 1985, 1984, 1982, 1983.85, 14 | 1983, 1983.25, 1984, 1984.1, 1983, 1984.2, 1985, 1986.3, 1985.5, 1986.95, 1987, 1986, 1986.15, 1986.95, 1987.4, 15 | 1987.55, 1985.7, 1986.5, 1987.75, 1990.1, 1990, 1990.6, 1986.5, 1987, 1989.5, 1986.5, 1986.3, 1987.35, 1988.15, 16 | 1988.35, 1987, 1986, 1980, 1981.2, 1981.2, 1984.15, 1982.5, 1982.2, 1982.05, 1982, 1982.2, 1983.15, 1981.2, 1980.75, 17 | 1977.15, 1977, 1982.65, 1986.4, 1984.75, 1985.55, 1986.4, 1985.55, 1988, 1985.05, 1984, 1981, 1984.8, 1985.05, 18 | 1985.4, 1985.05, 1985.5, 1984, 1985.9, 1985.95, 19 | ]; 20 | 21 | export const closes = [ 22 | 1975, 1984.65, 1983.3, 1986.55, 1987.8, 1986.75, 1989, 1990.1, 1992.7, 1997.35, 1995.6, 1987, 1986.1, 1982, 1979.95, 23 | 1983.95, 1985, 1983.15, 1983.5, 1983.95, 1980.95, 1984.1, 1984.9, 1985.85, 1985.95, 1985.95, 1985.25, 1984.35, 24 | 1985.2, 1983.95, 1984.5, 1984.75, 1985.25, 1984.6, 1985.5, 1985.2, 1986.4, 1987.9, 1987, 1987.85, 1987.9, 1988.35, 25 | 1988.25, 1988.4, 1988.35, 1987.95, 1987.7, 1988.25, 1990.35, 1990.9, 1991.45, 1991.45, 1987.75, 1990.35, 1990, 26 | 1987.3, 1989.7, 1989.25, 1989, 1988.95, 1987, 1987.7, 1984, 1981.2, 1984.2, 1986.9, 1984.5, 1984.85, 1983.05, 1985, 27 | 1983.5, 1983.85, 1983.45, 1981.05, 1978.05, 1986.95, 1993.45, 1991.05, 1990.2, 1988.95, 1987.35, 1989.65, 1989.85, 28 | 1985.05, 1986.65, 1984.8, 1988.5, 1987.2, 1986, 1985.9, 1985.95, 1988.6, 1987.65, 29 | ]; 30 | -------------------------------------------------------------------------------- /src/providers/extremum.ts: -------------------------------------------------------------------------------- 1 | import { CircularBuffer } from './circular-buffer'; 2 | 3 | export class Extremums extends CircularBuffer { 4 | private comparator: Function; 5 | private prevIx: number; 6 | 7 | constructor(public period = 100, private mode: 'max' | 'min') { 8 | super(period); 9 | this.comparator = mode === 'max' ? this.maxComporator : this.mminComporator; 10 | } 11 | 12 | nextValue(value: number) { 13 | this.push(value); 14 | 15 | return this.getExtremum(); 16 | } 17 | 18 | momentValue(value: number) { 19 | const rm = this.push(value); 20 | const extr = this.getExtremum(); 21 | this.pushback(rm); 22 | 23 | return extr; 24 | } 25 | 26 | private maxComporator(a: number, b: number) { 27 | if (a > b) { 28 | return true; 29 | } 30 | 31 | return false; 32 | } 33 | 34 | private mminComporator(a: number, b: number) { 35 | if (a < b) { 36 | return true; 37 | } 38 | 39 | return false; 40 | } 41 | 42 | public getExtremum(shallow?: boolean): number { 43 | let extremumIdx = (this.length + this.pointer - 2) % this.length; 44 | let extremum: number = this.mode === 'max' ? -Infinity : Infinity; 45 | 46 | if (!this.filled) { 47 | return 0; 48 | } 49 | 50 | while (extremumIdx !== this.pointer) { 51 | const before = this.buffer[(this.length + extremumIdx - 1) % this.length]; 52 | const after = this.buffer[(this.length + extremumIdx + 1) % this.length]; 53 | const foundExtremum = this.buffer[extremumIdx]; 54 | 55 | if ( 56 | this.comparator(foundExtremum, extremum) && 57 | this.comparator(foundExtremum, before) && 58 | this.comparator(foundExtremum, after) 59 | ) { 60 | extremum = foundExtremum; 61 | 62 | if (this.prevIx === extremumIdx) { 63 | return null; 64 | } 65 | 66 | this.prevIx = extremumIdx; 67 | 68 | if (shallow) { 69 | return extremum; 70 | } 71 | } 72 | 73 | extremumIdx = (this.length + extremumIdx - 1) % this.length; 74 | } 75 | 76 | if (isFinite(extremum)) { 77 | return extremum; 78 | } 79 | 80 | this.prevIx = null; 81 | 82 | return null; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/stochastic-rsi.ts: -------------------------------------------------------------------------------- 1 | import { SMA } from './sma'; 2 | import { MaxProvider } from './providers/max-value'; 3 | import { MinProvider } from './providers/min-value'; 4 | import { RSI } from './rsi'; 5 | 6 | /** 7 | * Developed by Tushar Chande and Stanley Kroll, StochRSI is an oscillator that measures the level of RSI relative 8 | * to its high-low range over a set time period. StochRSI applies the Stochastics formula to RSI values, rather 9 | * than price values, making it an indicator of an indicator. The result is an oscillator that 10 | * fluctuates between 0 and 1. 11 | */ 12 | export class StochasticRSI { 13 | private max: MaxProvider; 14 | private min: MinProvider; 15 | private rsi: RSI; 16 | private sma1: SMA; 17 | private sma2: SMA; 18 | 19 | constructor(rsiPeriod = 14, kPeriod = 3, dPeriod = 3, stochPeriod = 14) { 20 | this.rsi = new RSI(rsiPeriod); 21 | this.sma1 = new SMA(kPeriod); 22 | this.sma2 = new SMA(dPeriod); 23 | this.max = new MaxProvider(stochPeriod); 24 | this.min = new MinProvider(stochPeriod); 25 | } 26 | 27 | /** 28 | * Get next value for closed candle 29 | * affect all next calculations 30 | */ 31 | nextValue(close: number): { k: number; d: number; stochRsi: number } { 32 | const rsi = this.rsi.nextValue(close); 33 | 34 | if (rsi === undefined) { 35 | return; 36 | } 37 | 38 | const max = this.max.nextValue(rsi); 39 | const min = this.min.nextValue(rsi); 40 | 41 | if (!this.max.filled()) { 42 | return; 43 | } 44 | 45 | const stochRsi = ((rsi - min) / (max - min)) * 100; 46 | const k = this.sma1.nextValue(stochRsi); 47 | 48 | if (k === undefined) { 49 | return; 50 | } 51 | 52 | const d = this.sma2.nextValue(k); 53 | 54 | return { k, d, stochRsi }; 55 | } 56 | 57 | /** 58 | * Get next value for non closed (tick) candle hlc 59 | * does not affect any next calculations 60 | */ 61 | momentValue(close: number): { k: number; d: number; stochRsi: number } { 62 | if (!this.max.filled()) { 63 | return; 64 | } 65 | 66 | const rsi = this.rsi.momentValue(close); 67 | const max = this.max.momentValue(rsi); 68 | const min = this.min.momentValue(rsi); 69 | 70 | const stochRsi = ((rsi - min) / (max - min)) * 100; 71 | const k = this.sma1.momentValue(stochRsi); 72 | const d = this.sma2.momentValue(k); 73 | 74 | return { k, d, stochRsi }; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Dmitry Yurov", 3 | "name": "@debut/indicators", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "keywords": [ 8 | "analysis", 9 | "bollinger-bands", 10 | "finance", 11 | "financial-instruments", 12 | "indicators", 13 | "currency", 14 | "trading", 15 | "forex", 16 | "cryptocurrency", 17 | "bitfinex", 18 | "binance", 19 | "bybit", 20 | "bollinger-bands", 21 | "macd", 22 | "quant", 23 | "quantative-finance", 24 | "quantative-trading", 25 | "stock-analysis", 26 | "stock-market", 27 | "strategies", 28 | "technical", 29 | "technical-analysis", 30 | "trading-algorithms", 31 | "trading-strategies", 32 | "yahoo-finance", 33 | "tradingview" 34 | ], 35 | "description": "Implementation of most popular technical stock indicators for debut environment", 36 | "version": "1.3.22", 37 | "license": "GPL-3.0", 38 | "homepage": "https://github.com/debut-js/Indicators", 39 | "repository": { 40 | "type": "git", 41 | "url": "git@github.com:debut-js/Indicators.git" 42 | }, 43 | "scripts": { 44 | "build": "rollup -c ./rollup.config.js", 45 | "start": "ts-node --", 46 | "watch": "rollup -cw ./rollup.config.js", 47 | "test": "jest", 48 | "version": "npm run build", 49 | "postversion": "git push && git push --tags", 50 | "prettier": "prettier --config './.prettierrc' --write './**/*.ts'" 51 | }, 52 | "bugs": { 53 | "url": "https://github.com/debut-js/Indicators/issues" 54 | }, 55 | "main": "lib/indicators.cjs.js", 56 | "module": "lib/indicators.esm.js", 57 | "browser": "lib/indicators.umd.js", 58 | "types": "lib/index.d.ts", 59 | "devDependencies": { 60 | "@types/jest": "^27.0.6", 61 | "@typescript-eslint/eslint-plugin": "^3.7.0", 62 | "@typescript-eslint/parser": "^3.7.0", 63 | "acorn": "^7.3.1", 64 | "eslint": "^7.5.0", 65 | "eslint-config-prettier": "^6.11.0", 66 | "eslint-plugin-prettier": "^3.1.4", 67 | "jest": "^27.0.6", 68 | "prettier": "^2.0.5", 69 | "rollup": "^2.18.0", 70 | "rollup-plugin-buble": "^0.19.8", 71 | "rollup-plugin-commonjs": "^10.1.0", 72 | "rollup-plugin-node-resolve": "^5.2.0", 73 | "rollup-plugin-terser": "^6.1.0", 74 | "rollup-plugin-typescript2": "^0.27.1", 75 | "rollup-plugin-uglify-es": "0.0.1", 76 | "technicalindicators": "^3.1.0", 77 | "ts-jest": "^27.0.3", 78 | "ts-node": "^8.10.2", 79 | "typescript": "^3.9.5" 80 | }, 81 | "optionalDependencies": {} 82 | } 83 | -------------------------------------------------------------------------------- /src/supertrend.ts: -------------------------------------------------------------------------------- 1 | import { ATR } from './atr'; 2 | /** 3 | * SuperTrend indicator is one of the hybrid custom tools that show the current trend in the market. 4 | * The indicator name stands for Multi Time Frame SuperTrend. 5 | * The tool can show the direction of the trend on several timeframes at once. 6 | */ 7 | export class SuperTrend { 8 | private atr: ATR; 9 | private prevSuper: number; 10 | private prevUpper: number; 11 | private prevLower: number; 12 | private prevClose: number; 13 | 14 | constructor( 15 | period = 10, 16 | private multiplier = 3, 17 | smoothing: 'SMA' | 'EMA' | 'SMMA' | 'WEMA' | 'LWMA' | 'EWMA' | 'RMA' = 'WEMA', 18 | ) { 19 | this.atr = new ATR(period, smoothing); 20 | } 21 | 22 | nextValue(h: number, l: number, c: number) { 23 | const atr = this.atr.nextValue(h, l, c); 24 | 25 | if (atr) { 26 | const src = (h + l) / 2; 27 | let upper = src + this.multiplier * atr; 28 | let lower = src - this.multiplier * atr; 29 | 30 | if (this.prevLower) { 31 | lower = lower > this.prevLower || this.prevClose < this.prevLower ? lower : this.prevLower; 32 | upper = upper < this.prevUpper || this.prevClose > this.prevUpper ? upper : this.prevUpper; 33 | } 34 | 35 | let superTrend = upper; 36 | 37 | if (this.prevSuper === this.prevUpper) { 38 | superTrend = c > upper ? lower : upper; 39 | } else { 40 | superTrend = c < lower ? upper : lower; 41 | } 42 | 43 | const direction = superTrend === upper ? 1 : -1; 44 | 45 | this.prevUpper = upper; 46 | this.prevLower = lower; 47 | this.prevSuper = superTrend; 48 | this.prevClose = c; 49 | 50 | return { upper, lower, superTrend, direction }; 51 | } 52 | } 53 | 54 | momentValue(h: number, l: number, c: number) { 55 | const atr = this.atr.momentValue(h, l); 56 | const src = (h + l) / 2; 57 | 58 | let upper = src + this.multiplier * atr; 59 | let lower = src - this.multiplier * atr; 60 | 61 | if (this.prevLower) { 62 | lower = lower > this.prevLower || this.prevClose < this.prevLower ? lower : this.prevLower; 63 | upper = upper < this.prevSuper || this.prevClose > this.prevUpper ? upper : this.prevUpper; 64 | } 65 | 66 | let superTrend = upper; 67 | 68 | if (this.prevSuper === this.prevUpper) { 69 | superTrend = c > upper ? lower : upper; 70 | } else { 71 | superTrend = c < lower ? upper : lower; 72 | } 73 | 74 | const direction = superTrend === upper ? 1 : -1; 75 | 76 | return { upper, lower, superTrend, direction }; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/wave.ts: -------------------------------------------------------------------------------- 1 | import { percentChange } from './utils'; 2 | 3 | export type WaveData = { 4 | consolidate: number; 5 | power: number; 6 | streak: number; 7 | diff: number; 8 | startPrice: number; 9 | prevPeak: number; 10 | }; 11 | 12 | function clenWave(wave: WaveData) { 13 | wave.consolidate = 0; 14 | wave.power = 0; 15 | wave.streak = 0; 16 | wave.diff = 0; 17 | wave.startPrice = 0; 18 | wave.prevPeak = 0; 19 | 20 | return wave; 21 | } 22 | 23 | export class Wave { 24 | private up: WaveData; 25 | private down: WaveData; 26 | 27 | /** 28 | * Конструктор 29 | */ 30 | constructor() { 31 | this.up = { consolidate: 0, power: 0, streak: 0, startPrice: 0, diff: 0, prevPeak: 0 }; 32 | this.down = { consolidate: 0, power: 0, streak: 0, startPrice: 0, diff: 0, prevPeak: 0 }; 33 | } 34 | 35 | nextValue(open: number, close: number, high: number, low: number) { 36 | // bullish 37 | if (open < close || (this.up.streak && this.up.prevPeak < low)) { 38 | if (!this.up.startPrice) { 39 | this.up.startPrice = open; 40 | } 41 | 42 | if (this.down.streak) { 43 | clenWave(this.down); 44 | } 45 | 46 | const diff = close - open; 47 | this.up.streak++; 48 | this.up.prevPeak = low; 49 | 50 | if (this.up.diff > diff) { 51 | this.up.consolidate++; 52 | } else { 53 | this.up.consolidate = 0; 54 | } 55 | 56 | this.up.diff = diff; 57 | this.up.power = percentChange(close, this.up.startPrice); 58 | } 59 | 60 | // bearish 61 | if (open > close || (this.down.streak && this.down.prevPeak > high)) { 62 | if (!this.down.startPrice) { 63 | this.down.startPrice = open; 64 | } 65 | 66 | if (this.up.streak) { 67 | clenWave(this.up); 68 | } 69 | 70 | const diff = open - close; 71 | this.down.streak++; 72 | this.down.prevPeak = high; 73 | 74 | if (this.down.diff > diff) { 75 | this.down.consolidate++; 76 | } else { 77 | this.down.consolidate = 0; 78 | } 79 | 80 | this.down.diff = diff; 81 | this.down.power = percentChange(close, this.down.startPrice); 82 | } 83 | 84 | // doji is neutral 85 | if (open === close) { 86 | this.up.streak++; 87 | this.down.streak++; 88 | this.up.diff = this.down.diff = 0; 89 | } 90 | 91 | if (this.up.streak > this.down.streak) { 92 | return { ...this.up }; 93 | } else { 94 | return { ...this.down }; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/trendlines/readme.md: -------------------------------------------------------------------------------- 1 | # Trend Lines indicator 2 | 3 | ## Description 4 | 5 | Trendlines are a key part of delving into technical analysis and trading off of charts. When used correctly, they're a helpful, clear, and relatively simple tool for traders. 6 | There are many approaches to construct [trending lines](https://ru.tradingview.com/script/eXUYLaGv-Trend-Lines-v2/). All of them are based on building lines through extremes. There are some unavoidable issues of this method. First is uncertainty of any kind approximation. Second, large and expanding data array of extremes required to construct future lines. 7 | Here we propose new and much more effective method for the trading lines discovery. This method based on three actions: change incline, fork, and break. 8 | 9 | ## Method 10 | 11 | The search for support and resistance lines is carried out by squeezing to the center of the price movement. We search simultaneously support and resistance lines. Here and after we describe resistance line. Similar procedure provided on the support lines. 12 | 13 | - We begin from historical data. First line is drawn through the high point of the two candles. 14 | - If the next h-value is higher the line then we change incline of the line. 15 | - If the next h-value is lower the line then we fork: create new line from the current point with the same behavior. 16 | - If incline become > 0 then next fork will replace current. 17 | - If current line is crossing upper line then we delete it. 18 | 19 | This method doesn’t require keeping historical candles data for future calculations. We keep only 1 candle back in the history and metadata of non deleted lines (up to 60 lines). 20 | 21 | ## Sample result 22 | 23 | ![Trend lines provided by the indicator for TSLA](./sample2.jpg) 24 | ![Trend lines provided by the indicator for FTMUSDT](./sample3.png) 25 | ![Equity on FTMUSDT during 200 days on 30min candles](./sample4.png) 26 | 27 | ``` 28 | > @debut/strategies@1.0.0 testing 29 | > tester -- "--bot=ExpBot" "--ticker=FTMUSDT" "--days=200" 30 | 31 | History loading from [binance] 07.09.2021: 32 | 33 | [████████████████████████████████████████] 100% | 200 of 200 days 34 | 35 | ---- [binance] [FTMUSDT] ---- 36 | 37 | Tested in 9845 candles... 38 | Report data is ready... 39 | { 40 | startBalance: 600, 41 | balance: 3018.46, 42 | maxBalance: 3227.26, 43 | minBalance: 600, 44 | maxMarginUsage: 600, 45 | profit: 2418.46, 46 | long: 148, 47 | longRight: 65, 48 | short: 148, 49 | shortRight: 75, 50 | absoluteDD: 6.68, 51 | relativeDD: 14.38, 52 | maxWin: 52.91, 53 | maxLoose: -10.55, 54 | profitProb: 0.47, 55 | looseProb: 0.53, 56 | avgProfit: 35.76, 57 | avgLoose: 16.59, 58 | expectation: 8.17, 59 | failLine: 6, 60 | rightLine: 7, 61 | avgFailLine: 1.97, 62 | avgRightLine: 1.76, 63 | ticksHandled: 9845, 64 | candlesHandled: 9644 65 | } 66 | ``` 67 | ## Authors 68 | 69 | - [Dmitry Korotkov](https://github.com/inimatic) 70 | - [Dmitry Yurov](https://github.com/BusinessDuck) 71 | -------------------------------------------------------------------------------- /src/mfi.ts: -------------------------------------------------------------------------------- 1 | import { CircularBuffer } from './providers/circular-buffer'; 2 | 3 | /** 4 | * Money Flow Index (MFI) is a movement indicator used in technical analysis that looks at time and price 5 | * to measure the trading pressure — buying or selling. It is also called volume-weighted 6 | * Relative Strength Index (RSI), as it includes volume, unlike RSI, which only incorporates price. 7 | */ 8 | export class MFI { 9 | private positiveMoneyFlowSum = 0; 10 | private negativeMoneyFlowSum = 0; 11 | private pevTypicalPrice = 0; 12 | private posCircular: CircularBuffer; 13 | private negCircular: CircularBuffer; 14 | 15 | constructor(period = 14) { 16 | this.posCircular = new CircularBuffer(period); 17 | this.negCircular = new CircularBuffer(period); 18 | } 19 | 20 | nextValue(high: number, low: number, close: number, volume: number) { 21 | const typicalPrice = (high + low + close) / 3; 22 | const moneyFlow = typicalPrice * volume; 23 | 24 | if (!this.pevTypicalPrice) { 25 | this.pevTypicalPrice = typicalPrice; 26 | return; 27 | } 28 | 29 | const positiveMoneyFlow = typicalPrice > this.pevTypicalPrice ? moneyFlow : 0; 30 | const negativeMoneyFlow = typicalPrice < this.pevTypicalPrice ? moneyFlow : 0; 31 | 32 | this.pevTypicalPrice = typicalPrice; 33 | this.negativeMoneyFlowSum += negativeMoneyFlow; 34 | this.positiveMoneyFlowSum += positiveMoneyFlow; 35 | 36 | const posRedunant = this.posCircular.push(positiveMoneyFlow); 37 | const negRedunant = this.negCircular.push(negativeMoneyFlow); 38 | 39 | if (!this.posCircular.filled) { 40 | return; 41 | } 42 | 43 | this.negativeMoneyFlowSum -= negRedunant || 0; 44 | this.positiveMoneyFlowSum -= posRedunant || 0; 45 | 46 | const moneyFlowRatio = this.positiveMoneyFlowSum / this.negativeMoneyFlowSum; 47 | 48 | return 100 - 100 / (1 + moneyFlowRatio); 49 | } 50 | 51 | momentValue(high: number, low: number, close: number, volume: number) { 52 | const typicalPrice = (high + low + close) / 3; 53 | const moneyFlow = typicalPrice * volume; 54 | 55 | if (!this.pevTypicalPrice) { 56 | return; 57 | } 58 | 59 | const positiveMoneyFlow = typicalPrice > this.pevTypicalPrice ? moneyFlow : 0; 60 | const negativeMoneyFlow = typicalPrice < this.pevTypicalPrice ? moneyFlow : 0; 61 | 62 | if (!this.posCircular.filled) { 63 | return; 64 | } 65 | 66 | const posRedunant = this.posCircular.peek(); 67 | const negRedunant = this.negCircular.peek(); 68 | const negativeMoneyFlowSum = this.negativeMoneyFlowSum + negativeMoneyFlow - negRedunant; 69 | const positiveMoneyFlowSum = this.positiveMoneyFlowSum + positiveMoneyFlow - posRedunant; 70 | const moneyFlowRatio = positiveMoneyFlowSum / negativeMoneyFlowSum; 71 | 72 | return 100 - 100 / (1 + moneyFlowRatio); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/crsi.ts: -------------------------------------------------------------------------------- 1 | import { PercentRank } from './providers/percent-rank'; 2 | import { ROC } from './roc'; 3 | import { RSI } from './rsi'; 4 | 5 | /** 6 | * Connors RSI (CRSI) uses the above formula to generate a value between 0 and 100. 7 | * This is primarily used to identify overbought and oversold levels. 8 | * Connor's original definition of these levels is that a value over 90 9 | * should be considered overbought and a value under 10 should be considered oversold. 10 | * On occasion, signals occur during slight corrections during a trend. For example, 11 | * when the market is in an uptrend, Connors RSI might generate short term sell signals. 12 | * When the market is in a downtrend, Connors RSI might generate short term buy signals. 13 | * Original core here: https://tradingview.com/script/vWAPUAl9-Stochastic-Connors-RSI/ 14 | */ 15 | export class cRSI { 16 | private rsi: RSI; 17 | private updownRsi: RSI; 18 | private prevClose: number; 19 | private updownPeriod: number; 20 | private updownValue: number; 21 | private roc: ROC; 22 | private percentRank: PercentRank; 23 | 24 | constructor(private period = 3, updownRsiPeriod = 2, percentRankPeriod = 100) { 25 | this.rsi = new RSI(this.period); 26 | this.updownRsi = new RSI(updownRsiPeriod); 27 | this.roc = new ROC(1); 28 | this.percentRank = new PercentRank(percentRankPeriod); 29 | this.updownPeriod = 0; 30 | this.prevClose = 0; 31 | } 32 | 33 | nextValue(value: number) { 34 | const rsi = this.rsi.nextValue(value); 35 | const percentRank = this.percentRank.nextValue(this.roc.nextValue(value)); 36 | 37 | this.updownPeriod = this.getUpdownPeriod(value); 38 | this.prevClose = value; 39 | this.updownValue = this.updownRsi.nextValue(this.updownPeriod); 40 | 41 | if (!this.updownValue) { 42 | return; 43 | } 44 | 45 | return (rsi + this.updownValue + percentRank) / 3; 46 | } 47 | 48 | momentValue(value: number) { 49 | const rsi = this.rsi.momentValue(value); 50 | const percentRank = this.percentRank.momentValue(this.roc.momentValue(value)); 51 | const updownPeriod = this.getUpdownPeriod(value); 52 | const updownValue = this.updownRsi.momentValue(updownPeriod); 53 | 54 | if (updownValue === undefined) { 55 | return; 56 | } 57 | 58 | return (rsi + updownValue + percentRank) / 3; 59 | } 60 | 61 | private getUpdownPeriod(value: number) { 62 | let updownPeriod = this.updownPeriod; 63 | 64 | if (value > this.prevClose) { 65 | // reset negative streak 66 | if (this.updownPeriod < 0) { 67 | updownPeriod = 0; 68 | } 69 | 70 | updownPeriod++; 71 | } else if (value < this.prevClose) { 72 | // reset positive streak 73 | if (this.updownPeriod > 0) { 74 | updownPeriod = 0; 75 | } 76 | 77 | updownPeriod--; 78 | } else { 79 | updownPeriod = 0; 80 | } 81 | 82 | return updownPeriod; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /docs/AdaptiveMovingAverage.md: -------------------------------------------------------------------------------- 1 | # Adaptive Moving Average (AMA) 2 | 3 | The **Adaptive Moving Average (AMA)** is a trend-following technical indicator developed by Perry Kaufman. It adjusts its sensitivity to price changes based on market volatility, making it useful for smoothing price data and filtering out market noise. 4 | 5 | ## Key Features 6 | 7 | - **Dynamic Adjustments**: AMA adapts to market conditions by using a variable smoothing constant. This allows the indicator to react quickly to market trends while minimizing false signals during sideways markets. 8 | - **Sensitivity to Volatility**: The smoother the market (less volatility), the slower the AMA will move, filtering out the noise. In contrast, in volatile market conditions, the AMA will respond faster to price movements. 9 | - **Trend Detection**: AMA is particularly useful in identifying trends and spotting reversals. 10 | 11 | ## Calculation 12 | 13 | The formula for AMA involves several steps, including determining the efficiency ratio (ER), which measures the market's trend strength relative to volatility. 14 | 15 | 1. **Efficiency Ratio (ER)**: 16 | The ER is calculated by dividing the absolute price change over a period by the sum of the absolute price changes for each bar within the same period. 17 | 18 | \[ 19 | ER = \frac{\text{Price Change (Abs)}}{\text{Sum of Absolute Price Changes}} 20 | \] 21 | 22 | 2. **Smoothing Constant (SC)**: 23 | The smoothing constant adjusts based on the ER. A higher ER leads to a faster reaction, while a lower ER results in slower smoothing. The smoothing constant is calculated as: 24 | 25 | \[ 26 | SC = \left( ER \times (FastSC - SlowSC) + SlowSC \right)^2 27 | \] 28 | 29 | Where: 30 | - **FastSC**: Faster smoothing constant (commonly set to 2) 31 | - **SlowSC**: Slower smoothing constant (commonly set to 30) 32 | 33 | 3. **AMA Formula**: 34 | Once the ER and SC are determined, the AMA is calculated as: 35 | 36 | \[ 37 | AMA_{today} = AMA_{yesterday} + SC \times (Price_{today} - AMA_{yesterday}) 38 | \] 39 | 40 | ## Usage 41 | 42 | - **Identifying Trends**: AMA can be used to determine the overall trend direction. When the price is above the AMA, it indicates a bullish trend; when below, it suggests a bearish trend. 43 | - **Reversal Signals**: A significant change in AMA's direction may signal a potential market reversal. 44 | - **Filtering Noise**: By adjusting to market conditions, AMA helps traders avoid reacting to minor price fluctuations and focus on significant trends. 45 | 46 | ## Settings 47 | 48 | Typical parameters for the AMA indicator include: 49 | - **Fast Period**: 2 (for faster response) 50 | - **Slow Period**: 30 (for slower smoothing) 51 | 52 | ## Conclusion 53 | 54 | The Adaptive Moving Average (AMA) is a powerful tool for trend-following strategies, providing a dynamic approach to adjusting sensitivity based on market volatility. It’s widely used by traders to detect trends and reduce the impact of market noise, helping them make more informed trading decisions. 55 | 56 | --- 57 | 58 | For more details, refer to the official MetaTrader 5 documentation on [AMA](https://www.metatrader5.com/en/terminal/help/indicators/trend_indicators/ama). 59 | -------------------------------------------------------------------------------- /src/providers/circular-buffer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Circular buffers (also known as ring buffers) are fixed-size buffers that work as if the memory is contiguous & circular in nature. 3 | * As memory is generated and consumed, data does not need to be reshuffled – rather, the head/tail pointers are adjusted. 4 | * When data is added, the head pointer advances. When data is consumed, the tail pointer advances. 5 | * If you reach the end of the buffer, the pointers simply wrap around to the beginning. 6 | */ 7 | export class CircularBuffer { 8 | public filled = false; 9 | protected pointer = 0; 10 | protected buffer: Array; 11 | protected maxIndex: number; 12 | 13 | /** 14 | * Constructor 15 | * @param length fixed buffer length 16 | */ 17 | constructor(public length: number) { 18 | this.buffer = new Array(length); 19 | this.maxIndex = length - 1; 20 | } 21 | 22 | /** 23 | * Push item to buffer, when buffer length is overflow, push will rewrite oldest item 24 | */ 25 | public push(item: T) { 26 | const overwrited = this.buffer[this.pointer]; 27 | 28 | this.buffer[this.pointer] = item; 29 | this.iteratorNext(); 30 | 31 | return overwrited; 32 | } 33 | 34 | /** 35 | * Replace last added item in buffer (reversal push). May be used for revert push removed item. 36 | * @deprecated use peek instead 37 | */ 38 | public pushback(item: T) { 39 | this.iteratorPrev(); 40 | const overwrited = this.buffer[this.pointer]; 41 | this.buffer[this.pointer] = item; 42 | 43 | return overwrited; 44 | } 45 | 46 | /** 47 | * Get item for replacing, does not modify anything 48 | */ 49 | public peek() { 50 | return this.buffer[this.pointer]; 51 | } 52 | 53 | /** 54 | * Array like forEach loop 55 | */ 56 | public forEach(callback: (value: T, index?: number) => void) { 57 | let idx = this.pointer; 58 | let virtualIdx = 0; 59 | 60 | while (virtualIdx !== this.length) { 61 | callback(this.buffer[idx], virtualIdx); 62 | idx = (this.length + idx + 1) % this.length; 63 | virtualIdx++; 64 | } 65 | } 66 | 67 | /** 68 | * Array like forEach loop, but from last to first (reversal forEach) 69 | */ 70 | forEachRight(callback: (value: T, index?: number) => void) { 71 | let idx = (this.length + this.pointer - 1) % this.length; 72 | let virtualIdx = this.length - 1; 73 | 74 | while (virtualIdx !== this.length) { 75 | callback(this.buffer[idx], virtualIdx); 76 | idx = (this.length + idx - 1) % this.length; 77 | virtualIdx--; 78 | } 79 | } 80 | 81 | /** 82 | * Fill buffer 83 | */ 84 | public fill(item: T) { 85 | this.buffer.fill(item); 86 | this.filled = true; 87 | } 88 | 89 | /** 90 | * Get array from buffer 91 | */ 92 | public toArray() { 93 | return this.buffer; 94 | } 95 | 96 | /** 97 | * Move iterator to next position 98 | */ 99 | private iteratorNext() { 100 | this.pointer++; 101 | 102 | if (this.pointer > this.maxIndex) { 103 | this.pointer = 0; 104 | this.filled = true; 105 | } 106 | } 107 | 108 | /** 109 | * Move iterator to prev position 110 | */ 111 | private iteratorPrev() { 112 | this.pointer--; 113 | 114 | if (this.pointer < 0) { 115 | this.pointer = this.maxIndex; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/adx.ts: -------------------------------------------------------------------------------- 1 | import { getTrueRange } from './providers/true-range'; 2 | import { WEMA } from './wema'; 3 | import { WWS } from './wws'; 4 | 5 | /** 6 | * ADX values help traders identify the strongest and most profitable trends to trade. 7 | * The values are also important for distinguishing between trending and non-trending conditions. 8 | * Many traders will use ADX readings above 25 to suggest that the trend is strong enough for trend-trading strategies. 9 | * Conversely, when ADX is below 25, many will avoid trend-trading strategies. 10 | * ADX Value Trend Strength 11 | * 0-25 Absent or Weak Trend 12 | * 25-50 Strong Trend 13 | * 50-75 Very Strong Trend 14 | * 75-100 Extremely Strong Trend 15 | */ 16 | export class ADX { 17 | private prevHigh: number; 18 | private prevLow: number; 19 | private prevClose: number; 20 | private wma1: WWS; 21 | private wma2: WWS; 22 | private wma3: WWS; 23 | private wema: WEMA; 24 | 25 | constructor(public period: number = 14) { 26 | this.wma1 = new WWS(period); 27 | this.wma2 = new WWS(period); 28 | this.wma3 = new WWS(period); 29 | this.wema = new WEMA(period); 30 | } 31 | 32 | nextValue(h: number, l: number, c: number) { 33 | if (!this.prevClose) { 34 | this.prevHigh = h; 35 | this.prevLow = l; 36 | this.prevClose = c; 37 | 38 | return; 39 | } 40 | 41 | let pDM = 0; 42 | let nDM = 0; 43 | 44 | const hDiff = h - this.prevHigh; 45 | const lDiff = this.prevLow - l; 46 | 47 | if (hDiff > lDiff && hDiff > 0) { 48 | pDM = hDiff; 49 | } 50 | 51 | if (lDiff > hDiff && lDiff > 0) { 52 | nDM = lDiff; 53 | } 54 | 55 | if (pDM > nDM || nDM < 0) { 56 | nDM = 0; 57 | } 58 | 59 | const atr = this.wma1.nextValue(getTrueRange(h, l, this.prevClose)); 60 | const avgPDI = this.wma2.nextValue(pDM); 61 | const avgNDI = this.wma3.nextValue(nDM); 62 | 63 | this.prevHigh = h; 64 | this.prevLow = l; 65 | this.prevClose = c; 66 | 67 | if (avgPDI === undefined || avgNDI === undefined) { 68 | return; 69 | } 70 | 71 | const pDI = (avgPDI * 100) / atr; 72 | const nDI = (avgNDI * 100) / atr; 73 | const diAbs = pDI > nDI ? pDI - nDI : nDI - pDI; 74 | 75 | return { adx: this.wema.nextValue(100 * (diAbs / (pDI + nDI))), pdi: pDI, mdi: nDI }; 76 | } 77 | 78 | momentValue(h: number, l: number, c: number) { 79 | if (!this.prevClose) { 80 | return; 81 | } 82 | 83 | let pDM = 0; 84 | let nDM = 0; 85 | 86 | const hDiff = h - this.prevHigh; 87 | const lDiff = this.prevLow - l; 88 | 89 | if (hDiff > lDiff && hDiff > 0) { 90 | pDM = hDiff; 91 | } 92 | 93 | if (lDiff > hDiff && lDiff > 0) { 94 | nDM = lDiff; 95 | } 96 | 97 | if (pDM > nDM || nDM < 0) { 98 | nDM = 0; 99 | } 100 | 101 | const atr = this.wma1.momentValue(getTrueRange(h, l, this.prevClose)); 102 | const avgPDI = this.wma2.momentValue(pDM); 103 | const avgNDI = this.wma3.momentValue(nDM); 104 | 105 | if (avgPDI === undefined || avgNDI === undefined) { 106 | return; 107 | } 108 | 109 | const pDI = (avgPDI * 100) / atr; 110 | const nDI = (avgNDI * 100) / atr; 111 | const diAbs = pDI > nDI ? pDI - nDI : nDI - pDI; 112 | 113 | return { adx: this.wema.momentValue(100 * (diAbs / (pDI + nDI))), pdi: pDI, mdi: nDI }; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/trendlines/lines.model.ts: -------------------------------------------------------------------------------- 1 | import { LineEvent, LineDirective, Point } from './types'; 2 | import { LineModel } from './line.model'; 3 | 4 | /** 5 | * Lines Model class. 6 | * this.index - index in lineDirectives array 7 | */ 8 | export class LinesModel { 9 | list: [number[], number[]]; // [h: l] 10 | id: { 11 | [id: string]: LineModel; 12 | }; 13 | lineIndex: number = 0; 14 | private step: number; // Step of time in minutes 15 | public forkDiffH: number = null; 16 | public forkDiffL: number = null; 17 | constructor(step) { 18 | this.step = step; 19 | this.list = [[], []]; 20 | this.id = {}; 21 | } 22 | 23 | add(h: number, l: number, i: number, prevPoint = null, lineID = null) { 24 | // Если lineID определена - это экстремум 25 | let curIndex, 26 | holdLastForkY = null; 27 | if (lineID == null) { 28 | curIndex = this.lineIndex; 29 | this.lineIndex++; 30 | if (h != null) this.list[0].push(curIndex); 31 | else this.list[1].push(curIndex); 32 | } else { 33 | curIndex = lineID; 34 | // Сохраняем прежнюю точку ветвления или берем предыдущую 35 | holdLastForkY = this.id[curIndex].lastForkY || (h != null ? prevPoint.h : prevPoint.l); 36 | } 37 | this.id[curIndex] = new LineModel(h, l, i, this.step, curIndex, prevPoint); 38 | // Restore lastForkY from previous state 39 | if (holdLastForkY) { 40 | this.id[curIndex].lastForkY = holdLastForkY; 41 | } 42 | 43 | /** 44 | * Метод 3. Ищем последовательность: fork, rollback, fork, trade, wait same on opposite side 45 | */ 46 | 47 | /** 48 | * Метод 2. Для линии в начале ее ветвления сохраняем y и сравниваем. Сигнализируем, когда разница больше или меньше нуля 49 | * Метод дает слишком много сигналов из которых сложно выделить значимые. Стоит поискать более эффективный метод поиска разворота 50 | */ 51 | let sourceLineID, preSourceLineID; 52 | if (curIndex > 1 || (lineID != null && this.id[lineID] != null)) { 53 | sourceLineID = 54 | lineID != null 55 | ? lineID 56 | : this.list[this.id[curIndex].type == 'h' ? 0 : 1][ 57 | this.list[this.id[curIndex].type == 'h' ? 0 : 1].indexOf(curIndex) - 1 58 | ]; 59 | preSourceLineID = 60 | lineID != null 61 | ? lineID 62 | : this.list[this.id[curIndex].type == 'h' ? 0 : 1][ 63 | this.list[this.id[curIndex].type == 'h' ? 0 : 1].indexOf(curIndex) - 2 64 | ]; 65 | let lastForkVal = this.id[sourceLineID].lastForkY; 66 | // Если линия только создана, то берем экстремум у предыдущей в массиве линии preSourceLineID 67 | if (preSourceLineID && !lastForkVal) lastForkVal = this.id[preSourceLineID].lastForkY; 68 | let prevForkValue = h != null ? prevPoint.h : prevPoint.l; 69 | if (lastForkVal) { 70 | if (h) this.forkDiffH = lastForkVal >= prevForkValue ? -sourceLineID - 1 : sourceLineID + 1; 71 | else this.forkDiffL = lastForkVal >= prevForkValue ? -sourceLineID - 1 : sourceLineID + 1; 72 | } 73 | this.id[sourceLineID].lastForkY = prevForkValue; 74 | } 75 | 76 | return curIndex; 77 | } 78 | 79 | update(lineID, h, l, t) { 80 | return this.id[lineID].update(h, l, t); 81 | } 82 | 83 | delete(lineID) { 84 | if (!this.id[lineID]) return; 85 | if (this.id[lineID].type == 'h') this.list[0].splice(this.list[0].indexOf(lineID), 1); 86 | else this.list[1].splice(this.list[1].indexOf(lineID), 1); 87 | delete this.id[lineID]; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/ichimoku.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export interface IchimokuInput { 4 | tenkanPeriod?: number; // Period for Tenkan-sen 5 | kijunPeriod?: number; // Period for Kijun-sen 6 | senkouBPeriod?: number; // Period for Senkou Span B 7 | displacement?: number; // Displacement for Senkou Spans and Chikou Span 8 | } 9 | 10 | export interface IchimokuOutput { 11 | tenkanSen: number | null; // Tenkan-sen (Conversion Line) 12 | kijunSen: number | null; // Kijun-sen (Base Line) 13 | senkouSpanA: number | null; // Senkou Span A (Leading Span A) 14 | senkouSpanB: number | null; // Senkou Span B (Leading Span B) 15 | chikouSpan: number | null; // Chikou Span (Lagging Span) 16 | } 17 | 18 | export class Ichimoku { 19 | private tenkanPeriod: number; 20 | private kijunPeriod: number; 21 | private senkouBPeriod: number; 22 | private displacement: number; 23 | private highBuffer: number[]; 24 | private lowBuffer: number[]; 25 | private closeBuffer: number[]; 26 | private tenkanSen: number[]; 27 | private kijunSen: number[]; 28 | private senkouSpanA: number[]; 29 | private senkouSpanB: number[]; 30 | private chikouSpan: number[]; 31 | 32 | constructor(input: IchimokuInput = {}) { 33 | this.tenkanPeriod = input.tenkanPeriod || 9; 34 | this.kijunPeriod = input.kijunPeriod || 26; 35 | this.senkouBPeriod = input.senkouBPeriod || 52; 36 | this.displacement = input.displacement || 26; 37 | this.highBuffer = []; 38 | this.lowBuffer = []; 39 | this.closeBuffer = []; 40 | this.tenkanSen = []; 41 | this.kijunSen = []; 42 | this.senkouSpanA = []; 43 | this.senkouSpanB = []; 44 | this.chikouSpan = []; 45 | } 46 | 47 | // Calculate the highest high or lowest low over a period 48 | private calculateHighLow(period: number, isHigh: boolean): number | null { 49 | const buffer = isHigh ? this.highBuffer : this.lowBuffer; 50 | if (buffer.length < period) return null; 51 | return isHigh 52 | ? Math.max(...buffer.slice(-period)) 53 | : Math.min(...buffer.slice(-period)); 54 | } 55 | 56 | // Calculate Tenkan-sen or Kijun-sen 57 | private calculateLine(period: number): number | null { 58 | const high = this.calculateHighLow(period, true); 59 | const low = this.calculateHighLow(period, false); 60 | if (high === null || low === null) return null; 61 | return (high + low) / 2; 62 | } 63 | 64 | nextValue(high: number, low: number, close: number): IchimokuOutput{ 65 | return this.next({high, low, close}) 66 | } 67 | 68 | next(candle: { high: number; low: number; close: number }): IchimokuOutput { 69 | this.highBuffer.push(candle.high); 70 | this.lowBuffer.push(candle.low); 71 | this.closeBuffer.push(candle.close); 72 | 73 | // Calculate Tenkan-sen 74 | const tenkanSen = this.calculateLine(this.tenkanPeriod); 75 | if (tenkanSen !== null) this.tenkanSen.push(tenkanSen); 76 | 77 | // Calculate Kijun-sen 78 | const kijunSen = this.calculateLine(this.kijunPeriod); 79 | if (kijunSen !== null) this.kijunSen.push(kijunSen); 80 | 81 | // Calculate Senkou Span A 82 | let senkouSpanA: number | null = null; 83 | if (tenkanSen !== null && kijunSen !== null) { 84 | senkouSpanA = (tenkanSen + kijunSen) / 2; 85 | this.senkouSpanA.push(senkouSpanA); 86 | } 87 | 88 | // Calculate Senkou Span B 89 | const senkouSpanB = this.calculateLine(this.senkouBPeriod); 90 | if (senkouSpanB !== null) this.senkouSpanB.push(senkouSpanB); 91 | 92 | // Calculate Chikou Span (lagging close price) 93 | let chikouSpan: number | null = null; 94 | if (this.closeBuffer.length > this.displacement) { 95 | chikouSpan = this.closeBuffer[this.closeBuffer.length - 1 - this.displacement]; 96 | this.chikouSpan.push(chikouSpan); 97 | } 98 | 99 | // Maintain buffer size 100 | if (this.highBuffer.length > this.senkouBPeriod) { 101 | this.highBuffer.shift(); 102 | this.lowBuffer.shift(); 103 | this.closeBuffer.shift(); 104 | } 105 | 106 | // Return values for the current candle 107 | return { 108 | tenkanSen: tenkanSen, 109 | kijunSen: kijunSen, 110 | senkouSpanA: this.senkouSpanA.length > this.displacement ? this.senkouSpanA[this.senkouSpanA.length - 1 - this.displacement] : null, 111 | senkouSpanB: this.senkouSpanB.length > this.displacement ? this.senkouSpanB[this.senkouSpanB.length - 1 - this.displacement] : null, 112 | chikouSpan: chikouSpan, 113 | }; 114 | } 115 | } -------------------------------------------------------------------------------- /tests/heikenashi/excel-data.ts: -------------------------------------------------------------------------------- 1 | export const ohlc = [ 2 | { o: 93.25, h: 93.3, l: 93.3, c: 93.25 }, 3 | { o: 98.55, h: 98.55, l: 98.55, c: 98.55 }, 4 | { o: 100.98, h: 99.99, l: 100.98, c: 99.99 }, 5 | { o: 102.05, h: 102.05, l: 102.05, c: 102.05 }, 6 | { o: 103.0, h: 103.9, l: 103.9, c: 103.0 }, 7 | { o: 104.08, h: 104.23, l: 104.23, c: 104.08 }, 8 | { o: 104.9, h: 104.6, l: 105.0, c: 104.0 }, 9 | { o: 104.6, h: 104.6, l: 104.6, c: 104.6 }, 10 | { o: 105.9, h: 105.9, l: 105.9, c: 105.9 }, 11 | { o: 104.4, h: 104.4, l: 105.0, c: 103.51 }, 12 | { o: 105.18, h: 105.18, l: 105.18, c: 105.18 }, 13 | { o: 103.0, h: 102.6, l: 103.52, c: 102.6 }, 14 | { o: 100.0, h: 100.0, l: 100.0, c: 100.0 }, 15 | { o: 99.4, h: 98.95, l: 99.78, c: 98.95 }, 16 | { o: 99.0, h: 99.0, l: 99.0, c: 99.0 }, 17 | { o: 100.01, h: 100.9, l: 101.0, c: 100.01 }, 18 | { o: 102.0, h: 102.7, l: 102.7, c: 102.0 }, 19 | { o: 102.7, h: 102.0, l: 102.7, c: 102.0 }, 20 | { o: 103.3, h: 103.3, l: 103.3, c: 103.3 }, 21 | { o: 104.0, h: 104.3, l: 104.3, c: 104.0 }, 22 | { o: 104.5, h: 104.3, l: 104.5, c: 104.3 }, 23 | { o: 103.2, h: 103.2, l: 103.2, c: 103.2 }, 24 | { o: 102.2, h: 102.43, l: 102.43, c: 102.2 }, 25 | { o: 103.0, h: 103.0, l: 103.0, c: 103.0 }, 26 | { o: 105.5, h: 105.5, l: 105.5, c: 105.5 }, 27 | { o: 105.0, h: 105.0, l: 105.0, c: 105.0 }, 28 | { o: 103.0, h: 103.0, l: 103.0, c: 103.0 }, 29 | { o: 106.0, h: 106.4, l: 106.4, c: 106.0 }, 30 | { o: 106.6, h: 106.6, l: 106.6, c: 106.6 }, 31 | { o: 107.65, h: 107.65, l: 107.65, c: 107.65 }, 32 | { o: 108.4, h: 107.92, l: 108.47, c: 107.92 }, 33 | { o: 110.95, h: 110.95, l: 110.95, c: 110.95 }, 34 | { o: 111.0, h: 111.0, l: 111.0, c: 111.0 }, 35 | { o: 113.0, h: 112.0, l: 113.0, c: 112.0 }, 36 | { o: 114.68, h: 115.0, l: 115.25, c: 114.68 }, 37 | { o: 115.0, h: 115.0, l: 115.0, c: 115.0 }, 38 | { o: 113.95, h: 113.95, l: 113.95, c: 113.95 }, 39 | { o: 115.0, h: 115.0, l: 115.0, c: 115.0 }, 40 | { o: 115.0, h: 115.0, l: 115.0, c: 115.0 }, 41 | { o: 116.0, h: 116.0, l: 116.0, c: 116.0 }, 42 | { o: 117.33, h: 117.33, l: 117.33, c: 117.33 }, 43 | { o: 118.35, h: 118.35, l: 118.35, c: 118.35 }, 44 | ]; 45 | 46 | export const haValues = [ 47 | { o: 93.25, h: 93.3, l: 93.25, c: 93.275 }, 48 | { o: 93.26, h: 98.55, l: 93.26, c: 98.55 }, 49 | { o: 95.91, h: 100.49, l: 95.91, c: 100.485 }, 50 | { o: 98.2, h: 102.05, l: 98.2, c: 102.05 }, 51 | { o: 100.12, h: 103.9, l: 100.12, c: 103.45 }, 52 | { o: 101.79, h: 104.23, l: 101.79, c: 104.1552 }, 53 | { o: 102.97, h: 104.63, l: 102.97, c: 104.625 }, 54 | { o: 103.8, h: 104.6, l: 103.8, c: 104.6 }, 55 | { o: 104.2, h: 105.9, l: 104.2, c: 105.9 }, 56 | { o: 105.05, h: 105.05, l: 104.33, c: 104.3275 }, 57 | { o: 104.69, h: 105.18, l: 104.69, c: 105.175 }, 58 | { o: 104.93, h: 104.93, l: 102.93, c: 102.929975 }, 59 | { o: 103.93, h: 103.93, l: 100.0, c: 100 }, 60 | { o: 101.97, h: 101.97, l: 99.27, c: 99.26969 }, 61 | { o: 100.62, h: 100.62, l: 99.0, c: 99 }, 62 | { o: 99.81, h: 100.9, l: 99.81, c: 100.48 }, 63 | { o: 100.14, h: 102.7, l: 100.14, c: 102.35 }, 64 | { o: 101.25, h: 102.35, l: 101.25, c: 102.35 }, 65 | { o: 101.8, h: 103.3, l: 101.8, c: 103.3 }, 66 | { o: 102.55, h: 104.3, l: 102.55, c: 104.149995 }, 67 | { o: 103.35, h: 104.4, l: 103.35, c: 104.4 }, 68 | { o: 103.87, h: 103.87, l: 103.2, c: 103.2000025 }, 69 | { o: 103.54, h: 103.54, l: 102.32, c: 102.31631 }, 70 | { o: 102.93, h: 103.0, l: 102.93, c: 103 }, 71 | { o: 102.96, h: 105.5, l: 102.96, c: 105.5 }, 72 | { o: 104.23, h: 105.0, l: 104.23, c: 105 }, 73 | { o: 104.62, h: 104.62, l: 103.0, c: 103 }, 74 | { o: 103.81, h: 106.4, l: 103.81, c: 106.20004 }, 75 | { o: 105.0, h: 106.6, l: 105.0, c: 106.6 }, 76 | { o: 105.8, h: 107.65, l: 105.8, c: 107.65 }, 77 | { o: 106.73, h: 108.18, l: 106.73, c: 108.1767745 }, 78 | { o: 107.45, h: 110.95, l: 107.45, c: 110.95 }, 79 | { o: 109.2, h: 111.0, l: 109.2, c: 111 }, 80 | { o: 110.1, h: 112.5, l: 110.1, c: 112.4977 }, 81 | { o: 111.3, h: 115.0, l: 111.3, c: 114.90225 }, 82 | { o: 113.1, h: 115.0, l: 113.1, c: 115 }, 83 | { o: 114.05, h: 114.05, l: 113.95, c: 113.95 }, 84 | { o: 114.0, h: 115.0, l: 114.0, c: 115 }, 85 | { o: 114.5, h: 115.0, l: 114.5, c: 115.000005 }, 86 | { o: 114.75, h: 116.0, l: 114.75, c: 116 }, 87 | { o: 115.38, h: 117.33, l: 115.38, c: 117.33 }, 88 | { o: 116.35, h: 118.35, l: 116.35, c: 118.35 }, 89 | ]; 90 | -------------------------------------------------------------------------------- /tests/macd/excel-data.ts: -------------------------------------------------------------------------------- 1 | export const closes = [ 2 | 459.99, 448.85, 446.06, 450.81, 442.8, 448.97, 444.57, 441.4, 430.47, 420.05, 431.14, 425.66, 430.58, 431.72, 3 | 437.87, 428.43, 428.35, 432.5, 443.66, 455.72, 454.49, 452.08, 452.73, 461.91, 463.58, 461.14, 452.08, 442.66, 4 | 428.91, 429.79, 431.99, 427.72, 423.2, 426.21, 426.98, 435.69, 434.33, 429.8, 419.85, 426.24, 402.8, 392.05, 390.53, 5 | 398.67, 406.13, 405.46, 408.38, 417.2, 430.12, 442.78, 439.29, 445.52, 449.98, 460.71, 458.66, 463.84, 456.77, 6 | 452.97, 454.74, 443.86, 428.85, 434.58, 433.26, 442.93, 439.66, 441.35, 7 | ]; 8 | 9 | export const macdValues = [ 10 | undefined, 11 | undefined, 12 | undefined, 13 | undefined, 14 | undefined, 15 | undefined, 16 | undefined, 17 | undefined, 18 | undefined, 19 | undefined, 20 | undefined, 21 | undefined, 22 | undefined, 23 | undefined, 24 | undefined, 25 | undefined, 26 | undefined, 27 | undefined, 28 | undefined, 29 | undefined, 30 | undefined, 31 | undefined, 32 | undefined, 33 | undefined, 34 | undefined, 35 | { macd: 8.275269503908, signal: undefined, histogram: undefined }, 36 | { macd: 7.703378381457, signal: undefined, histogram: undefined }, 37 | { macd: 6.416074756957, signal: undefined, histogram: undefined }, 38 | { macd: 4.237519783266, signal: undefined, histogram: undefined }, 39 | { macd: 2.552583324867, signal: undefined, histogram: undefined }, 40 | { macd: 1.378885719855, signal: undefined, histogram: undefined }, 41 | { macd: 0.1029814912, signal: undefined, histogram: undefined }, 42 | { macd: -1.258401952802, signal: undefined, histogram: undefined }, 43 | { macd: -2.070558190094, signal: 3.03752586873489, histogram: -5.10808405882889 }, 44 | { macd: -2.621842328256, signal: 1.90565222933671, histogram: -4.52749455759271 }, 45 | { macd: -2.329066740454, signal: 1.05870843537857, histogram: -3.38777517583257 }, 46 | { macd: -2.181632114792, signal: 0.410640325344456, histogram: -2.59227244013646 }, 47 | { macd: -2.402626272865, signal: -0.152012994297435, histogram: -2.25061327856757 }, 48 | { macd: -3.342121681351, signal: -0.790034731708148, histogram: -2.55208694964285 }, 49 | { macd: -3.530363136076, signal: -1.33810041258172, histogram: -2.19226272349428 }, 50 | { macd: -5.507471248626, signal: -2.17197457979058, histogram: -3.33549666883542 }, 51 | { macd: -7.851274228559, signal: -3.30783450954426, histogram: -4.54343971901474 }, 52 | { macd: -9.719367455247, signal: -4.59014109868481, histogram: -5.12922635656219 }, 53 | { macd: -10.422866508006, signal: -5.75668618054905, histogram: -4.66618032745695 }, 54 | { macd: -10.260162158936, signal: -6.65738137622644, histogram: -3.60278078270956 }, 55 | { macd: -10.069209610133, signal: -7.33974702300775, histogram: -2.72946258712525 }, 56 | { macd: -9.571919611952, signal: -7.7861815407966, histogram: -1.7857380711554 }, 57 | { macd: -8.369633492444, signal: -7.90287193112608, histogram: -0.46676156131792 }, 58 | { macd: -6.301635723685, signal: -7.58262468963786, histogram: 1.28098896595286 }, 59 | { macd: -3.599681509145, signal: -6.78603605353929, histogram: 3.18635454439429 }, 60 | { macd: -1.720148360897, signal: -5.77285851501083, histogram: 4.05271015411383 }, 61 | { macd: 0.2690032323, signal: -4.56448616554866, histogram: 4.83348939784866 }, 62 | { macd: 2.180173247114, signal: -3.21555428301613, histogram: 5.39572753013013 }, 63 | { macd: 4.508637808611, signal: -1.6707158646907, histogram: 6.1793536733017 }, 64 | { macd: 6.118020153845, signal: -0.11296866098356, histogram: 6.23098881482856 }, 65 | { macd: 7.722430593514, signal: 1.45411118991595, histogram: 6.26831940359805 }, 66 | { macd: 8.327453808714, signal: 2.82877971367556, histogram: 5.49867409503844 }, 67 | { macd: 8.403441184625, signal: 3.94371200786545, histogram: 4.45972917675955 }, 68 | { macd: 8.508406323192, signal: 4.85665087093076, histogram: 3.65175545226124 }, 69 | { macd: 7.625761844028, signal: 5.41047306555021, histogram: 2.21528877847779 }, 70 | { macd: 5.649949082928, signal: 5.45836826902577, histogram: 0.19158081390223 }, 71 | { macd: 4.494654764882, signal: 5.26562556819702, histogram: -0.77097080331502 }, 72 | { macd: 3.432989361685, signal: 4.89909832689462, histogram: -1.46610896520962 }, 73 | { macd: 3.333473853633, signal: 4.5859734322423, histogram: -1.2524995786093 }, 74 | { macd: 2.956662856115, signal: 4.26011131701684, histogram: -1.30344846090184 }, 75 | { macd: 2.762561215825, signal: 3.96060129677847, histogram: -1.19804008095347 }, 76 | ]; 77 | -------------------------------------------------------------------------------- /src/providers/levels.ts: -------------------------------------------------------------------------------- 1 | import { WEMA } from '../wema'; 2 | import { EMA } from '../ema'; 3 | import { SMA } from '../sma'; 4 | import { Sampler, IndicatorConstructor } from './sampler'; 5 | 6 | /** 7 | * Level creation for dynamic data, upper and lower (configurated) 8 | */ 9 | export class UniLevel { 10 | private prevValue = 0; 11 | private lastUpperValue = 0; 12 | private lastLowerValue = 0; 13 | private samplerUp: Sampler; 14 | private samplerLow: Sampler; 15 | 16 | constructor(public redunant = 0.85, indicator: T, samples: number, private multiplier = 1, private offset = 1) { 17 | this.samplerUp = new Sampler(indicator, samples); 18 | this.samplerLow = new Sampler(indicator, samples); 19 | } 20 | 21 | public create(...args: ConstructorParameters) { 22 | this.samplerUp.create(...args); 23 | this.samplerLow.create(...args); 24 | } 25 | 26 | public nextValue(value: number) { 27 | if (value > 0) { 28 | this.lastUpperValue = Math.max(value, this.prevValue); 29 | } else { 30 | this.lastUpperValue = Math.max(0, this.lastUpperValue) * this.redunant; 31 | } 32 | 33 | if (value <= 0) this.lastLowerValue = Math.min(value, this.prevValue); 34 | else { 35 | this.lastLowerValue = Math.min(0, this.lastLowerValue) * this.redunant; 36 | } 37 | 38 | const low = (value <= 0 ? value : this.lastLowerValue) * this.multiplier; 39 | const up = (value >= 0 ? value : this.lastUpperValue) * this.multiplier; 40 | 41 | return [this.samplerUp.nextValue(up) + this.offset, this.samplerLow.nextValue(low) - this.offset]; 42 | } 43 | } 44 | 45 | /** 46 | * Smoothed level creation for dynamic data 47 | * @deprecated 48 | */ 49 | export class Level { 50 | private sample1Up: WEMA | SMA | EMA; 51 | private sample2Up: WEMA | SMA | EMA; 52 | private sample3Up: WEMA | SMA | EMA; 53 | private sample1Low: WEMA | SMA | EMA; 54 | private sample2Low: WEMA | SMA | EMA; 55 | private sample3Low: WEMA | SMA | EMA; 56 | 57 | private upper = 0; 58 | private lower = 0; 59 | private lastUpperValue = 0; 60 | private lastLowerValue = 0; 61 | 62 | constructor( 63 | public period: number, 64 | public samples: number, 65 | public redunant = 0.85, 66 | private type: 'WEMA' | 'EMA' | 'SMA' = 'WEMA', 67 | ) { 68 | this.sample1Up = this.createSample(); 69 | this.sample2Up = this.createSample(); 70 | this.sample3Up = this.createSample(); 71 | this.sample1Low = this.createSample(); 72 | this.sample2Low = this.createSample(); 73 | this.sample3Low = this.createSample(); 74 | } 75 | 76 | public nextValue(value: number) { 77 | if (value > 0) { 78 | this.upper = this.getUp(value); 79 | this.lastLowerValue *= this.redunant; 80 | this.lower = this.getDown(this.lastLowerValue); 81 | this.lastUpperValue = value; 82 | } else if (value < 0) { 83 | this.lower = this.getDown(value); 84 | this.lastUpperValue *= this.redunant; 85 | this.upper = this.getUp(this.lastUpperValue); 86 | this.lastLowerValue = value; 87 | } 88 | 89 | return { upper: this.upper, lower: this.lower }; 90 | } 91 | 92 | public getUp(value: number) { 93 | const sample1 = this.sample1Up.nextValue(value); 94 | let sample2: number | null = null; 95 | 96 | if (this.samples === 1) { 97 | return sample1; 98 | } 99 | 100 | if (sample1) { 101 | sample2 = this.sample2Up.nextValue(sample1); 102 | } 103 | 104 | if (this.samples === 2) { 105 | return sample2; 106 | } 107 | 108 | if (sample2) { 109 | return this.sample3Up.nextValue(sample2); 110 | } 111 | 112 | return null; 113 | } 114 | 115 | public getDown(value: number) { 116 | const sample1 = this.sample1Low.nextValue(value); 117 | let sample2: number | null = null; 118 | 119 | if (this.samples === 1) { 120 | return sample1; 121 | } 122 | 123 | if (sample1) { 124 | sample2 = this.sample2Low.nextValue(sample1); 125 | } 126 | 127 | if (this.samples === 2) { 128 | return sample2; 129 | } 130 | 131 | if (sample2) { 132 | return this.sample3Low.nextValue(sample2); 133 | } 134 | 135 | return null; 136 | } 137 | 138 | private createSample() { 139 | switch (this.type) { 140 | case 'EMA': 141 | return new EMA(this.period); 142 | case 'WEMA': 143 | return new WEMA(this.period); 144 | case 'SMA': 145 | return new SMA(this.period); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /tests/supertrend/data.ts: -------------------------------------------------------------------------------- 1 | export const ohlc = [ 2 | { o: 7099, h: 7150, l: 7060, c: 7104 }, 3 | { o: 7089.3, h: 7169, l: 6884.85, c: 7058.4 }, 4 | { o: 7097.55, h: 7278.2, l: 7092.25, c: 7269.1 }, 5 | { o: 7365, h: 7410, l: 7344.15, c: 7398.3 }, 6 | { o: 7429, h: 7500, l: 7407, c: 7489.45 }, 7 | { o: 7486.05, h: 7520, l: 7443, c: 7505.45 }, 8 | { o: 7490.15, h: 7535, l: 7457.3, c: 7496.25 }, 9 | { o: 7463.8, h: 7588, l: 7451.75, c: 7579.55 }, 10 | { o: 7583.3, h: 7584.6, l: 7491.6, c: 7540.1 }, 11 | { o: 7551, h: 7598, l: 7508, c: 7569.45 }, 12 | { o: 7611.35, h: 7647, l: 7584.65, c: 7598.35 }, 13 | { o: 7590.65, h: 7592, l: 7532.15, c: 7545.8 }, 14 | { o: 7541.75, h: 7598, l: 7492, c: 7587.35 }, 15 | { o: 7650, h: 7660.5, l: 7545.1, c: 7576.8 }, 16 | { o: 7600.15, h: 7668.4, l: 7587.8, c: 7658.95 }, 17 | { o: 7677.45, h: 7773.4, l: 7672.15, c: 7763.7 }, 18 | { o: 7748, h: 7791.55, l: 7714, c: 7782.8 }, 19 | { o: 7770, h: 7809.95, l: 7738.8, c: 7801.2 }, 20 | { o: 7777.95, h: 7796.7, l: 7686.4, c: 7718.65 }, 21 | { o: 7710, h: 7751.95, l: 7676.65, c: 7699.45 }, 22 | { o: 7742.1, h: 7822, l: 7726.8, c: 7814.9 }, 23 | { o: 7808.85, h: 7848.95, l: 7783.3, c: 7811.55 }, 24 | { o: 7780.05, h: 7800, l: 7733.7, c: 7776.2 }, 25 | { o: 7788.55, h: 7839.9, l: 7767.15, c: 7820.7 }, 26 | { o: 7769.2, h: 7790.15, l: 7645, c: 7657.7 }, 27 | { o: 7677, h: 7687.3, l: 7639, c: 7667.75 }, 28 | { o: 7664.75, h: 7670.7, l: 7590.1, c: 7599.3 }, 29 | { o: 7599.95, h: 7629.9, l: 7584.8, c: 7611.85 }, 30 | { o: 7628.95, h: 7743.5, l: 7566, c: 7735.3 }, 31 | { o: 7740.5, h: 7769, l: 7717.15, c: 7764.6 }, 32 | { o: 7824.5, h: 7926.8, l: 7818.7, c: 7902.55 }, 33 | { o: 7945.5, h: 7962, l: 7885.3, c: 7955.8 }, 34 | { o: 7985, h: 7996.5, l: 7930.15, c: 7969.9 }, 35 | { o: 8007.5, h: 8021.9, l: 7924, c: 7951.75 }, 36 | { o: 7936.4, h: 7980, l: 7922, c: 7952.1 }, 37 | { o: 7952, h: 7952, l: 7867.6, c: 7901.5 }, 38 | { o: 7878, h: 8029, l: 7871.1, c: 8017.2 }, 39 | { o: 7993.5, h: 8035.15, l: 7991, c: 8029.95 }, 40 | { o: 8024, h: 8037, l: 7884, c: 7895.3 }, 41 | { o: 7887, h: 7950, l: 7831.1, c: 7894.8 }, 42 | { o: 7858.2, h: 7874.45, l: 7805.1, c: 7840.1 }, 43 | { o: 7848.75, h: 7949.95, l: 7770.1, c: 7783.8 }, 44 | { o: 7755, h: 7789, l: 7725, c: 7732.75 }, 45 | { o: 7741, h: 7817.5, l: 7733, c: 7773.1 }, 46 | { o: 7733.7, h: 7770.6, l: 7700.95, c: 7760.25 }, 47 | { o: 7794.9, h: 7912.2, l: 7775, c: 7904.3 }, 48 | { o: 7894, h: 7933.55, l: 7867.15, c: 7919.75 }, 49 | { o: 7810.35, h: 7909.9, l: 7798.2, c: 7865.85 }, 50 | { o: 7886, h: 7939.4, l: 7860.4, c: 7928.2 }, 51 | { o: 7891.2, h: 7891.8, l: 7787.3, c: 7830.55 }, 52 | { o: 7840, h: 7898.1, l: 7780.1, c: 7887.2 }, 53 | { o: 7920, h: 7957.8, l: 7896, c: 7907.6 }, 54 | ]; 55 | 56 | export const stExcelCalculated = [ 57 | undefined, 58 | undefined, 59 | undefined, 60 | undefined, 61 | undefined, 62 | undefined, 63 | undefined, 64 | undefined, 65 | undefined, 66 | undefined, 67 | { upper: 7875.435, lower: 7356.215 }, 68 | { upper: 7778.095, lower: 7356.215 }, 69 | { upper: 7738.26, lower: 7356.215 }, 70 | { upper: 7738.26, lower: 7414.64 }, 71 | { upper: 7738.26, lower: 7441.96 }, 72 | { upper: 7738.26, lower: 7529.145 }, 73 | { upper: 7946.375, lower: 7559.175 }, 74 | { upper: 7946.375, lower: 7593.795 }, 75 | { upper: 7926.49, lower: 7593.795 }, 76 | { upper: 7896.3, lower: 7593.795 }, 77 | { upper: 7896.3, lower: 7593.795 }, 78 | { upper: 7896.3, lower: 7625.235 }, 79 | { upper: 7896.3, lower: 7625.235 }, 80 | { upper: 7896.3, lower: 7626.795 }, 81 | { upper: 7896.3, lower: 7626.795 }, 82 | { upper: 7843.47, lower: 7626.795 }, 83 | { upper: 7811.33, lower: 7626.795 }, 84 | { upper: 7783.07, lower: 7431.63 }, 85 | { upper: 7783.07, lower: 7466.49 }, 86 | { upper: 7783.07, lower: 7559.505 }, 87 | { upper: 7783.07, lower: 7681.25 }, 88 | { upper: 8117.36, lower: 7729.94 }, 89 | { upper: 8117.36, lower: 7771.915 }, 90 | { upper: 8117.36, lower: 7776.51 }, 91 | { upper: 8117.36, lower: 7778.1 }, 92 | { upper: 8089.94, lower: 7778.1 }, 93 | { upper: 8089.94, lower: 7778.1 }, 94 | { upper: 8089.94, lower: 7817.665 }, 95 | { upper: 8089.94, lower: 7817.665 }, 96 | { upper: 8089.94, lower: 7817.665 }, 97 | { upper: 8029.195, lower: 7817.665 }, 98 | { upper: 8029.195, lower: 7817.665 }, 99 | { upper: 7966.58, lower: 7547.42 }, 100 | { upper: 7966.58, lower: 7568.3 }, 101 | { upper: 7945.555, lower: 7568.3 }, 102 | { upper: 7945.555, lower: 7620.33 }, 103 | { upper: 7945.555, lower: 7695.38 }, 104 | { upper: 7945.555, lower: 7695.38 }, 105 | { upper: 7945.555, lower: 7695.38 }, 106 | { upper: 7945.555, lower: 7695.38 }, 107 | { upper: 7945.555, lower: 7695.38 }, 108 | { upper: 7945.555, lower: 7733.04 }, 109 | ]; 110 | -------------------------------------------------------------------------------- /src/pivot.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Pivot points are major support and resistance levels where there likely to be a retracement 3 | * of price used by traders to objectively determine potential entry and exit levels of underlying assets. 4 | * Pivot point breaks can be an entry marker, confirmation of trend direction 5 | * also confirmation of trend reversal -exit marker. 6 | * These retracement calculation is based on the last day trading data as we follow 7 | * the market open, high, low, close on every day. 8 | * You can also calculate these pivot level on weekly basis. 9 | * For weekly pivot you need to weekly high, low, and close prices. 10 | */ 11 | 12 | type PivotMode = 'classic' | 'woodie' | 'camarilla' | 'fibonacci'; 13 | 14 | interface PivotValue { 15 | r6?: number; 16 | r5?: number; 17 | r4?: number; 18 | r3?: number; 19 | r2: number; 20 | r1: number; 21 | pp: number; 22 | s1: number; 23 | s2: number; 24 | s3?: number; 25 | s4?: number; 26 | s5?: number; 27 | s6?: number; 28 | } 29 | export class Pivot { 30 | private calculator: (h: number, l: number, c: number) => PivotValue; 31 | 32 | constructor(private mode: PivotMode = 'classic') { 33 | switch (this.mode) { 34 | case 'classic': 35 | this.calculator = this.classic; 36 | break; 37 | case 'camarilla': 38 | this.calculator = this.camarilla; 39 | break; 40 | case 'woodie': 41 | this.calculator = this.woodie; 42 | break; 43 | case 'fibonacci': 44 | this.calculator = this.fibonacci; 45 | break; 46 | } 47 | } 48 | 49 | public nextValue(h: number, l: number, c: number) { 50 | return this.calculator(h, l, c); 51 | } 52 | 53 | // Classsic 54 | // Pivot Point (P) = (High + Low + Close)/3 55 | // Support 1 (S1) = (P x 2) - High 56 | // Support 2 (S2) = P - (High - Low) 57 | // Support 3 (S3) = Low – 2(High – PP) 58 | // Resistance 1 (R1) = (P x 2) - Low 59 | // Resistance 2 (R2) = P + (High - Low) 60 | // Resistance 3 (R3) = High + 2(PP – Low) 61 | private classic(h: number, l: number, c: number) { 62 | const pp = (h + l + c) / 3; 63 | const s1 = pp * 2 - h; 64 | const s2 = pp - (h - l); 65 | const s3 = l - 2 * (h - pp); 66 | const r1 = pp * 2 - l; 67 | const r2 = pp + (h - l); 68 | const r3 = h + 2 * (pp - l); 69 | 70 | return { r3, r2, r1, pp, s1, s2, s3 }; 71 | } 72 | 73 | // Woodie 74 | //R2 = PP + High – Low 75 | // R1 = (2 X PP) – Low 76 | // PP = (H + L + 2C) / 4 77 | // S1 = (2 X PP) – High 78 | // S2 = PP – High + Low 79 | private woodie(h: number, l: number, c: number) { 80 | const pp = (h + l + 2 * c) / 4; 81 | const r2 = pp + h - l; 82 | const r1 = 2 * pp - l; 83 | const s1 = 2 * pp - h; 84 | const s2 = pp - h + l; 85 | 86 | return { r2, r1, pp, s1, s2 }; 87 | } 88 | 89 | //Camarilla 90 | // pivot = (high + low + close ) / 3.0 91 | // range = high - low 92 | // h5 = (high/low) * close 93 | // h4 = close + (high - low) * 1.1 / 2.0 94 | // h3 = close + (high - low) * 1.1 / 4.0 95 | // h2 = close + (high - low) * 1.1 / 6.0 96 | // h1 = close + (high - low) * 1.1 / 12.0 97 | // l1 = close - (high - low) * 1.1 / 12.0 98 | // l2 = close - (high - low) * 1.1 / 6.0 99 | // l3 = close - (high - low) * 1.1 / 4.0 100 | // l4 = close - (high - low) * 1.1 / 2.0 101 | // h6 = h5 + 1.168 * (h5 - h4) 102 | // l5 = close - (h5 - close) 103 | // l6 = close - (h6 - close) 104 | private camarilla(h: number, l: number, c: number) { 105 | const delta = (h - l) * 1.1; 106 | const pp = (h + l + c) / 3; 107 | const r5 = (h / l) * c; 108 | const r4 = c + delta / 2; 109 | const r6 = r5 + 1.168 * (r5 - r4); 110 | const r3 = c + delta / 4; 111 | const r2 = c + delta / 6; 112 | const r1 = c + delta / 12; 113 | const s1 = c - delta / 12; 114 | const s2 = c - delta / 6; 115 | const s3 = c - delta / 4; 116 | const s4 = c - delta / 2; 117 | const s5 = c - (r5 - c); 118 | const s6 = c - (r6 - c); 119 | 120 | return { r6, r5, r4, r3, r2, r1, pp, s1, s2, s3, s4, s5, s6 }; 121 | } 122 | 123 | // Fibonacci Pivot Point 124 | // R3 = PP + ((High – Low) x 1.000) 125 | // R2 = PP + ((High – Low) x .618) 126 | // R1 = PP + ((High – Low) x .382) 127 | // PP = (H + L + C) / 3 128 | // S1 = PP – ((High – Low) x .382) 129 | // S2 = PP – ((High – Low) x .618) 130 | // S3 = PP – ((High – Low) x 1.000) 131 | private fibonacci(h: number, l: number, c: number) { 132 | const delta = h - l; 133 | const pp = (h + l + c) / 3; 134 | const r3 = pp + delta; 135 | const r2 = pp + delta * 0.618; 136 | const r1 = pp + delta * 0.382; 137 | const s1 = pp - delta * 0.382; 138 | const s2 = pp - delta * 0.618; 139 | const s3 = pp - delta; 140 | 141 | return { r3, r2, r1, pp, s1, s2, s3 }; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/volume-profile.ts: -------------------------------------------------------------------------------- 1 | type Candle = { o: number; h: number; l: number; c: number; v: number; time: number }; 2 | 3 | const getPriceBySourceDefault = (candle: Candle) => { 4 | return (candle.o + candle.h + candle.l + candle.c) / 4; 5 | }; 6 | 7 | /** 8 | * Is an indicator that gives a data representation of how much volume occurs 9 | * at each individual price over a certain period of time (session) 10 | */ 11 | export class VolumeProfile { 12 | private precision: number; 13 | private valueToRoundWith: number; 14 | private source: (candle: Candle) => number; 15 | private sum = 0; 16 | private sessionPricesLookup = new Set(); 17 | private sessionVolumes: Record = {}; 18 | private lastPrice: number; 19 | private prevCandle: Candle | null = null; 20 | 21 | constructor(precision: number = 4, source = getPriceBySourceDefault) { 22 | this.source = source; 23 | this.precision = precision; 24 | this.valueToRoundWith = 10 ** this.precision; 25 | } 26 | 27 | /** 28 | * Get volume profile for current session 29 | */ 30 | // Все еще не могу понять как считать важные уровни и группировать их 31 | getSession(candle: Candle, cleanOffset: number = 15) { 32 | const source = this.source(candle); 33 | let minDiff = Infinity; 34 | let middlePrice = source; 35 | 36 | this.sessionPricesLookup.forEach((price) => { 37 | const diff = this.diffPercent(price, source); 38 | 39 | if (diff > cleanOffset) { 40 | this.sessionPricesLookup.delete(price); 41 | delete this.sessionVolumes[price]; 42 | } else if (diff < minDiff) { 43 | minDiff = diff; 44 | middlePrice = price; 45 | } 46 | }); 47 | 48 | const prices = Array.from(this.sessionPricesLookup).sort(); 49 | const middlePriceIndex = prices.indexOf(middlePrice); 50 | const session = new Map(); 51 | 52 | if (middlePriceIndex === -1) { 53 | return session; 54 | } 55 | 56 | const segmentCount = 4; 57 | const segmentSize = Math.round(prices.length / segmentCount); 58 | let prevSegmentPrice = 0; 59 | 60 | for (let segment = 0; segment < segmentCount; segment++) { 61 | const start = segment * segmentSize; 62 | const end = start + segmentSize; 63 | 64 | let segmentVolume = 0; 65 | let segmentPrice = 0; 66 | 67 | for (let i = start; i < end; i++) { 68 | const currentPrice = prices[start]; 69 | const currentVolume = this.sessionVolumes[currentPrice]; 70 | 71 | if (segmentVolume < currentVolume) { 72 | segmentVolume = currentVolume; 73 | segmentPrice = currentPrice; 74 | } 75 | } 76 | 77 | if (this.diffPercent(prevSegmentPrice, segmentPrice) < 1) { 78 | segmentVolume = session.get(prevSegmentPrice) + segmentVolume; 79 | segmentPrice = this.roundPrice((segmentPrice + prevSegmentPrice) / 2); 80 | 81 | session.delete(prevSegmentPrice); 82 | } 83 | 84 | session.set(segmentPrice, segmentVolume); 85 | prevSegmentPrice = segmentPrice; 86 | } 87 | 88 | return session; 89 | } 90 | 91 | /** 92 | * Get session avg volume 93 | */ 94 | public getSessionAvg() { 95 | return this.sum / this.sessionPricesLookup.size; 96 | } 97 | 98 | /** 99 | * Get unformatted (raw) data for session 100 | */ 101 | public getRawSession() { 102 | return this.sessionVolumes; 103 | } 104 | 105 | /** 106 | * Add value to session 107 | */ 108 | nextValue(candle: Candle) { 109 | const priceSource = this.roundPrice(this.source(candle)); 110 | // Source volume calculated like diff between two candles or new candle volume 111 | // Next value might me used for each ticks or only once per candle 112 | const volume = this.prevCandle?.time === candle.time ? candle.v - this.prevCandle.v : candle.v; 113 | 114 | this.addToSession(priceSource, volume); 115 | this.sum += volume; 116 | this.lastPrice = priceSource; 117 | this.prevCandle = candle; 118 | } 119 | 120 | /** 121 | * Round price value 122 | */ 123 | private roundPrice(price: number): number { 124 | return Math.round((price + Number.EPSILON) * this.valueToRoundWith) / this.valueToRoundWith; 125 | } 126 | 127 | /** 128 | * Add volume to current session 129 | */ 130 | private addToSession(price: number, volume: number) { 131 | const hasPrice = this.sessionPricesLookup.has(price); 132 | 133 | if (!hasPrice) { 134 | this.sessionPricesLookup.add(price); 135 | this.sessionVolumes[price] = volume; 136 | } else { 137 | this.sessionVolumes[price] += volume; 138 | } 139 | } 140 | 141 | /** 142 | * Percent change 143 | */ 144 | private diffPercent(a: number, b: number): number { 145 | return a < b ? ((b - a) * 100) / a : ((a - b) * 100) / b; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /tests/adx/data.ts: -------------------------------------------------------------------------------- 1 | // Data from excel 2 | export const ohlc = [ 3 | { o: 55.22, h: 55.575, l: 55.07, c: 55.36 }, 4 | { o: 55.3, h: 55.92, l: 55.11, c: 55.65 }, 5 | { o: 55.49, h: 56.59, l: 55.21, c: 56.46 }, 6 | { o: 56.63, h: 56.77, l: 55.68, c: 56.39 }, 7 | { o: 56.29, h: 56.5, l: 55.485, c: 55.59 }, 8 | { o: 55.8, h: 56.23, l: 55.415, c: 55.78 }, 9 | { o: 51.91, h: 52.4297, l: 50.77, c: 51.78 }, 10 | { o: 51.78, h: 52.13, l: 51.63, c: 52.11 }, 11 | { o: 52.26, h: 52.35, l: 51.09, c: 51.44 }, 12 | { o: 51.48, h: 51.5, l: 50.55, c: 50.94 }, 13 | { o: 50.62, h: 50.77, l: 49.56, c: 49.9 }, 14 | { o: 49.35, h: 50.25, l: 49.35, c: 49.87 }, 15 | { o: 50, h: 50.75, l: 49.7799, c: 50.61 }, 16 | { o: 50.34, h: 50.41, l: 49.6, c: 49.78 }, 17 | { o: 49.84, h: 50.06, l: 49.46, c: 49.87 }, 18 | { o: 49.87, h: 50.3, l: 49.73, c: 49.94 }, 19 | { o: 49.92, h: 50.39, l: 49.66, c: 50.39 }, 20 | { o: 50.49, h: 50.585, l: 50, c: 50.07 }, 21 | { o: 50.33, h: 51.1, l: 50.19, c: 51.02 }, 22 | { o: 51.13, h: 51.78, l: 51, c: 51.05 }, 23 | { o: 51.2, h: 51.81, l: 50.92, c: 51.51 }, 24 | { o: 51.44, h: 51.895, l: 51.04, c: 51.08 }, 25 | { o: 50.8, h: 51.96, l: 50.75, c: 51.83 }, 26 | { o: 51.72, h: 51.73, l: 50.36, c: 50.51 }, 27 | { o: 50.48, h: 51.14, l: 50.3, c: 50.81 }, 28 | { o: 50.47, h: 50.62, l: 49.82, c: 50.32 }, 29 | { o: 50.48, h: 51.22, l: 50.4, c: 50.62 }, 30 | { o: 50.6, h: 50.68, l: 49.98, c: 50.03 }, 31 | { o: 50.7, h: 51.71, l: 50.4, c: 51.59 }, 32 | { o: 51.92, h: 52.49, l: 51.79, c: 52.12 }, 33 | { o: 51.93, h: 51.9793, l: 51.36, c: 51.89 }, 34 | { o: 51.92, h: 52.32, l: 51.77, c: 52.32 }, 35 | { o: 52.26, h: 53, l: 52.08, c: 53 }, 36 | { o: 52.44, h: 52.95, l: 52.44, c: 52.85 }, 37 | { o: 52.64, h: 52.74, l: 51.835, c: 52.48 }, 38 | { o: 52.38, h: 52.42, l: 51.6, c: 51.79 }, 39 | { o: 51.99, h: 52.35, l: 51.89, c: 52.13 }, 40 | { o: 52.24, h: 52.73, l: 52.1, c: 52.1 }, 41 | { o: 52.02, h: 52.44, l: 51.87, c: 52.04 }, 42 | { o: 52, h: 52, l: 51.49, c: 51.62 }, 43 | { o: 51.05, h: 52.05, l: 51.04, c: 51.48 }, 44 | { o: 49.58, h: 50.715, l: 49.06, c: 50.14 }, 45 | { o: 49.9, h: 50.1, l: 49.57, c: 49.83 }, 46 | { o: 49.78, h: 50.12, l: 49.69, c: 49.69 }, 47 | { o: 49.52, h: 50.47, l: 49.5068, c: 50.39 }, 48 | { o: 50.41, h: 50.43, l: 49.82, c: 50.13 }, 49 | { o: 50.64, h: 50.83, l: 50.03, c: 50.07 }, 50 | { o: 50.2, h: 51.43, l: 50.16, c: 51.19 }, 51 | { o: 51.08, h: 51.46, l: 50.9525, c: 50.99 }, 52 | { o: 51.28, h: 52.06, l: 51.15, c: 51.91 }, 53 | { o: 49.81, h: 50.94, l: 49.52, c: 49.83 }, 54 | { o: 49.1, h: 49.1466, l: 48.035, c: 48.43 }, 55 | { o: 48.92, h: 49.47, l: 48.67, c: 49.44 }, 56 | { o: 49.91, h: 50.72, l: 49.8, c: 50.54 }, 57 | { o: 50.72, h: 51.3, l: 50.5, c: 51.17 }, 58 | { o: 51.13, h: 51.72, l: 51.07, c: 51.16 }, 59 | { o: 50.83, h: 51.28, l: 50.74, c: 51.17 }, 60 | { o: 50.78, h: 51.54, l: 50.39, c: 51.38 }, 61 | { o: 51.42, h: 51.61, l: 51.07, c: 51.38 }, 62 | { o: 51.73, h: 52.36, l: 51.55, c: 52.3 }, 63 | { o: 52.5, h: 52.83, l: 52.47, c: 52.59 }, 64 | { o: 52.94, h: 53.4, l: 52.785, c: 53.21 }, 65 | { o: 53.56, h: 53.86, l: 53.18, c: 53.51 }, 66 | { o: 53.84, h: 53.99, l: 53.58, c: 53.74 }, 67 | { o: 53.95, h: 54, l: 53.21, c: 53.7 }, 68 | { o: 53.7, h: 54.34, l: 53.55, c: 53.96 }, 69 | { o: 53.71, h: 53.9, l: 52.93, c: 53.09 }, 70 | { o: 56.15, h: 56.84, l: 55.53, c: 55.91 }, 71 | { o: 55.98, h: 56.23, l: 55.76, c: 55.8 }, 72 | { o: 56.08, h: 56.6275, l: 55.78, c: 56.57 }, 73 | { o: 56.47, h: 56.74, l: 56.255, c: 56.73 }, 74 | { o: 56.52, h: 57.29, l: 56.51, c: 56.76 }, 75 | { o: 56.61, h: 56.8, l: 56.11, c: 56.19 }, 76 | { o: 56, h: 56.37, l: 55.72, c: 56.21 }, 77 | { o: 56.26, h: 56.76, l: 56.05, c: 56.68 }, 78 | { o: 56.6, h: 56.75, l: 56.14, c: 56.58 }, 79 | { o: 56.85, h: 56.9, l: 56.31, c: 56.58 }, 80 | { o: 56.68, h: 57.11, l: 56.49, c: 56.97 }, 81 | { o: 56.8, h: 57.5193, l: 56.67, c: 57.39 }, 82 | { o: 57.65, h: 58.21, l: 57.45, c: 57.96 }, 83 | { o: 58.055, h: 58.085, l: 57.78, c: 58.06 }, 84 | { o: 58.17, h: 58.5, l: 58.02, c: 58.2 }, 85 | { o: 58.16, h: 58.32, l: 57.82, c: 58.02 }, 86 | { o: 58.03, h: 58.45, l: 58.03, c: 58.3 }, 87 | { o: 58.03, h: 58.19, l: 57.62, c: 57.94 }, 88 | { o: 58.01, h: 58.5, l: 57.96, c: 58.12 }, 89 | { o: 57.61, h: 57.62, l: 57.27, c: 57.44 }, 90 | { o: 57.54, h: 57.68, l: 57.23, c: 57.56 }, 91 | { o: 57.42, h: 57.7, l: 57.27, c: 57.6 }, 92 | { o: 57.43, h: 57.73, l: 57.2, c: 57.62 }, 93 | { o: 57.6, h: 57.75, l: 57.26, c: 57.67 }, 94 | { o: 57.9, h: 58.18, l: 57.85, c: 57.89 }, 95 | { o: 57.8, h: 58.04, l: 57.715, c: 57.95 }, 96 | { o: 57.88, h: 58.29, l: 57.78, c: 58.17 }, 97 | { o: 58.28, h: 58.7, l: 57.69, c: 58.03 }, 98 | { o: 58.18, h: 58.6, l: 58.1, c: 58.1 }, 99 | { o: 57.98, h: 58.19, l: 57.61, c: 57.89 }, 100 | { o: 57.65, h: 57.8, l: 57.3, c: 57.46 }, 101 | { o: 57.01, h: 57.82, l: 57.01, c: 57.59 }, 102 | { o: 57.67, h: 58.19, l: 57.42, c: 57.67 }, 103 | { o: 57.78, h: 57.8, l: 57.21, c: 57.61 }, 104 | { o: 57.47, h: 57.84, l: 57.405, c: 57.66 }, 105 | { o: 57.63, h: 57.79, l: 57.18, c: 57.43 }, 106 | { o: 56.79, h: 57.52, l: 56.21, c: 56.21 }, 107 | { o: 56, h: 57.205, l: 55.61, c: 57.05 }, 108 | { o: 56.5, h: 56.65, l: 56.05, c: 56.53 }, 109 | ]; 110 | --------------------------------------------------------------------------------