//+------------------------------------------------------------------+
//| Basket Profit 2 Pairs sq.mq4
//+------------------------------------------------------------------+

#property copyright "Squalou"
#property link      ""


/* Weighted Basket Profit Indicator, by squalou:
 * draws a line representing the P/L of a Weighted Basket, in Account Currency amount.
 * The light-blue vertical line is the Reference Line showing pointing the "Basket BE point",
 * typically put on the bar where you would "buy" this basket.
 * It can be moved manually to change the "Basket BE point", the indicator will automatically adjust the BE horizontal line (0.00 value)
 * accordingly at the next incoming tick;
 *
 * This was developped to help research on this Dreamliner's thread:
 * http://www.forexfactory.com/showthread.php?t=160912
 * and its EA dev thread from SteveHopwood:
 * http://www.forexfactory.com/showthread.php?t=278476
 * 
 */
 
/* - it is HIGHLY RECOMMENDED to load the indicators on the pair that has the highest tick refresh rate 
 *   to get the most reactive Indicators/EAs update -- especially when using an EA;
 * 
 * Note that the update of the Indicators/EAs will take effect only at the next incoming tick;
 * When markets are closed, it is therefore necessary to manually refresh the chart (right-click+Refresh),
 * or better use the "MT4_ticker.exe" program to generate artificial ticks;
 *
 */

#property indicator_separate_window
//#property indicator_minimum -100
//#property indicator_maximum 100
#property indicator_level1 0
#property indicator_level2 100
#property indicator_level3 -100
#property indicator_level4 200
#property indicator_level5 -200
#property indicator_level6 300
#property indicator_level7 -300
#property indicator_buffers 1
#property indicator_color1 Red
#property indicator_color2 Orange
#property indicator_color3 MediumSlateBlue
#property indicator_color4 Green


extern string BasketPairs = "100*EURUSD -277*GBPUSD -22*USDCHF + 50*AUDUSD + 269*USDJPY + 8*USDCAD +133*NZDUSD";
extern string BasketFile  = ""; // will load the Basket pairs/weights from the given CSV file; CSV file can be generated by Arbomat-sq EA derived from 7bit's arbomat EA;
extern int DaysBackReference = 0; // where to put the "Reference Point 0"
extern double WeightFactor = 1;
extern string DollarIndexBasketNormalized = "-100*EURUSD + 24*USDJPY - 21*GBPUSD + 16*USDCAD + 7*USDSEK + 6*USDCHF";
//extern string RealDollarIndexBasket = "-0.576*EURUSD + 0.136*USDJPY - 0.119*GBPUSD + 0.091*USDCAD + 0.042*USDSEK + 0.036*USDCHF";

double buffer1[];
string m = "";
bool stopped;

string ref_line = "Basket BE Reference";
int ref_bar;
datetime ref_time;

int nbPairs;

string Pair[30];
double Weight[30],total_weight = 0;
double Value[30];
double ref[30];

string BasketString;

//+------------------------------------------------------------------+
int init()
//+------------------------------------------------------------------+
{
  int j;
  
  if (BasketFile != "") import_coeffs_from_CSV(BasketFile, nbPairs, Pair, Weight);
  else ExtractBasket(BasketPairs, nbPairs, Pair, Weight);

  for (j=0;j<nbPairs;j++) Weight[j] *= WeightFactor;

  // rebuild a Basket string for displaying purpose
  BasketString = MakeBasketString(nbPairs, Pair, Weight);
  Print(BasketString);
  
  IndicatorShortName(WindowExpertName()+" "+AccountCurrency()+" ("+BasketString+")");

  // plot BE Reference Line on DaysBackReference at 00:00
  ref_time = iTime(NULL,PERIOD_D1,DaysBackReference);
  ref_bar = iBarShift(NULL,0,iTime(NULL,PERIOD_D1,DaysBackReference));
  drawVLine(ref_line,ref_time,DodgerBlue,STYLE_SOLID);

  stopped=false;
  total_weight = 0;
  for (j = 0; j < nbPairs; j++) {
    total_weight += Weight[j];
    ref[j] = 0;
    if (MarketInfo(Pair[j],MODE_POINT)!=0) {
      Value[j] = PointValueFor1Lot(Pair[j])/MarketInfo(Pair[j],MODE_POINT);
      ref[j] = iClose(Pair[j],0,iBarShift(Pair[j],0,ref_time));
    }
    if (ref[j]==0) {
      Alert("!!! "+WindowExpertName()+" : no history data for "+Pair[j]+" !!!");
      stopped=true;
    }
  }

  //---- indicators
  SetIndexBuffer(0, buffer1);
  SetIndexLabel(0,WindowExpertName()+" "+AccountCurrency());

  return (0);
}

