//+------------------------------------------------------------------+ //| Radars_Multi-Dimensional_Array_Practise.mq4 | //| Copyright © 2013, Radar | //| | //+------------------------------------------------------------------+ #property copyright "Copyright © 2013, Radar" #property link "http://www.stevehopwoodforex.com" #property show_inputs /* I started writing this script on Thursday, 8th August, 2013, at 11:30pm, and completed the final code on Saturday, 10th August, 2013 at 10:30pm. I did have a 12 hour break in between... Four hours' sleep, then 6 hours of the daily grind, (I work as a fettler, so it really is a grind). When I started, I didn't quite have a grip on single dimension arrays... After 5 hours, 2 litres of coffee, and 30 grams of tobacco, I could *finally* write data into - and read data out of - a 2 dimension array, (and in the correct order, too, (yup, I wrote before I read)). As I coded, I stumbled over things that most writers of tutorials assume that you already know, so I ended up writing lots and lots and lots of comments to push all the concepts and methods deep into my head. ========================================================================================================================================================= When you create a multi-dimensional array, only the First Dimension (Dimension 0) can be resized, (if it has been declared in the right place, details to come). And now for some serious confuzzlementalization... It is commonly stated that the First Dimension (Dimension 0) is the only dimension that can be sorted (even the mql4 documentation says it), well, that's not quite the full story... In actual fact, it's the data in the First Element (Element 0) of the Last Dimension (the First Element (Element 0) of the Second Dimension (Dimension 1) in this case) that all sorting functions operate on. The elements in the First Dimension (Dimension 0) are containers, each holding an array of (in this case) 2 Elements... It only took me me all of Thursday night to work the implications of this little tidbit of information ;) To illustrate... Dimension 0 Dimension 1 Elements Elements __[0]______ [0]-----------------------------------/ \ \__[1] \ \ __[0]--------- Only the data in these 3 elements are used to sort... The containers, (Elements of Dimension 0) [1]-----------------------------------/ / are re-arranged according to that sort order, taking their contents with them. \__[1] / / I hope that makes sense ;) __[0]_____/ [2]-----------------------------------/ \__[1] ============================================================================================================================================================== A few things about this example script... Since you can't mix types, (strings, integers, and doubles), and I wanted to store the timeframe in minutes and the Stochastic value, I've declared the Array as "double MyTf[][2]". Having the first set of [] empty allows us to resize the array when we need to. If you manipulate Arrays inside a function, (add or remove elements, change values in elements, etc.), those changes will not be seen outside of that function, unless you declare the function like so: void DoSomething(double& MyArray[][]). To use the function, you call it as normal, with the name of the Array you wish to work with in parentheses, like so: DoSomething(MyTf). If your array holds strings, you would declare the function like this... void DoSomething(string& MyArray[][]), and for integer arrays... void DoSomething(int& MyArray[][]). The function type doesn't have to be void, it can be any of the standard function types, (int, double, bool, string, etc.). I really should mention that it's the "&" that allows us to manipulate the array within the function, and have those changes be seen globally. There's still some things I haven't gotten to work yet, such as working with string arrays, but I'll stumble across something that makes it easy to soak it in, then I'll update this :) I've been looking at code for around 18 months now, and what usually happens is that my eyes glaze over, and a swarm of hornets, (the planes! The planes!) start buzzing around inside my head. Occasionally, I can get away with a bit of CPE coding, but it's usually a lot of work for a minor change. This is the first bit of code that I've written from scratch, and it took less time to write the code than it did to write this intro! :D Anyways, I hope this helps when you first try playing with arrays:) If you see any errors in my assumptions or explanations, or have anything that can be added to this, please let me know, and I'll update this and re-upload, thanks :) Have fun! Radar =8^) */ extern bool RestoreDataFromDiscOnly=false; // Set to true for testing the Back-up/Restore functions extern int MagicNumber=28956237; extern bool UseM1=true; extern bool UseM5=true; extern bool UseM15=true; extern bool UseM30=true; extern bool UseH1=true; extern bool UseH4=true; extern bool UseD1=true; extern bool UseW1=true; extern bool UseMN1=true; extern int Delay=500; int a=0, i, b, SizeOfArray, handle; string BackupName; double M1=1; double M5=5; double M15=15; double M30=30; double H1=60; double H4=240; double D1=1440; double W1=10080; double MN1=43200; double CTF, LTF, HTF, CurrentStoch, EmptyElements; double GetStoch[]; // <-- Creates a 1 Dimensional Array, which can be resized as needed. double MyTf[][2]; // Creates a 2 Dimensional Array, named MyTf, to hold Doubles... The First Dimension (Dimension 0) doesn't have // it's number of Elements declared yet, which makes it Dynamic. It can be resized as needed anywhere in the programme. // The Second Dimension (Dimension 1) has had it's number of Elements fixed to 2, (Element 0, and Element 1). I *think* that you have // to declare the size of the 2nd, (and 3rd & 4th) Dimension here, as only the 1st dimension can be dynamic. (Hey! I'm still learning here!) ;) //+------------------------------------------------------------------+ //| Script initialization function | //+------------------------------------------------------------------+ int init() { if(!RestoreDataFromDiscOnly) { Alert("We are in Normal Operation Mode"); ArrayResize(MyTf,9); // Set the First Dimension, (Dimension 0) to hold 9 Elements int Dim0Size=ArrayRange(MyTf,0); // I put these two lines here for the following 2 alerts... int Dim1Size=ArrayRange(MyTf,1); // I could have just written the alerts like this... Alert("Dimension 0 has ", Dim0Size, " Elements"); // Alert("Dimension 0 has ", ArrayRange(MyTf,0), " Elements"); Alert("Dimension 1 has ", Dim1Size, " Elements"); // Alert("Dimension 1 has ", ArrayRange(MyTf,1), " Elements"); PopulateDimension1_Element0(); } if(RestoreDataFromDiscOnly) { Alert("We are in Restore Mode"); RestoreDoubleArrays(MyTf); ReadDimension1_Element0(); ReadDimension1_Element1(); Alert("We are in Restore Mode"); } //---- return(0); } //End init //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ int deinit() { //---- //---- return(0); } // End deinit //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int start() { //---- if(!RestoreDataFromDiscOnly) { Alert("We are in Normal Operation Mode"); Sleep(Delay); ReadDimension1_Element0(); Sleep(Delay); PopulateDimension1_Element1(); Sleep(Delay); ReadDimension1_Element1(); Sleep(Delay); BackUpDoubleArrays(MyTf); Alert("We are in Normal Operation Mode"); RetrieveEntryStoch(); } //---- return(0); } // End start //+------------------------------------------------------------------+ void PopulateDimension1_Element0() { // There's gotta be a better way to do this... Stuffed if I know what it is, though :( Well, at least I got to learn how to use ArraySort :D // First thing.... Put all timeframes into the First Dimension (Dimension 0) of the array... MyTf[0,0]=M1; MyTf[1,0]=M5; MyTf[2,0]=M15; MyTf[3,0]=M30; MyTf[4,0]=H1; MyTf[5,0]=H4; MyTf[6,0]=D1; MyTf[7,0]=W1; MyTf[8,0]=MN1; // Then, we set all Elements that are holding disabled timeframes to Zero... if(UseM1==false) MyTf[0,0]=0; if(UseM5==false) MyTf[1,0]=0; if(UseM15==false) MyTf[2,0]=0; if(UseM30==false) MyTf[3,0]=0; if(UseH1==false) MyTf[4,0]=0; if(UseH4==false) MyTf[5,0]=0; if(UseD1==false) MyTf[6,0]=0; if(UseW1==false) MyTf[7,0]=0; if(UseMN1==false) MyTf[8,0]=0; TrimDoubleArrays(MyTf); // We then send the array off to have elements containing 0 removed. } // End PopulateDimension1_Element0 void ReadDimension1_Element0() { for(int i=(ArrayRange(MyTf,0)-1); i>=0; i--) { CTF=MyTf[i,0]; // Sets CTF (Current TimeFrame) to hold the contents of the current Element in the First Dimension (Dimension 0). Alert("We are now reading Element ", i, " of Dimension 0"); if(!CTF==M1) {LTF=MyTf[(i-1),0];} else {LTF=0;} // Sets LTF (Lower TimeFrame to hold the contents of the next Lower Element in Dimension 0. if(!CTF==MN1) {HTF=MyTf[i+1,0];} else {HTF=0;} // Sets HTF (Higher TimeFrame) to hold the contents of the next Higher Element in Dimension 0. Alert("Lower Timeframe = ", LTF); // Shows the contents of the Next Element of Dimension 0 (First Dimension). Alert("Working Timeframe =", CTF); // Shows the contents of the Current Element of Dimension 0. Alert("Higher Timeframe = ", HTF); // Shows the content of the Previous Element of Dimension 0. Sleep(Delay); Alert("Finished reading Element 0 of Dimension 1 for this loop of the indicator"); } } // End ReadDimension1_Element0 void PopulateDimension1_Element1() { for(int i=(ArrayRange(MyTf,0)-1); i>=0; i--) { MyTf[i,1]=(iStochastic(Symbol(),MyTf[i,0],10,3,3,MODE_SMA,1,MODE_SIGNAL,0)); // Writing the Stochastic value of each enabled timeframe to Dimension 1 // Element 1 } } void ReadDimension1_Element1() { for(int i=(ArrayRange(MyTf,0)-1); i>=0; i--) { CurrentStoch=MyTf[i,1]; // Reading from Dimension 0 Alert("Stoch for ", MyTf[i,0], " is ", CurrentStoch); // Sleep(Delay); Alert("Finished reading Element 1 of Dimension 1 for this loop of the indicator"); } }// End ReadDimension1_Element1 void TrimDoubleArrays(double& MyArray[][]) // The & allows this function to accept a reference to the array you wish to be trimmed by this function. // Many thanks to Dietcoke on http://www.stevehopwoodforex.com for showing this to me :) For 3D Arrays, you would declare it { // like so... void (double& MyArray[][][]) 4D Arrays = void (double& MyArray[][][][]). if(ArrayRange(MyArray,0)>1) { for(int i=(ArrayRange(MyArray,0)-1); i>=0; i--) // Sets i to the total number of Elements in the First Dimension (Dimension 0), tests if i is greater than or { // equal to Zero, and runs the following code if true, then subracts 1 from i... Continues running the code // and subtracting 1 from i until i is less than Zero. EmptyElements=MyArray[i,0]; // Sets EmptyElements to hold the contents of each First Element (Element 0) in the Second Dimension (Dimension 1). // For EVERY element in the First Dimension, (Dimension 0), there is a corresponding First Element (Element 0) for the // Second Dimension (Dimension 1). if(EmptyElements==0) a++; // We count how many Elements hold the value 0. } ArraySort(MyArray,WHOLE_ARRAY,0,MODE_DESCEND); // We now sort the array so that all elements that hold Zero are placed at the end of the array int CurrentArrayRange=ArrayRange(MyArray,0); // Find out how many Elements in Dimension 0 Alert("Array Size is now ", CurrentArrayRange); // Send an alert to show the current range of the array (from Dimension 0, Element 0, to Dimension 0 // to Element ?? int NewArrayRange=CurrentArrayRange-a; // Subtract the number of Elements that hold 0 from the number of Elements currently in Dimension 0 ArrayResize(MyArray, NewArrayRange); // Chop the Elements that contain 0 off of the end of the array CurrentArrayRange=ArrayRange(MyArray,0); // Find out how many Elements in Dimension 0 Alert("Array Size is now ", CurrentArrayRange); // Send an alert to show the current range of the array (from Dimension 0, Element 0, to Dimension 0 // to Element ?? ArraySort(MyArray); // We now sort the array, lowest value into lowest element, highest value into highest element. } } // End TrimDoubleArrays double RetrieveEntryStoch() { a=ArrayRange(MyTf,0); double GetStoch[]; ArrayResize(GetStoch,a); for(int i=(ArrayRange(MyTf,0)-1); i>=0; i--) { GetStoch[i]=MyTf[i,1]; } a=ArrayRange(GetStoch,0); int MaxValue=ArrayMaximum(GetStoch); double StochMax=NormalizeDouble(GetStoch[MaxValue], Digits); Alert("The maximum stoch is ", StochMax); int MinValue=ArrayMinimum(GetStoch); double StochMin=NormalizeDouble(GetStoch[MinValue], Digits); Alert("The Minimum stoch is ", StochMin); for(i=(ArrayRange(GetStoch,0) -1); i>=0; i--) { b=b+GetStoch[i]; } double average=(NormalizeDouble(b, Digits))/(NormalizeDouble(a, 0)); Alert("The average Stoch value is ", average); } // End RetrieveEntryStoch void BackUpDoubleArrays(double& MyArray[][]) { ArrayResize(MyArray,20); // Resize array so that we know how many elements there are in total ((20 Elements in Dimension 0) * (2 Elements in Dimension 1) = 40) SizeOfArray=ArraySize(MyArray); // Get the total number of Elements in the Array Alert("MyTf Array has ", SizeOfArray, " Elements"); BackupName=StringConcatenate(MagicNumber, "_", Symbol(), "_", SizeOfArray, ".dat"); // I did it this way so I would know the size of the Array when restoring // it, (the idea being that if I knew how to manipulate strings, I could pull the size of the Array // out of the name before restoring it. Alert("The name of my backup is ", BackupName); handle=FileOpen(BackupName, FILE_BIN|FILE_WRITE); if(handle>0) { FileWriteArray(handle, MyArray, 0, SizeOfArray); // writing complete array to disc FileClose(handle); } TrimDoubleArrays(MyArray); } // End BackUpArray void RestoreDoubleArrays(double& MyArray[][]) // { BackupName=StringConcatenate(MagicNumber, "_", Symbol(), "_", "40", ".dat"); handle=FileOpen(BackupName, FILE_BIN|FILE_READ); if(handle>0) { FileReadArray(handle, MyArray, 0, 40); FileClose(handle); } TrimDoubleArrays(MyArray); } // End RestoreArray /*========================================================================================== The following 2 functions aren't used in this script, but I am using them in an EA I've modified. void StoreTrades(double& MyArray[][], double TicketNo) // Call this function from within the SendSingleTrade() function, once it has confirmed that the trade { // has been accepted by the broker. a=ArrayRange(MyArray,0); ArraySort(MyArray,WHOLE_ARRAY,0); if(NormalizeDouble(a,0)>NormalizeDouble(0,0)) { ArrayResize(MyArray,(a+1)); } ArraySort(MyArray,WHOLE_ARRAY,0,MODE_DESCEND); int b=ArrayRange(MyArray,0); MyArray[(b-1),0]=TicketNo; MyArray[(b-1),1]=(iStochastic(Symbol(),PERIOD_OSC,10,3,3,MODE_SMA,1,MODE_SIGNAL,0)); ArraySort(MyArray,WHOLE_ARRAY,0,MODE_DESCEND); } // End StoreTrades void ClearClosedTrades(double& MyArray[][], string strSymbol, int nMagic) // I call this from within the ManageTrades() function. { ArraySort(MyArray,WHOLE_ARRAY,0,MODE_DESCEND); a=ArrayRange(MyArray,0); double StillOpenTrades[][2]; ArrayResize(StillOpenTrades,a); for (int i=OrdersTotal()-1 ; i>=0 ; i--) { if (!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue; if ((OrderType() == OP_BUY)||(OrderType() == OP_SELL)) if (OrderMagicNumber() == nMagic) if (OrderSymbol() == strSymbol) { for(int b=(ArrayRange(MyArray,0)-1); b>=0; b--) { if(MyArray[b,0]==OrderTicket()) { StillOpenTrades[b,0]=NormalizeDouble(MyArray[b,0],0); StillOpenTrades[b,1]=NormalizeDouble(MyArray[b,1], Digits); } } } } ArraySort(StillOpenTrades,WHOLE_ARRAY,0,MODE_DESCEND); for(b=(ArrayRange(MyArray,0)-1); b>=0; b--) { // Set all Array Elements to Zero MyArray[b,0]=NormalizeDouble(0,0); MyArray[b,1]=NormalizeDouble(0,0); } for(int c=(ArrayRange(StillOpenTrades,0)-1); c>=0; c--) { //Copy all elements from StillOpenTrades to MyArray MyArray[c,0]=NormalizeDouble(StillOpenTrades[c,0],0); MyArray[c,1]=NormalizeDouble(StillOpenTrades[c,1], Digits); } ArrayResize(StillOpenTrades,0); // Not sure if this is needed... Did it anyways ;) ArraySort(MyArray,WHOLE_ARRAY,0,MODE_DESCEND); TrimDoubleArrays(MyArray); } // End ClearClosedTrades */