1. Qiita
  2. 投稿
  3. MT4

Ku-chartのソースコードの解説

  • 22
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

今日はKu-chartのソースコードを解説します。

Ku-chartはMetaTrader 4のカスタムインジケータであり、主要な8つの通貨(EUR、USD、JPY、CHF、GBP、AUD、CAD、NZD)の強弱関係を8つの時系列データ(チャートに描画されているそれぞれの足に対して計算される値)として計算し、8つの折れ線グラフとしてチャートに描画するものです。この8つの時系列データがそれぞれの通貨の強弱を表します。

Ku-chartのソースコードの全貌を以下に示します。

//+------------------------------------------------------------------+
//|                                               Ku-chart-Maker.mq4 |
//|                      Copyright ゥ 2010, MetaQuotes Software Corp. |
//|                                        http://www.metaquotes.net |
//+------------------------------------------------------------------+
#property copyright "Copyright ゥ 2010, MetaQuotes Software Corp."
#property link      "http://www.metaquotes.net"
#include <WinUser32.mqh>
#define  CHART_CMD_UPDATE_DATA            33324
#define  HEADER_BYTE                        148
#define  DATA_BYTE                           44

#property indicator_separate_window
#property indicator_buffers 8

extern int MaxBars = 2000;//計算するバーの本数

// 平滑化したい場合は下記2つを調整. デフォルトでは rawdataとなる。
extern int MAPeriod = 1;//3;
extern int MAPrice  = PRICE_CLOSE;//PRICE_TYPICAL;
extern int MAMethod = MODE_LWMA;

extern color Color_USD = Orange;
extern color Color_EUR = Red;
extern color Color_GBP = Lime;
extern color Color_CHF = Snow;
extern color Color_JPY = Turquoise;
extern color Color_AUD = RoyalBlue;
extern color Color_CAD = BlueViolet;
extern color Color_NZD = DeepPink;

extern int SelectedLineWidth = 3;//チャートの通貨のラインの太さ

extern int OriginTime = 1000;//1000なら1000本前基準

extern int ZeroLevelShift = 1000;//原点のレベル
extern bool UseZeroCheck = true;

extern bool UseUSD = true;//計算に使うかどうか。
extern bool UseEUR = true;
extern bool UseGBP = true;
extern bool UseCHF = true;
extern bool UseJPY = true;
extern bool UseAUD = true;
extern bool UseCAD = true;
extern bool UseNZD = true;

extern bool DoNotOverwrite = true;

// 基本の6ペア
string sEURUSD = "EURUSD";
string sUSDJPY = "USDJPY";
string sUSDCHF = "USDCHF";
string sGBPUSD = "GBPUSD";
string sAUDUSD = "AUDUSD";
string sUSDCAD = "USDCAD";
string sNZDUSD = "NZDUSD";

//---- buffers
double EURAV[];
double USDAV[];
double JPYAV[];
double CHFAV[];
double GBPAV[];
double AUDAV[];
double CADAV[];
double NZDAV[];

string Indicator_Name = " EUR USD JPY  CHF GBP AUD CAD NZD";
int Objs = 0;
int Pairs = 7;
string objname = "Ku-chart-OriginTime";
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init()
  {
//---- indicators
   SetIndexStyle(0,DRAW_LINE,EMPTY,GetWidth("EUR"),Color_EUR);
   SetIndexBuffer(0,EURAV);
   SetIndexLabel(0,"EUR");
   SetIndexStyle(1,DRAW_LINE,EMPTY,GetWidth("USD"),Color_USD);
   SetIndexBuffer(1,USDAV);
   SetIndexLabel(1,"USD");
   SetIndexStyle(2,DRAW_LINE,EMPTY,GetWidth("JPY"),Color_JPY);
   SetIndexBuffer(2,JPYAV);
   SetIndexLabel(2,"JPY");
   SetIndexStyle(3,DRAW_LINE,EMPTY,GetWidth("CHF"),Color_CHF);
   SetIndexBuffer(3,CHFAV);
   SetIndexLabel(3,"CHF");
   SetIndexStyle(4,DRAW_LINE,EMPTY,GetWidth("GBP"),Color_GBP);
   SetIndexBuffer(4,GBPAV);
   SetIndexLabel(4,"GBP");
   SetIndexStyle(5,DRAW_LINE,EMPTY,GetWidth("AUD"),Color_AUD);
   SetIndexBuffer(5,AUDAV);
   SetIndexLabel(5,"AUD");
   SetIndexStyle(6,DRAW_LINE,EMPTY,GetWidth("CAD"),Color_CAD);
   SetIndexBuffer(6,CADAV);
   SetIndexLabel(6,"CAD");
   SetIndexStyle(7,DRAW_LINE,EMPTY,GetWidth("NZD"),Color_NZD);
   SetIndexBuffer(7,NZDAV);
   SetIndexLabel(7,"NZD");

   if(!UseEUR) SetIndexStyle(0,DRAW_NONE);
   if(!UseUSD) SetIndexStyle(1,DRAW_NONE);
   if(!UseJPY) SetIndexStyle(2,DRAW_NONE);
   if(!UseCHF) SetIndexStyle(3,DRAW_NONE);
   if(!UseGBP) SetIndexStyle(4,DRAW_NONE);
   if(!UseAUD) SetIndexStyle(5,DRAW_NONE);
   if(!UseCAD) SetIndexStyle(6,DRAW_NONE);
   if(!UseNZD) SetIndexStyle(7,DRAW_NONE);

   if(!UseEUR) Color_EUR = DimGray;
   if(!UseUSD) Color_USD = DimGray;
   if(!UseJPY) Color_JPY = DimGray;
   if(!UseCHF) Color_CHF = DimGray;
   if(!UseGBP) Color_GBP = DimGray;
   if(!UseAUD) Color_AUD = DimGray;
   if(!UseCAD) Color_CAD = DimGray;
   if(!UseNZD) Color_NZD = DimGray;

   Pairs = 7;
   if(!UseEUR) Pairs--;
   if(!UseUSD) Pairs--;
   if(!UseJPY) Pairs--;
   if(!UseCHF) Pairs--;
   if(!UseGBP) Pairs--;
   if(!UseAUD) Pairs--;
   if(!UseCAD) Pairs--;
   if(!UseNZD) Pairs--;
   if(Pairs<1) Alert("Pairs is ",Pairs);

   SetLevelValue(0, ZeroLevelShift);

   IndicatorShortName(Indicator_Name);

   IndicatorDigits(2);

   Objs = 0;
   int cur = 0; 
   int st = 23;// ~の長さが23
   sl("~", cur, Color_EUR);
   cur += st;
   sl("~", cur, Color_USD);
   cur += st;
   sl("~", cur, Color_JPY);
   cur += st;
   sl("~", cur, Color_CHF);
   cur += st;
   sl("~", cur, Color_GBP);
   cur += st;
   sl("~", cur, Color_AUD);
   cur += st;       
   sl("~", cur, Color_CAD);
   cur += st;
   sl("~", cur, Color_NZD);
   cur += st;
//----

   return(0);
  }
void sl(string sym, int y, color col)
  { // ~記号を描く
   int window = WindowFind(Indicator_Name);
   string ID = Indicator_Name+Objs;
   Objs++;
   if(col<0) col=EMPTY_VALUE;//CLR_NONE;
   if(window == -1 ) return;
   ObjectCreate(ID, OBJ_LABEL, window, 0, 0);
   ObjectSet(ID, OBJPROP_XDISTANCE, y +6);
   ObjectSet(ID, OBJPROP_YDISTANCE, 0);
   ObjectSetText(ID, sym, 18, "Arial Black", col);
   //Print("ID=",ID," ",col);
  }   
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
int deinit()
  {
//----
     switch(UninitializeReason())
       {
        case REASON_CHARTCLOSE:  Print("KU-deinit REASON_CHARTCLOSE");break;
        case REASON_REMOVE:      Print("KU-deinit REASON_REMOVE");break;
        case REASON_RECOMPILE:   Print("KU-deinit REASON_RECOMPILE");break;
        case REASON_CHARTCHANGE: Print("KU-deinit REASON_CHARTCHANGE");break;
        case REASON_PARAMETERS:  Print("KU-deinit REASON_PARAMETERS");break;
        case REASON_ACCOUNT:     Print("KU-deinit REASON_ACCOUNT");break;
       }
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int GetWidth(string sym1){
   int index = StringFind(Symbol(),sym1);
   if(index == -1) return(1);
   return(SelectedLineWidth);// 選択通貨を含む場合のみラインを太くしている。
}

double GetVal(string sym1,datetime t1,datetime t2){
   double v1,v2;

   v1 = iMA(sym1,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym1,0,t1));
   v2 = iMA(sym1,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym1,0,t2));

   if(v2==0) return(0);
   return(MathLog(v1/v2)*1000);
}

double GetValM(string sym1,string sym2,datetime t1,datetime t2){
   double v1,v2;

   v1 = iMA(sym1,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym1,0,t1));
   v2 = iMA(sym1,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym1,0,t2));

   v1 = v1*iMA(sym2,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym2,0,t1));
   v2 = v2*iMA(sym2,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym2,0,t2));

   if(v2==0) return(0);
   return(MathLog(v1/v2)*1000);
}

double GetValD(string sym1,string sym2,datetime t1,datetime t2){
   double v1,v2,v3,v4;

   v1 = iMA(sym1,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym1,0,t1));
   v2 = iMA(sym1,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym1,0,t2));

   v3 = iMA(sym2,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym2,0,t1));
   v4 = iMA(sym2,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym2,0,t2));
   if(v3==0) return(0);
   if(v4==0) return(0);

   v1 = v1/v3;
   v2 = v2/v4;

   if(v2==0) return(0);
   return(MathLog(v1/v2)*1000);
}

datetime GetOriginTime(){
   // 過去に Ku-chart で起点となる時刻を決めていたら、それを再度利用する。
   datetime ret;


   if(DoNotOverwrite && ObjectFind(objname) != -1){
      ret = ObjectGet(objname, OBJPROP_TIME1);
      if(ret >= Time[Bars-1] && ret <=Time[0]) return(ret);
   }

   ret = Time[OriginTime];
   ObjectCreate(objname, OBJ_TEXT, 0, ret, 0);
   return(ret);
}

