زبان MQL4 برای تازه‌کارها، سوالات سخت با جملات ساده

زبان MQL۴ برای تازه‌کارها. سوالات سخت با جملات ساده

مقدمه

این دومین مقاله از سری مقالات “زبان MQL4 برای تازه‌کارها” است. در اولین مقاله،  “MQL4 Language for Newbies. Introduction”، توضیح دادیم که با MQL4 چه کارهایی می‌شود انجام داد، یاد گرفتیم اسکریپت‌های ساده بنویسیم، و فهمیدیم متغیر چیست، و کار با آن‌ها را فرا گرفتیم، همچنین توابع آنالیزشده، آرایه‌ها، آرایه‌ها و متغیرهای توکار، سیکل‌های “for” و شرط‌های ساده و پیچیده را با هم دیدیم.

اکنون به بررسی ساختار پیچیده و پیشرفته‌تر این زبان می پردازیم، گزینه‌های جدید را یاد می‌گیریم و می‌بینیم که چگونه می‌توان از آن‌ها در مسائل مربوطه استفاده کرد. با سیکل جدید “while” آشنا می‌شوید، نوع جدید شرط، یعنی “switch” را ‌می‌بینید، عملگرهای “break” و “continue” را هم خواهید شناخت. علاوه بر اینها، یاد می‌گیرید توابع خودتان را بنویسید و با آرایه‌های چند بُعدی نیز کار کنید. و به‌عنوان دسر کار، توضیحی درباره‌ی یک پیش‌پردازنده، آماده کرده‌ام.

نصیحت

اگر مقاله‌ی قبلی را کامل مسلط نیستید، خواندن این مقاله را آغاز نکنید. چراکه اشتباهات زیادی خواهید داشت و باز هم چیزی متوجه نخواهید شد. این مقاله بر اساس اصول [نسبتاً] قدیمی نوشته شده، پس عجله نکنید! البته بگذارید خیال‌تان را کمی راحت کنم-چیزهای سخت جدیدی که هنگام یادگیری با آ‌ن‌ها مواجه می‌شوید، فریب‌آمیز هستند! زمانی می‌رسد که دیگر نگران این نیستید که سیکل‌ها چگونه نوشته می‌شوند، و چه شرط‌هایی باید گذاشت – بلکه همه‌چیز ماشینی انجام خواهد شد. هرچه بیشتر با این زبان کار کنید، استفاده از آن برایتان راحت‌تر می‌شود.

نوع جدیدی از سیکل‌ها – while

می‌خواهم به این موضوع اشاره کنم که سیکل ‘for’ که در مقاله‌ی قبلی توضیح داده شد، یک سیکل جهانی است و می‌تواند جایگزینی برای هر نوع سیکل دیگری باشد که اکنون یاد می‌گیریم. اما همیشه راحت و مفید هم نیست! بعضی‌وقت‌ها، بهتر این است که از while استفاده کنیم. خیلی زود متوجه خواهید شد استفاده از کدام سیکل منطقی‌تر است. بیایید یک کار را به دو روش انجام دهیم: با استفاده از هر دو سیکل می‌خواهیم حجم کلی تمام کندل‌ها را پیدا کنیم و تفاوت‌ها را ببینیم:

// using the cycle for
double sum = 0.0;
 
for(int a = 0; a < Bars; a++)
    sum += Volume[a];
 
// now using while, the result is the same
double sum = 0.0;
int a = 0;
 
while(a < Bars)
  {
    sum += Volume[a];
    a++;
  }

اکنون می‌بینید که، شمارنده اعلام شده‌است و به‌شکل جداگانه از آن استفاده می‌شود. while در اینجا نشان می‌دهد وقتی شرط true باشد، سیکل ادامه خواهد یافت. در اینجا شکل کلی [این مسئله] را می‌بینیم:

while(condition of cycle fulfillment)
  {
    code;
  }

در اینجا مثالی ساده‌تر داریم:

while(I havent eaten up the apple) // condition
  {
    // what to do, if the condition is not fulfilled
bite more;                 
  }

در واقع، سیکل while تفاوتش با سیکل for در نبود شمارنده[۱] است. اگر به شمارنده نیازی ندارید، از while استفاده کنید، هرچند، ضرورتی هم ندارد! برای مثال، من خودم اغلب از while همراه با شمارنده استفاده می‌کنم، و در واقع، بحث سلیقه است. مثلاً در خصوص for، اگر بدنه‌ی سیکل[۲] شامل فقط یک دستور[۳] باشد، می‌توانید آکولادها[۴] را حذف کنید. همچنین، برای توسعه‌ی خودتان، معنای‌کلمه‌ی iteration (تکرار) را همیشه به‌یاد داشته باشید؛ که یکی از چندین عملگری (تکرارهایی) است که سیکل [انجام] آن را برعهده دارد. به‌عبارت دیگر، یک بار اجرای بدنه‌ی سیکل یعنی، یک تکرار (iteration) انجام شده‌است.

