/*
 * This file contains functions and variables global to the advanced graph page used by the
 * technical indicators.
 * 
 * $Id: chart_indicators_Advanced.js,v 1.1 2010/05/05 13:43:55 obo Exp $
 */  

function getIdTimeRangeDependingOnTechnicalIndicators(timeRange, valuesType, chart) {
  
  if (valuesType==swx.inv.Chart.prototype.statics.INTRADAY || valuesType==swx.inv.Chart.prototype.statics.INTERMEDIATE) {
    return timeRange; // nothing for intraday or intermediate
    
  } else {
 
    var defaultDomainMap = "_3m"; // if we don't have any date in domainDatesMap, use 3m
    var tmpDate = domainDatesMap["_"+timeRange];
    if (!tmpDate) tmpDate = domainDatesMap[defaultDomainMap];
    var smallestStartDate = new Date(tmpDate);
  
    for (var lineType in chart.showedLines) {
      
      if (lineType.indexOf("ti") == 0) {
            
        var techId = chart.showedLines[lineType];
        if (!techId || techId == "fake") continue;
  
        if ( (techId.indexOf("SMA") == 0 || techId.indexOf("EMA") == 0 ||
              techId.indexOf("RSI") == 0 || techId.indexOf("MOM") == 0) ) {
          
          var numberOfUnitsAverage = parseInt(techId.substring(3));
          var tmpDate = domainDatesMap["_"+timeRange];
          if (!tmpDate) tmpDate = domainDatesMap[defaultDomainMap];
          
          var startDate = new Date(tmpDate);
          // Estimate the trading date back
          startDate.setDate(startDate.getDate() - 7 / 4 * numberOfUnitsAverage);  
    
          if (startDate < smallestStartDate) {
            smallestStartDate = startDate;
          }
        } else if (techId.indexOf("BB") == 0) {
          var numberOfUnitsAverage = parseInt(techId.substring(2).split("\t")[0]);
          var tmpDate = domainDatesMap["_"+timeRange];
          if (!tmpDate) tmpDate = domainDatesMap[defaultDomainMap];
          var startDate = new Date(tmpDate);
          // Estimate the trading date back
          startDate.setDate(startDate.getDate() - 7 / 4 * numberOfUnitsAverage);  
          if (startDate < smallestStartDate) {
            smallestStartDate = startDate;
          }
        }
  
      }
    }
    
    return findTimeRangeFromStartDate(smallestStartDate);
  }
}

function hasToCalculateDataForTechnicalIndicator(lineType, chart) {
  var techId = chart.showedLines[lineType];
  
  if (techId == "fake") {
    return true; // do not download it 
  } else if (techId.indexOf("SMA") == 0) {
    return true; // calculate the data in javascript
  } else if (techId.indexOf("EMA") == 0) {
    return true; // calculate the data in javascript
  } else if (techId.indexOf("BB") == 0) {
    return true; // calculate the data in javascript
  } else if (techId.indexOf("RSI") == 0) {
    return true; // calculate the data in javascript
  } else if (techId.indexOf("MOM") == 0) {
    return true; // calculate the data in javascript
  } else {
    return false; // Download the data from the server
  }
}

/*
 * Ensures compatibility
 */
/*function calculateDataForTechnicalIndicator(lineType, timeRange, isTimeRangeIntraday) {
        if (!isTimeRangeIntraday) {
                calculateDataForTechnicalIndicator(lineType, timeRange, swx.inv.Chart.prototype.statics.HISTORICAL, null);
        } else {
                calculateDataForTechnicalIndicator(lineType, timeRange, swx.inv.Chart.prototype.statics.INTRADAY, null);
        }
}*/

function calculateDataForTechnicalIndicator(lineType, timeRange, valuesType) {
  var techId = advancedChart.showedLines[lineType];
  if (techId == "fake") {
    return; // do not calculate it 
  } else if (techId.indexOf("SMA") == 0) {
    // Calculate it
    var fakeAjaxResponse = getSimpleMovingAverageData(lineType, techId, valuesType);
    advancedChart._chartDataUpdater._consumeAjaxValues(fakeAjaxResponse, function() {});
  } else if (techId.indexOf("EMA") == 0) {
    // Calculate it
    var fakeAjaxResponse = getExponentialMovingAverageData(lineType, techId, valuesType);
    advancedChart._chartDataUpdater._consumeAjaxValues(fakeAjaxResponse, function() {});
  } else if (techId.indexOf("BB") == 0) {
    // Calculate it
    var fakeAjaxResponse = getBollingerBandData(techId, valuesType);
    advancedChart._chartDataUpdater._consumeAjaxValues(fakeAjaxResponse, function() {});
  } else if (techId.indexOf("RSI") == 0) {
    // Calculate it
    var fakeAjaxResponse = getRelativeStrengthIndexData(lineType, techId, valuesType);
    advancedChart._chartDataUpdater._consumeAjaxValues(fakeAjaxResponse, function() {});
  } else if (techId.indexOf("MOM") == 0) {
    // Calculate it
    var fakeAjaxResponse = getMomentumData(lineType, techId, valuesType);
    advancedChart._chartDataUpdater._consumeAjaxValues(fakeAjaxResponse, function() {});
  }
}