int start()
  {
   // ~を描くのに、WindowsFind を呼んでいるが、本当は init では呼んではいけないので、startから呼びなおす。
   static bool startInit= false;
   if(!startInit)  init();
   startInit= true;

   int i;
//----
   double EURUSD,EURJPY,EURCHF,EURGBP,USDJPY,USDCHF,GBPUSD,CHFJPY,GBPCHF,GBPJPY,AUDUSD,AUDCHF,AUDJPY,GBPAUD,EURAUD,AUDCAD,USDCAD,GBPCAD,EURCAD,CADCHF,CADJPY;
   double AUDNZD,EURNZD,GBPNZD,NZDCAD,NZDCHF,NZDJPY,NZDUSD;
   datetime t1;
   static datetime t2 = 0;
   static datetime HstWriteTime = 0;

   int indicator_counted = IndicatorCounted();
   int limit = MathMin(MaxBars,Bars);
   if(HstWriteTime !=0) limit = MathMin(limit,Bars-indicator_counted+1);
   if(limit <1) return;

   //if(checkDownloadBars()<0) return;
   int ret = checkDownloadBars();
   if(ret <-1) return;//bar が不足。
   if(ret ==2) { limit = MathMin(MaxBars,Bars);HstWriteTime = 0;Print("reload");}//全リロード

   //Print("limit=",limit);
   if(t2 == 0) t2 = GetOriginTime();//Time[OriginTime];
   ObjectCreate(objname, OBJ_TEXT, 0, t2, 0);

   for(i=limit;i>=0;i--){
      t1 = Time[i];

      EURUSD = GetVal(sEURUSD,t1,t2);
      USDJPY = GetVal(sUSDJPY,t1,t2);
      USDCHF = GetVal(sUSDCHF,t1,t2);
      GBPUSD = GetVal(sGBPUSD,t1,t2);
      AUDUSD = GetVal(sAUDUSD,t1,t2);
      USDCAD = GetVal(sUSDCAD,t1,t2);
      NZDUSD = GetVal(sNZDUSD,t1,t2);

      EURJPY = GetValM(sEURUSD,sUSDJPY,t1,t2);
      EURCHF = GetValM(sEURUSD,sUSDCHF,t1,t2);
      EURGBP = GetValD(sEURUSD,sGBPUSD,t1,t2);
      CHFJPY = GetValD(sUSDJPY,sUSDCHF,t1,t2);
      GBPCHF = GetValM(sGBPUSD,sUSDCHF,t1,t2);
      GBPJPY = GetValM(sGBPUSD,sUSDJPY,t1,t2);
      AUDCHF = GetValM(sAUDUSD,sUSDCHF,t1,t2);
      AUDJPY = GetValM(sAUDUSD,sUSDJPY,t1,t2);
      AUDCAD = GetValM(sAUDUSD,sUSDCAD,t1,t2);
      EURCAD = GetValM(sEURUSD,sUSDCAD,t1,t2);
      GBPCAD = GetValM(sGBPUSD,sUSDCAD,t1,t2);
      GBPAUD = GetValD(sGBPUSD,sAUDUSD,t1,t2);
      EURAUD = GetValD(sEURUSD,sAUDUSD,t1,t2);
      CADCHF = GetValD(sUSDCHF,sUSDCAD,t1,t2);
      CADJPY = GetValD(sUSDJPY,sUSDCAD,t1,t2);
      AUDNZD = GetValD(sAUDUSD,sNZDUSD,t1,t2);
      EURNZD = GetValD(sEURUSD,sNZDUSD,t1,t2);
      GBPNZD = GetValD(sGBPUSD,sNZDUSD,t1,t2);
      NZDCAD = GetValM(sNZDUSD,sUSDCAD,t1,t2);
      NZDCHF = GetValM(sNZDUSD,sUSDCHF,t1,t2);
      NZDJPY = GetValM(sNZDUSD,sUSDJPY,t1,t2);

      if(!UseNZD){
         AUDNZD = 0;EURNZD = 0;GBPNZD = 0;NZDCAD = 0;NZDCHF = 0;NZDJPY = 0;NZDUSD = 0;
      }
      if(!UseCAD){
         EURCAD = 0;USDCAD = 0;CADJPY = 0;CADCHF = 0;GBPCAD = 0;AUDCAD = 0;NZDCAD = 0;
      }
      if(!UseCHF){
         EURCHF = 0;USDCHF = 0;CHFJPY = 0;GBPCHF = 0;AUDCHF = 0;CADCHF = 0;NZDCHF = 0;
      }
      if(!UseAUD){
         EURAUD = 0;AUDUSD = 0;AUDJPY = 0;AUDCHF = 0;GBPAUD = 0;AUDCAD = 0;AUDNZD = 0;
      }
      if(!UseEUR){
         EURUSD = 0;EURJPY = 0;EURCHF = 0;EURGBP = 0;EURAUD = 0;EURCAD = 0;EURNZD = 0;
      }
      if(!UseUSD){
         EURUSD = 0;USDJPY = 0;USDCHF = 0;GBPUSD = 0;AUDUSD = 0;USDCAD = 0;NZDUSD = 0;
      }
      if(!UseJPY){
         EURJPY = 0;USDJPY = 0;CHFJPY = 0;GBPJPY = 0;AUDJPY = 0;CADJPY = 0;NZDJPY = 0;
      }
      if(!UseGBP){
         EURGBP = 0;GBPUSD = 0;GBPCHF = 0;GBPJPY = 0;GBPAUD = 0;GBPCAD = 0;GBPNZD = 0;
      }

      // マイナスの値はチャート化できないので ZeroLevelShift(100)を足す      
      EURAV[i]=( EURUSD+EURJPY+EURCHF+EURGBP+EURAUD+EURCAD+EURNZD)/Pairs + ZeroLevelShift;
      USDAV[i]=(-EURUSD+USDJPY+USDCHF-GBPUSD-AUDUSD+USDCAD-NZDUSD)/Pairs + ZeroLevelShift;
      JPYAV[i]=(-EURJPY-USDJPY-CHFJPY-GBPJPY-AUDJPY-CADJPY-NZDJPY)/Pairs + ZeroLevelShift;
      CHFAV[i]=(-EURCHF-USDCHF+CHFJPY-GBPCHF-AUDCHF-CADCHF-NZDCHF)/Pairs + ZeroLevelShift;
      GBPAV[i]=(-EURGBP+GBPUSD+GBPCHF+GBPJPY+GBPAUD+GBPCAD+GBPNZD)/Pairs + ZeroLevelShift;
      AUDAV[i]=(-EURAUD+AUDUSD+AUDJPY+AUDCHF-GBPAUD+AUDCAD+AUDNZD)/Pairs + ZeroLevelShift;
      CADAV[i]=(-EURCAD-USDCAD+CADJPY+CADCHF-GBPCAD-AUDCAD-NZDCAD)/Pairs + ZeroLevelShift;
      NZDAV[i]=(-EURNZD+NZDUSD+NZDJPY+NZDCHF-GBPNZD+NZDCAD-AUDNZD)/Pairs + ZeroLevelShift;

      if(UseZeroCheck){
         // ゼロより小さくなっていたら警告する。
         if(EURAV[i] < 0){ Alert("ERROR: EURAV is less than ZeroLevel. ",EURAV[i]);return;}
         if(USDAV[i] < 0){ Alert("ERROR: USDAV is less than ZeroLevel. ",USDAV[i]);return;}
         if(JPYAV[i] < 0){ Alert("ERROR: JPYAV is less than ZeroLevel. ",JPYAV[i]);return;}
         if(CHFAV[i] < 0){ Alert("ERROR: CHFAV is less than ZeroLevel. ",CHFAV[i]);return;}
         if(GBPAV[i] < 0){ Alert("ERROR: GBPAV is less than ZeroLevel. ",GBPAV[i]);return;}
         if(AUDAV[i] < 0){ Alert("ERROR: AUDAV is less than ZeroLevel. ",AUDAV[i]);return;}
         if(CADAV[i] < 0){ Alert("ERROR: CADAV is less than ZeroLevel. ",CADAV[i]);return;}
         if(NZDAV[i] < 0){ Alert("ERROR: NZDAV is less than ZeroLevel. ",NZDAV[i]);return;}
      }
   }

   if(HstWriteTime==0){
      // 初回
      WriteHistoryHeader("KU_EUR");
      WriteHistoryAll("KU_EUR",EURAV);
      WriteHistoryHeader("KU_USD");
      WriteHistoryAll("KU_USD",USDAV);
      WriteHistoryHeader("KU_JPY");
      WriteHistoryAll("KU_JPY",JPYAV);
      WriteHistoryHeader("KU_CHF");
      WriteHistoryAll("KU_CHF",CHFAV);
      WriteHistoryHeader("KU_GBP");
      WriteHistoryAll("KU_GBP",GBPAV);
      WriteHistoryHeader("KU_AUD");
      WriteHistoryAll("KU_AUD",AUDAV);
      WriteHistoryHeader("KU_CAD");
      WriteHistoryAll("KU_CAD",CADAV);
      WriteHistoryHeader("KU_NZD");
      WriteHistoryAll("KU_NZD",NZDAV);

      HstWriteTime = Time[0];

   }else if(HstWriteTime == Time[0]){
      // バーが増えないときの更新
      WriteHistory("KU_EUR",EURAV,2);
      WriteHistory("KU_USD",USDAV,2);
      WriteHistory("KU_JPY",JPYAV,2);
      WriteHistory("KU_CHF",CHFAV,2);
      WriteHistory("KU_GBP",GBPAV,2);
      WriteHistory("KU_AUD",AUDAV,2);
      WriteHistory("KU_CAD",CADAV,2);
      WriteHistory("KU_NZD",NZDAV,2);

   }else{
      // 新規バー追加の更新
      WriteHistory("KU_EUR",EURAV,1);
      WriteHistory("KU_USD",USDAV,1);
      WriteHistory("KU_JPY",JPYAV,1);
      WriteHistory("KU_CHF",CHFAV,1);
      WriteHistory("KU_GBP",GBPAV,1);
      WriteHistory("KU_AUD",AUDAV,1);
      WriteHistory("KU_CAD",CADAV,1);
      WriteHistory("KU_NZD",NZDAV,1);

      HstWriteTime = Time[0];
      //PlaySound("tick");
   }

  //チャートの更新
  UpdateChartWindow("KU_EUR");
  UpdateChartWindow("KU_USD");
  UpdateChartWindow("KU_JPY");
  UpdateChartWindow("KU_CHF");
  UpdateChartWindow("KU_GBP");
  UpdateChartWindow("KU_AUD");
  UpdateChartWindow("KU_CAD");
  UpdateChartWindow("KU_NZD");
//----
   return(0);
  }