نوع جدید شرط‌ها – switch

درست همانند موضوع سیکل‌ها، باید به این نکته اشاره کنیم که switch می‌تواند جایگزین ترکیبی آشنا برای شما، یعنی شرط‌های if و else، بشود. از ساختار switch وقتی استفاده می‌کنیم که نیاز داشته باشیم کارهایی را بسته به مقدار یک متغیر، انجام دهیم. دقیقاً مثل حالت عادی سوئیچ کردن در یک مایکروویو است. برای مثال تصور کنید که اکسپرتی را دارید می‌نویسید، و این اکسپرت عملکرد خود را بسته به شرایط بازار تغییر می‌دهد. بیایید متغییر int marketState را مسئول این کار قرار دهیم. ممکن است معانی زیر را در بر گیرد:

  • ۱- رو به بالا
  • ۲- رو به پایین
  • ۳- صاف

مهم نیست که این پوزیشن چگونه تعریف شده‌است. وظیفه‌ی ما این است که یک نوع مکانیزم را در اینجا تحقق ببخشیم، تا بسته به شرایط بازار اکسپرت کارهای مرتبط را انجام دهد. بسیار خب، می‌دانید این کار را چگونه انجام دهید. واضح‌ترین حالت این است:

if(marketState == 1)
  {
    // trading strategy for an uptrend
  }
else 
    if(marketState == 2)
      {
        // strategy for a downtrend
      }
    else 
        if(marketState == 3)
          {
            // strategy for a flat
          }
        else
          {
            // error: this state is not supported!
          }

در اینجا، برخی از خصوصیات را داریم:

  • تمام شرط‌ها با یک متغیر، و با همین متغیر، انجام شده‌اند؛
  • تمام شرط‌ها با یکی از معانی‌ایی که متغیر می‌تواند آن را بپذیرد، آن را (متغیر را) مقایسه می‌کنند.

بنابراین، تمام این‌ها به ساختار switch نیز برمی‌گردد. در اینجا کد را داریم که با استفاده از switch، نتیجه همان است:

switch(marketState)
  {
    case 1:  // trading strategy for an uptrend
             break;
    case 2:  // strategy for a downtrend
             break;
    case 3:  // strategy for a flat
             break;
    default: // error: this state is not supported!
             break;
  }

به‌یاد داشته باشید که اول تعریف می‌کنیم که کدام متغیر مقایسه خواهد شد:

// switch - key word, marketState - 
// a variable for comparison
switch(marketState)

و سپس مشخص می‌کنیم که در موارد خاص چه کاری باید انجام شود:

case 1:                        // case - key word; 
   // trading strategy         // if marketState is equal to 1, then 
   // for an uptrend           // perform this code
   break;                      // key word that indicates  
                               // the end of actions in this case
 
case 2:                        // if marketState is equal to 2, then
    // startegy for            // perform this
    // a downtrend          
    break;                     // end
 
case 3:                        // identical
    // strategy for flat
    break;
 
default:                       // otherwise, perform this
    // error: this   
    // state is not 
    // supported!
    break;

در یک دید کلی، switch چنین فرمی دارد:

switch(a variable for comparison)
  {
    case [a variable value]:
    // a code for this case 
    break;
   
    case [another value of the variable]
    // a code for this case
    break;
   
    default:
    // a code for all other cases
    break;
  }

وقتی می‌خواهید یک متغیر را با چند مقدار و یک بلوک خاص از کد که مرتبط با یک مقدار است، مقایسه کنید، از switch استفاده کنید. در موارد دیگر، از همان ترکیب معروف شرط‌های if و else استفاده کنید. بعضی‌وقت نیاز دارید یک کد  را در چندین مقدار یک متغیر اجرا کنید. برای مثال، اگر marketState == 1 or 2، آنگاه یک کد خاص اجرا شود. این کار را با استفاده از swtich اینگونه می‌توان انجام داد:

switch(marketState)
  {
    case 1:  // if marketState is equal to 1
    case 2:  // or if marketState is equal to 2, then
             // perform this
             break;
 
    default: // in any other case perform 
             // this code
             break;
  }

عملگرها: continue و break

تا الان فقط عملگر break را دیده‌ایم. از این عملگر برای فرار از بدنه‌ی switch استفاده می‌شود. علاوه بر این، می‌توانید از این عملگر برای فرار از یک سیکل هم استفاده کنید. برای مثال، اگر نیاز ندارید که یک سیکل، در برخی‌ شرط‌ها اجرا شود. فرض کنید نیاز داریم حجم اولین کندل‌هایی را پیدا کنیم که ۱۰۰۰ پوینت را در بر بگیرند. باید چنین کدی بنویسیم:

