چگونه شناسایی و بازیابی خطاها در کد اکسپرت را راحت‌تر کنیم

چگونه شناسایی و بازیابی خطاها در کد

چگونه شناسایی و بازیابی خطاها در کد اکسپرت را راحت‌تر کنیم

توسعه‌ی اکسپرت‌های معاملاتی در زبان MQL4، از چندین جهت، اصلاً کار ساده‌ای نیست:

  • اول اینکه، الگوریتم‌سازی برای سیستم‌های ترید، چه سیستم ما آسان باشد، چه پیچیده، خود کار بسیار دشواریست چراکه باید جزئیات بسیار زیادی را مدنظر قرار داد، از خصوصیات اکسپرت گرفته تا محیط خاص متاتریدر ۴.
  • دوم اینکه، حتی وجود دیگر الگوریتم‌های اکسپرت، سختی کار، که هنگام انتقال الگوریتم توسعه‌داده‌شده به زبان برنامه‌نویسی MQL4 پدیدار می‌شود را از بین نمی‌برد.

باید نسبت به پلتفرم تریدمان، یعنی متاتریدر ۴، عادلانه رفتار کنیم – وجود این زبان برنامه‌نویسی برای نوشتن اکسپرت‌ها، نسبت به گزینه‌هایی که قبلاً داشتیم، قدم رو به جلوی بزرگی به‌حساب می‌آید. کامپایلر هم کمک بزرگی در دُرست نوشتن اکسپرت‌ها به ما می‌کند. بلافاصله بعد از کلیک کردن روی “Compile”، گزارشی درباره‌ی تمام خطاهای سینتکس در کد، به شما داده می‌شود. اگر با یک زبان تفسیری سروکار داشتیم، چنین خطاهایی را فقط حین کارکردن اکسپرت می‌توانستیم پیدا کنیم، و این باعث می‌شد سختی کار و زمان نوشتن اکسپرت بسیار بالا برود. با این حال، به‌غیر از خطاهای سینتکس، هر اکسپرتی ممکن است خطاهای منطقی نیز داشته باشد. اکنون می‌خواهیم با چنین خطاهایی سروکله بزنیم.

خطاهایی که هنگام استفاده از توابع توکار ممکن است ببینیم

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

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

  • Error 130 – ERR_INVALID_STOPS
  • Error 146 – ERR_TRADE_CONTEXT_BUSY

در شرایطی که اخطار اول ظاهر می‌شود، اکسپرت دارد تلاش می‌کند یک معامله‌ی انتظاری را بسیار نزدیک به بازار، درج کند. وجود چنین خطایی ممکن است به‌صورت خیلی جدی، در برخی از موارد، خصوصیات اکسپرت شما را خراب کند. برای مثال، فرض کنید، اکسپرتی که یک پوزیشن سودده باز کرده است، سود را بعد از هر ۱۵۰ پوینت به حساب واریز می‌کند. اگر در تلاش بعدی [اکسپرت]، خطای ۱۳۰ اتفاق بیفتد، و قیمت به stop level قبلی برگردد، شما از سود قانونی خود محروم خواهید شد. علی‌رغم وجود چنین چیزی، این خطا را می‌توان از همان ابتدا برطرف کرد، و روش انجام این کار، قرار دادن تابعی در کد اکسپرت است که این تابع، حداقل فاصله‌ی قابل‌ قبول بین قیمت و استاپ‌ها (مثل حد ضرر) را تنظیم می‌کند.

خطای دوم، یعنی “trade content is busy” را نمی‌توان کاملاً حذف کرد. وقتی چند اکسپرت در یک نرم‌افزار کار می کنند، با این شرایط مواجه هستیم. اگر اکسپرت اول و دوم همزمان با هم بخواهند کاری را انجام دهند، مثلاً پوزیشنی باز کنند، با این خطا روبه‌رو می‌شویم، بنابراین، این خطا باید همیشه بررسی شود.

پس، همیشه لازم است بدانیم که کدام یک از توابع توکار، هنگامی که اکسپرت مشغول کار است، خطایی را برمی‌گرداند.

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

#include <stderror.mqh>
#include <stdlib.mqh>
 
