Repository: cinar/indicatorts (ts)

Issue:
Momentum: Percentage Price Oscillator (PPO)
The percentage price oscillator (PPO) is a technical momentum indicator that shows the relationship between two moving averages in percentage terms. The moving averages are a 26-period and 12-period exponential moving average (EMA).

https://www.investopedia.com/terms/p/ppo.asp


Pull request description:
Momentum: Percentage Price Oscillator (PPO)
# Describe Request

Momentum: Percentage Price Oscillator (PPO)

Fixed #19

# Change Type

New feature.


Patch:
diff --git a/README.md b/README.md
index 3e67858..fa343da 100644
--- a/README.md
+++ b/README.md
@@ -45,6 +45,7 @@ The following list of indicators are currently supported by this package:
 - [Chaikin Oscillator](src/indicator/momentum/index.md#chaikin-oscillator)
 - [Custom RSI](src/indicator/momentum/index.md#custom-rsi)
 - [Ichimoku Cloud](src/indicator/momentum/index.md#ichimoku-cloud)
+- [Percentage Price Oscillator (PPO)](src/indicator/momentum/index.md#percentage-price-oscillator-ppo)
 - [Percentage Volume Oscillator (PVO)](src/indicator/momentum/index.md#percentage-volume-oscillator-pvo)
 - [Relative Strength Index (RSI)](src/indicator/momentum/index.md#relative-strength-index-rsi)
 - [RSI 2](src/indicator/momentum/index.md#rsi-2)
diff --git a/src/index.ts b/src/index.ts
index d5c38ba..631d194 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -17,6 +17,7 @@ export * from './helper/regression';
 export * from './indicator/momentum/awesomeOscillator';
 export * from './indicator/momentum/chaikinOscillator';
 export * from './indicator/momentum/ichimokuCloud';
+export * from './indicator/momentum/percentagePriceOscillator';
 export * from './indicator/momentum/percentageVolumeOscillator';
 export * from './indicator/momentum/rsi';
 export * from './indicator/momentum/rsi2';
diff --git a/src/indicator/momentum/index.md b/src/indicator/momentum/index.md
index dc30a83..7b860a0 100644
--- a/src/indicator/momentum/index.md
+++ b/src/indicator/momentum/index.md
@@ -6,6 +6,7 @@ Momentum indicators measure the speed of movement.
 - [Chaikin Oscillator](#chaikin-oscillator)
 - [Custom RSI](#custom-rsi)
 - [Ichimoku Cloud](#ichimoku-cloud)
+- [Percentage Price Oscillator (PPO)](#percentage-price-oscillator-ppo)
 - [Percentage Volume Oscillator (PVO)](#percentage-volume-oscillator-pvo)
 - [Relative Strength Index (RSI)](#relative-strength-index-rsi)
 - [RSI 2](#rsi-2)
@@ -76,6 +77,35 @@ import {ichimokuCloud} from 'indicatorts';
 const result = ichimokuCloud(highs, lows, closings);
 ```
 
+#### Percentage Price Oscillator (PPO)
+
+The [percentagePriceOscillator](./percentagePriceOscillator.ts) function calculates a momentum oscillator for the price It is used to indicate the ups and downs based on the price. A breakout is confirmed when PPO is positive.
+
+```
+PPO = ((EMA(fastPeriod, prices) - EMA(slowPeriod, prices)) / EMA(longPeriod, prices)) * 100
+Signal = EMA(9, PPO)
+Histogram = PPO - Signal
+```
+
+```TypeScript
+import {percentagePriceOscillator} from 'indicatorts';
+
+const result = percentagePriceOscillator(
+    fastPeriod,
+    slowPeriod,
+    signalPeriod,
+    prices
+);
+```
+
+The [defaultPercentagePriceOscillator](./percentagePriceOscillator.ts) function calculates it with the default periods of 12, 26, 9.
+
+```TypeScript
+import {defaultPercentagePriceOscillator} from 'indicatorts';
+
+const result = defaultPercentagePriceOscillator(prices);
+```
+
 #### Percentage Volume Oscillator (PVO)
 
 The [percentageVolumeOscillator](./percentageVolumeOscillator.ts) function calculates a momentum oscillator for the volume It is used to indicate the ups and downs based on the volume. A breakout is confirmed when PVO is positive.
diff --git a/src/indicator/momentum/percentagePriceOscillator.ts b/src/indicator/momentum/percentagePriceOscillator.ts
new file mode 100644
index 0000000..1edb982
--- /dev/null
+++ b/src/indicator/momentum/percentagePriceOscillator.ts
@@ -0,0 +1,75 @@
+// Copyright (c) 2022 Onur Cinar. All Rights Reserved.
+// https://github.com/cinar/indicatorts
+
+import { divide, multiplyBy, substract } from '../../helper/numArray';
+import { ema } from '../trend/ema';
+
+/** Default fast period value. */
+const DEFAULT_FAST_PERIOD = 12;
+
+/** Default slow period value. */
+const DEFAULT_SLOW_PERIOD = 26;
+
+/** Default signal period value. */
+const DEFAULT_SIGNAL_PERIOD = 9;
+
+/**
+ * Percentage price oscillator result.
+ */
+export interface PercentagePriceOscillator {
+  ppo: number[];
+  signal: number[];
+  histogram: number[];
+}
+
+/**
+ * Percentage Price Oscillator (PPO). It is a momentum oscillator for the price.
+ * It is used to indicate the ups and downs based on the price. A breakout is
+ * confirmed when PPO is positive.
+ *
+ * PVO = ((EMA(fastPeriod, prices) - EMA(slowPeriod, prices)) / EMA(longPeriod, prices)) * 100
+ * Signal = EMA(9, PVO)
+ * Histogram = PVO - Signal
+ *
+ * @param fastPeriod fast period.
+ * @param slowPeriod slow period.
+ * @param signalPeriod signal period.
+ * @param prices price values.
+ * @returns oscillator result.
+ */
+export function percentagePriceOscillator(
+  fastPeriod: number,
+  slowPeriod: number,
+  signalPeriod: number,
+  prices: number[]
+): PercentagePriceOscillator {
+  const fastEma = ema(fastPeriod, prices);
+  const slowEma = ema(slowPeriod, prices);
+
+  const ppo = multiplyBy(100, divide(substract(fastEma, slowEma), slowEma));
+  const signal = ema(signalPeriod, ppo);
+  const histogram = substract(ppo, signal);
+
+  return {
+    ppo,
+    signal,
+    histogram,
+  };
+}
+
+/**
+ * Default Percentage Price Oscillator calculates it with the default periods of 12, 26, 9.
+ *
+ * @param prices price values.
+ * @returns oscillator result.
+ */
+export function defaultPercentagePriceOscillator(
+  prices: number[]
+): PercentagePriceOscillator {
+  return percentagePriceOscillator(
+    DEFAULT_FAST_PERIOD,
+    DEFAULT_SLOW_PERIOD,
+    DEFAULT_SIGNAL_PERIOD,
+    prices
+  );
+}


Test patch:
diff --git a/src/indicator/momentum/percentagePriceOscillator.test.ts b/src/indicator/momentum/percentagePriceOscillator.test.ts
new file mode 100644
index 0000000..6171603
--- /dev/null
+++ b/src/indicator/momentum/percentagePriceOscillator.test.ts
@@ -0,0 +1,63 @@
+// Copyright (c) 2022 Onur Cinar. All Rights Reserved.
+// https://github.com/cinar/indicatorts
+
+import { deepStrictEqual } from 'assert';
+import { roundDigitsAll } from '../../helper/numArray';
+import { defaultPercentagePriceOscillator } from './percentagePriceOscillator';
+
+describe('Percent Price Oscillator (PPO)', () => {
+  it('should be able to compute ppo', () => {
+    const prices = [
+      3674.84, 3666.77, 3789.99, 3735.48, 3749.63, 3900.86, 4017.82, 4115.77,
+      4160.68, 4121.43, 4108.54, 4176.82, 4101.23, 4132.15, 4158.24, 4057.84,
+      3978.73, 3941.48, 3973.75, 3901.36, 3900.79, 3923.68, 4088.85, 4008.01,
+      4023.89, 3930.08, 3935.18, 4001.05, 3991.24, 4123.34, 4146.87, 4300.17,
+      4175.48, 4155.38, 4131.93, 4287.5, 4183.96, 4175.2, 4296.12, 4271.78,
+      4393.66, 4459.45, 4462.21, 4391.69, 4392.59, 4446.59, 4397.45, 4412.53,
+      4488.28, 4500.21, 4481.15, 4525.12, 4582.64, 4545.86, 4530.41, 4602.45,
+      4631.6, 4575.52, 4543.06, 4520.16, 4456.24, 4511.61, 4461.18, 4463.12,
+      4411.67, 4357.86, 4262.45, 4173.11, 4204.31, 4259.52, 4277.88, 4170.7,
+      4201.09, 4328.87, 4363.49, 4386.54, 4306.26, 4373.94, 4384.65, 4288.7,
+      4225.5, 4304.76, 4348.87, 4380.26, 4475.01, 4471.07, 4401.67, 4418.64,
+      4504.08, 4587.18,
+    ];
+
+    const expectedPpo = [
+      0, -0.02, 0.24, 0.31, 0.4, 0.79, 1.33, 1.93, 2.47, 2.77, 2.95, 3.19, 3.18,
+      3.2, 3.22, 3.01, 2.65, 2.26, 2, 1.63, 1.31, 1.1, 1.25, 1.19, 1.16, 0.94,
+      0.77, 0.75, 0.71, 0.94, 1.15, 1.6, 1.69, 1.7, 1.64, 1.88, 1.84, 1.77,
+      1.93, 1.98, 2.23, 2.52, 2.72, 2.71, 2.68, 2.71, 2.62, 2.55, 2.6, 2.62,
+      2.58, 2.6, 2.68, 2.65, 2.56, 2.6, 2.64, 2.55, 2.39, 2.19, 1.9, 1.75, 1.52,
+      1.33, 1.07, 0.77, 0.34, -0.16, -0.49, -0.65, -0.73, -0.99, -1.12, -0.98,
+      -0.79, -0.59, -0.58, -0.43, -0.3, -0.36, -0.53, -0.5, -0.4, -0.25, 0.04,
+      0.25, 0.3, 0.36, 0.55, 0.85,
+    ];
+
+    const expectedSignal = [
+      0, -0, 0.04, 0.1, 0.16, 0.28, 0.49, 0.78, 1.12, 1.45, 1.75, 2.04, 2.27,
+      2.45, 2.61, 2.69, 2.68, 2.6, 2.48, 2.31, 2.11, 1.91, 1.77, 1.66, 1.56,
+      1.44, 1.3, 1.19, 1.1, 1.06, 1.08, 1.19, 1.29, 1.37, 1.42, 1.51, 1.58,
+      1.62, 1.68, 1.74, 1.84, 1.98, 2.13, 2.24, 2.33, 2.41, 2.45, 2.47, 2.49,
+      2.52, 2.53, 2.55, 2.57, 2.59, 2.58, 2.59, 2.6, 2.59, 2.55, 2.48, 2.36,
+      2.24, 2.1, 1.94, 1.77, 1.57, 1.32, 1.03, 0.72, 0.45, 0.21, -0.03, -0.25,
+      -0.39, -0.47, -0.5, -0.51, -0.5, -0.46, -0.44, -0.46, -0.47, -0.45, -0.41,
+      -0.32, -0.21, -0.11, -0.01, 0.1, 0.25,
+    ];
+
+    const expectedHistogram = [
+      0, -0.01, 0.19, 0.22, 0.24, 0.5, 0.83, 1.15, 1.35, 1.32, 1.2, 1.15, 0.91,
+      0.75, 0.62, 0.32, -0.03, -0.33, -0.48, -0.68, -0.79, -0.81, -0.52, -0.47,
+      -0.4, -0.49, -0.54, -0.44, -0.38, -0.13, 0.07, 0.42, 0.4, 0.33, 0.22,
+      0.36, 0.26, 0.15, 0.25, 0.24, 0.39, 0.55, 0.6, 0.47, 0.35, 0.31, 0.17,
+      0.08, 0.1, 0.1, 0.05, 0.05, 0.11, 0.06, -0.02, 0.01, 0.05, -0.04, -0.16,
+      -0.28, -0.46, -0.49, -0.57, -0.61, -0.69, -0.8, -0.98, -1.18, -1.22, -1.1,
+      -0.95, -0.96, -0.88, -0.58, -0.32, -0.09, -0.06, 0.06, 0.16, 0.07, -0.07,
+      -0.04, 0.05, 0.16, 0.36, 0.46, 0.4, 0.37, 0.45, 0.6,
+    ];
+
+    const actual = defaultPercentagePriceOscillator(prices);
+    deepStrictEqual(roundDigitsAll(2, actual.ppo), expectedPpo);
+    deepStrictEqual(roundDigitsAll(2, actual.signal), expectedSignal);
+    deepStrictEqual(roundDigitsAll(2, actual.histogram), expectedHistogram);
+  });
+});