int a = 0;
double volume = 0.0;
 
while(volume < 1000.0)
  {
    volume += Volume[a]; // equivalent to volume = volume + Volume[a]; 
    a++;
  }
 
// now variable "a" includes the amount of bars, the volume of their sums 
// is no less than 1000 points

حالا بیایید کدی مشابه کد قبلی بنویسیم و از عملگر break استفاده کنیم:

int a = 0;
double volume = 0.0;
 
while(a < Bars)
  {
    // if the volume is exceeds 1000 points, then
    if(volume > 1000.0) // exit the cycle   
        break;            
 
    volume += Volume[a];
    a++;
  }

همانطور که می‌بینید، استفاده از عملگر break آسان است و به شما اجازه می‌دهد از تکرارهای ناخواسته‌ی سیکل دور باشید. عملگر مفید دیگری به‌نام continue داریم که برای “حذف” تکرارهای ناخواسته می‌توان از آن استفاده کرد. فرض کنید می‌خواهیم یک حجم کلی را محاسبه کنیم، اما نباید حجم کندل‌هایی که در لحظه‌ی انتشار اخبار مهم ایجاد شده‌اند را، در محاسبات خود بیاوریم.

همانطور که می‌دانید، اخبار مهم، حجم زیادی را با خود دارند و به کندل‌ها منتقل می‌کنند. بیایید بگوییم بچه‌ی کَفِ وال‌استریت هستیم و فرض را بر این بگذاریم که حجم کندل اگر بیشتر از ۵۰ پوینت باشد، آنگاه آن کندل، خبر است. برای حل این موضوع، از عملگر continue استفاده می‌کنیم:

int a = -1;
double volume = 0.0;
 
while(a < Bars)
  {
    a++;
    // if the volume exceeds 50 points, then it must 
    // be news, omit it 
    if(Volume[a] > 50.0)          
        continue;          
    volume += Volume[a];
  }

می‌بینید که استفاده از عملگر continue، شاید خیلی چیز خاصی نباشد، اما بعضی‌وقت‌ها کمک‌تان می‌کند، و البته واضح است که این اسکریپت برای تایم‌فریم‌های کوچک نوشته شده‌است.

نوشتن توابع خودمان

اما چرا به آن‌ها نیاز داریم؟ واقعیت این است که اغلب می‌توانید کپی کد خودتان را پیدا کنید. به‌عبارت دیگر، از یک سری از دستورات در موارد مختلف استفاده خواهید کرد. برای اینکه زمان و انرژی خود را حفظ کنید، می‌توانید این کد کپی‌شده را در یک تابع مجزا بنویسید. و هر وقت به آن نیاز داشتید، به‌راحتی نام تابع را نوشته و بقیه چیزها انجام خواهند شد. بیایید ببینیم این موضوع چگونه است…

فرض کنید می‌خواهید رنگ یک کندل را پیدا کنید. و می‌دانیم که کندل سفید، آن کندلی است که بالاتر از بازشدن، بسته شده‌است، و برای کندل سیاه، برعکس این قضیه صادق است. بیایید کد تعیین رنگ یک کندل را بنویسیم:

bool color; // as there are only 2 variants 
            // (white or black candlestick), 
            // then suppose that the velue 
            // false corresponds to a black 
            // candlestick, and true - white
 
if(Close[0] > Open[0])
    color = true;      // white candlestick
    
if(Open[0] > Close[0])
    color = false;     // black candlestick

همه‌ی کار همین بود، اکنون متغیر رنگ، رنگِ آخرین کندل را در خود دارد . برای تعیین رنگ کندل دیگر، برای مثال، کندل یکی مانده به آخر، می‌بایستی شاخص ۰ را به ۱ تغییر دهیم. اما آیا هر بار که می‌خواهید رنگ یک کندل را پیدا کنید، می‌خواهید این کد را وارد کنید؟ و اگر ده‌ها مورد اینطوری باشد، چه می‌کنید؟ به همین دلیل است که به توابع نیاز داریم. بیایید فکر کنیم چگونه باید عمل کرد…

چنین تابعی باید یک استدلال[۵] را بپذیرد – شاخص یک کندل، که می‌خواهید رنگ آن را تعیین کنید، و رنگ را به‌شکل یک متغیر از نوع منطقی برگرداند. تصور می‌کنیم این تابع نوشته شده، و آن را فعال کرده‌ایم:

bool color; // here will be the color of a wanted candlestick
 
color = GetColor(0);