void logError(string functionName, string msg, int errorCode = -1)
  {
    Print("ERROR: in " + functionName + "()");
    Print("ERROR: " + msg );
    
    int err = GetLastError();
    if(errorCode != -1) 
        err = errorCode;
        
    if(err != ERR_NO_ERROR) 
      {
        Print("ERROR: code=" + err + " - " + ErrorDescription( err ));
      }    
  }

در ساده‌ترین حالت، باید آن را اینگونه استفاده کرد:

void openLongTrade()
  {
    int ticket = OrderSend(Symbol(), OP_BUY, 1.0, Ask + 5, 5, 0, 0);
    if(ticket == -1) 
        logError("openLongTrade", "could not open order");
  }

اولین پارامتر تابع ()logError، نام تابع را نشان می‌دهد، که در آن خطایی شناسایی شده‌است، که در اینجا می‌شود تابع ()openLongTrade. اگر اکسپرت ما، تابع ()OrderSend را در چند جا فرابخواند، فرصت این را خواهیم داشت که دقیقاً تعیین کنیم [در کجا] و در کدام مورد خطا رخ داده است. پارامتر دوم، توضیحات خطا را به ما می‌‌دهد و ما را قادر می‌سازد بفهمیم دقیقاً در کجای تابع openLongTrade، خطا شناسایی شده‌است. ممکن است توضیح کوتاهی ببینیم، یا با جزئیات، شامل مقادیر تمام پارامترهایی که به این تابع توکار منتقل شده‌اند، باشد.

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

 ERROR: in openLongTrade()
 ERROR: could not open order
 ERROR: code=138 - requote

به‌عبارت دیگر، به ما می‌گوید:

  1. در کدام تابع خطا رخ داده است؛
  2. خطا به چه چیزی برمی‌گردد (در اینجا تلاش برای باز کردن پوزیشن بوده است)؛
  3. دقیقاً چه خطایی ظاهر شده‌است (کد خطا و توضیحات آن).

اکنون بیایید سومین پارامتر آپشنالِ تابعِ ()logError را با هم ببینیم. وقتی می‌خواهید نوع خاصی از خطا را پردازش کنید، این پارامتر نیاز می‌شود؛ دیگر خطاها نیز، همانند قبل، در فایل گزارش ذخیره می‌شوند:

void updateStopLoss(double newStopLoss)
  {
    bool modified = OrderModify(OrderTicket(), OrderOpenPrice(), 
                newStopLoss, OrderTakeProfit(), OrderExpiration());
    
    if(!modified)
      {
        int errorCode = GetLastError();
        if(errorCode != ERR_NO_RESULT ) 
            logError("updateStopLoss", "failed to modify order", errorCode);
      }
  }

در اینجا در تابع ()updateStopLoss، تابع توکارِ ()OrderModify فراخوانده شده‌است. این تابع تفاوت‌های ناچیزی با ()OrderSend به‌لحاظ پردازش خطاها، دارد. اگر هیچ‌یک از پارامتر‌های یک معامله‌ی تغییریافته با پارامترهای کنونی آن تفاوتی نداشته باشد، این تابع خطای ERR_NO_RESULT را برمی‌گرداند. اگر چنین شرایطی در اکسپرت ما قابل قبول است، بهتر است این خطا را نادیده بگیریم. برای انجام این کار، مقداری که تابع ()GetLastError برگردانده است را آنالیز می‌کنیم. اگر خطایی با کد ERR_NO_RESULT رخ داد، هیچ‌چیزی داخل فایل گزارش نمی‌نویسیم.

هرچند، اگر خطای دیگری رخ داد، کمافی‌السابق، باید آن را گزارش دهیم. برای انجام این کار دقیقاً نتیجه‌ی تابع ()GetLastError را در یک متغیر متوسط ذخیره کرده و با استفاده از پارامتر سوم، آن را به تابع ()logError منتقل می‌کنیم. در حقیقت، تابع توکارِ ()GetLastError، به‌صورت خودکار، بعد از اینکه فراخوانده شد، آخرین کد خطا را صفر می‌کند. اگر کد خطا را داخل ()logError منتقل نکنیم، در فایل گزارش اکسپرت خطایی با کد ۰ و توضیح “no error” خواهیم دید.

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

