Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempted to fix the Projection Upper band and lower band calculation #329

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
115 changes: 101 additions & 14 deletions src/indicator/volatility/projectionOscillator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@

import {
add,
addBy,
divide,
generateNumbers,
multiply,
multiplyBy,
subtract,
} from '../../helper/numArray';
import { movingLeastSquare } from '../../helper/regression';
import { ema } from '../trend/ema';
import { mmax } from '../trend/mmax';
import { mmin } from '../trend/mmin';
subtractBy,
} from "../../helper/numArray";
import { movingLeastSquare } from "../../helper/regression";
import { ema } from "../trend/ema";
import { mmax } from "../trend/mmax";
import { mmin } from "../trend/mmin";

/**
* Projection oscillator result object.
Expand All @@ -22,15 +24,31 @@ export interface ProjectionOscillator {
spo: number[];
}

// Just pads a number array with zeros to the right so that the length of the arr becomes desiredLength
function padWithZeros(desiredLength: number, arr: number[]) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very nice helper function to add to ../../helper/numArray probably?

const currentLength: number = arr.length;
const result: number[] = new Array(desiredLength);

for (let i = 0; i < desiredLength; i++) {
if (i < currentLength) {
result[i] = arr[i];
} else {
result[i] = 0;
}
}
return result;
}

/**
* ProjectionOscillator calculates the Projection Oscillator (PO). The PO
* uses the linear regression slope, along with highs and lows.
*
* Period defines the moving window to calculates the PO, and the smooth
* period defines the moving windows to take EMA of PO.
*
* PL = Min(period, (high + MLS(period, x, high)))
* PU = Max(period, (low + MLS(period, x, low)))
* mHighs = MLS(period, x, high)
* mLows = MLS(period, x, low)
* PL[i] = Min(lows[i - period + 1, i] + mLows[i] * [0, period - 1])
* PU[i] = Max(highs[i - period + 1, i] + mHighs[i] * [0, period - 1])
* PO = 100 * (Closing - PL) / (PU - PL)
* SPO = EMA(smooth, PO)
*
Expand All @@ -46,17 +64,64 @@ export function projectionOscillator(
smooth: number,
highs: number[],
lows: number[],
closings: number[]
closings: number[],
): ProjectionOscillator {
const x = generateNumbers(0, closings.length, 1);
const lsHighs = movingLeastSquare(period, x, highs);
const lsLows = movingLeastSquare(period, x, lows);
// const vHighs = add(highs, multiply(lsHighs.m, x));
// const vLows = add(lows, multiply(lsLows.m, x));

const arr_period: number[] = generateNumbers(0, period, 1);
const pu = new Array(closings.length);
for (let i = 0; i < highs.length; i++) {
// for (let j = 0; j < period; j++) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we remove these comments?

// if (i < j) {
// continue;
// }
// pu[i] = Math.max(
// pu[i],
// highs[i - j] + lsHighs.m[i] * arr_period[j],
// );
// }
///////////////////////////////////
// LOOK HERE
///////////////////////////////////
const newHighs = padWithZeros(
period,
highs.slice(Math.max(0, i - period + 1), i + 1),
);
const vHighs = add(newHighs, multiplyBy(lsHighs.m[i], arr_period));
pu[i] = Math.max(...vHighs);
}

const pl = new Array(closings.length);
for (let i = 0; i < lows.length; i++) {
// for (let j = 0; j < period; j++) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we remove these comments?

// if (i < j) {
// continue;
// }
// pl[i] = Math.min(
// pl[i],
// lows[i - j] + lsLows.m[i] * arr_period[j],
// );
// }
///////////////////////////////////
// LOOK HERE
///////////////////////////////////
const newLows = padWithZeros(
period,
lows.slice(Math.max(0, i - period + 1), i + 1),
);
const vLows = add(newLows, multiplyBy(lsLows.m[i], arr_period));
pl[i] = Math.min(...vLows);
}

const vHighs = add(highs, multiply(lsHighs.m, x));
const vLows = add(lows, multiply(lsLows.m, x));
console.log(pu);
console.log(pl);

const pu = mmax(period, vHighs);
const pl = mmin(period, vLows);
// const pu = mmax(period, vHighs);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we remove these comments?

// const pl = mmin(period, vLows);

const po = divide(multiplyBy(100, subtract(closings, pl)), subtract(pu, pl));
const spo = ema(smooth, po);
Expand All @@ -77,7 +142,29 @@ export function projectionOscillator(
export function defaultProjectionOscillator(
highs: number[],
lows: number[],
closings: number[]
closings: number[],
): ProjectionOscillator {
return projectionOscillator(14, 3, highs, lows, closings);
}
function rng(mean: number, stdDev: number) {
return mean + stdDev * (Math.random() - 0.5);
}

function generateRandomNumbers(count: number, mean: number, stdDev: number) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be the function can be named differently to reflect how you are using the mean and stdDev? I guess they are not just random number right? Also would be nice to have this in ../../helper/numArray.ts as well.

const result: number[] = new Array(count);
for (let i = 0; i < count; i++) {
result[i] = rng(mean, stdDev);
}
return result;
}

const closings = generateRandomNumbers(100, 30, 5);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess some test code got left over here?

const highs = addBy(0.5, closings);
const lows = subtractBy(0.5, closings);

const result = projectionOscillator(14, 5, highs, lows, closings);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you have a test case here, can we put it into the unit test?


// Check if any of the values are below 0 or above 100 in the result array
console.log(result);
console.log(result.po.some((v) => v < 0 || v > 100));
// This is returning false, which means that all the values are between 0 and 100