//+------------------------------------------------------------------+
int checkDownloadBars()
{
   // ここはもう少し丁寧にチェックしたほうが良いだろう・・・
   if(iBars(sEURUSD,0)<MaxBars){ Comment("ERROR: "+sEURUSD+" is "+iBars(sEURUSD,0)); return(-1);}
   if(iBars(sUSDJPY,0)<MaxBars){ Comment("ERROR: "+sUSDJPY+" is "+iBars(sUSDJPY,0)); return(-1);}
   if(iBars(sUSDCHF,0)<MaxBars){ Comment("ERROR: "+sUSDCHF+" is "+iBars(sUSDCHF,0)); return(-1);}
   if(iBars(sGBPUSD,0)<MaxBars){ Comment("ERROR: "+sGBPUSD+" is "+iBars(sGBPUSD,0)); return(-1);}
   if(iBars(sAUDUSD,0)<MaxBars){ Comment("ERROR: "+sAUDUSD+" is "+iBars(sAUDUSD,0)); return(-1);}
   if(iBars(sUSDCAD,0)<MaxBars){ Comment("ERROR: "+sUSDCAD+" is "+iBars(sUSDCAD,0)); return(-1);}
   if(iBars(sNZDUSD,0)<MaxBars){ Comment("ERROR: "+sNZDUSD+" is "+iBars(sNZDUSD,0)); return(-1);}

   //いずれかの通貨ペアのバーの本数が2本以上増えたら、すべて再描画する。
   static int EURUSDbar=0,USDJPYbar=0,USDCHFbar=0,GBPUSDbar=0,AUDUSDbar=0,USDCADbar=0,NZDUSDbar=0;

   bool RefreshChart = false;
   if(iBars(sEURUSD,0)-EURUSDbar > 1) RefreshChart = true;
   if(iBars(sUSDJPY,0)-USDJPYbar > 1) RefreshChart = true;   
   if(iBars(sUSDCHF,0)-USDCHFbar > 1) RefreshChart = true;
   if(iBars(sGBPUSD,0)-GBPUSDbar > 1) RefreshChart = true;
   if(iBars(sAUDUSD,0)-AUDUSDbar > 1) RefreshChart = true;
   if(iBars(sUSDCAD,0)-USDCADbar > 1) RefreshChart = true;
   if(iBars(sNZDUSD,0)-NZDUSDbar > 1) RefreshChart = true;

   EURUSDbar = iBars(sEURUSD,0);
   USDJPYbar = iBars(sUSDJPY,0);
   USDCHFbar = iBars(sUSDCHF,0);
   GBPUSDbar = iBars(sGBPUSD,0);
   AUDUSDbar = iBars(sAUDUSD,0);
   USDCADbar = iBars(sUSDCAD,0);
   NZDUSDbar = iBars(sNZDUSD,0);

   if(RefreshChart) { Comment("KU-CHART Refreshed..");return(2);}

   Comment("");
   return(1);
}

//+------------------------------------------------------------------+

int WriteHistoryHeader(string MySymbol)
{
   // hst ファイルのヘッダを書く
   string c_copyright;
   int    i_digits = 2;
   int    i_unused[13] = {0};
   int    version = 400; 
   int FileHandle;  

   if(DoNotOverwrite){
      FileHandle = FileOpenHistory(MySymbol + Period()+".hst", FILE_BIN|FILE_WRITE|FILE_READ);
   }else{
      FileHandle = FileOpenHistory(MySymbol + Period()+".hst", FILE_BIN|FILE_WRITE);
   }

   //既にファイルがあればヘッダを書かない。
   if(DoNotOverwrite && FileSize(FileHandle)>0 ){
      //Print("Header "+MySymbol+" skipped");
      FileClose(FileHandle);
      return (0);
   }
   //Print("Header "+MySymbol+" wrote..");
   FileSeek(FileHandle,0, SEEK_CUR);
   c_copyright = "(C)opyright 2003, MetaQuotes Software Corp.";
   FileWriteInteger(FileHandle, version, LONG_VALUE);
   FileWriteString(FileHandle, c_copyright, 64);
   FileWriteString(FileHandle, MySymbol, 12);
   FileWriteInteger(FileHandle, Period(), LONG_VALUE);
   FileWriteInteger(FileHandle, i_digits, LONG_VALUE);
   FileWriteInteger(FileHandle, 0, LONG_VALUE);       //timesign
   FileWriteInteger(FileHandle, 0, LONG_VALUE);       //last_sync
   FileWriteArray(FileHandle, i_unused, 0, ArraySize(i_unused));
   FileFlush(FileHandle);
   FileClose(FileHandle);
   return (0);
}

//+------------------------------------------------------------------+
int WriteHistoryAll(string MySymbol,double &data[])
{
   // 初回は、全データを書き出す
   int FileHandle = FileOpenHistory(MySymbol + Period()+".hst", FILE_BIN|FILE_WRITE|FILE_READ);

   //追加データが既に書かれていたらスキップ
   if(DoNotOverwrite && FileSize(FileHandle)>HEADER_BYTE){
      //Print("WriteHistoryAll "+MySymbol+" skipped "+ FileSize(FileHandle));
      FileClose(FileHandle);
      return (0);
   }
   //Print("WriteHistoryAll "+MySymbol+" wrote... "+ FileSize(FileHandle));

   FileSeek(FileHandle,FileSize(FileHandle), SEEK_CUR);

   //1本目はO=H=L=C
   FileWriteInteger(FileHandle, Time[MaxBars], LONG_VALUE);//4
   FileWriteDouble(FileHandle, data[MaxBars], DOUBLE_VALUE);//8
   FileWriteDouble(FileHandle, data[MaxBars], DOUBLE_VALUE);
   FileWriteDouble(FileHandle, data[MaxBars], DOUBLE_VALUE);
   FileWriteDouble(FileHandle, data[MaxBars], DOUBLE_VALUE);
   FileWriteDouble(FileHandle, Volume[MaxBars], DOUBLE_VALUE);

   //2本目以降は、始値=前の足の終値
   for(int i=MaxBars-1;i>=0;i--){
      double o,h,l,c;
      o = data[i+1];//前の足の終値
      c = data[i];
      h = MathMax(o,c);
      l = MathMin(o,c);

      FileWriteInteger(FileHandle, Time[i], LONG_VALUE);
      FileWriteDouble(FileHandle, o, DOUBLE_VALUE);
      FileWriteDouble(FileHandle, l, DOUBLE_VALUE);
      FileWriteDouble(FileHandle, h, DOUBLE_VALUE);
      FileWriteDouble(FileHandle, c, DOUBLE_VALUE);
      FileWriteDouble(FileHandle, Volume[i], DOUBLE_VALUE); 
   }
   FileFlush(FileHandle);
   FileClose(FileHandle);
   return (0);
}
//+------------------------------------------------------------------+
int WriteHistory(string MySymbol,double &data[],int back)
{
   // 2回目からは少し戻って上書きしつつ書き足す
   int FileHandle = FileOpenHistory(MySymbol + Period()+".hst", FILE_BIN|FILE_WRITE|FILE_READ);

   if(DoNotOverwrite && FileSize(FileHandle) < HEADER_BYTE+DATA_BYTE*back ){
   //戻れない。
    //back = 0;
    //Print("WriteHistory Re-write WriteHistoryAll "+MySymbol);
    WriteHistoryAll(MySymbol,data);
   }

   FileSeek(FileHandle,FileSize(FileHandle)-DATA_BYTE*back, SEEK_CUR);
   double oldOpen[2],oldHigh[2],oldLow[2],oldTime[2];
   double tmp;
   int i;

   //2本(1本)分のデータを読み直す。
   for(i=1;i>=0;i--){
      oldTime[i] = FileReadInteger(FileHandle, LONG_VALUE);
      oldOpen[i] = FileReadDouble(FileHandle, DOUBLE_VALUE);
      oldLow[i] = FileReadDouble(FileHandle, DOUBLE_VALUE);
      oldHigh[i] = FileReadDouble(FileHandle, DOUBLE_VALUE);

      FileReadDouble(FileHandle, DOUBLE_VALUE);
      FileReadDouble(FileHandle, DOUBLE_VALUE);
   }

   //データを読んだので、もう一度戻る。
   if(oldTime[1] == Time[1])
      FileSeek(FileHandle,FileSize(FileHandle)-DATA_BYTE*back, SEEK_SET);

   for(i=1;i>=0;i--){
      FileWriteInteger(FileHandle, Time[i], LONG_VALUE);
      double o,h,l,c;
      o = data[i];
      h = data[i];
      l = data[i];
      c = data[i];
      if(oldOpen[i] > 0 && oldTime[i] == Time[i]){
         o = oldOpen[i];
         h = MathMax(oldOpen[i],MathMax(oldHigh[i],c));
         l = MathMin(oldOpen[i],MathMin(oldLow[i],c));
      }

      FileWriteDouble(FileHandle, o, DOUBLE_VALUE);
      FileWriteDouble(FileHandle, l, DOUBLE_VALUE);
      FileWriteDouble(FileHandle, h, DOUBLE_VALUE);
      FileWriteDouble(FileHandle, c, DOUBLE_VALUE);
      FileWriteDouble(FileHandle, Volume[i], DOUBLE_VALUE);
   }
   FileFlush(FileHandle);
   FileClose(FileHandle);
   return (0);
}

//+------------------------------------------------------------------+
int UpdateChartWindow(string MySymbol)
{
   int hwnd = 0;

   hwnd = WindowHandle(MySymbol, Period());
   if(hwnd!= 0) {
      if (IsDllsAllowed() == false) {
         //DLL calls must be allowed
         Alert("ERROR: [Allow DLL imports] NOT Checked.");
         return (-1);
      }
      if (PostMessageA(hwnd,WM_COMMAND,CHART_CMD_UPDATE_DATA,0) == 0) {
         //PostMessage failed, chart window closed
         hwnd = 0;
      } else {
         //PostMessage succeed
         return (0);
      }
   }
   //window not found or PostMessage failed
   return (-1);
}
//+------------------------------------------------------------------+

では早速1行目から見ていきましょう。

  1. 表題
 //+------------------------------------------------------------------+
 //|                                               Ku-chart-Maker.mq4 |
 //|                      Copyright ゥ 2010, MetaQuotes Software Corp. |
 //|                                        http://www.metaquotes.net |
 //+------------------------------------------------------------------+

表題がコメントとして記載されています。コメント部分は記述の如何を問わず、コンパイルの際コンパイラに無視されます。コメントには通常、注釈や使い方や解説などを記載しますが、コンパイルにより生成されるプログラム(エキスパートアドバイザやカスタムインジケータなど)の内容には一切影響しないため、基本的に自由に何を書いても構いません。