همانطور که شاید حدس زده باشید، نام تابع ما GetColor است. در فراخوانی این تابع، می‌خواهیم رنگ آخرین کندل را پیدا کنیم. به همین دلیل است که تنها استدلال ما برابر با صفر است. تابع، رنگ یک کندل را برمی‌گرداند، و ما به‌یک‌باره یک واگذاری را ایجاد کرده‌ایم. لحظه‌ی بسیار مهمی است! یک متغیر درون تابع ایجاد شده‌است، سپس مقدار آن، فراخوانی تابع را جایگزین خواهد کرد.

در نهایت اینکه، فراخوانی تابع و کد تعیین تابع، که در بالا توضیح داده شدند، همان نتیجه را به ما خواهند داد – متغیر رنگ، رنگِ آخرین کندل را در خود دارد، اما استفاده از تابع انرژی کمتری از ما می‌گیرد.

اکنون به‌شکل غیرمنتظره‌ای برمی‌گردیم به کد یک اسکریپت خالی. در واقع، این اسکریپت خالی، هم‌اکنون توضیح کاملی از تابع start() را در خود دارد. جالب این است که تمام این مدت مشغول نوشتن اسکریپت‌ها در این تابع بوده‌اید! وقتی اسکریپت خود را اجرا می‌کنید، نرم‌افزار تابع start() را فعال می‌کند. بیایید کد یک اسکریپت‌خالی را بررسی کنیم:

int start()

این خط بسیار مهم است! و نام تابع در آن است. به‌عبارت دیگر، یک کلمه‌ی کلیدی، که آن را برای فعال‌سازی این تابع می‌نویسید. در اینجا، آن کلمه، “start” است. همینطور نوع مقدار بازگشتی نیز در آن است- یعنی int. و این یعنی اینکه بعد از اجرای این تابع، مقدارهایی از نوع int به ما برمی‌گردد. کروشه‌ها شامل لیستی از استدلال‌ها هستند، اما در اینجا تابع هیچ‌ نوع پارامتری را نمی‌پذیرد.

سپس در آکولادها، توضیحات تابع را می‌بینید، به‌عبارت دیگر، کدی را می‌بینید که در فراخوانی تابع اجرا خواهد شد:

{
    //----
    // a code that will be performed 
    // at the function call.
    //----
    return(0);
  }

واضح است که کد را در بدنه‌ی تابع start() نوشته‌ایم. در انتهای تابع عملگر return را می‌بینیم که مقدار تابع را برمی‌گرداند. در اینجا، صفر را برمی‌گرداند.

اکنون به فرم کلی نوشتن یک تابع نگاهی بیندازیم:

[type of return value] [function name] ([list of arguments])
  {
    // function code
    return([a value, which the function returns]);
  }

اکنون به کندل‌های خودمان و تابع GetColor برمی‌گردیم. به کد این تابع نگاهی بیندازید:

bool GetColor(int index)
  {
    bool color;
    if(Close[index] > Open[index])
        color = true;      // white candlestick
    if(Open[index] > Close[index])
        color = false;     // black candlestick
    return(color);
  }

بیایید خط اول را بررسی کنیم:

bool GetColor(int index)

در اینجا داریم: bool – نوع مقدار بازگشتی؛ GetColor – نام تابع؛ int – نوع استدلال؛ index – نام استدلال. به‌یاد داشته باشید، index را درون بدنه‌ی تابع استفاده می‌کنیم، اما در فراخوانی تابع، به این نام هرگز اشاره‌ای نمی‌شود. برای مثال:

bool lastColor = GetColor(0);

سپس:

{
   bool color;
   if(Close[index]>Open[index])
       color=true;      // white candlestick
   if(Open[index]>Close[index])
       color=false;     // black candlestick

بدنه‌ی این تابع، یک کد عمومی است، که در هر بار فراخوانی تابع اجرا می‌شود. پس از آن:

return(color);
}

عملگر return مشخص می‌کند که تابع چه چیزی را باید برگرداند. مقدار بازگشتی، باید مرتبط با نوع باشد، که در همان ابتدا آن را تعیین کردیم. در صورت نیاز، از چند عملگرد return در یک تابع استفاده کنید. برای مثال:

bool GetColor(int index)
  {
   if(Close[index] > Open[index])
       return(true);      // white candlestick
   if(Open[index] > Close[index])
       return(false);     // black candlestick
  }

مشخص است که استفاده از چند عملگر return به ما اجازه داده از متغیر color اجتناب کنیم. علاوه بر این، در عملگرد return می‌توانید حتی از بیان منطقی نیز استفاده کنید:

return(Close[index] > Open[index]);

این کار ممکن است، زیرا عملگرهای مقایسه‌ای، متغیرهایی از نوع منطقی (true or false) را نیز برمی‌گردانند، درست مانند دیگر توابع رایج. به‌نظر سخت می‌آید اما به‌زودی به آن عادت می‌کنید.