//+------------------------------------------------------------------+
int deinit()
//+------------------------------------------------------------------+
{
  return (0);
}

//+------------------------------------------------------------------+
int start()
//+------------------------------------------------------------------+
{
  if (stopped) return;

  int BarsBack = Bars;// DaysBackReference*PERIOD_D1/Period();
  
  int j;
  int i, counted_bars = IndicatorCounted();
  if (counted_bars > 0) counted_bars--;
  int limit = Bars - counted_bars;
  limit = MathMin(limit,BarsBack);

  if (ObjectFind(ref_line)>=0) {  
    double new_ref = ObjectGet(ref_line,OBJPROP_TIME1);
    if (new_ref!=ref_time) {
      ref_time = new_ref;
      Print("BE Reference Line moved to "+TimeToStr(ref_time,TIME_DATE|TIME_MINUTES));
      drawVLine(ref_line,ref_time,DodgerBlue,STYLE_SOLID);
      for (j=0;j<nbPairs;j++) ref[j] = iClose(Pair[j],0,iBarShift(Pair[j],0,ref_time));
      limit=BarsBack;
    }
  }
  
  for(i=limit; i>=0; i--)
  {
    double v=0;
    for (j=0;j<nbPairs;j++) v += ( iClose(Pair[j],0,iBarShift(Pair[j],0,Time[i])) - ref[j] ) * Value[j] * Weight[j];
    buffer1[i] = v;
  }

  return (0);
}

//+------------------------------------------------------------------
void drawVLine(string objname, int time, color c, int style=STYLE_SOLID, int width=0)
//+------------------------------------------------------------------
{
  ObjectCreate(objname, OBJ_VLINE, 0, time, 0);
  ObjectSet(objname, OBJPROP_COLOR, c);
  ObjectSet(objname, OBJPROP_STYLE, style);
}

//--------------------------------------------------------------------------------------
void drawFixedLbl(string objname, string s, int Corner, int DX, int DY, int FSize, string Font, color c, bool bg)
//--------------------------------------------------------------------------------------
{
   if (ObjectFind(objname) < 0) ObjectCreate(objname, OBJ_LABEL, 0, 0, 0);
   
   ObjectSet(objname, OBJPROP_CORNER, Corner);
   ObjectSet(objname, OBJPROP_XDISTANCE, DX);
   ObjectSet(objname, OBJPROP_YDISTANCE, DY);
   ObjectSet(objname,OBJPROP_BACK, bg);      
   ObjectSetText(objname, s, FSize, Font, c);
} // drawFixedLbl

//+------------------------------------------------------------------+
double PointValueFor1Lot(string symbol)
//+------------------------------------------------------------------+
{
  // PtVal = TickValue * Point / TickSize;
  return ( MarketInfo(symbol,MODE_TICKVALUE)*MarketInfo(symbol,MODE_POINT)/MarketInfo(symbol,MODE_TICKSIZE) );
}

//+------------------------------------------------------------------+
string MakeBasketString(int nbPairs, string Pair[], double Weight[])
{
  string s;

  for (int j = 0; j < nbPairs; j++) {
    string w = DoubleToStrStripped(Weight[j],2);
    if (Weight[j]!=0) {
      if (Weight[j]>0 && j>0) s = s+"+"+w;
      else s = s+w;
      s = s+"*"+Pair[j];
    }
  }
  return(s);
}

string DoubleToStrStripped(double v, int max_precision)
{
  string s = DoubleToStr(v,max_precision);
  // now strip all right-most 0's
  for(int i=StringLen(s)-1;i>0;i--) {
    if (StringGetChar(s,i)=='.') return(StringSubstr(s,0,i));
    if (StringGetChar(s,i)!='0') return(StringSubstr(s,0,i+1));
  }
  return(s);
}

//+------------------------------------------------------------------+