特筆に値することは何もありませんが、著作権表示がMetaQuotes Software社になっていて、ウェブサイトのアドレスがMetaQuotes Software社のものになっているのが不可解です。

  1. #propertyプリプロセッサ指令1
 #property copyright "Copyright ゥ 2010, MetaQuotes Software Corp."
 #property link      http://www.metaquotes.net

#propertyはプリプロセッサ指令の1つであり、コンパイル前にプリプロセッサによって処理されます。

#propertyプリプロセッサ指令は#property <プロパティ名> <値>という使い方をし、<プロパティ名>によって表されるプロパティの値として<値>を設定します。プロパティには様々な種類のものがあるようです。

copyrightプロパティには著作権表示を設定します。このカスタムインジケータでは何故かMetaQuotes Software社になっています。魔訶不思議です。

linkプロパティにはウェブサイトのアドレスを設定します。このカスタムインジケータでは何故かMetaQuotes Software社のものになっています。魔訶魔訶不思議です。

  1. #includeプリプロセッサ指令
 #include <WinUser32.mqh>

#includeもプリプロセッサ指令の1つです。

#includeプリプロセッサ指令は#include <<ファイル名>>又は#include "<ファイル名>"という使い方をし、<ファイル名>によって示されるファイルの内容をプログラムのソースコードの一部として取り込みます。<ファイル名>が<>で囲まれている場合にはファイルが探索されるディレクトリは標準ディレクトリ(通常は"<MetaTrader 4をインストールしたディレクトリ>\MQL4\Inc")となり、""で囲まれている場合にはカレントディレクトリ(ソースコードのファイルが格納されているディレクトリ)となります。

ここでは"WinUser32.mqh"という名称のファイルを標準ディレクトリで探索し、プログラムのソースコードの一部として取り込みます。"WinUser32.mqh"はMetaTrader 4が標準で提供している、"user32.dll"というライブラリの関数を呼び出すために必要となるファイルであり、このカスタムインジケータではPostMessage関数という"user32.dll"の関数を使用するためこの#includeプリプロセッサ指令が必要となります。

  1. #defineプリプロセッサ指令
 #define  CHART_CMD_UPDATE_DATA            33324
 #define  HEADER_BYTE                        148
 #define  DATA_BYTE                           44

#defineもプリプロセッサ指令の1つです。

#defineプリプロセッサ指令は#define <識別子> <式>という使い方をし、以降のソースコードに現れる<識別子>を<式>に置換します。

1行目はCHART_CMD_UPDATE_DATAという識別子を33324という値に置換するプリプロセッサ指令です。

2行目はHEADER_BYTEという識別子を148という値に置換するプリプロセッサ指令です。

3行目はDATA_BYTEという識別子を44という値に置換するプリプロセッサ指令です。

  1. #propertyプリプロセッサ指令2
 #property indicator_separate_window
 #property indicator_buffers 8

インジケータ関連のプロパティの設定です。

indicator_chart_windowプロパティを設定すると、インジケータはチャートウィンドウ(チャートが描画されている領域)に描画され、indicator_separate_windowプロパティを設定すると、セパレートウィンドウ(チャートが描画されている領域の下に新たに追加される領域)に描画されます。インジケータをどの領域に描画するかによってindicator_chart_windowプロパティかindicator_separate_windowプロパティを設定します。なお、これらのプロパティには値を設定しません。このカスタムインジケータの場合、セパレートウィンドウに描画するので、indicator_separate_windowプロパティが設定されています。

indicator_buffersプロパティにはインジケータの時系列データの数を設定します(ただし、時系列データは最大でも8つまでしか設定することができません)。このカスタムインジケータの場合、時系列データの数は最大で8(対象となっている全ての通貨の数。1つの時系列データが1つの通貨の強弱を表すため)なので、8が設定されています(つまり、このカスタムインジケータでは最も多い場合で設定可能な最大数の時系列データを設定します。これ以上時系列データを増やすことはできません)。

  1. パラメータ
 extern int MaxBars = 2000;//計算するバーの本数

 // 平滑化したい場合は下記2つを調整. デフォルトでは rawdataとなる。
 extern int MAPeriod = 1;//3;
 extern int MAPrice  = PRICE_CLOSE;//PRICE_TYPICAL;
 extern int MAMethod = MODE_LWMA;

 extern color Color_USD = Orange;
 extern color Color_EUR = Red;
 extern color Color_GBP = Lime;
 extern color Color_CHF = Snow;
 extern color Color_JPY = Turquoise;
 extern color Color_AUD = RoyalBlue;
 extern color Color_CAD = BlueViolet;
 extern color Color_NZD = DeepPink;

 extern int SelectedLineWidth = 3;//チャートの通貨のラインの太さ

 extern int OriginTime = 1000;//1000なら1000本前基準

 extern int ZeroLevelShift = 1000;//原点のレベル
 extern bool UseZeroCheck = true;

 extern bool UseUSD = true;//計算に使うかどうか。
 extern bool UseEUR = true;
 extern bool UseGBP = true;
 extern bool UseCHF = true;
 extern bool UseJPY = true;
 extern bool UseAUD = true;
 extern bool UseCAD = true;
 extern bool UseNZD = true;

 extern bool DoNotOverwrite = true;

大域変数をextern変数として宣言すると、その変数はプログラムの外部から設定できるパラメータとなり、カスタムインジケータの場合、パラメータは[Custom Indicator]ウィンドウの[パラメータの入力]タブから設定することができます。

MaxBars変数は最新の足から過去に向かって最新の足を含めないで最大で何本目の足までに対してインジケータの値を計算するかを表します。最新の足を含めないでという点に注意してください。たとえば、たとえこの変数の値が0であっても、最新の足だけは計算対象となります。つまり、この変数の値と計算対象となる足の本数は一致せず、1ずれます(この変数の値は最新の足を含めない場合の本数です)。ややややこしくなっているのはプログラム作成者の所為です。私の所為ではありません。私がプログラムを書くときにはこういった回避可能なややこしさは極力回避するようにするのですが。また、最大でという点にも注意してください。あくまでもこの本数は絶対に超過しない本数ということでしかなく、逆に実際の本数がこの本数より少ないということはあり得ます(寧ろ、このカスタムインジケータを追加した直後の1回目の値の計算以外の計算の場合は殆どの場合この本数より少なくなるのですが)。

MAPeriod変数、MAPrice変数及びMAMethod変数は移動平均関連のパラメータとなります。このカスタムインジケータは値の計算の際に移動平均の計算を用います。そのため、移動平均関連のパラメータが設定可能になっています。MAPeriod変数は移動平均の期間(起点となる足から過去に向かって何本分の足の値の平均を取るか)を表します(こちらは上のMaxBars変数とは異なり、起点となる足も含めた場合の本数です。為念)。なお、この値が1である場合には起点となる足の値のみの平均を取ることになりますが、それは他でもない起点となる足の値そのものです。MAPrice変数は移動平均の計算に四本値(始値、高値、安値及び終値)やその派生値の内何れの値を使うかを表します。PRICE_OPEN(始値)、PRICE_HIGH(高値)、PRICE_LOW(安値)、PRICE_CLOSE(終値)、PRICE_MEDIAN(高値と安値の平均値)、PRICE_TYPICAL(高値と安値と終値の平均値)、PRICE_WEIGHTED(四本値の平均値)の何れかを指定します。MAMethod変数は移動平均としてどのような計算方法を使うかを表します。MODE_SMA(単純移動平均)、MODE_EMA(指数移動平均)、MODE_SMMA(平滑移動平均)、MODE_LWMA(線型加重移動平均)の何れかを指定します。

Color_USD変数以下の8つの変数は色関連のパラメータとなります。それぞれの通貨の強弱を表す時系列データ(以後「通貨の時系列データ」と言います)が何色で描画されるかを表します。任意のcolor型の値を指定します。

SelectedLineWidth変数は線の太さを表します。このカスタムインジケータを表示しているチャートの通貨ペアの構成通貨の時系列データはこの変数で指定された太さの線として描画されます。ちなみに、それ以外の時系列データは太さ1の線として描画されます。

OriginTime変数は1回目のインジケータの値の計算において基準となる足が最新の足から過去に向かって最新の足を0本目の足と考えた場合に何本目の足であるかを表します。2回目以降の計算においては1回目において決定されたものをそのまま使用します。なお、「基準となる足」の意味については後述します。

ZeroLevelShift変数はインジケータの原点の値を表します。たとえば、この値が1000である場合には、インジケータの値は1000を原点として(中心にして)計算されることになります。更に、インジケータの描画領域においてこの値の水準に水平線が描画されます。この水平線が原点の位置を視覚的に表します。

UseZeroCheck変数はインジケータの値が0未満である場合に警告を発出するかどうかを表します。警告が発生する場合には、上のZeroLevelShift変数を調整して値が0未満にならないようにして警告が発生しないようにすることになるでしょう。

UseUSD変数以下の8つの変数はそれぞれの通貨の強弱を表す値(以後「通貨の値」と言います)を計算するかどうかを表します。それに加えて、他の通貨の値を計算する際に他の通貨のその通貨に対する強弱を考慮に入れるかどうかも表します。すなわち、ある通貨の値を計算しない設定になっている場合には、他の通貨の値を計算する際にも他の通貨のその通貨に対する強弱は全く考慮されなくなります。たとえば、EURの値を計算しない設定になっている場合には、EURの値を計算しないのは勿論、たとえば、USDの値を計算する際にもUSDのEURに対する強弱は全く考慮されなくなります。

DoNotOverwrite変数はファイルへのインジケータの値の出力の際に上書きを行うかどうかを表します。

  1. 通貨ペア名
 // 基本の6ペア
 string sEURUSD = "EURUSD";
 string sUSDJPY = "USDJPY";
 string sUSDCHF = "USDCHF";
 string sGBPUSD = "GBPUSD";
 string sAUDUSD = "AUDUSD";
 string sUSDCAD = "USDCAD";
 string sNZDUSD = "NZDUSD";

為替レートを取得する通貨ペアの(MetaTrader 4上での)名称です。全てドルストレートであり、かつ、対象となっているUSD以外の全ての通貨のドルストレートであることに注意してください。そのため、通貨ペアは7ペアあります。コメントには「基本の6ペア」と書かれていますが、7ペアの間違いです。

  1. 時系列データ格納用の配列
 //---- buffers
 double EURAV[];
 double USDAV[];
 double JPYAV[];
 double CHFAV[];
 double GBPAV[];
 double AUDAV[];
 double CADAV[];
 double NZDAV[];