اکنون برمی‌گردیم به لیست استدلال‌ها. فقط از استدلال int index در تابع‌مان استفاده کرده‌ایم. اگر می‌خواهید از چند استدلال استفاده کنید، یک به یک آن‌ها را نام برده و با کاما جدا کنید:

bool SomeСomplicatedFunction(int fistArgument, int secondArgument, 
                             sting stringArgument)

برای اینکه به استدلال‌ها ارجاع دهیم، از نام آن‌ها، همانند تابع قبلی استفاده کنید. وقتی تابعی را با چندین استدلال فرا می‌خوانید، به‌ ترتیبِ آورده‌شدنِ استدلال‌ها دقت کنید: هیچ چیزی را با دیگری ترکیب نکنید!   اگر تابع نباید هیچ مقداری را برگرداند، از کلیدواژه void استفاده کنید. دقت کنید که در اینجا از عملگر return استفاده نشده‌‌است:

void function()
  {
    // code
  }

یک مورد دیگر: ممکن است مقادیر را برای استدلال‌های تابع، روی پیش‌فرض تعیین کنید. این چیست؟ فرض کنید تابع پیچیده‌ای نوشته‌اید که شامل ۵ استدلال است، که روی‌ عملکرد تابع تاثیر می‌گذارند. اما از چند استدلال آخر، تقریباً همیشه با مقادیر یکسان استفاده می‌کنید. فقط برای ۲۴ فراخوان تابع نیاز به استفاده از مقادیر متفاوت دارید. برای اینکه هر بار مقادیر استدلال‌های آخر را مشخص نکنیم، که تقریباً همیشه مشابه هستند، مقادیر پیش‌فرض استدلال‌ها استفاده می‌شوند.

در چنین شرایطی، خیلی ساده، استدلال‌های آخر را حذف می‌کنید، طوریکه انگار وجود نداشته‌اند، هرچند در حقیقت از آنها استفاده شده، اما مقادیر آنها، پیش‌فرض تعیین شده‌است. وقتی با چنین شرایط خاصی مواجه می‌شوید، تمام استدلال‌ها را مشخص کنید. بیایید ببینیم چگونه می‌توانیم تابعی را با استدلا‌های پیش‌فرض، اعلام کنیم:

void someFunction(int argument1, int argument2, 
                  int specialArgument = 1)
  {
    // code
  }

همانطور که می‌بینید، همه‌چیز ساده است: مقدار مورد نیاز را به استدلال مورد نیاز اختصاص داده و اکنون می‌توانیم آن را در فراخوانی تابع حذف کنیم:

someFunction(10,20);   // we omitted the last argument, but 
                       // actually it is assigned a value by default
 
someFunction(10,20,1); // this activation is fully identical to the previous one
 
someFunction(10,20,2); // here we indicate another value,  
                       // it is a rare case

می‌توانید تا حد دلخواه، مقادیر پیش‌فرض استدلال‌ها را مشخص کنید. اما یک قانون مهم را به‌یاد داشته باشید. باید همه‌ی آن‌ها را در انتها قرار دهید. برای مثال:

void someFunction(int argument1, int argument2, 
                  int specialArgument = 1)   // all right
 
void someFunction(int argument1, int argument2 = 10, 
                  int specialArgument=1)     // all right
 
void someFunction(int argument1, int argument2 = 10, 
                  int specialArgument)     // wrong! default
                                           // values must stay
                                           // at the end of the 
                                           // list of arguments
                                                                           
void someFunction(int argument1 = 0, int argument2 = 10, 
                  int specialArgument = 1) // you can assign  
                                           // default values  
                                           // to all arguments

آرایه‌های چندبُعدی

حین برنامه‌نویسی اغلب از آرایه‌ها استفاده می‌کنید و در موارد اصلی، آرایه‌های تک‌بُعدی کافی هستند. اما در برخی موارد، به دوبُعدی‌ها یا سه‌بعدی‌ها نیاز دارید. اکنون یاد می‌گیریم چگونه از آنها استفاده کنیم.

برای شروع بیایید یک آرایه‌ی تک‌بعدی، تجدید نظر در اعلام (revise declaration)، مقداردهی اولیه، شاخص‌ها و مقدار را، به‌صورت بصری ببینیم:

زبان MQL۴ برای تازه‌کارها، سوالات سخت با جملات ساده

 

هر آرایه‌ی تک‌بعدی را می‌توان به‌شکل یک ردیف از مقادیر هم‌نوع، نمایش داد. نگاه کنید، چگونه منابع مختلف برای یک آرایه یک‌بعدی پردازش می‌شوند:

زبان MQL۴ برای تازه‌کارها، سوالات سخت با جملات ساده

زبان MQL۴ برای تازه‌کارها، سوالات سخت با جملات ساده

 

آرایه‌های دوبعدی مانند جداول معمولی هستند. نگاه کنید:

