BasketChartCreator

Download script
Author: Copyright � 2015, MetaQuotes Software Corp., Khalil Abokwaik
BasketChartCreator

Overview

The BasketChartCreator is a specialized utility script designed for traders who want to monitor a group of different currency pairs simultaneously as a single, unified "basket" chart. Instead of looking at ten different charts to see how a group of currencies (like all Australian Dollar pairs) is performing, this tool calculates a single mathematical average and generates a custom "Off-Line Chart" that represents the collective movement of those assets.

Core Logic & Functionality

1. Custom Basket Definition

The script allows users to define a specific "basket" by listing multiple currency pairs in a single input field. For each pair, the user can also assign a Directional Weight. This means you can decide how much influence each individual pair has on the final basket price. Some weights can even be negative, allowing for complex portfolio modeling (e.g., creating a "hedged" basket where some pairs move against others to reduce risk).

2. The Geometric Mean Calculation

To ensure the resulting basket price is mathematically accurate and reflects the relative value of all included currencies fairly, the script utilizes the Geometric Mean method. This is a sophisticated way of calculating an average that prevents one extremely volatile currency from disproportionately skewing the entire basket's movement. It ensures the "synthetic" price behaves realistically in relation to its components.

3. Automated History Generation

Once the user defines their basket, the script performs the following automated actions:

  • Data Processing: It scans the historical data of every pair included in your list.
  • Calculation: It applies your specific weights and the geometric mean formula to every historical price point.
  • File Creation: It automatically generates a custom .hst (history) file. This file is essentially a "fake" history for a brand-new currency that doesn't exist on the market but represents your specific basket.

4. Real-Time Updates

The script does not just work on past data; it also monitors live market ticks. As new prices come in for all the pairs in your list, the script calculates the new basket price in real-time and updates the history file instantly. This allows the user to see a live, moving line on their chart that reacts to the combined movement of the entire group.

5. Seamless Integration

The tool is designed to be "plug and play." It includes features such as:

  • Automatic Template Application: If you have a specific visual layout (colors, indicators, etc.) saved as a template, the script can automatically apply it to the new basket chart once it is created.
  • Offline Chart Accessibility: After the script finishes its work, it provides an alert telling the user exactly where to find the new basket in the MT4 "Offline Charts" menu, making it easy to switch from individual pair trading to holistic basket analysis.

Summary for the User

In short, this script takes a messy group of different currency pairs and "fuses" them into one single, clean chart. It handles all the heavy lifting of weighted mathematics and data processing, providing you with a unified visual tool to track your portfolio's overall direction.

Price Data Components
Series array that contains open prices of each barSeries array that contains close prices for each bar
Miscellaneous
It issuies visual alerts to the screenIt writes information to fileIt writes information to fileIt writes information to file
11 Views
0 Downloads
0 Favorites
BasketChartCreator
//------------------------------------------------------------------------------------------------
//--- BasketChartCreator.mq4  
//--- Copyright © 2015, Khalil Abokwaik. 
//--- Originaly posted at "Basket Robots" thread on Forex Factory
//--- Link  : http://www.forexfactory.com/showthread.php?t=529896
//------------------------------------------------------------------------------------------------
#property copyright     "Copyright © 2015, MetaQuotes Software Corp., Khalil Abokwaik"
#property link          "https://www.mql5.com/en/users/abokwaik"
#property description   "Basket Chart Creator"
#property description   "\nThis script creates MT4 Off-Line Basket Charts for user-defined Baskets of Pairs."
#property description   "The code is based on the MT4 standard PeriodConverter script."
#property description   "Quality M1 history data for basket pairs is recommended."
#property description   "\nCalculations are based on Geometric Mean method."

#property version "1.00"
#property strict
#property show_inputs
#include <WinUser32.mqh>

#define   MAX_PRICE 999999999.9
//+--- user inputs ---------------------------------------------------------------+
input string   Basket_Name="#COM#";//Basket Chart Name
input ENUM_TIMEFRAMES
               Time_Frame=PERIOD_H1;//Basket Time Frame
               //-- Commodity Currencies BASKET example