それぞれの通貨の時系列データが格納される配列です。

  1. 大域変数
 string Indicator_Name = " EUR USD JPY  CHF GBP AUD CAD NZD";
 int Objs = 0;
 int Pairs = 7;
 string objname = "Ku-chart-OriginTime";

大域変数の宣言です。

Indicator_Name変数はこのカスタムインジケータの短い名称を表します。対象となっている全ての通貨の名称を空白で結合したものとなっています。後述するように、この名称はインジケータの描画領域の左上に描画されることになりますが、JPYとCHFの間に空白が1つではなく2つ入っているのは、描画位置を調整するためであると思われます。

Objs変数は下の2つの関数の中で使用されます。

Pairs変数はそれぞれの通貨の値を計算する際に関与する通貨ペアの数を表します。たとえば、JPYの場合、JPYの値を計算する際には(対象となっている全ての通貨の値を計算する設定になっている場合)USD/JPY、EUR/JPY、GBP/JPY、CHF/JPY、CAD/JPY、AUD/JPY、NZD/JPYの7つの通貨ペアに対する中間的な値を用いるため(「中間的な値」の意味については後述します)、通貨ペアの数は7となります。

objname変数は基準となる足を決定する用途でチャートに追加されるオブジェクトの名称を表します。

  1. init関数1
 //+------------------------------------------------------------------+
 //| Custom indicator initialization function                         |
 //+------------------------------------------------------------------+
 int init()
   {

init関数はMQL4の特殊な関数の1つです。所謂初期化関数であり、インジケータが追加された際に、初めに1回だけ呼び出される関数です。

  1. init関数2 ~時系列データの設定1~
 //---- indicators
    SetIndexStyle(0,DRAW_LINE,EMPTY,GetWidth("EUR"),Color_EUR);
    SetIndexBuffer(0,EURAV);
    SetIndexLabel(0,"EUR");
    SetIndexStyle(1,DRAW_LINE,EMPTY,GetWidth("USD"),Color_USD);
    SetIndexBuffer(1,USDAV);
    SetIndexLabel(1,"USD");
    SetIndexStyle(2,DRAW_LINE,EMPTY,GetWidth("JPY"),Color_JPY);
    SetIndexBuffer(2,JPYAV);
    SetIndexLabel(2,"JPY");
    SetIndexStyle(3,DRAW_LINE,EMPTY,GetWidth("CHF"),Color_CHF);
    SetIndexBuffer(3,CHFAV);
    SetIndexLabel(3,"CHF");
    SetIndexStyle(4,DRAW_LINE,EMPTY,GetWidth("GBP"),Color_GBP);
    SetIndexBuffer(4,GBPAV);
    SetIndexLabel(4,"GBP");   
    SetIndexStyle(5,DRAW_LINE,EMPTY,GetWidth("AUD"),Color_AUD);
    SetIndexBuffer(5,AUDAV);
    SetIndexLabel(5,"AUD");
    SetIndexStyle(6,DRAW_LINE,EMPTY,GetWidth("CAD"),Color_CAD);
    SetIndexBuffer(6,CADAV);
    SetIndexLabel(6,"CAD");
    SetIndexStyle(7,DRAW_LINE,EMPTY,GetWidth("NZD"),Color_NZD);
    SetIndexBuffer(7,NZDAV);
    SetIndexLabel(7,"NZD");

SetIndexBuffer関数でインジケータの時系列データを格納する配列を設定します。第1引数にはインジケータの時系列データの番号(何番目の時系列データであるかを表す0~7までの数値)を指定し、第2引数には第1引数で指定した番号の時系列データを格納するのに使用する配列を指定します。つまり、この関数によってインジケータの時系列データの番号と時系列データを実際に格納する配列とを結び付けます。

たとえば、SetIndexBuffer(0,EURAV)によってEURAVという(名称から分明であるようにEURの時系列データを格納するための)配列がインジケータの0番目の時系列データを格納する配列として設定されます。

SetIndexStyle関数はインジケータの時系列データを描画する際の様式を設定します。第1引数にはインジケータの時系列データの番号を指定し、第2引数には図形様式(時系列データのそれぞれの値をどのような図形として描画するか。線や棒など。それぞれの値を線として描画する場合には時系列データは全体として折れ線グラフとして描画されることになり、棒として描画する場合には棒グラフとして描画されることになります)を指定します。線として描画したい場合にはDRAW_LINEを指定し、棒として描画したい場合にはDRAW_HISTOGRAMを指定します。第3引数には描画様式(線の種類。実線や破線など)を指定します。実線として描画したい場合にはSTYLE_SOLIDを指定し、破線として描画したい場合にはSTYLE_DASHを指定します。Emptyを指定すると既定の設定を使用します。第4引数には線の太さを1~5までの数値として指定します。Emptyを指定すると既定の設定を使用します。第5引数には線の色をcolor型の値として指定します。CLR_NONEを指定すると既定の設定を使用します。

たとえば、SetIndexStyle(0,DRAW_LINE,EMPTY,GetWidth("EUR"),Color_EUR)によってインジケータの0番目の時系列データを描画する際の様式が設定されます。第2引数の指定によって時系列データのそれぞれの値は線として描画され、第3引数の指定によって線の種類の設定としては既定の設定が使用されます(特に何もしていなければ実線として描画されるのではないかと思います)。第4引数の指定によって線の太さが決まりますが、ここではGetWidth関数というユーザ定義関数が呼び出されています。GetWidth関数の内容については後述しますが、結果だけ言うと、引数として与えられた通貨(この場合はEUR)がインジケータを表示しているチャートの通貨ペアの構成通貨である場合のみ線の太さが太くなります。第5引数の指定によって線の色が決まります。ここではColor_EURというextern変数が指定されているため、この名称のパラメータに指定されている値の色で線が描画されることになります。

SetIndexLabel関数はインジケータの時系列データに結び付けられるラベルを設定します。第1引数にはインジケータの時系列データの番号を指定し、第2引数にはラベルを指定します。ラベルはデータ・ウィンドウとツールチップに表示されます。

たとえば、SetIndexLabel(0,"EUR")によってEURという文字列がインジケータの0番目の時系列データのラベルとして設定されます。

このようにして、インジケータの時系列データに対する設定が行われます。ここで結び付けられた配列にインジケータの実際の時系列データを計算して格納すると、その時系列データがインジケータの時系列データとして設定された様式で実際にチャートに描画されることになります。このカスタムインジケータの場合、時系列データとはそれぞれの通貨の時系列データです。

  1. init関数3 ~時系列データの設定2~
    if(!UseEUR) SetIndexStyle(0,DRAW_NONE);
    if(!UseUSD) SetIndexStyle(1,DRAW_NONE);
    if(!UseJPY) SetIndexStyle(2,DRAW_NONE);
    if(!UseCHF) SetIndexStyle(3,DRAW_NONE);
    if(!UseGBP) SetIndexStyle(4,DRAW_NONE);
    if(!UseAUD) SetIndexStyle(5,DRAW_NONE);
    if(!UseCAD) SetIndexStyle(6,DRAW_NONE);
    if(!UseNZD) SetIndexStyle(7,DRAW_NONE);

    if(!UseEUR) Color_EUR = DimGray;
    if(!UseUSD) Color_USD = DimGray;
    if(!UseJPY) Color_JPY = DimGray;
    if(!UseCHF) Color_CHF = DimGray;
    if(!UseGBP) Color_GBP = DimGray;
    if(!UseAUD) Color_AUD = DimGray;
    if(!UseCAD) Color_CAD = DimGray;
    if(!UseNZD) Color_NZD = DimGray;

上で説明したように、UseEUR変数などはそれぞれの通貨の値を計算するかどうかを表すパラメータです。このパラメータの値がfalseである場合、すなわち、計算しない設定になっている場合には、何も描画を行わないように図形様式としてDRAW_NONEを指定しています。また、線の色をDimGrayに設定しています(そもそも描画自体行わないので、仮にこの設定がなかったとしても、特に問題はないかと思いますが)。

  1. init関数4 ~通貨ペアの数~
    Pairs = 7;
    if(!UseEUR) Pairs--;
    if(!UseUSD) Pairs--;
    if(!UseJPY) Pairs--;
    if(!UseCHF) Pairs--;
    if(!UseGBP) Pairs--;
    if(!UseAUD) Pairs--;
    if(!UseCAD) Pairs--;
    if(!UseNZD) Pairs--;
    if(Pairs<1) Alert("Pairs is ",Pairs);

それぞれの通貨の値を計算するかどうかに応じて それぞれの通貨の値を計算する際に関与する通貨ペアの数を求めています。たとえば、対象となっている全ての通貨の値を計算する設定になっている場合、通貨ペアの数は7ペアとなります。対象となっている全ての通貨の内EURの値のみ計算しない設定になっている場合、通貨ペアの数は6ペアとなります。なお、通貨ペアの数が0ペアの場合には、警告を発出します(Alert関数は警告を発出します)。

  1. init関数5 ~その他の設定~
    SetLevelValue(0, ZeroLevelShift);

    IndicatorShortName(Indicator_Name);

    IndicatorDigits(2);

SetLevelValue関数を用いてインジケータの描画領域に水平線を描画することができます。第1引数には水平線の番号(何番目の水平線であるかを表す0~31までの数値)を指定し、第2引数には水平線の値を指定します。

SetLevelValue(0, ZeroLevelShift)によってZeroLevelShift変数の値を使って水平線が描画されます。ZeroLevelShift変数はインジケータの原点の値を表すので、このカスタムインジケータではこの水平線がインジケータの原点を示します。

IndicatorShortName関数はインジケータの短い名称を設定します。第1引数に短い名称を指定するだけです。この名称はインジケータの描画領域の左上に描画されます。

IndicatorShortName(Indicator_Name)によってIndicator_Name変数の値がインジケータの短い名称として設定されます。Indicator_Name変数の値は対象となっている全ての通貨の名称を1つの空白(ただし、JPYとCHFの間は2つの空白)で結合したものなので、それがそのままインジケータの描画領域の左上に描画されます。

IndicatorDigits関数はインジケータの値として小数点以下何桁まで表示するか設定します。

IndicatorDigits(2)によってインジケータの値は小数点以下2桁まで表示されることになります。

  1. init関数6 ~波線の描画~
    Objs = 0;
    int cur = 0; 
    int st = 23;// ~の長さが23
    sl("~", cur, Color_EUR);
    cur += st;
    sl("~", cur, Color_USD);
    cur += st;
    sl("~", cur, Color_JPY);
    cur += st;
    sl("~", cur, Color_CHF);
    cur += st;
    sl("~", cur, Color_GBP);
    cur += st;
    sl("~", cur, Color_AUD);
    cur += st;       
    sl("~", cur, Color_CAD);
    cur += st;
    sl("~", cur, Color_NZD);
    cur += st;
 //----

    return(0);
   }