زبان MQL۴ برای تازه‌کارها، سوالات سخت با جملات ساده

همانطور که در تصویر می‌بینید، آرایه‌های دوبعدی در حال حاضر، دو شاخص (two indexes) برای بازگشت به مقدار[۶]، دارند: اولین شاخص ردیف را مشخص می‌کند، و دومی ستون را. همانند آرایه‌های یک‌بعدی، از لیستی از مقادیر برای مقداردهی اولیه، استفاده شده‌است. اینگونه به مقادیر سلول‌های جدول ارجاع داده می‌شود:

زبان MQL۴ برای تازه‌کارها، سوالات سخت با جملات ساده

زبان MQL۴ برای تازه‌کارها، سوالات سخت با جملات ساده

زبان MQL۴ برای تازه‌کارها، سوالات سخت با جملات ساده

زبان MQL۴ برای تازه‌کارها، سوالات سخت با جملات ساده

همانطور که دیدید، همه‌چیز واضح است. بیایید بررسی کنیم، چگونه می‌شود تمام مقادیر یک آرایه‌ی دوبعدی را بررسی کرد. از دو سیکل باید استفاده کنیم:

int array2D[3][3]={10,20,30,
                   ۴۰,۵۰,۶۰,
                   ۷۰,۸۰,۹۰};
 
for(int y=0;y<3;y++)
   for(int x=0;x<3;x++)
      MessageBox("array2D["+y+"]["+x+"]="+array2D[y][x]);

در این مثال، ارجاع (referencing)، به سمت پایین از چپ به راست می‌رود. صرفِ تمرین، بیایید سعی کنیم جهت را عوض کنیم، برای مثال، به سمت بالا.

در آرایه‌های سه‌بعدی فقط حضور یک شاخص بیشتر برای ارجاع به مقادیر سلول‌ها را داریم. یک آرایه‌ی سه‌بعدی می‌تواند به‌سادگی به‌شکل چندین جدول (آرایه‌های دوبعدی) نمایش داده شود. بدین صورت می‌توانیم تمام اِلمان‌های یک آرایه‌ی سه‌بعدی را بررسی کنیم:

int array3D[3][3][3] = {11, 12, 13,
                        ۱۴, ۱۵, ۱۶,
                        ۱۷, ۱۸, ۱۹,
 
                        ۲۱, ۲۲, ۲۳,
                        ۲۴, ۲۵, ۲۶,
                        ۲۷, ۲۸, ۲۹,
 
                        ۳۱, ۳۲, ۳۳,
                        ۳۴, ۳۵, ۳۶,
                        ۳۷, ۳۸, ۳۹};
 
for(int z = 0; z < 3; z++)
    for(int y = 0; y < 3; y++)
        for(int x = 0; x < 3; x++)
            MessageBox("array3D["+z+"]["+y+"]["+x+"]=" + 
                       array3D[z][y][x]);

به‌طور کامل آرایه‌های دوبعدی و سه‌بعدی را مطالعه کنید، زیرا مبحث بسیار مهمی است. یک بار دیگر خیلی دقیق و موشکافانه، به تصاویر و توضیحات آن‌ها توجه کنید. بسیاری از مسائل را با استفاده از آرایه‌ها حل کردیم، بنابراین جا دارد وقت کافی برای آن‌ها بگذارید چراکه در آینده نیز به کمک شما خواهند آمد. اگر اصول کار با آرایه‌ها را یاد گرفته باشید، دیگر مشکلی حتی برای کار کردن با آرایه‌های n بعدی هم نخواهید داشت.

برخی از توابع برای کار کردن با آرایه‌ها

بیایید با توابع ساده شروع کنیم.

int ArraySize(object array[]);

این تابع حجم (amount) اِلمان‌هایی که در آرایه هستند را، برمی‌گرداند، و با همه‌ نوع آرایه کار می‌کند. برای مثال،

// create two different arrays
int arrayInt[] = {1, 2, 3, 4};           
double arrayDouble[] = {5.9, 2.1, 4.7};
// here store the amount of elements 
int amount;                        
 
amount = ArraySize(arrayInt);      // note: 
                                   // to define a specific 
                                   // array, you need to indicate
                                   // only its name.
                                   // Now amount is equal to 4
 
 
amount = ArraySize(arrayDouble);   // amount is equal to 3

تابع بعدی:

int ArrayInitialize(object array[],double value);

ArrayInitialize

یک مقدار را به تمام اِلمان‌های آرایه اختصاص می‌دهد، و حجم اِلمان‌ها را، به اِلمان‌هایی برمی‌گرداند که یک مقدار برایشان اختصاص یافته است.  از این تابع همراه با آرایه‌هایی از نوع int و double استفاده کنید.

بعدی:

int ArrayMaximum(double array[], int count = WHOLE_ARRAY, 
                 int start = 0);