تشخیص خطاهای منطقی

خطاهای منطقی در کد یک اکسپرت، به‌اندازه‌ی کافی دردسر درست خواهند کرد. نبودِ گزینه‌ای برای این مورد در اکسپرت، مبارزه با چنین خطاهایی را به کاری کاملاً ناخوشایند تبدیل کرده است. ابزار اصلی شناسایی چنین خطایی در حال حاضر، تابع توکار ()Print است. با استفاده از آن می‌توانیم مقادیر کنونی متغیرهای مهم را چاپ گرفته و جریان عملیاتی اکسپرت را ثبت کنیم. وقتی در حال اِشکال‌زدایی از یک اکسپرت حین تست با حالت بصری هستیم، تابع توکارِ ()Comment هم می‌تواند مفید باشد. این قانون است که اگر عملکرد اشتباه یک اکسپرت تایید شود، باید به‌طور موقت فراخوانی تابع ()Print را اضافه کرده، و در جاهایی که فکر می‌کنیم خطا آنجا ظاهر شده، وضعیت داخلی اکسپرت را ثبت ‌کنیم.

اما گاهی‌وقت‌ها شناسایی شرایطی که در آن خطا رخ داده، کار بسیار مشکلی است، برای انجام این کار بعضی‌وقت‌ها نیاز داریم یک دوجین از چنین فراخوان‌های شناسایی تابع ()Print را اضافه کنیم. و بعد از شناسایی مشکل و ریکاوری، فراخوان‌های تابع یا باید حذف شوند یا کامنت‌گذاری. این کار برای بیش از حد پُرنشدنِ فایل گزارش اکسپرت و کندنشدن سرعت تست آن ضروری است. اگر کد اکسپرت، برای ثبت کردن وضعیت‌های مختلف به‌صورت دوره‌ای، از قبل، تابع ()Print را در خود داشته باشد، شرایط حتی از این هم بدتر می‌شود. آنگاه فراخوانی موقت تابع ()Print را نمی‌شود با جستجوی ساده‌ی کلمه‌ی “Print” در کد اکسپرت، از آن حذف کرد. فرد باید تصمیم بگیرد تابع را پاک بکند یا نه.

برای مثال، هنگام ثبت خطاهای توابع ()OrderSend()، OrderModify، و ()OrderClose، کار بسیار خوبیست که در یک فایل گزارش، مقدار کنونی متغیرهای Bid و Ask را ثبت کنیم. این کار پیدا کردن دلایل برای جستجوی چنین خطاهایی، مانند ERR_INVALID_STOPS و ERR_OFF_QUOTES را راحت‌تر می‌کند.

برای ثبت این اطلاعات شناسایی در یک فایل گزارش، توصیه می‌کنم از تابع اضافی زیر استفاده کنید:

void logInfo(string msg)
  {
    Print("INFO: " + msg);
  }

زیرا:

  • اول اینکه، اکنون، چنین تابعی، در جستجو، با “Print” اشتباه گرفته نمی شود؛
  • دوم، این تابع یک ویژگی پُرکاربرد دیگر نیز دارد که بعداً به آن می‌پردازیم.

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

void openLongTrade(double stopLoss)
  {
    int ticket = OrderSend(Symbol(), OP_BUY, 1.0, Ask, 5, stopLoss, 0);
    if(ticket == -1) 
        logError("openLongTrade", "could not open order");
  }

در این شرایط، وقتیکه یک پوزیشن بلند باز می‌کنیم، واضح است که در عملکرد صحیح اکسپرت، مقدار پارامتر stopLoss نمی‌تواند بیشتر یا برابر با قیمت Bid (قیمت پیشنهادی خرید) کنونی باشد. به‌عبارت دیگر، این گزاره صحیح است که هنگام فراخوانی تابع ()openLongTrade ، این شرط که stopLoss < Bid است، همیشه برقرار است. می‌توانیم از این موضوع، همانطور که قبلاً در بخش نوشتن تابع آنالیزشده دیدیم، به این شکل استفاده کنیم:

void openLongTrade( double stopLoss )
  {
    assert("openLongTrade", stopLoss < Bid, "stopLoss < Bid");
    
    int ticket = OrderSend(Symbol(), OP_BUY, 1.0, Ask, 5, stopLoss, 0);
    if(ticket == -1) 
        logError("openLongTrade", "could not open order");
  }

به‌عبارت دیگر، گزاره‌ی خودمان را در کد با استفاده از تابع اضافی جدید، یعنی ()assert، وارد می‌کنیم. خود تابع کاملاً ساده است:

void assert(string functionName, bool assertion, string description = "")
  {
    if(!assertion) 
        Print("ASSERT: in " + functionName + "() - " + description);
  }

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

  • نام این تابع، که در آن شرط برآورده نشده‌است؛
  • توضیحات این شرط.

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

البته مثال داده‌شده تا حد ممکن ساده‌سازی شده‌است. اما امیدوارم که ایده را کاملاً شفاف منعکس کند. هرچه عملکرد اکسپرت بهتر می‌شود، واضح‌تر می‌بینیم که چگونه کار می‌کند و چه شرایط و پارامترهای ورودی‌ایی قابل قبول هستند، و چه چیزهایی نیستند. با استفاده از تابع ()assert و وارد کردن در آن کد یک اکسپرت، اطلاعات بسیار خوبی درباره‌ی جایی که منطق عملکرد اکسپرت به‌هم‌ریخته است، کسب می‌کنیم. علاوه بر این، تا اندازه‌ای، ضرورت اضافه و حذف کردن فراخوان‌های موقت تابع ()Print را از بین می‌بریم، زیرا تابع ()assert در فایل گزارش اکسپرت، فقط در لحظه‌ی پیدا شدن اختلاف در شرایط مورد انتظار، پیغام‌های شناسایی را تولید می‌کند.

یک روش کاربردی دیگر، استفاده از این تابع قبل از هر عملیات تقسیم است. در واقع، این خطا یا آن خطای منطقی بعضی‌وقت‌ها نتیجه‌شان می‌شود تقسیم [شدن] بر صفر. در این حالت، اکسپرت از کار می‌اُفتد و یک خط در فایل گزارش ظاهر می‌شود: “zero divide”. و اگر از این عملیات تقسیم بارها در کد استفاده شود، تشخیص آنکه کجا این خطا اتفاق افتاده، سخت می‌شود. در اینجا تابع ()assert ممکن است خیلی به‌درد بخورد. خیلی ساده فقط باید بررسی مربوطه را قبل از هر عملیات تقسیم، [درون کد] وارد کنیم:

حالا اگر تقسیم بر صفر داشته باشیم، فقط کافیست در فایل گزارش بگردیم تا ببینیم دقیقاً خطا کجا اتفاق افتاده است.

تحلیل فایل گزارش اکسپرت برای شناسایی خطاها

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

مایکروسافت اِکسل را باز کرده و فایل گزارش عملکرد اکسپرت را به‌شکل یک فایل با پسوند csv دانلود کنید، و به‌عنوان جداکننده، یک space یا چندspace  باید استفاده شود. اکنون “Autofilter” را فعال کنید. این کار به شما یک فرصت راحت می‌دهد تا نگاهی به گزینه‌های فیلتر در دو ستون مجاور (خودتان متوجه می‌شوید کدام ستون‌ها) داشته باشید، تا بفهمید آیا فایل گزارش حاوی خطا یا خطاهایی هست که نرم‌افزار آن خطا یا خطاها را درون آن ثبت کرده باشد یا خیر. تمام مدخل‌هایی که توابع ()logInfo()، logError، و ()assert، آن‌ها را تولید کرده‌اند، با پسوند (“INFO:”, “ERROR:”, “ASSERT:”) شروع می‌شوند. همچنین، مدخل‌های نرم‌افزار که درباره‌ی خطاها هستند را می‌توان به‌سادگی بین چند نوع از مدخل‌های نوعی درباره‌ی کار کردن با معاملات، دید.

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

نتیجه‌گیری

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

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

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

 

مقالات پیشنهادی :

M23admin

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

اندیکاتور Taichi – ایده‌ای ساده برای شکل‌دهی به مقادیر ایچی موکو

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

ویژگی‌های اکسپرت‌ها

۴۰ مورد نظر

نوشتن نظر شما

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