input string   Pairs             = "AUDUSD,NZDUSD,USDCAD,EURAUD,EURNZD,EURCAD,GBPAUD,GBPNZD,GBPCAD";//Basket Pairs (comma seperated)
input string   Pairs_DW          = "  +1  ,  +1  ,  -1  ,  -1  ,  -1  ,  -1  ,  -1  ,  -1  ,  -1  ";//Directional Weights (comma seperated)
input string   Template_Name="0";//Template to load on basket chart, "0" to disable
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//=- Below are sample currency baskets for YEN, EUR and GBP . These are just examples. -=
//=- However possibilities are only limited by your imagination!. Khalil Abokwaik.     -=
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//-- YEN BASKET
//input string   Pairs             = "USDJPY,EURJPY,GBPJPY,AUDJPY,CADJPY,NZDJPY,CHFJPY";//Basket Pairs
//input string   Pairs_DW          = "-1000 ,-1100 , -1200,-900  ,-900  , -900 , -1000";//Pairs Directional Weight
//input string   Pairs_Lot_Multi   = "   1  ,   1  ,   1  ,   1  ,   1  ,   1  ,   1  ";//Lot Multiplier

//-- EUR BASKET
//input string   Pairs           = "EURUSD,EURGBP,EURAUD,EURCAD,EURCHF,EURNZD,EURJPY";//Basket Pairs
//input string   Pairs_DW        = "+1    ,+1    ,+1    ,+1    ,+1    ,+1    ,+0.01 ";//Pairs Directional Weight

//-- GBP BASKET
//input string   Pairs           = "GBPUSD,EURGBP,GBPAUD,GBPCAD,GBPCHF,GBPNZD,GBPJPY";//Basket Pairs
//input string   Pairs_DW        = "+1    ,-1    ,+1    ,+1    ,+1    ,+1    ,+0.01 ";//Pairs Directional Weight
//+------------------------------------------------------------------+
int      ExtHandle=-1;
int      InpPeriodMultiplier=0;// Period multiplier factor
string   symbol;
double   low=0;
double   high=0;
string   pairs[],xxx;
string   pair_dw_s[];
double   pair_dw[];
double   pdw=0;
double   irate=0;
int      i_digits=4;//Basket Digits    
string   sep=",";//symbols separator
int      j=0;
int      symb_cnt=0;
int      time_frame=0;

//--- Get the separator code
ushort   u_sep=StringGetCharacter(sep,0);
//+------------------------------------------------------------------+
//| script program Initilization function                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(Period()!=PERIOD_M1) Alert("Generic Basket Creator Script better be attached M1 chart");
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   datetime time0;
   ulong    last_fpos=0;
   long     last_volume=0;
   int      i,start_pos,periodseconds;
   int      hwnd=0,cnt=0;

//---- History header
   int      file_version=401;
   string   c_copyright;
   string   c_symbol=Basket_Name;
   int      i_period;

   int      i_unused[13];
   MqlRates rate;
   time_frame=Time_Frame;
   i_period=Period()*time_frame;
//--- set history file handle 
   ExtHandle=FileOpenHistory(c_symbol+IntegerToString(time_frame)+".hst",FILE_BIN|FILE_WRITE|FILE_SHARE_WRITE|FILE_SHARE_READ|FILE_ANSI);
   if(ExtHandle<0)
      return;
   c_copyright="(C)opyright 2003, MetaQuotes Software Corp.";
   ArrayInitialize(i_unused,0);
// initialize symbols
//--------------------------
   symb_cnt=StringSplit(Pairs,u_sep,pairs);
   StringSplit(Pairs_DW,u_sep,pair_dw_s);
   ArrayResize(pair_dw,symb_cnt);
   double root=((double) 1/symb_cnt);
   for(j=0;j<ArraySize(pair_dw_s);j++)
     {
      pair_dw[j]=StrToDouble(pair_dw_s[j]);
     }
   Comment(c_symbol,",",Pairs,",",Pairs_DW);   //The comment is specially formatted like this to be red from Expert Advisor
//--- write history file header
   FileWriteInteger(ExtHandle,file_version,LONG_VALUE);
   FileWriteString(ExtHandle,c_copyright,64);
   FileWriteString(ExtHandle,c_symbol,12);
   FileWriteInteger(ExtHandle,time_frame,LONG_VALUE);
   FileWriteInteger(ExtHandle,i_digits,LONG_VALUE);
   FileWriteInteger(ExtHandle,0,LONG_VALUE);
   FileWriteInteger(ExtHandle,0,LONG_VALUE);
   FileWriteArray(ExtHandle,i_unused,0,13);
//--- write history file
   periodseconds=time_frame*60;
   start_pos=Bars-1;
   rate.open=0.0;
   rate.low=MAX_PRICE;
   rate.high=0.0;
   rate.close=0.0;
   rate.spread=0;
   rate.tick_volume=100;

   for(j=0;j<symb_cnt;j++)
     {
      if(pair_dw[j]>0)
        {
         if(rate.open==0) rate.open=NormalizeDouble(iOpen(pairs[j],0,start_pos)*pair_dw[j],i_digits);
         else            rate.open*=NormalizeDouble(iOpen(pairs[j],0,start_pos)*pair_dw[j],i_digits);
        }
      else
        {
         irate=iOpen(pairs[j],0,start_pos);
         if(irate>0)
           {
            if(rate.open==0) rate.open=NormalizeDouble(((double)1/irate)*MathAbs(pair_dw[j]),i_digits);
            else            rate.open*=NormalizeDouble(((double)1/irate)*MathAbs(pair_dw[j]),i_digits);
           }

        }

      rate.tick_volume=100;
      rate.spread=0;
      rate.real_volume=0;
     }
   rate.open=NormalizeDouble(MathPow(rate.open,root),i_digits);
   rate.low=MathMin(rate.low,rate.open);
   rate.high=MathMax(rate.high,rate.open);

//--- normalize open time
   rate.time=Time[start_pos]/periodseconds;
   rate.time*=periodseconds;

   for(i=start_pos-1; i>=0; i--)
     {
      if(IsStopped())
         break;
      time0=Time[i];
      //--- history may be updated
      if(i==0)
        {
         //--- modify index if history was updated
         if(RefreshRates())
            i=iBarShift(NULL,0,time0);
        }
      //---

      if(time0>=rate.time+periodseconds || i==0)
        {
         if(i==0 && time0<rate.time+periodseconds)
           {
            rate.close=0;
            for(j=0;j<symb_cnt;j++)
              {
               rate.tick_volume=100;
               if(pair_dw[j]>0)
                 {
                  if(rate.close==0) rate.close=NormalizeDouble(iClose(pairs[j],0,0)*pair_dw[j],i_digits);
                  else             rate.close*=NormalizeDouble(iClose(pairs[j],0,0)*pair_dw[j],i_digits);
                 }
               else
                 {
                  irate=iClose(pairs[j],0,0);
                  if(irate>0)
                    {
                     if(rate.close==0) rate.close=NormalizeDouble(((double)1/irate)*MathAbs(pair_dw[j]),i_digits);
                     else             rate.close*=NormalizeDouble(((double)1/irate)*MathAbs(pair_dw[j]),i_digits);
                    }
                 }
              }
            rate.close=NormalizeDouble(MathPow(rate.close,root),i_digits);
            low=MathMin(low,MathMin(rate.close,rate.open));
            high=MathMax(high,MathMax(rate.close,rate.open));

            if(rate.low>low) rate.low=low;
            if(rate.high<high) rate.high=high;

           }
         last_fpos=FileTell(ExtHandle);
         last_volume=(long)Volume[i];
         FileWriteStruct(ExtHandle,rate);
         cnt++;
         if(time0>=rate.time+periodseconds)
           {
            rate.time=time0/periodseconds;
            rate.time*=periodseconds;
            rate.open=0.0;
            rate.low=MAX_PRICE;
            rate.high=0.0;
            rate.spread=0;
            rate.tick_volume=0;
            rate.close=0.0;
            for(j=0;j<symb_cnt;j++)
              {
               if(pair_dw[j]>0)
                 {
                  if(rate.open==0) rate.open=NormalizeDouble(iOpen(pairs[j],0,i)*pair_dw[j],i_digits);
                  else            rate.open*=NormalizeDouble(iOpen(pairs[j],0,i)*pair_dw[j],i_digits);
                  if(rate.close==0) rate.close=NormalizeDouble(iClose(pairs[j],0,i)*pair_dw[j],i_digits);
                  else             rate.close*=NormalizeDouble(iClose(pairs[j],0,i)*pair_dw[j],i_digits);

                 }
               else
                 {
                  irate=iOpen(pairs[j],0,i);
                  if(irate>0)
                    {
                     if(rate.open==0) rate.open=NormalizeDouble(((double)1/irate)*MathAbs(pair_dw[j]),i_digits);
                     else            rate.open*=NormalizeDouble(((double)1/irate)*MathAbs(pair_dw[j]),i_digits);
                    }
                  irate=iClose(pairs[j],0,i);
                  if(irate>0)
                    {
                     if(rate.close==0) rate.close=NormalizeDouble(((double)1/irate)*MathAbs(pair_dw[j]),i_digits);
                     else             rate.close*=NormalizeDouble(((double)1/irate)*MathAbs(pair_dw[j]),i_digits);
                    }
                 }

              }
            rate.open=NormalizeDouble(MathPow(rate.open,root),i_digits);
            rate.close=NormalizeDouble(MathPow(rate.close,root),i_digits);
            rate.low=MathMin(rate.low,MathMin(rate.open,rate.close));
            rate.high=MathMax(rate.high,MathMax(rate.open,rate.close));
            rate.tick_volume=100;

           }
        }
      else
        {

         low=MAX_PRICE;
         high=0.0;
         rate.close=0.0;
         for(j=0;j<symb_cnt;j++)
           {
            rate.tick_volume=100;
            if(pair_dw[j]>0)
              {
               if(rate.close==0) rate.close=NormalizeDouble(iClose(pairs[j],0,i)*pair_dw[j],i_digits);
               else             rate.close*=NormalizeDouble(iClose(pairs[j],0,i)*pair_dw[j],i_digits);
              }
            else
              {
               irate=iClose(pairs[j],0,i);
               if(irate>0)
                 {
                  if(rate.close==0) rate.close=NormalizeDouble(((double)1/irate)*MathAbs(pair_dw[j]),i_digits);
                  else             rate.close*=NormalizeDouble(((double)1/irate)*MathAbs(pair_dw[j]),i_digits);
                 }

              }
           }
         rate.close=NormalizeDouble(MathPow(rate.close,root),i_digits);
         low=MathMin(low,rate.close);
         high=MathMax(high,rate.close);

         if(rate.low>low) rate.low=low;
         if(rate.high<high) rate.high=high;

        }
     }
   FileFlush(ExtHandle);
   Print(cnt," record(s) written");
   Alert("Off-Line Chart Created, Look it up in File > Open Offline > ",c_symbol,",",periodName(time_frame));
//--- collect incoming ticks ---------------------------------------------------------------------
   datetime last_time=LocalTime()-5;
   while(!IsStopped())
     {
      datetime cur_time=LocalTime();
      //--- check for new rates
      if(RefreshRates())
        {
         time0=Time[0];
         FileSeek(ExtHandle,last_fpos,SEEK_SET);
         //--- is there current bar?
         if(time0<rate.time+periodseconds)
           {
            low=MAX_PRICE;
            high=0.0;
            rate.close=0.0;
            for(j=0;j<symb_cnt;j++)
              {
               rate.tick_volume=100;
               if(pair_dw[j]>0)
                 {
                  if(rate.close==0) rate.close=NormalizeDouble(iClose(pairs[j],0,0)*pair_dw[j],i_digits);
                  else             rate.close*=NormalizeDouble(iClose(pairs[j],0,0)*pair_dw[j],i_digits);
                 }
               else
                 {
                  irate=iClose(pairs[j],0,0);
                  if(irate>0)
                    {
                     if(rate.close==0) rate.close=NormalizeDouble(((double)1/irate)*MathAbs(pair_dw[j]),i_digits);
                     else             rate.close*=NormalizeDouble(((double)1/irate)*MathAbs(pair_dw[j]),i_digits);
                    }

                 }
              }
            rate.close=NormalizeDouble(MathPow(rate.close,root),i_digits);
            low=MathMin(low,rate.close);
            high=MathMax(high,rate.close);

            if(rate.low>low) rate.low=low;
            if(rate.high<high) rate.high=high;
           }
         else
           {
            //--- no, there is new bar
            rate.tick_volume=100;
            if(rate.low>MathMin(rate.close,rate.open)) rate.low=MathMin(rate.close,rate.open);
            if(rate.high<MathMax(rate.close,rate.open)) rate.high=MathMax(rate.close,rate.open);

            //--- write previous bar remains
            FileWriteStruct(ExtHandle,rate);
            last_fpos=FileTell(ExtHandle);
            //----**********************
            rate.time=time0/periodseconds;
            rate.time*=periodseconds;
            rate.open=0.0;
            rate.low=MAX_PRICE;
            rate.high=0.0;
            rate.spread=0;
            rate.tick_volume=0;
            rate.close=0.0;
            for(j=0;j<symb_cnt;j++)
              {
               if(pair_dw[j]>0)
                 {
                  if(rate.open==0) rate.open=NormalizeDouble(iOpen(pairs[j],0,0)*pair_dw[j],i_digits);
                  else            rate.open*=NormalizeDouble(iOpen(pairs[j],0,0)*pair_dw[j],i_digits);
                  if(rate.close==0) rate.close=NormalizeDouble(iClose(pairs[j],0,0)*pair_dw[j],i_digits);
                  else           rate.close*=NormalizeDouble(iClose(pairs[j],0,0)*pair_dw[j],i_digits);

                 }
               else
                 {
                  irate=iOpen(pairs[j],0,0);
                  if(irate>0)
                    {
                     if(rate.open==0) rate.open=NormalizeDouble(((double)1/irate)*MathAbs(pair_dw[j]),i_digits);
                     else            rate.open*=NormalizeDouble(((double)1/irate)*MathAbs(pair_dw[j]),i_digits);
                    }
                  irate=iClose(pairs[j],0,0);
                  if(irate>0)
                    {
                     if(rate.close==0) rate.close=NormalizeDouble(((double)1/irate)*MathAbs(pair_dw[j]),i_digits);
                     else             rate.close*=NormalizeDouble(((double)1/irate)*MathAbs(pair_dw[j]),i_digits);
                    }

                 }
              }
            rate.open=NormalizeDouble(MathPow(rate.open,root),i_digits);
            rate.close=NormalizeDouble(MathPow(rate.close,root),i_digits);
            rate.low=MathMin(rate.low,MathMin(rate.open,rate.close));
            rate.high=MathMax(rate.high,MathMax(rate.open,rate.close));
            rate.tick_volume=100;

           }
         //----
         FileWriteStruct(ExtHandle,rate);
         FileFlush(ExtHandle);
         //---

         if(hwnd==0)
           {
            hwnd=WindowHandle(c_symbol,i_period);
            if(hwnd!=0)
              {
               Print("Chart window detected");
               if(Template_Name!="0") apply_template();
              }
           }

         //--- refresh window not frequently than 1 time in 2 seconds
         if(hwnd!=0 && cur_time-last_time>=2)
           {
            PostMessageA(hwnd,WM_COMMAND,33324,0);
            last_time=cur_time;
           }
        }
      Sleep(50);
     }

//---
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   if(ExtHandle>=0)
     {
      FileClose(ExtHandle);
      ExtHandle=-1;
     }
//---
  }
//+------------------------------------------------------------------+

int StringToTimeFrame(string tfs)
  {
   int tf=0;
   if(tfs=="M1" || tfs=="1"     ||tfs=="m1")   tf=PERIOD_M1;
   if(tfs=="M5" || tfs=="5"     ||tfs=="m5")   tf=PERIOD_M5;
   if(tfs=="M15"|| tfs=="15"    ||tfs=="m15")  tf=PERIOD_M15;
   if(tfs=="M30"|| tfs=="30"    ||tfs=="m30")  tf=PERIOD_M30;
   if(tfs=="H1" || tfs=="60"    ||tfs=="h1")   tf=PERIOD_H1;
   if(tfs=="H4" || tfs=="240"   ||tfs=="h4")   tf=PERIOD_H4;
   if(tfs=="D1" || tfs=="1440"  ||tfs=="d1")   tf=PERIOD_D1;
   if(tfs=="W1" || tfs=="10080" ||tfs=="w1")   tf=PERIOD_W1;
   if(tfs=="MN" || tfs=="43200" ||tfs=="mn")   tf=PERIOD_MN1;
   if(tf==0) tf=Period();
   return(tf);
  }
//---------------------
void apply_template()
  {
   string sym_code="";
   long currChart,prevChart=ChartFirst();
   currChart=prevChart;
   int i=1,limit=100;
   while(i<limit)// We have certainly not more than 100 open charts
     {
      if(currChart<0) break;          // Have reached the end of the chart list
      sym_code=ChartSymbol(currChart);
      if(sym_code==Basket_Name)
        {
         if(ChartApplyTemplate(currChart,Template_Name))
           {
            Print("The template ",Template_Name," applied successfully");
            Alert("The template ",Template_Name," applied successfully");
           }
         else
           {
            Print("Failed to apply template ",Template_Name," error code ",GetLastError());
            Alert("Failed to apply template ",Template_Name," error code ",GetLastError());
           }
         break;
        }
      prevChart=currChart;// let's save the current chart ID for the ChartNext()
      currChart=ChartNext(prevChart); // Get the new chart ID by using the previous chart ID
      i++;// Do not forget to increase the counter
     }

  }
//--------------------------------------------------------------
string periodName(int period) // Convert time frame period minutes into string (ex. 60 to H1)
  {
   switch(period)
     {
      case 1: return("M1");
      case 5: return("M5");
      case 15: return("M15");
      case 30: return("M30");
      case 60: return("H1");
      case 240: return("H4");
      case 1440: return("D1");
      case 10080: return("W1");
      case 43200: return("MN1");
      default: return("");
     }
  }
//--------------------------------------------------------------

Comments