void ExtractBasket(string InputString, int &nbPairs, string &Pair[], double &Weight[])
{
  // List of weighted pairs are of the form [doubleValue*]SYMBOL -+ [doubleValue*]SYMBOL
  // A typical example would be : 
  // "100*GBPUSD -36*EURUSD +14*USDCHF -18*AUDUSD -97*USDJPY -3*USDCAD - 48*NZDUSD";
  // Note that coeffs can be non-integers (e.g. 1.08*AUDJPY)
  // This basket example was extracted from 7bit's "Synthetic hedges, cointegration, mean reversion and similar stuff" fantastic thread;
  // Many thanks to 7bit for yet another fantastic idea and invaluable work!

  //cleanup leading and trailing spaces first
  InputString = StringTrimRight(StringTrimLeft(InputString));
  
  //grab possible "strange pair name extension", like "m" in "EURUSDm" or alike; will be added to actual pair names for collecting price data
  string extension = StringSubstr(Symbol(),6,4);

#define IDLE  0
#define COEFF 1
#define PAIR  2

  int i=0, c, state, sign;
  double coeff,decimal;
  string pair;
  nbPairs = -1;
  state=IDLE; sign=1; coeff=0; decimal=0; pair="";
  for (i=0; i<StringLen(InputString); i++)
  {
    c = StringGetChar(InputString,i);
    if (c==' ') continue;// ignore spaces

    switch (state) 
    {
      case IDLE: //searching for sign, coef, or symbol name
        switch (c) {
          case '+': sign=1;  break;
          case '-': sign=-1; break;
          case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9':
          case '.':case '*':
            // first digit of a COEFF, or . or * sign:
            state = COEFF; // look for coeff or pair now
            i--; // parse again the first digit in the COEFF state
            break;
          default: // assume this is the first char of a PAIR name
            state = PAIR; // look for pair name now
            coeff=1;
            i--; // parse again the first char in the PAIR state
            break;
        }
        break;

      case COEFF:
        switch (c) {
          case '.': decimal=1; break;
          case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9':// digit: part of a COEFF
            if (decimal==0) { coeff = coeff*10+c-'0'; }
            else {
              decimal /= 10;
              coeff = coeff+ (c-'0')*decimal;
            }
            //Print("coeff=",DoubleToStr(coeff,3));
            break;
          case '*': // multiplier: look for pair now
            if (coeff==0) coeff=1; // missing coeff value, assume 1
            state = PAIR;
            break;
          default: // assume this is the first char of a PAIR name
            state = PAIR; // look for pair name now
            i--; // parse again the first char in the PAIR state
            break;
        }
        break;

      case PAIR:
        switch (c) {
          case '+': 
          case '-': // marks the next pair: terminate the current pair
            nbPairs++;
            Pair[nbPairs] = pair+extension;
            Weight[nbPairs] = sign*coeff;
            Print("Pair[",nbPairs,"]=",DoubleToStrStripped(Weight[nbPairs],3),"*",Pair[nbPairs]);
            i--; // parse again the sign char for the next pair
            // prepare for next pair
            state=IDLE; sign=1; coeff=0; decimal=0; pair="";
            break;
          default: // char of a PAIR name
            pair = pair+CharToStr(c);
            break;
        }
        break;
        
    }//switch(state)
      
  }//for()

  // reached end of the input string: terminate properly
  if (state == PAIR) {
    // the current pair was not terminated, terminate it
    nbPairs++;
    Pair[nbPairs] = pair+extension;
    Weight[nbPairs] = sign*coeff;
    Print("Pair[",nbPairs,"]=",DoubleToStr(Weight[nbPairs],3),"*",Pair[nbPairs]);
  }
  
  nbPairs++;// adjust count of pairs
  if (nbPairs==0)Print("NO PAIRS found in list: ",InputString);
}

/** import_coeffs_from_CSV():
* Import a complete Basket description from a CSV file;
*
* The format of each line in the CSV file is: "symbol;weight;"
* Example CSV file contents:
*   EURUSD;1.0
*   USDCHF;1.5
*   NZDUSD;-0.85
*
* Function arguments:
* Output:
*   pairs, symb[], and coef[] are loaded with the imported values;
* Returned values:
* >0: reading was successful, returns the number of read pairs/coefs;
* -1: could not open CSV file;
*/
int import_coeffs_from_CSV(string filename, int &pairs, string &symb[], double &coef[])
{
  int read_pairs=0; // number of imported pairs;
  int F = FileOpen(filename, FILE_READ|FILE_CSV);
  if(F == -1) return(-1); // error -1: could not open CSV file; no pair imported;
  // read each pair/coef
  while(!FileIsEnding(F)) {
    symb[pairs] = FileReadString(F);
    if (FileIsEnding(F)) break;
    coef[pairs] = StrToDouble(FileReadString(F));
    pairs++;
    if (pairs==8) break;//read all entries
  }
  FileClose(F);
  return(pairs);
}

//end