int ArrayMinimum(double array[], int count = WHOLE_ARRAY, 
                 int start = 0);

این دو تابع شاخص حداکثری و حداقلی مقدار سلول را برمی‌گردانند. برای استفاده از آنها، خیلی ساده مشخص کنید در کدام آرایه باید در جستجوی آنها باشیم:

int array[] = {10, 100, 190, 3, 1};
// will be returned 1, because array[1] - maximal value
ArrayMaximum(array);
// will be returned 4, because array[4] - minimal value
ArrayMinimum(array);

بعدی:

int ArrayDimension(object array[]);

با استفاده از این توابع، می‌توانید ابعاد یک آرایه را تعیین کنید، به‌عبارت دیگر، می‌توانید تعیین کنید، یک‌بعدی است، دوبعدی است یا n بعدی. برای مثال:

int array1D[15];
int array4D[3][3][3];
 
ArrayDimension(array1D); // get 1
ArrayDimension(array3D); // 3

در اینجا توابع پیچیده‌تر و پرکاربردتری را داریم:

int ArraySort(double&array[], int count = WHOLE_ARRAY, int start = 0,
              int sort_dir = MODE_ASCEND);

این تابع، اِلمان‌ها را مرتب می‌کند. اگر شما مستقیماً استدلال‌ها را روی پیش‌فرض مشخص نکنید، برای مثال اینگونه:

int array[5] = {1, 10, 5, 7, 8};
 
ArraySort(array);

اِلمان‌ها به‌صورت صعودی (ascending) مرتب خواهند شد. می‌توانید از پارامترهای اضافی برای مشخص کردن عملکرد تابع استفاده کنید:

  • int count – تعداد اِلمان‌هایی که باید مرتب شوند
  • int start – شاخص یک اِلمان که از آن مرتب‌سازی باید آغاز شود
  • int sort_dir – جهتِ مرتب‌سازی (صعودی – MODE_ASCEND یا نزولی – MODE_DESCEND)

اینجا باید تعجب کنید: MODE_ASCEND و MODE_DESCEND دیگر چیست؟ int احتمال قوی integer است! با توجه به این، اصلاً نگران نباشید، همه‌چیز در بخش بعدی، “پیش‌پردازنده”، شفاف خواهد شد. برای مثال، اگر می‌خواهید ۵ اِلمان را، از دومی، به‌صورت نزولی مرتب کنید، چیزی مثل این را نیاز دارید:

ArraySort(array, 5, 1, MODE_DESCEND);

و آخرین تابع برای امروز:

int ArrayCopy(object&dest[], object source[], int start_dest = 0,
              int start_source=0, int count=WHOLE_ARRAY);

از این تابع برای کپی کردن یک آرایه به آرایه دیگر استفاده می‌شود. بیایید پارامترهای واجب را با هم ببینیم:

  • dest[] – که در آن آرایه کپی خواهد شد
  • source[] – که از آن آرایه کپی خواهد شد

پارامترهای اختیاری:

  • start_dest – شاخص یک اِلمانِ آرایه، که درون آن کپی انجام می‌شود
  • start_source – شاخص یک اِلمانِ آرایه، که از آن کپی انجام می‌شود
  • int count – تعداد اِلمان‌ها برای کپی

این تابع تعداد اِلما‌ن‌های کپی‌شده را برمی‌گرداند. از ArrayCopy بسیار با دقت استفاد کنید: مطمئن شوید که آرایه‌ها، وقتی دارید درون آن‌ها کپی انجام می‌دهید، به‌اندازه‌ی کافی ظرفیت دارند!

پیش‌پردازنده

این دیگر چیست؟! پیش‌پردازنده یک مکانیزم خاص است که برای پردازش سورس کد درنظر گرفته شده‌است. به‌عبارت دیگر، ابتدا پیش‌پردازنده یک کد را آماده می‌کند و سپس آن را برای کامپایل‌شدن منتقل می‌کند. امروز، یک گزینه‌ی کاربردی دیگر یاد خواهیم گرفت – constants.

فایده‌ی این کار چیست؟ برای فهمیدن این موضوع، بیایید مثالی از بخش switch را به‌یاد بیاوریم:

switch(marketState)
  {
    case 1:
    // trading strategy for an uptrend
    break;
 
    case 2:
    // strategy for a downtrend
    break;
 
    case 3:
    // strategy for a flat
    break;
 
    default:
    // error: this state is not supported!
    break;
  }

در اینجا مکانیزمی را فعال کردیم که به طُرق مختلف بسته به وضعیت بازار، عمل ‌می‌کند. یادتان آمد؟ بنابراین، ساده‌تر و توصیفی‌تر این است که به‌جای ۱، ۲ و ۳، چیزی مانند TREND_UP، TREND_DOWN، FLAT، را بنویسیم:

