EVE-Tools/node-43

View on GitHub
lib/messagePipeline/orderCalculateRegionStats.js

Summary

Maintainability
C
1 day
Test Coverage
var async = require('async');
var postgres = require('../postgres');
var Stats = require('fast-stats').Stats;

function getRegionPrices(regionID, typeID, callback) {
  // Get connection from pool
  postgres(function(err, pgClient, done) {

    if (err) {
      // Return connection to pool
      done();

      err.module = 'pgConnect';

      // Handle errors
      callback(err, null);
    } else {
      pgClient.query('SELECT price, is_bid, volume_remaining ' +
                       'FROM market_data_orders WHERE mapregion_id = $1 ' +
                                                 'AND invtype_id = $2 ' +
                                                 'AND is_active = \'t\'', [regionID, typeID], function(err, result) {
                                                    done(); // Return connection to pool

                                                    if(err) {
                                                      err.module = 'orderCalculateRegionStats#getRegionPrices';
                                                    }

                                                    callback(err, result); // Run callback of waterfall
                                                 });
    }
  });
}

function splitPrices(result, callback) {
  //
  // Splits prices into two arrays
  //

  // Aggregate arrays
  var bidPrices = [];
  var askPrices = [];

  // Put prices into array
  for (var counter = 0; counter < result.rows.length; counter++) {
    if (result.rows[counter].is_bid === true) {
      bidPrices.push(result.rows[counter].price);
    } else {
      askPrices.push(result.rows[counter].price);
    }
  }

  // Throw error if there are not enough prices in our DB
  if (bidPrices.length > 0 && askPrices.length > 0) {
    callback(null, result.rows, {bid: bidPrices, ask: askPrices});
  } else {
    var error = new Error("Not enough prices to calculate stats!");
    error.severity = 0;
    callback(error, null);
  }
}

function calculateSums(metrics, data) {
  //
  // Summarizes ask/bid prices and volumes
  //

  // Manually bandpass array
  for (var counter = 0; counter < data.length; counter++) {

    if (data[counter].is_bid === true) {

      // Process bids
      if (data[counter].price >= metrics.bidPercentile5 && data[counter].price <= metrics.bidPercentile95) {
        metrics.bidPricesSum += data[counter].price * data[counter].volume_remaining;
        metrics.bidPricesVolume += data[counter].volume_remaining;
      }
    } else {

      // Process asks
      if (data[counter].price >= metrics.askPercentile5 && data[counter].price <= metrics.askPercentile95) {
        metrics.askPricesSum = data[counter].price * data[counter].volume_remaining;
        metrics.askPricesVolume += data[counter].volume_remaining;
      }
    }
  }

  return metrics;
}

function performCalculations(data, prices, callback) {
  //
  // Calculates statistical properties of prices
  //

  // Convert arrays to Stats vector
  var bidPrices = new Stats().push(prices.bid);
  var askPrices = new Stats().push(prices.ask);

  var metrics = {};

  // Filter top/bottom 5%
  metrics.bidPercentile5 = bidPrices.percentile(5);
  metrics.bidPercentile95 = bidPrices.percentile(95);
  var bidPrices95 = bidPrices.band_pass(metrics.bidPercentile5, metrics.bidPercentile95);

  if(bidPrices95.length < 5) {
    bidPrices95 = bidPrices; // There is no point in calculating metrics for less than 5 values
  }

  metrics.askPercentile5 = bidPrices.percentile(5);
  metrics.askPercentile95 = askPrices.percentile(95);
  var askPrices95 = askPrices.band_pass(metrics.askPercentile5, metrics.askPercentile95);

  if(askPrices95.length < 5) {
    askPrices95 = askPrices; // There is no point in calculating metrics for less than 5 values
  }

  // Calculate various values
  metrics.bidMean = bidPrices95.amean();
  metrics.bidMedian = bidPrices95.median();
  metrics.bidStdDev = bidPrices95.stddev();

  metrics.askMean = askPrices95.amean();
  metrics.askMedian = askPrices95.median();
  metrics.askStdDev = askPrices95.stddev();

  //
  // Calculate weighted average
  //

  metrics.bidPricesSum = 0;
  metrics.bidPricesVolume = 0;

  metrics.askPricesSum = 0;
  metrics.askPricesVolume = 0;

  // Summarize prices and volumes
  metrics = calculateSums(metrics, data);

  // Actually calculate the weighted average
  metrics.bidWeightedAverage = (metrics.bidPricesSum / metrics.bidPricesVolume);
  metrics.askWeightedAverage = (metrics.askPricesSum / metrics.askPricesVolume);

  callback(null, metrics);
}

exports = module.exports = function(resultSet, callback) {
  //
  // Calculates order statistics for itemRegionStats and itemRegionStatsHistory
  //

  if(resultSet.objects.length === 0) {

    // SKIP EMPTY MESSAGES
    return callback(null, resultSet);

  } else {

    async.waterfall([
      function (callback) {
        getRegionPrices(resultSet.regionID, resultSet.typeID, callback);   // Get statistical info of region/type pair
      },
      splitPrices,                              // Split bid/ask prices into separate arrays
      performCalculations                       // Calculate statistical properties
    ], function (error, result) {
      resultSet.metrics = result;

      // Error handling
      callback(error, resultSet);
    });
  }

};