インジケータの描画領域の左上に短い名称として描画される、対象となっている全ての通貨の名称の列挙の下にそれぞれの通貨の時系列データを描画するのと同じ色で波線(~)を描画します(多分凡例のつもりなのでしょう)。それぞれの波線の描画はslユーザ定義関数で行われています。st変数の値である23は波線を描画した場合の横幅(と間隔)をピクセル数で数えたものです。Objs変数はslユーザ定義関数の中で使用します。最初に値を0にしておきます。cur変数はインジケータの描画領域の中で波線を描画するX座標です。最初に値を0にしておき、slユーザ定義関数を呼び出した後に毎回st変数の値を加えることで正しいX座標を求めます。slユーザ定義関数には描画する文字列である波線と描画するX座標であるcur変数の値と描画する色を順番に渡します。

  1. slユーザ定義関数
 void sl(string sym, int y, color col)
   { // ~記号を描く
    int window = WindowFind(Indicator_Name);
    string ID = Indicator_Name+Objs;
    Objs++;
    if(col<0) col=EMPTY_VALUE;//CLR_NONE;
    if(window == -1 ) return;
    ObjectCreate(ID, OBJ_LABEL, window, 0, 0);
    ObjectSet(ID, OBJPROP_XDISTANCE, y +6);
    ObjectSet(ID, OBJPROP_YDISTANCE, 0);
    ObjectSetText(ID, sym, 18, "Arial Black", col);
    //Print("ID=",ID," ",col);
   }   

slユーザ定義関数は第1引数として指定された文字列を第2引数として指定されたX座標に第3引数として指定された色で描画します。この時のY座標は0となります。

WindowFind関数は指定された短い名称のインジケータを含むウィンドウの番号を返します。指定された短い名称のインジケータを含むウィンドウが存在しない場合には-1を返します。

ObjectCreate関数は新しいオブジェクトを作成します。第1引数にはオブジェクトの名称を指定し、第2引数にはオブジェクトの種類を指定し、第3引数には作成したオブジェクトを追加するウィンドウの番号を指定します。

ObjectSet関数はオブジェクトのプロパティの値を設定します。第1引数にはオブジェクトの名称を指定し、第2引数にはプロパティ名を指定し、第3引数にはプロパティの値を指定します。

ObjectSetText関数はオブジェクトの説明文を設定します。第1引数にはオブジェクトの名称を指定し、第2引数にはオブジェクトの説明文を指定し、第3引数にはフォントの大きさを指定し、第4引数にはフォント名を指定し、第5引数には色を指定します。

3行目でIndicator_Name変数の値が短い名称となっているようなインジケータ(すなわち、このカスタムインジケータ)を含むウィンドウの番号をwindow変数に代入しています。

4行目でオブジェクトの名称を生成してID変数に代入しています。Objsの値は最初は0であり、新しいオブジェクトが作成される度に1ずつ増加していきます。したがって、オブジェクトの名称はこのカスタムインジケータの短い名称の後に0から始まる番号を続けたものとなります。

6行目で第3引数の値が異常であった場合には修正を行うようにしています。

7行目でwindow変数が-1であった場合には何も描画しないまま関数を抜けるようにしています。

8行目で新しいオブジェクトを作成しています。第2引数にオブジェクトの種類としてOBJ_LABELを指定すると、ラベル(任意の文字列を描画するオブジェクト)が作成されます。第2引数にOBJ_LABELを指定した場合には、第4引数及び第5引数は無視されるのでどんな値を指定しても構いません(ここでは0を指定しています)。

9行目で8行目で作成したラベルを描画するX座標を設定しています。第2引数にプロパティ名としてOBJPROP_XDISTANCEを指定し、第3引数にX座標を指定します。ここではy + 6としています(X座標を設定するというのにyという名称の変数を使っていますが気にしないでおきましょう)。

10行目で8行目で作成したラベルを描画するY座標を設定しています。第2引数にプロパティ名としてOBJPROP_YDISTANCEを指定し、第3引数にY座標を指定します。ここでは0としています。したがって、このラベルはインジケータの描画領域の最上部に描画されることになります。

11行目で8行目で作成したラベルの説明文を設定しています。ラベルの場合、説明文がそのまま描画される文字列となり、ObjectSetText関数の第3引数、第4引数及び第5引数として指定されたフォントと色で描画されます。

  1. deinit関数
 //+------------------------------------------------------------------+
 //| Custom indicator deinitialization function                       |
 //+------------------------------------------------------------------+
 int deinit()
   {
 //----
      switch(UninitializeReason())
        {
         case REASON_CHARTCLOSE:  Print("KU-deinit REASON_CHARTCLOSE");break;
         case REASON_REMOVE:      Print("KU-deinit REASON_REMOVE");break;
         case REASON_RECOMPILE:   Print("KU-deinit REASON_RECOMPILE");break;
         case REASON_CHARTCHANGE: Print("KU-deinit REASON_CHARTCHANGE");break;
         case REASON_PARAMETERS:  Print("KU-deinit REASON_PARAMETERS");break;
         case REASON_ACCOUNT:     Print("KU-deinit REASON_ACCOUNT");break;
        }
 //----
    return(0);
   }

deinit関数はMQL4の特殊な関数の1つであり、インジケータが削除された際に、1回だけ呼び出される関数です。

UninitializeReason関数はプログラムが終了した理由を返します。理由には幾つか種類があるようですが、ここでは何れの理由の場合でも如何なる理由でプログラムが終了したか明示して運用記録を出力するようにしています(Print関数は運用記録を出力します)。

  1. GetWidthユーザ定義関数
 //+------------------------------------------------------------------+
 //| Custom indicator iteration function                              |
 //+------------------------------------------------------------------+
 int GetWidth(string sym1){
    int index = StringFind(Symbol(),sym1);
    if(index == -1) return(1);
    return(SelectedLineWidth);// 選択通貨を含む場合のみラインを太くしている。
 }

Symbol関数はインジケータを表示しているチャートの通貨ペアの名称を返します。

StringFind関数は文字列中の文字列の検索を行います。第1引数に検索される文字列を指定し、第2引数に検索する文字列を指定します。すなわち、第1引数として指定された文字列の中に第2引数として指定された文字列が含まれているか判定し、含まれている場合には第1引数として指定された文字列の何文字目から第2引数として指定された文字列が出現するかを返し、含まれていない場合には-1を返します。

そのため、上のGetWidthユーザ定義関数は第1引数として指定された通貨がチャートの通貨ペアの構成通貨である場合にはSelectedLineWidth変数の値(3)を返し、そうでない場合には1を返します。前述したように、この関数の返り値はインジケータの時系列データを描画する線の太さになるため、結果としてこのカスタムインジケータを表示しているチャートの通貨ペアの構成通貨の時系列データを描画する場合だけ線の太さが太くなります。

  1. GetValユーザ定義関数
 double GetVal(string sym1,datetime t1,datetime t2){
    double v1,v2;

    v1 = iMA(sym1,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym1,0,t1));
    v2 = iMA(sym1,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym1,0,t2));

    if(v2==0) return(0);
    return(MathLog(v1/v2)*1000);
 }

iBarShift関数は指定された通貨ペアの指定された時間枠の指定された開始時間を有する足が最新の足から過去に向かって最新の足を0本前の足と考えた場合に何本前のものであるかを返します。第1引数に通貨ペアを指定し、第2引数に時間枠を指定し、第3引数に開始時間を指定します。第2引数に0を指定すると、インジケータを表示しているチャートの時間枠となります。

iMA関数は指定された通貨ペアの指定された時間枠の移動平均を計算して返します。第1引数に通貨ペアを指定し、第2引数に時間枠を指定し、第3引数に移動平均の期間を指定し、第4引数に移動平均の変位(起点となる足を何本分ずらすか)を指定し、第5引数に移動平均としてどのような計算方法を使うかを指定し、第6引数に移動平均の計算に四本値やその派生値の内何れの値を使うかを指定し、第7引数に最新の足から過去に向かって最新の足を0本前の足と考えた場合に何本前のものに対する移動平均を計算するかを指定します。第2引数に0を指定すると、インジケータを表示しているチャートの時間枠となります。

そのため、iMA関数の第7引数においてiBarShift関数を使用すれば、iBarShift関数の第3引数に指定した開始時間における移動平均を計算することができます(勿論iMA関数とiBarShift関数の呼び出しでは通貨ペアと時間枠を合わせなければなりません)。

MathLog関数は指定された値の自然対数を返します。

上のGetValユーザ定義関数のv1変数には第1引数として指定された通貨ペアの第2引数として指定された時刻(t1)における移動平均が代入され、v2変数には第1引数として指定された通貨ペアの第3引数として指定された時刻(t2)における移動平均が代入されます。v2変数の値が0である場合には0を返し、そうでない場合にはv1変数の値をv2変数の値で割り、自然対数を取り、1000を掛けた値を返します(v2変数の値が0である場合に0を返すのはゼロ除算を回避するため)。

v1変数の値をv2変数の値で割ることによって通貨ペアのt1における移動平均のt2における移動平均に対する変動の割合を計算しています。たとえば、t1における移動平均の値(v1変数の値)が101であり、t2における移動平均の値(v2変数の値)が100である場合、v1/v2=101/100=1.01となりますが、これはt1における移動平均の値がt2における移動平均の値から1%増加しているということです。t1における移動平均の値が99であり、t2における移動平均の値が100である場合、v1/v2=99/100=0.99となり、これはt1における移動平均の値がt2における移動平均の値から1%減少しているということです。このように、ある時間から別のある時間までの間に移動平均の値がどれだけの割合変動したかを求め、これをこのカスタムインジケータの値の基礎としています。何故変動の割合を基礎とするのかは、ある期間における通貨ペアの変動の割合がある期間における通貨の強弱を決定する(要素となる)という考え方によるものと思われます。

自然対数を取っているのは微細な変動を相対的に重視し、著大な変動を相対的に軽視するためです。自然対数を取ることによってそのような値の変換が可能なのは自然対数のグラフが次のような軌跡を描くからです。

また、自然対数を取った後の2つ以上の割合を合成する場合には、乗算ではなく加算をしなければならないことに注意してください。これは次のような等式が成り立つためです。

$$
\log ab... = \log a + \log b + ...
$$

1000を掛けているのは値の大きさを調整するためです。1000を掛けることによって値が3桁大きくなります。

  1. GetValMユーザ定義関数
 double GetValM(string sym1,string sym2,datetime t1,datetime t2){
    double v1,v2;

    v1 = iMA(sym1,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym1,0,t1));
    v2 = iMA(sym1,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym1,0,t2));

    v1 = v1*iMA(sym2,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym2,0,t1));
    v2 = v2*iMA(sym2,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym2,0,t2));

    if(v2==0) return(0);
    return(MathLog(v1/v2)*1000);
 }

GetValMユーザ定義関数もGetValユーザ定義関数と同様の計算を行っていますが、引数として2つの通貨ペアを取り、2つの通貨ペアの移動平均の値から2つの通貨ペアを合成した通貨ペアの移動平均の値を算出している点が異なります。たとえば、EUR/USDの為替レートが1.1000でUSD/JPYの為替レートが100.00である場合、この2つの為替レートを掛け合わせて1.1000*100.00=110.00とすることでEUR/JPYの為替レートを計算することができます。同様に、2つの通貨ペアの移動平均の値を掛け合わせて2つの通貨ペアを合成した通貨ペアの移動平均の値を算出しています。

  1. GetValDユーザ定義関数
 double GetValD(string sym1,string sym2,datetime t1,datetime t2){
    double v1,v2,v3,v4;

    v1 = iMA(sym1,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym1,0,t1));
    v2 = iMA(sym1,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym1,0,t2));

    v3 = iMA(sym2,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym2,0,t1));
    v4 = iMA(sym2,NULL,MAPeriod,0,MAMethod,MAPrice,iBarShift(sym2,0,t2));
    if(v3==0) return(0);
    if(v4==0) return(0);

    v1 = v1/v3;
    v2 = v2/v4;

    if(v2==0) return(0);
    return(MathLog(v1/v2)*1000);
 }

GetValMユーザ定義関数は、たとえば、USD/JPYの移動平均の値とUSD/CADの移動平均の値からCAD/JPYの移動平均の値を算出したいような場合には使用することができません。たとえば、USD/JPYの移動平均の値が110.00でUSD/CADの移動平均の値が1.1000である場合、CAD/JPYの移動平均の値を算出するには、USD/JPYの移動平均の値をUSD/CADの移動平均の値で割って110.00/1.1000=100.00と計算しなければなりません。GetValDユーザ定義関数はこのような計算方法によって2つの通貨ペアの移動平均の値から2つの通貨ペアを合成した通貨ペアの移動平均の値を算出しています。

このように、2つの通貨ペアの移動平均の値から2つの通貨ペアを合成した通貨ペアの移動平均の値を算出するためには通貨ペアによってGetValMユーザ定義関数とGetValDユーザ定義関数を使い分けなければなりません。

  1. GetOriginTimeユーザ定義関数
 datetime GetOriginTime(){
    // 過去に Ku-chart で起点となる時刻を決めていたら、それを再度利用する。
    datetime ret;

    if(DoNotOverwrite && ObjectFind(objname) != -1){
       ret = ObjectGet(objname, OBJPROP_TIME1);
       if(ret >= Time[Bars-1] && ret <=Time[0]) return(ret);
    }

    ret = Time[OriginTime];
    ObjectCreate(objname, OBJ_TEXT, 0, ret, 0);
    return(ret);
 }

ObjectFind関数は指定された名称のオブジェクトを含むウィンドウの番号を返します。指定された名称のオブジェクトを含むウィンドウが存在しない場合には-1を返します。

ObjectGet関数は第1引数として指定された名称のオブジェクトの第2引数として指定された名称のプロパティの値を返します。

Bars変数の値はチャートに描画されている全ての足の本数となります。

Time変数にはチャートに描画されているそれぞれの足の開始時間が最新の足から過去に向かって格納されています。

GetOriginTimeユーザ定義関数は基準となる足の開始時間を返します。基準となる足の開始時間は基本的には最新の足から過去に向かって最新の足を0本目の足と考えた場合におけるOriginTime変数の値本目の足の開始時間です。GetOriginTimeユーザ定義関数は1回目のインジケータの値の計算においてのみ呼び出されて基準となる足が決定され、2回目以降の計算においては1回目において決定されたものをそのまま使用します。したがって、結局のところ、基準となる足は1回目のインジケータの値の計算の時点での最新の足から過去に向かって最新の足を0本目の足と考えた場合におけるOriginTime変数の値本目の足ということになります。

  1. start関数1
 int start()
   {
    // ~を描くのに、WindowsFind を呼んでいるが、本当は init では呼んではいけないので、startから呼びなおす。
    static bool startInit= false;
    if(!startInit)  init();
    startInit= true;

start関数はMQL4の特殊な関数の1つであり、インジケータを表示しているチャートで新しいティックが発生する度に呼び出される関数です。

start関数にインジケータの値を計算するコードを記述し、基本的にはインジケータを表示しているチャートで新しいティックが発生する度にインジケータの再計算が必要な部分の値を計算するようにします。

start関数でinit関数を呼び出しているのはコメントに書かれている通りの理由のためのようです。startInit変数を静的変数として宣言することで1回目のstart関数の呼び出しが終わっても値が破棄されないで保持されるようにし、2回目以降の呼び出しでも1回目の呼び出しで設定された値が参照できるようにしています。init関数の呼び出しの前にstartInit変数の値がtrueでないか確認し、呼び出しの後にstartInit変数の値をtrueに設定しているため、init関数は1回目の呼び出しでしか呼び出されることはありません。このようにしてstart関数の中で初期化処理を実行することもできなくはないです。

  1. start関数2 ~局所変数~
    int i;
 //----
    double EURUSD,EURJPY,EURCHF,EURGBP,USDJPY,USDCHF,GBPUSD,CHFJPY,GBPCHF,GBPJPY,AUDUSD,AUDCHF,AUDJPY,GBPAUD,EURAUD,AUDCAD,USDCAD,GBPCAD,EURCAD,CADCHF,CADJPY;
    double AUDNZD,EURNZD,GBPNZD,NZDCAD,NZDCHF,NZDJPY,NZDUSD;
    datetime t1;
    static datetime t2 = 0;
    static datetime HstWriteTime = 0;

start関数で使用する局所変数の宣言です。

i変数はインジケータの値を計算するためのループにおけるループ変数です。

EURUSD変数以下の一連の変数とAUDNZD変数以下の一連の変数は中間的な値を保持するための変数です(何故2行に分かれているのかふしぎ)。

t1変数はインジケータの値を計算する足の開始時間を保持するための変数です。

t2変数は基準となる足の開始時間を保持するための変数です。静的変数として宣言されているのはstart関数の1回目の呼び出しで基準となる足が決定された後、2回目以降の呼び出しでも同一の足を使うため、関数を抜けても値が破棄されないで保持されるようにするためです。

HstWriteTime変数はインジケータの値の計算を行った最後の時点におけるチャートの最新の足の開始時間を保持するための変数です。この変数はインジケータの値を計算する足の範囲を制御するために使用します。初期値としては0が設定されますが、インジケータの値の計算が行われる度に適切な値が設定されます。また、チャート全体のインジケータの値を再計算したい場合などにはこの変数の値を0にします。関数を抜けても値が破棄されないで保持されるようにするため、静的変数として宣言されています。

  1. start関数3 ~計算範囲~
    int indicator_counted = IndicatorCounted();
    int limit = MathMin(MaxBars,Bars);
    if(HstWriteTime !=0) limit = MathMin(limit,Bars-indicator_counted+1);
    if(limit <1) return;

インジケータの値を計算する足の範囲を決定しています。このカスタムインジケータをチャートに追加し、初めてインジケータの値を計算する場合にはチャート全体のインジケータの値を計算しなければなりませんが、新しいティックが発生したような場合にはチャートの最新の足しか変化しないため、チャート全体のインジケータの値を計算する必要はなく、最新の足についての値しか計算する必要はありません。

IndicatorCounted関数は前回のstart関数の呼び出しから今回の呼び出しまでの間に更新されなかった足の本数を返します。そのため、逆にBars - IndicatorCounted()は更新された足の本数を返します。たとえば、1回目の呼び出しではチャートに描画されている全ての足が更新されているということになるので、チャートに描画されている全ての足の本数が返ります。2回目以降の呼び出しではチャートに描画されている最新の足のみが更新されているということになるので、基本的には1が返ります。ただし、MetaTrader 4の内部的な問題により、2回目以降の呼び出しで、前回の呼び出しと比較して新たな足が生成されている場合には、2が返ります(しかし、残念なことにMetaTrader 4にはこの辺りの実装にバグがあるようで、このような値が返らない場合もあるようです。もしかしたら、間違っているのはMetaTrader 4の解説文書の方で実装ひいては実際の挙動の方が正しいのかもしれませんが)。

MathMin関数は引数として与えられた2つの値でより小さいものを返します。

上で説明したように、MaxBars変数は最新の足から過去に向かって最新の足を含めないで最大で何本目の足までに対してインジケータの値を計算するか(以後「最大値」と言います)を表すパラメータです。

limit変数は最新の足から過去に向かって最新の足を含めないで実際に何本目の足までに対してインジケータの値を計算するか(以後「実際値」と言います)を格納する変数です。

HstWriteTime変数の値はチャート全体のインジケータの値を計算しなければならない場合には0となり、そうでない場合にはそれ以外の値となります。

したがって、チャート全体のインジケータの値を計算しなければならない場合には実際値は最大値とチャートに描画されている全ての足の本数でより小さいものとなります(実際値がチャートに描画されている全ての足の本数となる場合、実際値は最新の足を含まないのでインジケータの値を計算する足の本数がチャートに描画されている全ての足の本数を超過しそうなのですが、あまり気にしないでおきましょう)。そうでない場合には実際値は最大値とチャートに描画されている全ての足の本数と前回のstart関数の呼び出しから今回の呼び出しまでの間に更新された足の本数+1(ただし、Bars - IndicatorCounted() + 1を使用するため上述したMetaTrader 4のIndicatorCounted関数の仕様の影響を受ける)の内最小のものとなります。基本的にはこれらの値の内最小なのはBars - IndicatorCounted() + 1であるはずです。1を加えているのはMetaTrader 4のサンプルコードがそのようになっているためであると思われます。また、そのように実装すると上述したMetaTrader 4のバグによる問題が発生しなくなるという報告があります(余分なループなのか?のコメント欄など)。

4行目でインジケータの値を計算する足が1本もない場合には関数を抜けます。

  1. start関数4 ~チャートの足の本数の確認~
    //if(checkDownloadBars()<0) return;
    int ret = checkDownloadBars();
    if(ret <-1) return;//bar が不足。
    if(ret ==2) { limit = MathMin(MaxBars,Bars);HstWriteTime = 0;Print("reload");}//全リロード

checkDownloadBarsユーザ定義関数はこのカスタムインジケータの値を計算するのに必要な通貨ペアのチャートの足の本数を確認します。checkDownloadBarsユーザ定義関数の返り値が-1未満である場合には関数を抜け(これは明らかに0の間違いではないかと思います・・・)、2である場合にはチャート全体のインジケータの値を再計算するようにします。

  1. start関数5 ~基準となる足の決定~
    //Print("limit=",limit);
    if(t2 == 0) t2 = GetOriginTime();//Time[OriginTime];
    ObjectCreate(objname, OBJ_TEXT, 0, t2, 0);

基準となる足を決定します。t2変数が0である場合には基準となる足が未確定なので、GetOriginTimeユーザ定義関数を呼び出して基準となる足を決定してt2変数に基準となる足の開始時間を格納します。

  1. start関数5 ~インジケータの値を計算するループ~
    for(i=limit;i>=0;i--){

このループでインジケータの値を計算します。1回のループで1つの足に対する値を計算します。また、古い足から新しい足に向かって計算していきます。

  1. start関数6 ~インジケータの値を計算する足~
       t1 = Time[i];

インジケータの値を計算する足の開始時間をt1変数に代入しています。インジケータの値を計算する足は最新の足から過去に向かって最新の足を0本目の足と考えた場合におけるi変数の値本目の足となります。

  1. start関数6 ~中間的な値の計算~
       EURUSD = GetVal(sEURUSD,t1,t2);
       USDJPY = GetVal(sUSDJPY,t1,t2);
       USDCHF = GetVal(sUSDCHF,t1,t2);
       GBPUSD = GetVal(sGBPUSD,t1,t2);
       AUDUSD = GetVal(sAUDUSD,t1,t2);
       USDCAD = GetVal(sUSDCAD,t1,t2);
       NZDUSD = GetVal(sNZDUSD,t1,t2);

       EURJPY = GetValM(sEURUSD,sUSDJPY,t1,t2);
       EURCHF = GetValM(sEURUSD,sUSDCHF,t1,t2);
       EURGBP = GetValD(sEURUSD,sGBPUSD,t1,t2);
       CHFJPY = GetValD(sUSDJPY,sUSDCHF,t1,t2);
       GBPCHF = GetValM(sGBPUSD,sUSDCHF,t1,t2);
       GBPJPY = GetValM(sGBPUSD,sUSDJPY,t1,t2);
       AUDCHF = GetValM(sAUDUSD,sUSDCHF,t1,t2);
       AUDJPY = GetValM(sAUDUSD,sUSDJPY,t1,t2);
       AUDCAD = GetValM(sAUDUSD,sUSDCAD,t1,t2);
       EURCAD = GetValM(sEURUSD,sUSDCAD,t1,t2);
       GBPCAD = GetValM(sGBPUSD,sUSDCAD,t1,t2);
       GBPAUD = GetValD(sGBPUSD,sAUDUSD,t1,t2);
       EURAUD = GetValD(sEURUSD,sAUDUSD,t1,t2);
       CADCHF = GetValD(sUSDCHF,sUSDCAD,t1,t2);
       CADJPY = GetValD(sUSDJPY,sUSDCAD,t1,t2);
       AUDNZD = GetValD(sAUDUSD,sNZDUSD,t1,t2);
       EURNZD = GetValD(sEURUSD,sNZDUSD,t1,t2);
       GBPNZD = GetValD(sGBPUSD,sNZDUSD,t1,t2);
       NZDCAD = GetValM(sNZDUSD,sUSDCAD,t1,t2);
       NZDCHF = GetValM(sNZDUSD,sUSDCHF,t1,t2);
       NZDJPY = GetValM(sNZDUSD,sUSDJPY,t1,t2);

中間的な値を計算します。中間的な値は対象となっている全ての通貨から作ることのできる全ての通貨ペアに対する、基準となる足からインジケータの値を計算する足までの間に移動平均が変動した割合(を加工したもの)です。

  1. start関数7 ~中間的な値の計算2~
       if(!UseNZD){
          AUDNZD = 0;EURNZD = 0;GBPNZD = 0;NZDCAD = 0;NZDCHF = 0;NZDJPY = 0;NZDUSD = 0;
       }
       if(!UseCAD){
          EURCAD = 0;USDCAD = 0;CADJPY = 0;CADCHF = 0;GBPCAD = 0;AUDCAD = 0;NZDCAD = 0;
       }
       if(!UseCHF){
          EURCHF = 0;USDCHF = 0;CHFJPY = 0;GBPCHF = 0;AUDCHF = 0;CADCHF = 0;NZDCHF = 0;
       }
       if(!UseAUD){
          EURAUD = 0;AUDUSD = 0;AUDJPY = 0;AUDCHF = 0;GBPAUD = 0;AUDCAD = 0;AUDNZD = 0;
       }
       if(!UseEUR){
          EURUSD = 0;EURJPY = 0;EURCHF = 0;EURGBP = 0;EURAUD = 0;EURCAD = 0;EURNZD = 0;
       }
       if(!UseUSD){
          EURUSD = 0;USDJPY = 0;USDCHF = 0;GBPUSD = 0;AUDUSD = 0;USDCAD = 0;NZDUSD = 0;
       }
       if(!UseJPY){
          EURJPY = 0;USDJPY = 0;CHFJPY = 0;GBPJPY = 0;AUDJPY = 0;CADJPY = 0;NZDJPY = 0;
       }
       if(!UseGBP){
          EURGBP = 0;GBPUSD = 0;GBPCHF = 0;GBPJPY = 0;GBPAUD = 0;GBPCAD = 0;GBPNZD = 0;
       }

それぞれの通貨の値を計算しない設定になっている場合には、その通貨を構成通貨とする通貨ペアに対する中間的な値は0となります。

  1. start関数8 ~値の計算~
       // マイナスの値はチャート化できないので ZeroLevelShift(100)を足す      
       EURAV[i]=( EURUSD+EURJPY+EURCHF+EURGBP+EURAUD+EURCAD+EURNZD)/Pairs + ZeroLevelShift;
       USDAV[i]=(-EURUSD+USDJPY+USDCHF-GBPUSD-AUDUSD+USDCAD-NZDUSD)/Pairs + ZeroLevelShift;
       JPYAV[i]=(-EURJPY-USDJPY-CHFJPY-GBPJPY-AUDJPY-CADJPY-NZDJPY)/Pairs + ZeroLevelShift;
       CHFAV[i]=(-EURCHF-USDCHF+CHFJPY-GBPCHF-AUDCHF-CADCHF-NZDCHF)/Pairs + ZeroLevelShift;
       GBPAV[i]=(-EURGBP+GBPUSD+GBPCHF+GBPJPY+GBPAUD+GBPCAD+GBPNZD)/Pairs + ZeroLevelShift;
       AUDAV[i]=(-EURAUD+AUDUSD+AUDJPY+AUDCHF-GBPAUD+AUDCAD+AUDNZD)/Pairs + ZeroLevelShift;
       CADAV[i]=(-EURCAD-USDCAD+CADJPY+CADCHF-GBPCAD-AUDCAD-NZDCAD)/Pairs + ZeroLevelShift;
       NZDAV[i]=(-EURNZD+NZDUSD+NZDJPY+NZDCHF-GBPNZD+NZDCAD-AUDNZD)/Pairs + ZeroLevelShift;

       if(UseZeroCheck){
          // ゼロより小さくなっていたら警告する。
          if(EURAV[i] < 0){ Alert("ERROR: EURAV is less than ZeroLevel. ",EURAV[i]);return;}
          if(USDAV[i] < 0){ Alert("ERROR: USDAV is less than ZeroLevel. ",USDAV[i]);return;}
          if(JPYAV[i] < 0){ Alert("ERROR: JPYAV is less than ZeroLevel. ",JPYAV[i]);return;}
          if(CHFAV[i] < 0){ Alert("ERROR: CHFAV is less than ZeroLevel. ",CHFAV[i]);return;}
          if(GBPAV[i] < 0){ Alert("ERROR: GBPAV is less than ZeroLevel. ",GBPAV[i]);return;}
          if(AUDAV[i] < 0){ Alert("ERROR: AUDAV is less than ZeroLevel. ",AUDAV[i]);return;}
          if(CADAV[i] < 0){ Alert("ERROR: CADAV is less than ZeroLevel. ",CADAV[i]);return;}
          if(NZDAV[i] < 0){ Alert("ERROR: NZDAV is less than ZeroLevel. ",NZDAV[i]);return;}
       }
    }

それぞれの通貨の値を計算します。それぞれの通貨を構成通貨とする通貨ペアに対する中間的な値を累積し、通貨ペアの数で割り、ZeroLevelShift変数の値を加えます。ただし、それぞれの通貨が通貨ペアの中で主軸通貨である場合には加算し、決済通貨である場合には減算することに注意してください。これは、たとえば、EUR/USDの変動率が5%であると言う場合、EURがUSDに対してプラス方向に5%変動したことを表しますが、逆に、USDの方はEURに対してマイナス方向に5%変動したことを表しているからです。また、2つ以上の割合を累積する場合にはそれらの割合を掛け合わせなければなりません。たとえば、+5%、+8%、-4%という変動率を累積する場合には1.05*1.08*0.96=1.08864と計算して+8.864%としなければなりません。しかしながら、中間的な値を計算する際に自然対数を取っているので、掛け合わせる代わりに足し合わせることになります。ZeroLevelShift変数の値を加えているのは原点を0からZeroLevelShift変数の値分だけ移動するためです(つまり、ZeroLevelShift変数の値が原点の値となります)。

このようにしてインジケータの値が計算されます。

UseZeroCheck変数がtrueである場合には、それぞれの値がマイナスでないか確認し、マイナスである場合には警告を発出して関数を抜けます。

これ以降の部分

これ以降の部分でインジケータの値のファイルへの書き込みなどを行っていますが、本質的に重要な部分ではないかと思うので、解説はここまでで終わりにしたいと思います。