function getSimpleMovingAverageData(lineType, smaString, valuesType) {
    
  var numberOfUnitsAverage = parseInt(smaString.substring(3)); // remove "SMA"
  var retObj = {};
  retObj[lineType] = {};
  var oldAverageSum = -1; // Used for optimisation  
  var values;
  
  if (!valuesType || valuesType==swx.inv.Chart.prototype.statics.HISTORICAL) {
    values = advancedChart.values_days;
        
    if (!values.id || numberOfUnitsAverage > values.id.vals.length) {
      alert("Not enough points to calculate an average of this size!");
      return null;
    }
  
    values[lineType] = null; // remove all old values, we will recalculate new ones
    retObj[lineType].historical = {};
    retObj[lineType].historical.vals = [];
    
    // Calculate the points
    for (var i=numberOfUnitsAverage; i<values.id.vals.length; i++) {
      var val = values.id.vals[i];
      
      var average = 0;
      if (oldAverageSum == -1) { // first time, calulate the sum of the x elements (slow)
        oldAverageSum = 0;
        for (var j=0; j<numberOfUnitsAverage; j++) {  // just sum up the last x points
          oldAverageSum += values.id.vals[i-j].y;
        }
      } else { // don't recalculate the sum, make changes to it!
        oldAverageSum -= values.id.vals[i-numberOfUnitsAverage].y; // remove old point
        oldAverageSum += values.id.vals[i].y;  // add new one
      }
      average = oldAverageSum / numberOfUnitsAverage;
      
      var newval = {}; 
      newval.d = values.id.vals[i].d / 100000; // hack to gain bandwith
      newval.c = average;
      
      retObj[lineType].historical.vals.push(newval);
    }
     
  } else if (valuesType==swx.inv.Chart.prototype.statics.INTERMEDIATE) {
  
    values = advancedChart.values_inter;
        
    values[lineType] = null;
    retObj[lineType].intermediate = {};
    retObj[lineType].intermediate.vals = [];
    
    // first trading point  
    var startIndex = 0;
    while (startIndex<values.id.vals.length &&
           !values.id.vals[startIndex].y) {
      startIndex++;
    }
    
    // Last trading point
    var stopIndex = values.id.vals.length-1;
    while (stopIndex>0 &&
           !values.id.vals[stopIndex].y) {
      stopIndex--;
    }
    
    var average = 0;
    var avgPoints = 0;
    
    for (var i=startIndex; i<stopIndex; i++) {
      var current = values.id.vals[i];
      var start = 0;
      average = 0;
      avgPoints = 0;
      if (i > numberOfUnitsAverage) start = i-numberOfUnitsAverage;
      
      for (var j=start; j<i; j++) {
        if (!values.id.vals[j].y) continue;
        if (values.id.vals[j].d > (current.d - numberOfUnitsAverage * 60 * 60 * 1000)) {
          average += values.id.vals[j].y;
          avgPoints ++;
        }
      }
      
      if (avgPoints==0) continue;
      
      average /= avgPoints;
            
      var newval = {}; 
      newval.d = values.id.vals[i].d / 100000;
      newval.c = average;
           
      retObj[lineType].intermediate.vals.push(newval);
    }
    
    retObj[lineType].intermediate.timezoneChanged = true;  
    
  } else if (valuesType==swx.inv.Chart.prototype.statics.INTRADAY) {
    
    values = advancedChart.values_intra;
              
    values[lineType] = null; // remove all old values, we will recalculate new ones
    
    retObj[lineType].intraDay = {};
    retObj[lineType].intraDay.vals = [];
    retObj[lineType].intraDay.oldClose = 1; // fake oldClose but needed for _consumeAjaxValuesByLineType
      
    // first trading point  
    var startIndex = 0;
    while (startIndex<values.id.vals.length &&
           !values.id.vals[startIndex].y) {
      startIndex++;
    }
    
    // Last trading point in the intraday values (we can't see in the future! (Créating 
    var stopIndex = values.id.vals.length-1;
    while (stopIndex>0 &&
           !values.id.vals[stopIndex].y) {
      stopIndex--;
    }
    
    var average = 0;
    var averagePoints = 0;
    var newDay = true; // just to cheat because we will move first days points
    
    // Calculate the points
    for (var i=startIndex; i<=stopIndex; i++) {
      var val = values.id.vals[i];
      if (val.newDay) {
        newDay = true;
      }
      
      average = 0;
      averagePoints = 0;
      for (var j=0; j<=i; j++) { // Go back as long as we are in the period
        if (values.id.vals[i-j].d > (val.d - numberOfUnitsAverage * 60 * 1000)) {
          if (values.id.vals[i-j].y) {
            average += values.id.vals[i-j].y;
            averagePoints++;
          }
        } else {
          break; // out of the period
        }
      }
      if (averagePoints == 0) {
        // no points in this period, go to next one
        continue;
      }
      average /= averagePoints;
      
      var newval = {}; 
      newval.d = values.id.vals[i].d;
      newval.c = average;
      
      if (newDay) { // Cheat
        newval.d += swx.inv.ChartValuesHelper.prototype.statics.PAID_INTERVAL_MS;
        newDay = false;
      }
      
      retObj[lineType].intraDay.vals.push(newval);
    }
    
    retObj[lineType].intraDay.timezoneChanged = true;  
  }
  
  return retObj;
}  