switch(marketState)
  {
    case TREND_UP:
    // trading strategy for an uptrend
    break;
 
    case TREND_DOWN:
    // strategy for a downtrend
    break;
 
    case FLAT:
    // strategy for a flat
    break;
 
    default:
    // error: this state is not supported!
    break;
  }

در این شرایط، فهمیدن سورس کد بسیار ساده‌تر و شفاف‌تر است، مگه نه؟ پس، constants اجازه می‌دهد که قبل از کامپایل کردن، TREND_UP، TREND_DOWN، FLAT، را با مقادیر مرتبط، یعنی ۱، ۲ و ۳، جایگزین کنیم. تمام کاری که باید انجام دهید این است که مشخص کنید پیش‌پردازنده چه چیزی را باید تغییر دهد. این کار به‌وسیله‌ی دستورات پیش‌پردازنده[۷] انجام خواهد شد، که از یک نماد خاص یعنی “#” آغاز می‌شود. دستورات پیش‌پردازنده باید در ابتدای سورس فایل، همراه با دیگر دستورات، قرار بگیرند. بیایید به مثالی کامل از استفاده از constants نگاهی بیندازیم:

//+------------------------------------------------------------------+
//|                                                 preprocessor.mq4 |
//|         Copyright © ۲۰۰۷, Antonio Banderass. All rights reserved |
//|                                               banderassa@ukr.net |
//+------------------------------------------------------------------+
#property copyright "Copyright © ۲۰۰۷, Antonio Banderass. All rights reserved"
#property link      "banderassa@ukr.net"
 
#define TREND_UP   1
#define TREND_DOWN 2
#define FLAT       3
 
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
   MessageBox("TREND_UP=" + TREND_UP + " TREND_DOWN=" + TREND_DOWN + 
              " FLAT=" + FLAT);
 
   return(0);
  }

لطفاً به‌یاد داشته باشید که اعلام constants را در ابتدای فایل، زیر دیگر دستورات پیش‌پردازنده، قرار دادیم. بیایید اعلام را از نزدیک بررسی کنیم:

#define TREND_UP 1

ابتدا کلیدواژه‌ی #define را می‌نویسیم. این کار به پیش‌پردازنده نشان می‌دهد که بعد از آن، اعلام constant را داریم. سپس، نام constant را می‌نویسیم – معین‌کننده‌ی هویت آن را – به‌عبارت دیگر، یک کلمه که با آن به مقدار constant، رجوع می‌کنیم. در اینجا TREND_UP است. مقدارِ ۱ بعد از آن می‌آید. اکنون، وقتیکه پیش‌پردازنده TREND_UP را در سورس کد می‌بیند، آن را با ۱ جایگزین می‌کند، و همین رویه برای تمام دیگر constantها هم به همین شکل است. در اینجا سورس کد مثال‌مان را قبل از پردازش توسط یک پیش‌پردازنده، می‌بینید:

int start()
  {
   MessageBox("TREND_UP=" + TREND_UP + " TREND_DOWN=" + 
              TREND_DOWN + " FLAT=" + FLAT);
   return(0);
  }

و سپس:

int start()
  {
   MessageBox("TREND_UP=" + 1 + " TREND_DOWN=" + 2 + 
              " FLAT=" + 3);
   return(0);
  }

اکنون باید فهمیده باشید که MODE_ASCEND و MODE_DESCEND از بخش قبلی چه معنایی می‌دهند. اینها constantهایی هستند با مقادیر مرتبط.

نتیجه‌گیری

چیزهایی زیادی از این مقاله یاد گرفتید: مدل جدید سیکل – while؛ مدل جدید شرط – switch؛ عملگرهای break و continue. یاد گرفتید که توابع خودتان را بنویسید و با آرایه‌های چند بعدی کار کنید. یاد  گرفتید از constantها چگونه استفاده کنید. تمام این‌ها ابزارهای اصلی شما برای نوشتن چیزهای پیشرفته‌تر هستند، مثل اندیکاتور و اکسپرت. به همین دلیل باید مطمئن شوید تمام مطالب را کاملاً یاد گرفته‌اید زیرا علاوه بر اهمیت مطالب، از آن‌ها دائماً در آینده استفاده خواهید کرد.

این مقاله دارای فایل پیوست است.

[۱]) Counter

[۲]) Cycle body

[۳]) Instruction

[۴]) Bracers

[۵]) Argument

[۶]) Reference to the value

[۷]) Preprocessor directives

M23admin

→ خواندن مطلب قبلی

مــبـــانــی کـدنـویـســی اکسپرت هِــــج

خواندن مطلب بعدی ←

روشی برای ترسیم خطوط ساپورت و رزیستنس

۲۶ مورد نظر

نوشتن نظر شما

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *