All files / src pick-closest-to-median.ts

100% Statements 24/24
100% Branches 0/0
100% Functions 7/7
100% Lines 23/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 605x       5x 5x   5x       46x 46x   46x 115x       5x       115x 115x 157x                 115x             46x 115x       122x     5x 46x 46x 115x 157x 157x     46x    
import {
  SignedDataPackage,
  SignedDataPackagePlainObj,
} from "@redstone-finance/protocol";
import { MathUtils, SafeNumber } from "@redstone-finance/utils";
import _ from "lodash";
 
export const pickDataFeedPackagesClosestToMedian = (
  dataFeedPackages: SignedDataPackagePlainObj[],
  count: number
): SignedDataPackage[] => {
  const allValues = getAllValues(dataFeedPackages) as Record<string, number[]>;
  const allMedians = _.mapValues(allValues, MathUtils.getMedian);
 
  return sortByDistanceFromMedian(dataFeedPackages, allMedians)
    .map((diff) => SignedDataPackage.fromObj(diff.dp))
    .slice(0, count);
};
 
const getMaxDistanceFromMedian = (
  dataPackage: SignedDataPackagePlainObj,
  medians: Record<string, number>
) => {
  let maxDistanceFromMedian = 0;
  for (const dataPoint of dataPackage.dataPoints) {
    maxDistanceFromMedian = Math.max(
      maxDistanceFromMedian,
      SafeNumber.createSafeNumber(dataPoint.value)
        .sub(medians[dataPoint.dataFeedId])
        .abs()
        .div(medians[dataPoint.dataFeedId])
        .unsafeToNumber()
    );
  }
  return maxDistanceFromMedian;
};
 
function sortByDistanceFromMedian(
  dataFeedPackages: SignedDataPackagePlainObj[],
  medians: Record<string, number>
) {
  return dataFeedPackages
    .map((dp) => ({
      dp,
      diff: getMaxDistanceFromMedian(dp, medians),
    }))
    .sort((first, second) => first.diff - second.diff);
}
 
const getAllValues = (dataPackages: SignedDataPackagePlainObj[]) => {
  const allValues: Partial<Record<string, number[]>> = {};
  for (const dataPackage of dataPackages) {
    for (const dataPoint of dataPackage.dataPoints) {
      allValues[dataPoint.dataFeedId] ??= [];
      allValues[dataPoint.dataFeedId]!.push(Number(dataPoint.value));
    }
  }
  return allValues;
};