function getBollingerBandData(techId, valuesType) {
  var periodAndSigmaFactor = techId.substring(2).split("\t"); // removes "BB" and splits by tab
  return getBolingerBands(parseInt(periodAndSigmaFactor[0]), 
                          parseFloat(periodAndSigmaFactor[1]),
                          valuesType);
}

function getBolingerBands(numberOfUnitsAverage, sigmaFactor, valuesType) {
  var lineTypeMiddle = "ti1";
  var lineTypeUp = "ti3";
  var lineTypeDown = "ti4";
  
  var retObj = {};
  retObj[lineTypeMiddle] = {};
  retObj[lineTypeUp] = {};
  retObj[lineTypeDown] = {};
  
  if (!valuesType || valuesType==swx.inv.Chart.prototype.statics.HISTORICAL) {
    values = advancedChart.values_days;
    
    values[lineTypeMiddle] = null; // remove all old values, we will recalculate new ones
    retObj[lineTypeMiddle].historical = {};
    retObj[lineTypeMiddle].historical.vals = [];
    values[lineTypeUp] = null; // remove all old values, we will recalculate new ones
    retObj[lineTypeUp].historical = {};
    retObj[lineTypeUp].historical.vals = [];
    values[lineTypeDown] = null; // remove all old values, we will recalculate new ones
    retObj[lineTypeDown].historical = {};
    retObj[lineTypeDown].historical.vals = [];
  
    var oldAverageSum = -1;
    // Calculate the points
    for (var i=numberOfUnitsAverage; i<values.id.vals.length; i++) {
      var val = values.id.vals[i];
      
      var average = 0;
      if (oldAverageSum == -1) { // first time, calulate the sum of the x elements (slow)
        oldAverageSum = 0;
        for (var j=0; j<numberOfUnitsAverage; j++) {  // just sum up the last x points
          oldAverageSum += values.id.vals[i-j].y;
        }
      } else { // don't recalculate the sum, make changes to it!
        oldAverageSum -= values.id.vals[i-numberOfUnitsAverage].y; // remove old point
        oldAverageSum += values.id.vals[i].y;  // add new one
      }
      average = oldAverageSum / numberOfUnitsAverage;
      
      var sum = 0;
      for (var j=0; j<numberOfUnitsAverage; j++) {  // just sum up the last x points (slow but...)
        sum += (values.id.vals[i-j].y - average) * (values.id.vals[i-j].y - average);
      }
      var sigma = Math.sqrt(sum / numberOfUnitsAverage); // this is the standard deviation
      
      var d = values.id.vals[i].d / 100000; // hack to gain bandwith
      
      var newvalMiddle = { d: d, c: average }; 
      retObj[lineTypeMiddle].historical.vals.push(newvalMiddle);
      
      var newvalUp = { d: d, c: average + sigmaFactor * sigma }; 
      retObj[lineTypeUp].historical.vals.push(newvalUp);
      
      var newvalDown = { d: d, c: average - sigmaFactor * sigma }; 
      retObj[lineTypeDown].historical.vals.push(newvalDown);
      
    }
    
  } else if (valuesType==swx.inv.Chart.prototype.statics.INTERMEDIATE) {
    values = advancedChart.values_inter;     
    
    values[lineTypeMiddle] = null; // remove all old values, we will recalculate new ones
    retObj[lineTypeMiddle].intermediate = {};
    retObj[lineTypeMiddle].intermediate.vals = [];
    values[lineTypeUp] = null; // remove all old values, we will recalculate new ones
    retObj[lineTypeUp].intermediate = {};
    retObj[lineTypeUp].intermediate.vals = [];
    values[lineTypeDown] = null; // remove all old values, we will recalculate new ones
    retObj[lineTypeDown].intermediate = {};
    retObj[lineTypeDown].intermediate.vals = [];
    
    // first trading point  
    var startIndex = 0;
    while (startIndex<values.id.vals.length &&
           !values.id.vals[startIndex].y) {
      startIndex++;
    }
    
    // Last trading point
    var stopIndex = values.id.vals.length-1;
    while (stopIndex>0 &&
           !values.id.vals[stopIndex].y) {
      stopIndex--;
    }
    
    var average = 0;
    var averagePoints = 0;
    var newDay = true; // just to cheat because we will move first days points
    
    // Calculate the points
    for (var i=startIndex; i<=stopIndex; i++) {
      var val = values.id.vals[i];
      if (val.newDay) {
        newDay = true;
      }
      
      average = 0;
      averagePoints = 0;
      for (var j=0; j<=i; j++) { // Go back as long as we are in the period
        if (values.id.vals[i-j].d > (val.d - numberOfUnitsAverage * 60 * 60 * 1000)) {
          if (values.id.vals[i-j].y) {
            average += values.id.vals[i-j].y;
            averagePoints++;
          }
        } else {
          break; // out of the period
        }
      }
      if (averagePoints == 0) {
        // no points in this period, go to next one
        continue;
      }
      average /= averagePoints;
      
      var sum = 0;
      for (var j=0; j<=i; j++) { // Go back as long as we are in the period
        if (values.id.vals[i-j].d > (val.d - numberOfUnitsAverage * 60 * 60 * 1000)) {
          if (values.id.vals[i-j].y) {
            sum += (values.id.vals[i-j].y - average) * (values.id.vals[i-j].y - average);
          }
        } else {
          break; // out of the period
        }
      }
      var sigma = Math.sqrt(sum / averagePoints); // this is the standard deviation
      
      var d = values.id.vals[i].d;
      if (newDay) { // Cheat
        d += swx.inv.ChartValuesHelper.prototype.statics.PAID_INTERVAL_INTER_MS;
        newDay = false;
      }
      
      d = d / 100000; // hack to gain bandwith */
      
      var newvalMiddle = { d: d, c: average }; 
      retObj[lineTypeMiddle].intermediate.vals.push(newvalMiddle);
      
      var newvalUp = { d: d, c: average + sigmaFactor * sigma }; 
      retObj[lineTypeUp].intermediate.vals.push(newvalUp);
      
      var newvalDown = { d: d, c: average - sigmaFactor * sigma }; 
      retObj[lineTypeDown].intermediate.vals.push(newvalDown);
      
    }
    retObj[lineTypeUp].intermediate.timezoneChanged = true;
    retObj[lineTypeMiddle].intermediate.timezoneChanged = true;
    retObj[lineTypeDown].intermediate.timezoneChanged = true;
    
  } else if (valuesType==swx.inv.Chart.prototype.statics.INTRADAY) {
    values = advancedChart.values_intra;
    
    values[lineTypeMiddle] = null; // remove all old values, we will recalculate new ones
    retObj[lineTypeMiddle].intraDay = {};
    retObj[lineTypeMiddle].intraDay.vals = [];
    retObj[lineTypeMiddle].intraDay.oldClose = 1; // fake oldClose but needed for _consumeAjaxValuesByLineType
    values[lineTypeUp] = null; // remove all old values, we will recalculate new ones
    retObj[lineTypeUp].intraDay = {};
    retObj[lineTypeUp].intraDay.vals = [];
    retObj[lineTypeUp].intraDay.oldClose = 1; // fake oldClose but needed for _consumeAjaxValuesByLineType
    values[lineTypeDown] = null; // remove all old values, we will recalculate new ones
    retObj[lineTypeDown].intraDay = {};
    retObj[lineTypeDown].intraDay.vals = [];
    retObj[lineTypeDown].intraDay.oldClose = 1; // fake oldClose but needed for _consumeAjaxValuesByLineType
  
    // first trading point  
    var startIndex = 0;
    while (startIndex<values.id.vals.length &&
           !advancedChart.values_intra.id.vals[startIndex].y) {
      startIndex++;
    }
    
    // Last trading point in the intraday values (we can't see in the future! (Créating 
    var stopIndex = values.id.vals.length-1;
    while (stopIndex>0 &&
           !values.id.vals[stopIndex].y) {
      stopIndex--;
    }
    
    var average = 0;
    var averagePoints = 0;
    var newDay = true; // just to cheat because we will move first days points
    
    // Calculate the points
    for (var i=startIndex; i<=stopIndex; i++) {
      var val = values.id.vals[i];
      if (val.newDay) {
        newDay = true;
      }
      
      average = 0;
      averagePoints = 0;
      for (var j=0; j<=i; j++) { // Go back as long as we are in the period
        if (values.id.vals[i-j].d > (val.d - numberOfUnitsAverage * 60 * 1000)) {
          if (values.id.vals[i-j].y) {
            average += values.id.vals[i-j].y;
            averagePoints++;
          }
        } else {
          break; // out of the period
        }
      }
      if (averagePoints == 0) {
        // no points in this period, go to next one
        continue;
      }
      average /= averagePoints;
      
      var sum = 0;
      for (var j=0; j<=i; j++) { // Go back as long as we are in the period
        if (values.id.vals[i-j].d > (val.d - numberOfUnitsAverage * 60 * 1000)) {
          if (values.id.vals[i-j].y) {
            sum += (values.id.vals[i-j].y - average) * (values.id.vals[i-j].y - average);
          }
        } else {
          break; // out of the period
        }
      }
      var sigma = Math.sqrt(sum / averagePoints); // this is the standard deviation
      
      var d = values.id.vals[i].d;
      if (newDay) { // Cheat
        d += swx.inv.ChartValuesHelper.prototype.statics.PAID_INTERVAL_MS;
        newDay = false;
      }
      
      var newvalMiddle = { d: d, c: average }; 
      retObj[lineTypeMiddle].intraDay.vals.push(newvalMiddle);
      
      var newvalUp = { d: d, c: average + sigmaFactor * sigma }; 
      retObj[lineTypeUp].intraDay.vals.push(newvalUp);
      
      var newvalDown = { d: d, c: average - sigmaFactor * sigma }; 
      retObj[lineTypeDown].intraDay.vals.push(newvalDown);
        
    }
    retObj[lineTypeUp].intraDay.timezoneChanged = true;  
    retObj[lineTypeMiddle].intraDay.timezoneChanged = true;
    retObj[lineTypeDown].intraDay.timezoneChanged = true;
  }
  
  return retObj;
}

function getExponentialMovingAverageData(lineType, emaString, valuesType) {
  
  var numberOfUnitsAverage = parseInt(emaString.substring(3)); // remove "EMA"
  
  var retObj = {};
  retObj[lineType] = {};
  
  if (!valuesType || valuesType==swx.inv.Chart.prototype.statics.HISTORICAL) {
    values = advancedChart.values_days;
          
    if (numberOfUnitsAverage > values.id.vals.length) {
      alert("Not enough points to calculate an average of this size!");
      return null;
    }
  
    values[lineType] = null; // remove all old values, we will recalculate new ones
    retObj[lineType].historical = {};
    retObj[lineType].historical.vals = [];
  
    var alpha = 2 / (numberOfUnitsAverage + 1);
    var ema = -1; // Exponential moving average
    // Calculate the points
    for (var i=numberOfUnitsAverage; i<values.id.vals.length; i++) {
      var val = values.id.vals[i];
      
      if (ema == -1) { // first time, first ema = simple moving average
        ema = 0;
        for (var j=0; j<numberOfUnitsAverage; j++) {  // just sum up the last x points
          ema += values.id.vals[i-j].y;
        }
        ema /= numberOfUnitsAverage;
      } else { // don't recalculate the sum, make changes to it!
        ema = alpha * values.id.vals[i].y + (1 - alpha) * ema;
      }
      
      var newval = {}; 
      newval.d = values.id.vals[i].d / 100000; // hack to gain bandwith
      newval.c = ema;
      
      retObj[lineType].historical.vals.push(newval);
    }
    
    
  } else if (valuesType==swx.inv.Chart.prototype.statics.INTERMEDIATE) {
          
    values = advancedChart.values_inter;
    
    values[lineType] = null; // remove all old values, we will recalculate new ones
    retObj[lineType].intermediate = {};
    retObj[lineType].intermediate.vals = [];
        
    // first trading point  
    var startIndex = 0;
    while (startIndex<values.id.vals.length &&
           !values.id.vals[startIndex].y) {
      startIndex++;
    }
    
    // Last trading point
    var stopIndex = values.id.vals.length-1;
    while (stopIndex>0 &&
           !values.id.vals[stopIndex].y) {
      stopIndex--;
    }
    
    var ema = 0;
    var firstEma = true;
    var avgPoints = 0;
    
    for (var i=startIndex; i<stopIndex; i++) {
      avgPoints = 0;
      var current = values.id.vals[i];
      var start = 0;
      if (i > numberOfUnitsAverage) start = i-numberOfUnitsAverage;
            
      avgPoints = 0;
      
      for (var j=start; j<=i; j++) {
        if (!values.id.vals[j].y) continue;
        if (values.id.vals[j].d > (current.d - numberOfUnitsAverage * 60 * 60 * 1000)) {
          if (firstEma) {
            ema += values.id.vals[j].y;
          }
          avgPoints ++;
        }
      }
      
      if (avgPoints==0) continue;
      
      if (firstEma) {
        firstEma = false;
        ema /= avgPoints;
      } else {
        if (!current.y) {
          continue;
        }
        var alpha = 2 / (avgPoints + 1);
        ema = alpha * current.y + (1 - alpha) * ema;     
      }
      
      var newval = {}; 
      newval.d = values.id.vals[i].d / 100000;
      newval.c = ema;
      retObj[lineType].intermediate.vals.push(newval);
    }
    
    retObj[lineType].intermediate.timezoneChanged = true;  
    
  } else if (valuesType==swx.inv.Chart.prototype.statics.INTRADAY) {
    values = advancedChart.values_intra;
          
    values[lineType] = null; // remove all old values, we will recalculate new ones
    retObj[lineType].intraDay = {};
    retObj[lineType].intraDay.vals = [];
    retObj[lineType].intraDay.oldClose = 1; // fake oldClose but needed for _consumeAjaxValuesByLineType
  
    // first trading point  
    var startIndex = 0;
    while (startIndex<values.id.vals.length &&
           !values.id.vals[startIndex].y) {
      startIndex++;
    }
    
    // Last trading point in the intraday values (we can't see in the future! (Créating 
    var stopIndex = values.id.vals.length-1;
    while (stopIndex>0 &&
           !values.id.vals[stopIndex].y) {
      stopIndex--;
    }
    
    var ema = 0;
    var firstEma = true;
    var averagePoints = 0;
    var newDay = true; // just to cheat because we will move first days points
    
    // Calculate the points
    for (var i=startIndex; i<=stopIndex; i++) {
      var val = values.id.vals[i];
      if (val.newDay) {
        newDay = true;
      }
      
      averagePoints = 0;
      for (var j=0; j<=i; j++) { // Go back as long as we are in the period
        if (values.id.vals[i-j].d > (val.d - numberOfUnitsAverage * 60 * 1000)) {
          if (values.id.vals[i-j].y) {
            if (firstEma) {
              ema += values.id.vals[i-j].y;
            }
            averagePoints++;
          }
        } else {
          break; // out of the period
        }
      }
      if (averagePoints == 0) {
        // no points in this period, go to next one
        continue;
      }
      if (firstEma) {
        firstEma = false;
        ema /= averagePoints;
      } else {
        if (!values.id.vals[i].y) {
          // no price at this point
          continue;
        }
        var alpha = 2 / (averagePoints + 1);
        ema = alpha * values.id.vals[i].y + (1 - alpha) * ema;
      }
      
      var newval = {}; 
      newval.d = values.id.vals[i].d;
      newval.c = ema;
      
      if (newDay) { // Cheat
        newval.d += swx.inv.ChartValuesHelper.prototype.statics.PAID_INTERVAL_MS;
        newDay = false;
      }
      retObj[lineType].intraDay.vals.push(newval);
    }
    
    retObj[lineType].intraDay.timezoneChanged = true;  
  }
  
  return retObj;
}  

function getRelativeStrengthIndexData(lineType, rsiString, valuesType) {
  
  var numberOfUnitsAverage = parseInt(rsiString.substring(3)); // remove "RSI"
  
  var retObj = {};
  retObj[lineType] = {};
  
  if (!valuesType || valuesType==swx.inv.Chart.prototype.statics.HISTORICAL) {
    values = advancedChart.values_days;
          
    if (numberOfUnitsAverage+1 > values.id.vals.length) {
      alert("Not enough points to calculate an average of this size!");
      return null;
    }
  
    values[lineType] = null; // remove all old values, we will recalculate new ones
    retObj[lineType].historical = {};
    retObj[lineType].historical.isAddon = true;
    retObj[lineType].historical.vals = [];
  
    var avgG = 0; // Average Gain
    var avgL = 0; // Average Loss
    var firstAvg = true;
    var lastPrice = values.id.vals[0].y; // first price we have
     
    // Calculate the points ( we use +1 because the first point is the reference )
    for (var i=numberOfUnitsAverage+1; i<values.id.vals.length; i++) {
      var val = values.id.vals[i];
      
      if (firstAvg) { // first time, first average = simple moving average
        for (var j=numberOfUnitsAverage; j>0; j--) {  // just sum up the last x points
          var currentGainOrLoss = values.id.vals[i-j].y - lastPrice; 
          if (currentGainOrLoss > 0) {
            avgG += currentGainOrLoss;
          } else {
            avgL -= currentGainOrLoss; // loss is a negative value. -- = +
          }
          lastPrice = values.id.vals[i-j].y;
        }
        avgG /= numberOfUnitsAverage;
        avgL /= numberOfUnitsAverage;
        firstAvg = false;
      } else { // after first average... use exponential moving average
        var currentGainOrLoss = values.id.vals[i].y - values.id.vals[i-1].y;
        if (currentGainOrLoss > 0) { // See http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:relative_strength_index_rsi
          avgG = (avgG * (numberOfUnitsAverage - 1) + currentGainOrLoss) / numberOfUnitsAverage;
          avgL = (avgL * (numberOfUnitsAverage - 1)) / numberOfUnitsAverage;;
        } else {
          avgG = (avgG * (numberOfUnitsAverage - 1)) / numberOfUnitsAverage;;
          avgL = (avgL * (numberOfUnitsAverage - 1) - currentGainOrLoss) / numberOfUnitsAverage;
        }
      }
      
      var newval = {}; 
      newval.d = values.id.vals[i].d / 100000; // hack to gain bandwith
      newval.c = 100 * (avgG / (avgG + avgL));
      
      retObj[lineType].historical.vals.push(newval);
    }
    
    
  } else if (valuesType==swx.inv.Chart.prototype.statics.INTERMEDIATE) {
    values = advancedChart.values_inter;
    
    values[lineType] = null; // remove all old values, we will recalculate new ones
    retObj[lineType].intermediate = {};
    retObj[lineType].intermediate.isAddon = true;
    retObj[lineType].intermediate.vals = [];
        
    // first trading point  
    var startIndex = 0;
    while (startIndex<values.id.vals.length &&
           !values.id.vals[startIndex].y) {
      startIndex++;
    }
    
    // Last trading point
    var stopIndex = values.id.vals.length-1;
    while (stopIndex>0 &&
           !values.id.vals[stopIndex].y) {
      stopIndex--;
    }
    
    var avgG = 0; // Average Gain
    var avgL = 0; // Average Loss
    var firstAvg = true;
    var lastPrice = values.id.oldClose; // old closing price
    var averagePoints = 0;
    
    // Calculate the points
    for (var i=startIndex; i<=stopIndex; i++) {
      var current = values.id.vals[i];
      if (!lastPrice) lastPrice = current.y;
      
      // Find the number of trading points we have in this period
      var startPeriodIndex = 0;
      averagePoints = 0;
      for (var j=0; j<=i; j++) { // Go back as long as we are in the period
        if (values.id.vals[i-j].d > (current.d - numberOfUnitsAverage * 60 * 60 * 1000)) {
          if (values.id.vals[i-j].y) {
            averagePoints++;
          }
        } else {
          startPeriodIndex = i-j;
          break; // out of the period
        }
      }
      
      if (averagePoints == 0) {
        // no points in this period, go to next one
        continue;
      }

      // First point, take the SMA
      if (firstAvg) {
        for (var j=startPeriodIndex+1; j<=i; j++) { // Go back as long as we are in the period
          if (values.id.vals[j].y) {
            var currentGainOrLoss = values.id.vals[j].y - lastPrice; 
            if (currentGainOrLoss > 0) {
              avgG += currentGainOrLoss;
            } else {
              avgL -= currentGainOrLoss; // loss is a negative value. -- = +
            }
            lastPrice = values.id.vals[j].y;
          }
        }
        avgG /= averagePoints;
        avgL /= averagePoints;
        firstAvg = false;
      } else {
        if (!current.y) {
          // no price at this point
          continue;
        }
        
        var currentGainOrLoss = current.y - lastPrice;
        if (currentGainOrLoss > 0) { // See http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:relative_strength_index_rsi
          avgG = (avgG * (averagePoints - 1) + currentGainOrLoss) / averagePoints;
          avgL = (avgL * (averagePoints - 1)) / averagePoints;;
        } else {
          avgG = (avgG * (averagePoints - 1)) / averagePoints;;
          avgL = (avgL * (averagePoints - 1) - currentGainOrLoss) / averagePoints;
        }
        lastPrice = current.y;
      }
      
      var newval = {}; 
      newval.d = current.d / 100000;
      newval.c = 100 * (avgG / (avgG + avgL));
          
      retObj[lineType].intermediate.vals.push(newval);
    }
    retObj[lineType].intermediate.timezoneChanged = true;  
    
  } else if (valuesType==swx.inv.Chart.prototype.statics.INTRADAY) {
    values = advancedChart.values_intra;  
          
    values[lineType] = null; // remove all old values, we will recalculate new ones
    retObj[lineType].intraDay = {};
    retObj[lineType].intraDay.isAddon = true;
    retObj[lineType].intraDay.vals = [];
    retObj[lineType].intraDay.oldClose = 1; // fake oldClose but needed for _consumeAjaxValuesByLineType
  
    // first trading point  
    var startIndex = 0;
    while (startIndex<values.id.vals.length && !values.id.vals[startIndex].y) {
      startIndex++;
    }
    
    // Last trading point in the intraday values (we can't see in the future! (Créating 
    var stopIndex = values.id.vals.length-1;
    while (stopIndex>0 && !values.id.vals[stopIndex].y) {
      stopIndex--;
    }
    
    var avgG = 0; // Average Gain
    var avgL = 0; // Average Loss
    var firstAvg = true;
    var lastPrice = values.id.oldClose; // old closing price
    
    var averagePoints = 0;
    var newDay = true; // just to cheat because we will move first days points
    
    // Calculate the points
    for (var i=startIndex; i<=stopIndex; i++) {
      var val = values.id.vals[i];
      if (val.newDay) {
        newDay = true;
      }
      
      // Find the number of trading points we have in this period
      var startPeriodIndex = 0;
      averagePoints = 0;
      for (var j=0; j<=i; j++) { // Go back as long as we are in the period
        if (values.id.vals[i-j].d > (val.d - numberOfUnitsAverage * 60 * 1000)) {
          if (values.id.vals[i-j].y) {
            averagePoints++;
          }
        } else {
          startPeriodIndex = i-j;
          break; // out of the period
        }
      }
      
      if (averagePoints == 0) {
        // no points in this period, go to next one
        continue;
      }

      // First point, take the SMA
      if (firstAvg) {
        for (var j=startPeriodIndex+1; j<=i; j++) { // Go back as long as we are in the period
          if (advancedChart.values_intra.id.vals[j].y) {
            var currentGainOrLoss = values.id.vals[j].y - lastPrice; 
            if (currentGainOrLoss > 0) {
              avgG += currentGainOrLoss;
            } else {
              avgL -= currentGainOrLoss; // loss is a negative value. -- = +
            }
            lastPrice = values.id.vals[j].y;
          }
        }
        avgG /= averagePoints;
        avgL /= averagePoints;
        firstAvg = false;
      } else {
        if (!values.id.vals[i].y) {
          // no price at this point
          continue;
        }
        
        var currentGainOrLoss = values.id.vals[i].y - lastPrice;
        if (currentGainOrLoss > 0) { // See http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:relative_strength_index_rsi
          avgG = (avgG * (averagePoints - 1) + currentGainOrLoss) / averagePoints;
          avgL = (avgL * (averagePoints - 1)) / averagePoints;;
        } else {
          avgG = (avgG * (averagePoints - 1)) / averagePoints;;
          avgL = (avgL * (averagePoints - 1) - currentGainOrLoss) / averagePoints;
        }
        lastPrice = values.id.vals[i].y;
      }
      
      var newval = {}; 
      newval.d = values.id.vals[i].d;
      newval.c = 100 * (avgG / (avgG + avgL));
      
      if (newDay) { // Cheat
        newval.d += swx.inv.ChartValuesHelper.prototype.statics.PAID_INTERVAL_MS;
        newDay = false;
      }
      retObj[lineType].intraDay.vals.push(newval);
    }
    retObj[lineType].intraDay.timezoneChanged = true;  
  }
  
  return retObj;
}  

function getMomentumData(lineType, rsiString, valuesType) {
  var period = parseInt(rsiString.substring(3)); // remove "RSI"
  
  var retObj = {};
  retObj[lineType] = {};
  
  if (!valuesType || valuesType==swx.inv.Chart.prototype.statics.HISTORICAL) {
    values = advancedChart.values_days;
            
    if (period > values.id.vals.length) {
      alert("Not enough points to calculate a momentum of this size!");
      return null;
    }
  
    values[lineType] = null; // remove all old values, we will recalculate new ones
    retObj[lineType].historical = {};
    retObj[lineType].historical.isAddon = true;
    retObj[lineType].historical.vals = [];
  
    // Calculate the points ( we use +1 because the first point is the reference )
    for (var i=period; i<values.id.vals.length; i++) {
      var val = values.id.vals[i];
      var numberOfUnitsOldVal = values.id.vals[i - period];
      
      var roc = (val.y - numberOfUnitsOldVal.y) / numberOfUnitsOldVal.y * 100;
      
      var newval = {}; 
      newval.d = values.id.vals[i].d / 100000; // hack to gain bandwith
      newval.c = 100 + roc;
      
      retObj[lineType].historical.vals.push(newval);
    }
  
  
  } else if (valuesType==swx.inv.Chart.prototype.statics.INTERMEDIATE) {
    values = advancedChart.values_inter;  
    
    values[lineType] = null; // remove all old values, we will recalculate new ones
    retObj[lineType].intermediate = {};
    retObj[lineType].intermediate.isAddon = true;
    retObj[lineType].intermediate.vals = [];
       
    // first trading point  
    var startIndex = 0;
    while (startIndex<values.id.vals.length &&
           !values.id.vals[startIndex].y) {
      startIndex++;
    }
    
    // Last trading point
    var stopIndex = values.id.vals.length-1;
    while (stopIndex>0 &&
           !values.id.vals[stopIndex].y) {
      stopIndex--;
    }
      
    // Calculate the points
    for (var i=startIndex; i<=stopIndex; i++) {
      var current = values.id.vals[i];
      if (!current.y) continue; // no trade on this point, so no closing price!
      
      // Find the last closing price before this period
      var lastClosingPrice = -1;
      
      var startPeriodIndex = 0;
      for (var j=1; j<=i; j++) { // Go back outside of the period, and find the closing price
        if (values.id.vals[i-j].d <= (current.d - period * 60 * 60 * 1000)) {
          if (values.id.vals[i-j].y) {
            lastClosingPrice = values.id.vals[i-j].y;
            break;
          }
        }
      }
      
      if (lastClosingPrice == -1) {
        // no closing price before this period = no point
        continue;
      }

      var roc = (current.y - lastClosingPrice) / lastClosingPrice * 100;
      
      var newval = {}; 
      newval.d = current.d / 100000;
      newval.c = 100 + roc;
      
      retObj[lineType].intermediate.vals.push(newval);
    }
    retObj[lineType].intermediate.timezoneChanged = true;  
    
  } else if (valuesType==swx.inv.Chart.prototype.statics.INTRADAY) {
    values = advancedChart.values_intra;  
    
    values[lineType] = null; // remove all old values, we will recalculate new ones
    retObj[lineType].intraDay = {};
    retObj[lineType].intraDay.isAddon = true;
    retObj[lineType].intraDay.vals = [];
    retObj[lineType].intraDay.oldClose = 1; // fake oldClose but needed for _consumeAjaxValuesByLineType
  
    // first trading point  
    var startIndex = 0;
    while (startIndex<values.id.vals.length && !values.id.vals[startIndex].y) {
      startIndex++;
    }
    
    // Last trading point in the intraday values (we can't see in the future! (Créating 
    var stopIndex = values.id.vals.length-1;
    while (stopIndex>0 && !values.id.vals[stopIndex].y) {
      stopIndex--;
    }
    
    var newDay = true; // just to cheat because we will move first days points
    
    // Calculate the points
    for (var i=startIndex; i<=stopIndex; i++) {
      var val = values.id.vals[i];
      if (!val.y) continue; // no trade on this point, so no closing price!
      
      if (val.newDay) {
        newDay = true;
      }
      
      // Find the last closing price before this period
      var lastClosingPrice = -1;
      
      var startPeriodIndex = 0;
      for (var j=1; j<=i; j++) { // Go back outside of the period, and find the closing price
        if (values.id.vals[i-j].d <= (val.d - period * 60 * 1000)) {
          if (values.id.vals[i-j].y) {
            lastClosingPrice = values.id.vals[i-j].y;
            break;
          }
        }
      }
      
      if (lastClosingPrice == -1) {
        // no closing price before this period = no point
        continue;
      }

      var roc = (val.y - lastClosingPrice) / lastClosingPrice * 100;
      
      var newval = {}; 
      newval.d = values.id.vals[i].d;
      newval.c = 100 + roc;
      
      if (newDay) { // Cheat
        newval.d += swx.inv.ChartValuesHelper.prototype.statics.PAID_INTERVAL_MS;
        newDay = false;
      }
      retObj[lineType].intraDay.vals.push(newval);
    }
    retObj[lineType].intraDay.timezoneChanged = true;  
  }
  
  return retObj;
}
