دورة البرمجة في الفوركس للمبتدئين – الدرس التاسع

Using Include Files

ضمن دورة البرمجة وللحفاظ على تنظيم وظائفنا لسهولة الإدراج في ملفات الشفرة المصدرية الخاصة بنا، سنضع الوظائف في ملف الإدراج. يمكن أن يتكون ملف الإدراج من تصريحات الوظائف، والوظائف المستوردة، وأي متغيرات عامة أو خارجية ترغب في تضمينها في خبير المستشار.

لا يتطلب ملفات الإدراج أي صياغة خاصة. يتم إعلان الوظائف والمتغيرات في ملف الإدراج تمامًا كما تفعل في أي ملف شفرة مصدري. لا يجب أن يحتوي ملف الإدراج على وظائف init() أو start() أو deinit(). يجب أن يكون للملف امتداد .mqh ويتم وضعه في المجلد \experts\include.

استخدام المكتبات
Using Libraries

المكتبة هي مجموعة مترجمة من الوظائف. في حين أن ملف الإدراج هو ملف شفرة مصدرية تتم “إدراج” محتوياته في الملف التنفيذي، فإن المكتبة هي تطبيق منفصل يحتوي على الوظائف المستوردة. لذلك، يجب أن يكون لديك كل من ملف الخبير المستشار التنفيذي وملف التنفيذي للمكتبة لتشغيل خبير المستشار الخاص بك.

تُخزَّن المكتبات في مجلد \experts\libraries. تحمل ملفات شفرة المصدر امتداد .mq4، ويحمل الملف التنفيذي امتداد .ex4. لا تحتوي المكتبات على وظائف start() أو init() أو deinit(). لتعلن عن ملف كمكتبة، يجب وضع توجيه معالج مسبق #property library في بداية الملف.

يتميز استخدام المكتبات بأنها تتم معالجتها، لذلك إذا كنت بحاجة إلى توزيع مكتبة وظائف، فيمكنك القيام بذلك دون عرض ملكيتك الفكرية كما يحدث إذا قمت بتوزيع ملف إدراج. كما يمكنك إصلاح الأخطاء في المكتبة دون الحاجة إلى إعادة ترجمة خبراء المستشارين الخاصة بك، طالما لم تقم بإجراء أي تغييرات على تصريحات الوظائف، مثل إضافة وإزالة الوسائط أو الوظائف.

هناك بعض العيوب المرتبطة باستخدام المكتبات أيضاً. بما أنها معالجة مسبقًا، فمن غير الممكن للمترجم التحقق من صحة المعاملات. لا يمكنك تحديد قيمة افتراضية للمعامل في وظيفة المكتبة، مما يعني أنه يجب تحديد قيمة لكل معامل في استدعاء الوظيفة. لا يمكنك استخدام المتغيرات الخارجية في المكتبة، أو إنشاء متغيرات بنطاق عالمي يمكن لخبير المستشار الخاص بك الوصول إليها.

ستحتاج إلى استخدام التوجيه #import لاستيراد وظائف المكتبة إلى خبير المستشار الخاص بك. إذا كانت المكتبة تحتوي على العديد من الوظائف، فمن المفضل إنشاء ملف إدراج يحتوي على تعليمات #import. وهذا يزيد من عدد الملفات التي ستحتاج إلى العمل بها. ما لم يكن لديك سبب وجيه لاستخدام المكتبات، فمن المقترح الالتزام باستخدام ملفات الإدراج لتخزين وظائفك.

يمكنك أيضًا استيراد الوظائف من مكتبات DLL لنظام Windows باستخدام توجيهات #import. يحتوي ملف الإدراج WinUser32.mqh الموجود في المسار \experts\includes على العديد من الأمثلة التي تستخدم لوظيفة MessageBox(). (سنناقش وظيفة MessageBox()). استخدام وظائف DLL هو استخدام متقدم لن نغطيه هنا. هناك مقالات على موقع MQL4 حول استخدام مكتبات DLL للمهتمين.

مصدر خبير المستشار البسيط
A Simple Expert Advisor (with Functions)

هذا هو مصدر خبير المستشار البسيط بوظائفه كما يظهر في ملف الشفرة المصدرية. سنفترض أن الوظائف التي تم إنشاؤها في هذا الفصل معلنة في ملف IncludeExample.mqh.

كود :

// Preprocessor
#include <IncludeExample.mqh>
// External Variables
extern bool DynamicLotSize = true;
extern double EquityPercent = 2;
extern double FixedLotSize = 0.1;
extern double StopLoss = 50;
extern double TakeProfit = 100;
extern int Slippage = 5;
extern int MagicNumber = 123;
extern int FastMAPeriod = 10;
extern int SlowMAPeriod = 20;
// Global Variables
int BuyTicket;
int SellTicket;
double UsePoint;
int UseSlippage;
// Init function
int init()
{
UsePoint = PipPoint(Symbol());
UseSlippage = GetSlippage(Symbol(),Slippage);
}
// Start Function
int start()
{
// Moving Average
double FastMA = iMA(NULL,0,FastMAPeriod,0,0,0,0);
double SlowMA = iMA(NULL,0,SlowMAPeriod,0,0,0,0);
// Calculate Lot Size
double LotSize = CalcLotSize(DynamicLotSize,EquityPercent,StopLoss, FixedLotSize);
LotSize = VerifyLotSize(LotSize);
// Buy Order
if(FastMA > SlowMA && BuyTicket == 0)
{
if(SellTicket > 0) int Closed = CloseSellOrder(Symbol(),SellTicket,UseSlippage);
SellTicket = 0;
BuyTicket = OpenBuyOrder(Symbol(),LotSize,UseSlippage,MagicNum ber);
if(BuyTicket > 0 && (StopLoss > 0 || TakeProfit > 0))
{
OrderSelect(BuyTicket,SELECT_BY_TICKET);
double OpenPrice = OrderOpenPrice();
double BuyStopLoss = CalcBuyStopLoss(Symbol(),StopLoss,OpenPrice);
if(BuyStopLoss > 0)
{
BuyStopLoss = AdjustBelowStopLevel(Symbol(),BuyStopLoss,5);
}
double BuyTakeProfit = CalcBuyTakeProfit(Symbol(),TakeProfit,OpenPrice);
if(BuyTakeProfit > 0)
{
BuyTakeProfit = AdjustAboveStopLevel(Symbol(),BuyTakeProfit,5);
}
AddStopProfit(BuyTicket,BuyStopLoss,BuyTakeProfit) ;
}
}
// Sell Order
if(FastMA < SlowMA && SellTicket == 0)
{
if(BuyTicket > 0) Closed = CloseBuyOrder(Symbol(),BuyTicket,Slippage);
BuyTicket = 0;
SellTicket = OpenSellOrder(Symbol(),LotSize,UseSlippage,MagicNu mber);
if(SellTicket > 0 && (StopLoss > 0 || TakeProfit > 0))
{
OrderSelect(SellTicket,SELECT_BY_TICKET);
OpenPrice = OrderOpenPrice();
double SellStopLoss = CalcSellStopLoss(Symbol(),StopLoss,OpenPrice);
if(SellStopLoss > 0)
{
SellStopLoss = AdjustAboveStopLevel(Symbol(),SellStopLoss,5);
}
double SellTakeProfit = CalcSellTakeProfit(Symbol(),TakeProfit,OpenPrice);
if(SellTakeProfit > 0)
{
SellTakeProfit = AdjustBelowStopLevel(Symbol(),SellTakeProfit,5);
}
AddStopProfit(SellTicket,SellStopLoss,SellTakeProf it);
}
}
return(0);

نبدأ بتضمين الملف الذي يحتوي على وظائفنا فيه، في هذه الحالة IncludeExample.mqh. تعريفات المتغيرات ومحتويات وظيفة init() هي نفسها كما كانت من قبل. في بداية وظيفة start()، نستخدم CalcLotSize() وVerifyLotSize() لحساب والتحقق من حجم اللوت.

في كتل أمر الشراء والبيع، نستخدم CloseBuyOrder() وCloseSellOrder() لإغلاق الأمر العكسي. تتم فتح الأوامر الجديدة باستخدام OpenBuyOrder() أو OpenSellOrder(). قبل حساب وقف الخسارة وجني الربح، نتحقق من أن الأمر تم فتحه وتم تحديد مستوى وقف الخسارة أو جني الربح.

نقوم بجلب سعر الافتتاح للأمر باستخدام OrderSelect() و OrderOpenPrice(). ثم نحسب قيمة وقف الخسارة باستخدام CalcBuyStopLoss() أو CalcSellStopLoss() والهدف باستخدام CalcBuyTakeProfit() أو CalcSellTakeProfit().

نتحقق من أن قيمة وقف الخسارة أو الهدف أكبر من 0، ونستخدم AdjustAboveStopLevel() و AdjustBelowStopLevel() للتحقق من صحة أسعار وقف الخسارة والهدف. في النهاية، نمرر تلك الأسعار إلى دالة AddOrderProfit() التي تضيف وقف الخسارة والهدف إلى الأمر.

الخبير المستشار أعلاه يقوم بالشيء نفسه الذي يبدأ في الصفحة 51 ، ولكنه أسهل بكثير في القراءة. من خلال تقسيم الشفرة إلى وظائف ، لقد أزلنا الفوضى في الشفرة المصدرية وجعلنا الخبير المستشار أسهل في الإدارة. سنضيف بعض المزيد من الميزات إلى هذا الخبير المستشار .

العمل الأولي في إنشاء هذه الوظائف سيستغرق بعض الوقت ، ولكنه سيوفر لك الوقت في المدى البعيد ، حيث ستتمكن بسهولة أكبر من تجريب أفكار التداول وتحويل الخبير المستشار العامل في وقت قصير.

Order Management

لقد تم تقديم وظيفة OrderSelect () في الفصل الثاني. في هذا القسم ، سنستخدم وظيفة OrderSelect () ، جنبًا إلى جنب مع مشغلات الدورة for و while ، لتكرار تحديد مجموعة الطلبات واسترداد معلومات الطلب. سيتم استخدام هذه الطريقة لإغلاق العديد من الطلبات ، وإضافة التوقفات المتحركة ، وعد الطلبات المفتوحة ، وأكثر من ذلك.

حلقة الأمر (Order Loop)
The Order Loop

مشغل for
يتم استخدام مشغل for لتكرار كتلة من الشفرة لعدد محدد مسبقًا من المرات. نقوم بتعريف متغير صحيح لاستخدامه كعداد وتعيين قيمة بداية. نشير إلى الشرط الذي ، إذا صحيح ، سيتسبب في تشغيل الحلقة. نشير أيضًا إلى تعبير لزيادة متغير العداد.
هنا مثال لحلقة for:

كود PHP:

for(int Counter = 1; Counter <= 3; Counter++)
{
// Code to loop

التعبير الأول ، int Counter = 1، يبدأ متغير الـ Counter الخاص بنا بقيمة 1. التعبير الثاني ، Counter <= 3 ، هو الشرط الذي ، إذا كان صحيحًا ، سينفذ الشفرة داخل الأقواس. إذا كانت غير صحيحة ، يتم إنهاء الحلقة ، ويتم استمرار التنفيذ بعد الأقواس (}).

التعبير الثالث ، Counter ++ ، يعني “زيادة قيمة المتغير Counter بمقدار واحد”. التعبير Counter– سينقص القيمة بمقدار واحد ، و Counter + 2 سيزيد القيمة بمقدار اثنين. في كل مرة يكتمل فيها الدورة ، يتم زيادة قيمة المتغير العداد أو تنقيصه. في التكرار التالي للحلقة ، يتم إعادة تقييم الوسيطة الثانية ، في هذه الحالة Counter <= 3. يجب ملاحظة عدم وجود فاصلة منقوطة بعد التعبير الثالث.

المثال السابق سينفذ الحلقة ثلاث مرات. بعد كل تكرار ، يتم زيادة العداد بمقدار واحد ، وبعد التكرار الثالث ، ستتم إنهاء الحلقة.

مشغل while
The while Operator

يعد مشغل while طريقة أبسط للتكرار في MQL. تعد حلقة for هي الأفضل إذا كنت تعرف بالضبط كم مرة تريد تنفيذ الحلقة. إذا كنت غير متأكد من عدد التكرارات ، فسيكون استخدام حلقة while أكثر ملاءمة.

فيما يلي مثال على حلقة while:

كود PHP:

while(Something == true)
{
// Loop code

في هذا المثال ، يتم استخدام متغير منطقي يسمى Something. إذا كان Something يساوي true ، فسيتم تنفيذ الحلقة. بالطبع ، إذا لم تتغير قيمة Something ، فسيتم تشغيل الحلقة بلا نهاية. لذلك ، من الضروري وجود شرط لتغيير قيمة Something في نقطة ما خلال الحلقة. بمجرد أن يكون هذا الشرط صحيحًا ، يتم تغيير قيمة Something إلى false ، وسيتوقف تنفيذ الحلقة.

يمكنك أيضًا زيادة متغير ، تمامًا كما تفعل باستخدام مشغل for:

كود PHP:

int Counter = 1;
while(Counter <= 3)
{
Counter++;

هذا الكود سينفذ بالضبط مثل الحلقة التكرارية for أعلاه!

The Order Loop

فيما يلي هو الكود الذي سنستخدمه للتكرار عبر مجموعة الطلبات المفتوحة:

كود PHP:

for(Counter = 0; Counter <= OrdersTotal()-1; Counter++)
{
OrderSelect(Counter,SELECT_BY_POS);
// Evaluate condition

سنقوم بتعيين قيمة Counter على 0، وسنكرر الحلقة ما دامت قيمة Counter أقل من أو تساوي قيمة OrdersTotal()، مع الحرص على طرح واحد منها. وسيتم زيادة قيمة Counter بمقدار 1 بعد كل تكرار للحلقة.

دالة OrdersTotal() ترجع عدد الطلبات المفتوحة حاليًا. ولماذا نقوم بطرح 1 من قيمة OrdersTotal()؟ سنشرح كيف يعمل مجموعة الطلبات:
يحتوي مجموعة الطلبات على جميع الطلبات المفتوحة حاليًا في المنصة، بما في ذلك الطلبات التي يتم وضعها يدويًا وكذلك الطلبات التي يتم وضعها بواسطة الخبراء المستشارين. يتم ترقيم فهارس الطلبات ابتداءً من الرقم صفر. إذا كان هناك طلب واحد مفتوح، فإن فهرسه هو 0. عند فتح طلب آخر، يكون فهرسه 1. إذا تم فتح طلب ثالث، فإن فهرسه سيكون 2، وهكذا. يكون الفهرس 0 هو الطلب الأقدم، والفهرس 2 هو الأحدث. ولذلك نقوم بطرح 1 من عدد OrdersTotal() لأن الفهرس يبدأ من الرقم صفر،

تقوم دالة OrdersTotal() بإرجاع عدد الأوامر المفتوحة حاليًا. في المثال أعلاه، لدينا ثلاثة أوامر مفتوحة. ولكن نظرًا لأن مؤشر الأمر لدينا يبدأ من الصفر، نريد أن يعد متغير العداد حتى 2 فقط. يجب أن يتوافق قيمة العداد مع أرقام مؤشرات الأمر، ولذلك يجب طرح 1 من OrdersTotal().

عندما يتم إغلاق أمر في بركة الأوامر المفتوحة، سيتم تخفيض مؤشرات الأوامر الأحدث في البركة. على سبيل المثال، إذا تم إغلاق الأمر ذي المؤشر 0، فإن الأمر ذي المؤشر 1 يصبح ذي مؤشر 0، ويصبح المؤشر 2 ذي مؤشر 1. وهذا أمر مهم عند إغلاق الأوامر، وسنغطي هذا بالتفصيل قريبًا.

فيما يتعلق بحلقة الأوامر الخاصة بنا: تستخدم عبارة OrderSelect() متغير العداد الخاص بنا كمؤشر موضع الأمر. كما شرحنا آنفاً، سنزيد من خلال حوض الأوامر من الأقدم إلى الأحدث. يشير المعامل SELECT_BY_POS إلى أننا نختار الأمر بموقعه في حوض الأوامر، بدلاً من رقم التذكرة الخاص به.

في التكرار الأول لهذه الحلقة، سيكون Counter يساوي 0 وسنقوم بتحديد أقدم أمر من حوض الأوامر باستخدام OrderSelect(). يمكننا بعد ذلك فحص معلومات الأمر باستخدام الوظائف مثل OrderTicket() أو OrderStopLoss()، وتعديل الأمر أو إغلاقه حسب الحاجة.

Order Counting

من المفيد جداً في كثير من الأحيان معرفة عدد الطلبات التي يفتحها الخبير الخاص بنا، ونوعها. سنقوم بإنشاء عدة وظائف لعد الطلبات الحالية المفتوحة، بناءً على نوع الطلب. وسيقوم الوظيفة التالية بعدد إجمالي عدد الطلبات المفتوحة:

كود PHP:

int TotalOrderCount(string argSymbol, int argMagicNumber)
{
int OrderCount;
for(Counter = 0; Counter <= OrdersTotal()-1; Counter++)
{
OrderSelect(Counter,SELECT_BY_POS);
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol)
{
OrderCount++;
}
}
return(OrderCount);

لقد قمنا بتسمية وظيفة عد الطلبات الخاصة بنا باسم “TotalOrderCount()”، وهي ستعيد قيمة صحيحة تشير إلى عدد الطلبات التي تم فتحها حاليًا على رمز الرسم البياني المحدد المطابق لرقم السحري الذي تم تمريره كوسيط للدالة.

نبدأ بتعريف متغير “OrderCount”. ونظرًا لعدم تحديد قيمة افتراضية، فإن “OrderCount” سيتم تهيئته بقيمة صفر. وسوف تتعرف على المشغل “for” ودالة “OrderSelect()” من القسم السابق.

نظرًا لأن مجموعة الطلبات تحتوي على جميع الطلبات المفتوحة، بما في ذلك تلك التي تم تثبيتها بواسطة خبير آخر، فمن الضروري بالنسبة لنا تحديد الطلبات التي تم وضعها بواسطة الخبير الخاص بنا. ونحن نتحقق أولاً من OrderSymbol() للطلب المحدد، ونتأكد من مطابقتها للوسيط argSymbol. ثم نتحقق من رقم السحري في الطلب.

إذا كان OrderMagicNumber() مطابقًا للوسيط argMagicNumber، فيمكننا التأكد بشكل معقول من أن هذا الطلب تم وضعه بواسطة هذا الخبير. طالما أن المستخدم لا يقوم بتشغيل اثنين من خبراء الخبراء على نفس الرمز النقدي بنفس الرقم السحري، فيمكننا التأكد من أن هذا الطلب تم وضعه بواسطة هذا الخبير. عند تشغيل العديد من خبراء الخبراء على نفس الصك، يجب التأكد من استخدام رقم سحري فريد لكل خبير.

إذا كان الطلب يتطابق مع رقم السحري ورمز الرسم البياني الخاص بنا، فسيتم زيادة قيمة “OrderCount” بمقدار واحد. بعد الانتهاء من الحلقة على جميع الطلبات في مجموعة الطلبات، نقوم بإرجاع قيمة “OrderCount” للدالة المستدعية.

وفيما يلي مثال على كيفية استخدام هذا في الشفرة:

كود PHP:

if(TotalOrderCount(Symbol(),MagicNumber) > 0 && CloseOrders == true)
{
// Close all orders

إذا كانت هناك طلبات مفتوحة تم وضعها بواسطة هذا الخبير، وكانت قيمة “CloseOrders” صحيحة (ونفترض أن هذا تم تعيينه في مكان آخر في البرنامج)، فسيتم تشغيل الشفرة داخل الأقواس لإغلاق جميع الطلبات المفتوحة.

دعونا نعدل الروتين الخاص بعداد الطلبات ليحسب فقط عدد الطلبات الشرائية بالسوق:

كود PHP:

int BuyMarketCount(string argSymbol, int argMagicNumber)
{
int OrderCount;
for(Counter = 0; Counter <= OrdersTotal()-1; Counter++)
{
OrderSelect(Counter,SELECT_BY_POS);
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol
&& OrderType() == OP_BUY)
{
OrderCount++;
}
}
return(OrderCount);

الشفرة مطابقة لما كانت عليه سابقاً باستثناء إضافة دالة OrderType() للتحقق من نوع الطلب المحدد حالياً. OP_BUY هو الثابت الذي يشير إلى طلب شراء بالسوق. لحساب أنواع طلبات أخرى، يمكن ببساطة استبدال OP_BUY بالثابت المناسب لنوع الطلب، وإعادة تسمية الدالة لتعكس نوع الطلب.

Closing Multiple Orders

غالبًا ما سيكون علينا إغلاق عدة طلبات من نوع واحد. سندمج حلقة الطلب الخاصة بنا مع إجراءات إغلاق الطلب لإغلاق عدة طلبات دفعة واحدة. ستقوم هذه الوظيفة بإغلاق جميع طلبات السوق الشرائية التي تم وضعها بواسطة خبيرنا المستشار:

كود PHP:

void CloseAllBuyOrders(string argSymbol, int argMagicNumber, int argSlippage)
{
for(int Counter = 0; Counter <= OrdersTotal()-1; Counter++)
{
OrderSelect(Counter,SELECT_BY_POS);
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol
&& OrderType() == OP_BUY)
{
// Close Order
int CloseTicket = OrderTicket();
double CloseLots = OrderLots();
while(IsTradeCon****Busy()) Sleep(10);
double ClosePrice = MarketInfo(argSymbol,MODE_BID);
bool Closed = OrderClose(CloseTicket,CloseLots,ClosePrice,argSli ppage,Red);
// Error Handling
if(Closed == false)
{
ErrorCode = GetLastError();
string ErrDesc = ErrorDescription(ErrorCode);
string ErrAlert = StringConcatenate("Close All Buy Orders - Error ",
ErrorCode,": ",ErrDesc);
Alert(ErrAlert);
string ErrLog = StringConcatenate("Bid: ",
MarketInfo(argSymbol,MODE_BID), " Ticket: ",CloseTicket,
" Price: ",ClosePrice);
Print(ErrLog);
}
else Counter--;
}
}

يرجى ملاحظة أننا نستخدم void كنوع بيانات الوظيفة. لقد تم تحديد عدم وجود بيانات مفيدة لإرجاعها من هذه الوظيفة، لذلك لا نحتاج إلى عامل إرجاع في الوظيفة.

ستلاحظ حلقة الـ for والدالة OrderSelect() من رمز حلقة الطلب الخاص بنا. سنقوم بتكرار الحلقة عبر مجموعة الطلبات وفحص كل طلب لمعرفة ما إذا كان علينا إغلاقه. إذا كان الطلب الحالي هو طلب سوق شراء، كما يشير OP_BUY، وإذا كان يتطابق مع رمز الرسم البياني الخاص بنا والرقم السحري، فسنقوم بإغلاق الطلب.

نستدعي دالة OrderTicket() لاسترداد رقم التذكرة للطلب الحالي. من هنا، يتم تطبيق رمزنا بالكامل كما هو الحال في رمز إغلاق سوق الشراء الذي تم شرحه في الفصول السابقة. يرجى ملاحظة العبارة الأخيرة جدًا: Counter–. إذا تم إغلاق الطلب بشكل صحيح، فسيتم تخفيض قيمة المتغير Counter بمقدار واحد.

شرحنا في وقت سابق أنه عند إغلاق طلب، يتم تخفيض فهرس جميع الطلبات التي تأتي بعده بمقدار واحد. إذا لم نقم بتخفيض متغير العداد بعد إغلاق طلب، فسيتم تخطي الطلبات التالية.

هناك سبب جيد جدًا للدوران عبر الطلبات من الأقدم إلى الأحدث: فقد طبقت لوائح NFA التي دخلت حيز التنفيذ في صيف عام 2009 للوسطاء الأمريكيين، متطلبًا بأن يتم إغلاق عدة طلبات وضعت على نفس رمز العملة وفقًا للترتيب الذي تم وضعها به. يُطلق على هذا القانون اسم “FIFO”، والذي يعني “الأولوية للأول”، والدوران عبر الطلبات من الأقدم إلى الأحدث يضمن التزامنا بقانون FIFO عند إغلاق الطلبات.

لإغلاق طلبات بيع بالسوق باستخدام الكود المذكور أعلاه، يتم تغيير نوع الطلب إلى OP_SELL وسعر الإغلاق ClosePrice إلى سعر العرض Ask للرمز.

دعنا نفحص الكود لإغلاق العديد من الطلبات المعلقة. سيتم إغلاق كافة طلبات وقف الشراء Buy Stop بهذا المثال. الفرق بين هذا الكود والكود لإغلاق طلبات الشراء بالسوق المذكور أعلاه هو أننا نحدد OP_BUYSTOP كنوع للطلب، ونستخدم OrderDelete() لإغلاق الطلبات.

سيعمل هذا الكود على جميع أنواع الطلبات المعلقة – يتم تغيير مقارنة نوع الطلب إلى النوع الذي ترغب في إغلاقه.

Trailing Stops

يمكننا أيضًا استخدام حلقة الأمر الخاصة بنا لتعديل عدة أوامر. مثال شائع على ذلك هو الإيقاف المتحرك. يتحرك الإيقاف المتحرك للخسائر إلى الأعلى أو إلى الأسفل مع سعر الأمر بمجرد أن يحقق الأمر ربحًا. هذا يحجز الربح ويوفر حماية فعالة للخسائر.

يتم التعبير عن الإيقاف المتحرك على أنه أقصى عدد من النقاط. على سبيل المثال ، إذا كان إيقافك المتحرك يبلغ 50 نقطة ، فلن يتجاوز إيقاف الخسارة أبدًا 50 نقطة بعيدًا عن سعرك. إذا انعكست الأسعار وانخفض الربح ، فإن إيقاف الخسارة سيظل في مكانه. يتحرك الإيقاف فقط في اتجاه الربح وليس في الاتجاه المعاكس.

عند تعديل إيقاف متحرك ، يجب علينا التحقق مما إذا كان المسافة بين السعر الحالي وإيقاف الخسارة الحالي أكبر من الإيقاف المتحرك. إذا كان الأمر كذلك ، فسيتم تعديل إيقاف الخسارة بحيث يكون المسافة من السعر الحالي بالنقاط مساوية لعدد النقاط في إعداد الإيقاف المتحرك.

يتم حساب وقف الخسارة المتحرك بالنسبة لسعر الإغلاق، والذي يكون العرض في حالة الأوامر الشرائية والطلب في حالة الأوامر البيع. يجب الانتباه إلى أن هذا هو العكس من سعر الافتتاح. دعونا نفحص الشفرة المصدرية لتعديل وقف الخسارة المتحرك. أولاً ، نعلن المتغير الخارجي لإعدادات وقف الخسارة المتحرك:

كود PHP:

extern double TrailingStop = 50; 

هذا الكود يتحقق من جميع أوامر السوق الشرائية ويعدل وقف الخسارة حسب الحاجة:

كود PHP:

for(int Counter = 0; Counter <= OrdersTotal()-1; Counter++)
{
OrderSelect(Counter,SELECT_BY_POS);
double MaxStopLoss = MarketInfo(Symbol(),MODE_BID) -
(TrailingStop * PipPoint(Symbol()));
MaxStopLoss = NormalizeDouble(MaxStopLoss,MarketInfo(OrderSymbol (),MODE_DIGITS));
double CurrentStop = NormalizeDouble(OrderStopLoss(),
MarketInfo(OrderSymbol(),MODE_DIGITS));
// Modify Stop
if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol()
&& OrderType() == OP_BUY && CurrentStop < MaxStopLoss)
{
bool Trailed = OrderModify(OrderTicket(),OrderOpenPrice(),MaxStop Loss,
OrderTakeProfit(),0);
// Error Handling
if(Trailed == false)
{
ErrorCode = GetLastError();
string ErrDesc = ErrorDescription(ErrorCode);
string ErrAlert = StringConcatenate("Buy Trailing Stop - Error ",
ErrorCode,": ",ErrDesc);
Alert(ErrAlert);
string ErrLog = StringConcatenate("Bid: "MarketInfo(Symbol(),MODE_BID),
" Ticket: ",CloseTicket," Stop: ",OrderStopLoss()," Trail: ",
MaxStopLoss);
Print(ErrLog);
}
}

بعد تحديد الأمر من الحوض باستخدام OrderSelect()، نحدد أقصى مسافة لوقف الخسارة عن طريق طرح إعدادات التوقف الآلي التي حددناها من السعر الحالي Bid المضروب ب PipPoint(). يتم تخزين هذا في المتغير MaxStopLoss.

نستخدم دالة MQL NormalizeDouble() لتقريب متغير MaxStopLoss إلى العدد الصحيح من الأرقام العشرية بعد الفاصلة. يمكن أن تتم الأسعار في MetaTrader بما يصل إلى ثماني أماكن عشرية.

باستخدام NormalizeDouble()، نقوم بتقريب ذلك إلى 4 أو 5 أرقام (2-3 أرقام لأزواج JPY).
بعد ذلك، نسترد وقف الخسارة للأمر المحدد حاليًا، ونقوم بتقريبه باستخدام NormalizeDouble() فقط للتأكد. ثم نسند هذه القيمة إلى المتغير CurrentStop.

ثم نتحقق مما إذا كان الأمر الحالي بحاجة إلى تعديل. إذا تطابق الرقم السحري والرمز ونوع الأمر ، وكان الخسارة الحالية (CurrentStop) أقل من MaxStopLoss ، فسوف نقوم بتعديل خسارة الأمر. نمرر متغير MaxStopLoss كنقطة توقف جديدة إلى وظيفة OrderModify().

إذا لم تكن وظيفة OrderModify() ناجحة ، فستتم معالجة الأخطاء ، وستتم طباعة معلومات السعر الحالي ورقم التذكرة وخسارة التوقف الحالية وخسارة التوقف المعدلة في السجل.
شروط تعديل أوامر البيع مختلفة عن شروط أوامر الشراء ويجب معالجتها بشكل منفصل. هنا هي الشروط لتعديل أمر بيع:

كود PHP:

// Modify Stop
if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol()
&& OrderType() == OP_SELL && (CurrentStop > MaxStopLoss || CurrentStop == 0)) 

لاحظ الشرط (CurrentStop > MaxStopLoss || CurrentStop == 0). إذا لم يتم وضع وقف الخسارة مع الأمر، فلن تكون الشرط CurrentStop > MaxStopLoss صحيحة أبدًا، لأن MaxStopLoss لن يكون أقل من الصفر. لذلك، نضيف شرطًا أو، CurrentStop == 0. إذا كان وقف الخسارة الحالي للأمر هو 0 (لا يوجد وقف خسارة)، فسيتم وضع وقف الخسارة المتحرك، طالما تستوفي الشروط المتبقية.

Minimum Profit

دعونا نعزز توقفنا الخلفي عن طريق إضافة مستوى ربح أدنى. في المثال أعلاه ، سيتم تشغيل توقف الخلفي على الفور. إذا قمت بتعيين توقف خسارة أولي بمقدار 100 نقطة ، وكان توقف الخلفي الخاص بك هو 50 نقطة ، فسيتم تعيين توقف الخسارة إلى 50 نقطة على الفور ، مما يبطل توقف الخسارة الأولي بمقدار 100 نقطة.

سيسمح إضافة مستوى ربح أدنى بتعيين توقف الخسارة الأولي ، في حين تأخير توقف الخلفي حتى يتم الوصول إلى مستوى محدد من الربح. في هذا المثال ، دعونا نفترض تعيين توقف خسارة أولي بمقدار 100 نقطة عند تقديم الطلب. نحن نستخدم توقفًا خلفيًا بمقدار 50 نقطة ، مع مستوى ربح أدنى بمقدار 50 نقطة. عندما يصل الربح للطلب إلى 50 نقطة ، سيتم ضبط توقف الخسارة على النقطة صفر.

دعونا نضيف متغيرًا خارجيًا لإعداد ربحنا الأدنى:

كود PHP:

extern int TrailingStop = 50;
extern int MinimumProfit = 50; 

تقوم الدالة التالية بتعديل توقف الخسارة لجميع أوامر الشراء بالسوق ، مع فحص الربح الأدنى قبل ذلك:

كود PHP:

void BuyTrailingStop(string argSymbol, int argTrailingStop, int argMinProfit,
int argMagicNumber)
{
for(int Counter = 0; Counter <= OrdersTotal()-1; Counter++)
{
OrderSelect(Counter,SELECT_BY_POS);
// Calculate Max Stop and Min Profit
double MaxStopLoss = MarketInfo(argSymbol,MODE_BID) -
(TrailingStop * PipPoint(argSymbol));
MaxStopLoss = NormalizeDouble(MaxStopLoss,
MarketInfo(OrderSymbol(),MODE_DIGITS));
double CurrentStop = NormalizeDouble(OrderStopLoss(),
MarketInfo(OrderSymbol(),MODE_DIGITS));
double PipsProfit = MarketInfo(argSymbol,MODE_BID) - OrderOpenPrice();
double MinProfit = MinimumProfit * PipPoint(argSymbol));
// Modify Stop
if(OrderMagicNumber() == argMagicNumber && OrderSymbol() == argSymbol
&& OrderType() == OP_BUY && CurrentStop < MaxStopLoss
&& PipsProfit >= MinProfit)
{
bool Trailed = OrderModify(OrderTicket(),OrderOpenPrice(),MaxStop Loss,
OrderTakeProfit(),0);
// Error Handling
if(Trailed == false)
{
ErrorCode = GetLastError();
string ErrDesc = ErrorDescription(ErrorCode);
string ErrAlert = StringConcatenate("Buy Trailing Stop - Error ",
ErrorCode,": ",ErrDesc);
Alert(ErrAlert);
string ErrLog = StringConcatenate("Bid: ",
MarketInfo(argSymbol,MODE_BID), " Ticket: ",CloseTicket,
" Stop: ",OrderStopLoss()," Trail: ",MaxStopLoss);
Print(ErrLog);
}
}
}

نحن نحسب الربح الحالي للطلب بالنقاط عن طريق طرح سعر فتح الأمر الحالي (OrderOpenPrice()) من سعر العرض الحالي (Bid) ، وتخزينه في المتغير PipsProfit. نقارن ذلك بإعدادات الربح الأدنى لدينا ، التي يتم ضربها في نقاط النقطة وتخزينها في المتغير MinProfit.

إذا كان الربح الحالي بالنقاط (PipsProfit) أكبر من أو يساوي الربح الأدنى (MinProfit) ، وكانت جميع الظروف الأخرى صحيحة ، فسيتم تعديل التوقف.

تعتبر تقنية التوقف الخلفي مع إعدادات الربح الأدنى أكثر مرونة ، لذا سترغب على الأرجح في استخدام هذه الدالة في مستشارك الخبير.

توقف الانعكاس
Break Even Stop

يمكنك أيضًا استخدام هذه الطريقة لتطبيق تعديل توقف الانعكاس (Break Even Stop) على طلباتك. يعد توقف الانعكاس تعديلاً لتوقف الخسارة ليصبح يساوي سعر فتح الطلب بعد تحقيق مستوى معين من الربح. يعمل توقف الانعكاس بشكل مستقل عن وظائف توقف الخسارة الأولية وتقنية التوقف الخلفي.

فيما يلي المتغير الخارجي الخاص بإعدادات ربح التعادل. يتم تحديد الحد الأدنى للربح بالنقاط.

كود PHP:

extern double BreakEvenProfit = 25; 

سيعدل هذا الشفرة توقف الخسارة على جميع أوامر الشراء التي تتم عبر السوق على التعادل بمجرد أن يصل الربح في النقاط إلى BreakEvenProfit أو يتجاوزه. لن نقوم بإنشاء دالة لهذا ، ولكن يمكنك فعل ذلك إذا شعرت أنه سيكون مفيدًا.

كود PHP:

for(int Counter = 0; Counter <= OrdersTotal()-1; Counter++)
{
OrderSelect(Counter,SELECT_BY_POS);
*******Rates();
double PipsProfit = Bid – OrderOpenPrice();
double MinProfit = BreakEvenProfit * PipPoint(OrderSymbol()));
if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol()
&& OrderType() == OP_BUY && PipsProfit >= MinProfit
&& OrderOpenPrice() != OrderStopLoss())
{
bool BreakEven = OrderModify(OrderTicket(),OrderOpenPrice(),
OrderOpenPrice(),OrderTakeProfit(),0);
if(BreakEven == false)
{
ErrorCode = GetLastError();
string ErrDesc = ErrorDescription(ErrorCode);
string ErrAlert = StringConcatenate("Buy Break Even - Error ",
ErrorCode,": ",ErrDesc);
Alert(ErrAlert);
string ErrLog = StringConcatenate("Bid: ",Bid,", Ask: ",Ask,
", Ticket: ",CloseTicket,", Stop: ",OrderStopLoss(),", Break: ",
MinProfit);
Print(ErrLog);
}
}

نقوم بطرح سعر فتح الطلب من سعر العرض الحالي لحساب الربح الحالي بالنقاط، ونخزن هذا الرقم في PipsProfit. نحسب الحد الأدنى للربح بالنقاط ونخزنه في MinProfit.
إذا كان PipsProfit أكبر من أو يساوي MinProfit، فسنعدل توقف الخسارة ليصبح مساوياً لسعر فتح الطلب.
نتحقق أيضاً من عدم تعيين توقف الخسارة بالفعل على سعر التعادل. إذا كان OrderOpenPrice() لا يساوي OrderStopLoss()، يمكننا المتابعة.

Updating the Expert Advisor

دعنا نقوم بتعديل وظيفة start() في خبير المستشار المتخصص بتداول اتجاهات المتوسط المتحرك لتعكس الوظائف الجديدة التي قمنا بإنشائها. أولاً، سنتحقق مما إذا كانت هناك أي أوامر شراء مفتوحة قبل أن نقوم بفتح المزيد. وبدلاً من إغلاق أمر بيع واحد، سنستخدم الوظيفة ببساطة لإغلاق جميع أوامر البيع. وهذه الطريقة لا تتطلب استخدام تذكرة الأمر.

كود PHP:

/ Buy Order
if(FastMA > SlowMA && BuyTicket == 0 && BuyMarketCount(Symbol(),MagicNumber) == 0)
{
if(SellMarketCount(Symbol(),MagicNumber) > 0)
{
CloseAllSellOrders(Symbol(),MagicNumber,Slippage);
}
SellTicket = 0;
BuyTicket = OpenBuyOrder(Symbol(),LotSize,UseSlippage,MagicNum ber);


قمنا باستخدام الوظيفة BuyMarketCount() التي قمنا بتعريفها في الصفحة 84 لإرجاع عدد أوامر الشراء المفتوحة حاليًا. سنحتفظ بفحص BuyTicket، حتى لا يتم فتح أي أوامر شراء / بيع بشكل متتالٍ.

تقوم الوظيفة CloseAllSellOrders() بإغلاق أي أوامر بيع مفتوحة. نتحقق أولاً من وجود أي أوامر بيع يجب إغلاقها باستخدام SellMarketCount(). هذه الوظيفة لا تتطلب تذكرة أمر، على عكس وظيفة CloseSellOrder() التي تم تناولها سابقا . يوصى باستخدام هذه الطريقة لإغلاق الأوامر المعاكسة في المستشار الخبير الخاص بك ، حيث أنها أكثر صلابة.

بقية كود وضع الأوامر للشراء هو كما كان سابقاً. ويتم عرض كود وضع الأوامر للبيع المقابل أدناه:

كود PHP:

// Sell Order
if(FastMA < SlowMA && SellTicket == 0 && SellMarketCount(Symbol(),MagicNumber) == 0)
{
if(BuyMarketCount(Symbol(),MagicNumber) > 0)
{
CloseAllBuyOrders(Symbol(),MagicNumber,Slippage);
}
BuyTicket = 0;
SellTicket = OpenSellOrder(Symbol(),LotSize,UseSlippage,MagicNu mber);

الآن، دعنا نضيف وظائف التوقف المتحرك إلى أمرنا. سنقوم بتنفيذ الروتين الخاص بالتوقف المتحرك بعد وضع الأمر. كما سبق، سنتحقق من وجود أوامر شراء أو بيع مفتوحة قبل استدعاء وظيفة التوقف المتحرك. لنضيف المتغيرات الخارجية التالية إلى المستشار الخبير الخاص بنا:

كود PHP:

extern int TrailingStop = 50;
extern int MinimumProfit = 50; 

هذا هو الكود لفحص وتعديل التوقف المتحرك. يجب ملاحظة أننا نتحقق من وجود مدخل لإعداد TrailingStop. إذا تم تعيينه على 0، فهو معطل بشكل فعال:

كود PHP:

if(BuyMarketCount(Symbol(),MagicNumber) > 0 && TrailingStop > 0)
{
BuyTrailingStop(Symbol(),TrailingStop,MinimumProfi t,MagicNumber);
}
if(SellMarketCount(Symbol(),MagicNumber) > 0 && TrailingStop > 0)
{
SellTrailingStop(Symbol(),TrailingStop,MinimumProf it,MagicNumber);

المستشار الخبير باستخدام الوظائف
هذا هو المستشار الخبير باستخدام الوظائف التي تم تقديمها سابقا. لقد قمنا بإضافة وظائف “إغلاق جميع الأوامر” و”توقف متحرك” 5، وميزة “تنفيذ مرة واحدة في كل شريط” .

كود الاكسبيرت كاملا:

كود PHP:

// Preprocessor
#property copyright "Andrew Young"
#include <IncludeExample.mqh>
// External variables
extern bool DynamicLotSize = true;
extern double EquityPercent = 2;
extern double FixedLotSize = 0.1;
extern double StopLoss = 50;
extern double TakeProfit = 100;
extern int TrailingStop = 50;
extern int MinimumProfit = 50;
extern int Slippage = 5;
extern int MagicNumber = 123;
extern int FastMAPeriod = 10;
extern int SlowMAPeriod = 20;
extern bool CheckOncePerBar = true;
// Global variables
int BuyTicket;
int SellTicket;
double UsePoint;
int UseSlippage;
datetime CurrentTimeStamp;
// Init function
int init()
{
UsePoint = PipPoint(Symbol());
UseSlippage = GetSlippage(Symbol(),Slippage);
}
// Start function
int start()
{
// Execute on bar open
if(CheckOncePerBar == true)
{
int BarShift = 1;
if(CurrentTimeStamp != Time[0])
{
CurrentTimeStamp = Time[0];
bool NewBar = true;
}
else NewBar = false;
}
else
{
NewBar = true;
BarShift = 0;
}
// Moving averages
double FastMA = iMA(NULL,0,FastMAPeriod,0,0,0,BarShift);
double SlowMA = iMA(NULL,0,SlowMAPeriod,0,0,0,BarShift);
double LastFastMA = iMA(NULL,0,FastMAPeriod,0,0,0,BarShift+1);
double LastSlowMA = iMA(NULL,0,SlowMAPeriod,0,0,0,BarShift+1);
// Calculate lot size
double LotSize = CalcLotSize(DynamicLotSize,EquityPercent,StopLoss, FixedLotSize);
LotSize = VerifyLotSize(LotSize);
// Begin trade block
if(NewBar == true)
{
// Buy order
if(FastMA > SlowMA && LastFastMA <= LastSlowMA &&
BuyMarketCount(Symbol(),MagicNumber) == 0)
{
// Close sell orders
if(SellMarketCount(Symbol(),MagicNumber) > 0)
{
CloseAllSellOrders(Symbol(),MagicNumber,Slippage);
}
// Open buy order
BuyTicket = OpenBuyOrder(Symbol(),LotSize,UseSlippage,MagicNum ber);
// Order modification
if(BuyTicket > 0 && (StopLoss > 0 || TakeProfit > 0))
{
OrderSelect(BuyTicket,SELECT_BY_TICKET);
double OpenPrice = OrderOpenPrice();
// Calculate and verify stop loss and take profit
double BuyStopLoss = CalcBuyStopLoss(Symbol(),StopLoss,OpenPrice);
if(BuyStopLoss > 0) BuyStopLoss = AdjustBelowStopLevel(Symbol(),
BuyStopLoss,5);
double BuyTakeProfit = CalcBuyTakeProfit(Symbol(),TakeProfit,
OpenPrice);
if(BuyTakeProfit > 0) BuyTakeProfit = AdjustAboveStopLevel(Symbol(),
BuyTakeProfit,5);
// Add stop loss and take profit
AddStopProfit(BuyTicket,BuyStopLoss,BuyTakeProfit) ;
}
}
// Sell Order
if(FastMA < SlowMA && LastFastMA >= LastSlowMA
&& SellMarketCount(Symbol(),MagicNumber) == 0)
{
if(BuyMarketCount(Symbol(),MagicNumber) > 0)
{
CloseAllBuyOrders(Symbol(),MagicNumber,Slippage);
}
SellTicket = OpenSellOrder(Symbol(),LotSize,UseSlippage,MagicNu mber);
if(SellTicket > 0 && (StopLoss > 0 || TakeProfit > 0))
{
OrderSelect(SellTicket,SELECT_BY_TICKET);
OpenPrice = OrderOpenPrice();
double SellStopLoss = CalcSellStopLoss(Symbol(),StopLoss,OpenPrice);
if(SellStopLoss > 0) SellStopLoss = AdjustAboveStopLevel(Symbol(),
SellStopLoss,5);
double SellTakeProfit = CalcSellTakeProfit(Symbol(),TakeProfit,
OpenPrice);
if(SellTakeProfit > 0) SellTakeProfit = AdjustBelowStopLevel(Symbol(),
SellTakeProfit,5);
AddStopProfit(SellTicket,SellStopLoss,SellTakeProf it);
}
}
} // End trade block
// Adjust trailing stops
if(BuyMarketCount(Symbol(),MagicNumber) > 0 && TrailingStop > 0)
{
BuyTrailingStop(Symbol(),TrailingStop,MinimumProfi t,MagicNumber);
}
if(SellMarketCount(Symbol(),MagicNumber) > 0 && TrailingStop > 0)
{
SellTrailingStop(Symbol(),TrailingStop,MinimumProf it,MagicNumber);
}
return(0);

Order Conditions and Indicators

لقد قمنا في الدروس الأخيرة بإنشاء وظائف تنفيذ آليات الطلبات التي تكون مشتركة في كل خبير مستشار. تم تصميم هذه الوظائف للاستخدام في مجموعة متنوعة من حالات التداول، ويجب أن تكون قابلة لإعادة الاستخدام ومرنة قدر الإمكان. هذا يتيح لنا التركيز على برمجة الشروط التداول الفريدة لنظام التداول الخاص بنا.

هنا ستكون معظم جهودك مركزة – تحقيق تداول الخبير المستشار لنظامك بأقصى دقة ممكنة. سنحتاج إلى تحديد الشروط الدقيقة لفتح وإغلاق الطلبات، بالإضافة إلى تحديد أسعار وقف الخسارة وأسعار جني الربح. تستخدم معظم أنظمة التداول بيانات الأسعار و/أو المؤشرات. دعنا نستعرض الطرق التي يمكننا من خلالها الوصول إلى هذه المعلومات واستخدامها في خبير المستشارات الخاص بنا.

بيانات السعر
Price Data

بالإضافة إلى سعر العرض أو الطلب الحالي (الذي تمت مناقشته في الفصول السابقة)، قد تحتاج إلى استخدام بيانات الأسعار للشريط، وتحديدًا الأعلى، الأدنى، الافتتاح أو الإغلاق لشريط معين. بالنسبة للرسم البياني الحالي، يمكنك استخدام مجموعات الأرقام المتسلسلة المحددة مسبقًا High[]، Low[]، Open[] و Close[].

المجموعة هي متغير يحتوي على قيم متعددة. يمكنك تنقل خلال القيم عن طريق تغيير الفهرس الذي يتم تضمينه في الأقواس المربعة. على سبيل المثال، فإن Open[0] هو سعر الافتتاح للشريط الحالي. العدد 0 هو الفهرس، ومن خلال تغييره يمكننا الحصول على سعر الافتتاح للأشرطة الأخرى. ستكون لدينا بشكل متكرر إما قيم الأشرطة الحالية أو السابقة للأسعار.

إذا كنت بحاجة إلى قيمة الأعلى، الأدنى، الافتتاح أو الإغلاق لرمز آخر غير الرسم البياني الحالي، أو إذا كنت بحاجة إلى بيانات الأسعار لفترة زمنية أخرى غير فترة الرسم البياني الحالية، يمكنك استخدام الوظائف iHigh()، iLow()، iOpen() و iClose(). فيما يلي بناء الجملة الخاص بتلك الوظائف، باستخدام iClose() كمثال لدينا:

كود PHP:

double iClose(string Symbol, int Period, int Shift) 

Symbol- رمز زوج العملات المستخدم.

Period- فترة الرسم البياني المستخدمة، بالدقائق.

Shift- التحرك الخلفي بالنسبة للشريط الحالي.

دعنا نستخدم iClose() للحصول على سعر الإغلاق لفترة رسم بياني مختلفة. على سبيل المثال، إذا كنا نستخدم رسم بياني بفترة ساعة ولكن نرغب في التحقق من سعر الإغلاق للشريط السابق في رسم بياني بفترة 4 ساعات:

كود PHP:

double H4Close = iClose(NULL,PERIOD_H4,1); 

NULL يشير إلى رمز الرسم البياني الحالي. PERIOD_H4 هو ثابت عددي يشير إلى فترة الرسم البياني H4. 1 هو الانتقال الذي يمثل الشريط السابق للشريط الحالي. دعنا نستخدم مثالًا آخر يُرجع إغلاق الشريط الحالي في رسم بياني آخر:

كود PHP:

double GBPClose = iClose(GBPUSD,0,0); 

GBPUSD هو الرمز الذي نستخدمه. لقد حددنا 0 كفترة لدينا، لذلك ستكون فترة الرسم البياني التي نقوم بالتحقق منها على GBPUSD هي نفسها كفترة الرسم البياني الحالي. الانتقال هو 0، وهو الشريط الحالي.

يمكنك استخدام مشغل حلقة مثل “for” أو “while” لزيادة معامل الانتقال والتنقل عبر تاريخ الرسم البياني. تُستخدم حلقة “for” هذه لاسترداد سعر الإغلاق لكل من آخر عشرة أشرطة، وتقوم بطباعته في السجل:

كود PHP:

for(int Count = 0; Count <= 9; Count++)
{
double CloseShift = iClose(NULL,0,Count);
Print(Count+" "+CloseShift);
}

​ 

Indicators

تعتمد غالبية أنظمة التداول على استخدام المؤشرات لتحديد إشارات التداول. تتضمن منصة MetaTrader أكثر من 20 مؤشرًا شائعًا، بما في ذلك المتوسط المتحرك (Moving Average) ومؤشر مؤشر قوة العملة المتحرك (MACD) ومؤشر نسبة القوة النسبية (RSI) والاستوكاستيك (Stochastics). تحتوي لغة برمجة MQL على وظائف مدمجة للمؤشرات المالية. يمكنك أيضًا استخدام مؤشرات مخصصة في خبيرك المستشار (Expert Advisor) الخاص بك.

مؤشرات الاتجاه
Trend Indicators

لنلقِ نظرة على المؤشر الذي استخدمناه طوال هذا الدروس: المتوسط المتحرك. المتوسط المتحرك هو مؤشر اتجاه. يُظهر ما إذا كانت الأسعار قد ارتفعت أم انخفضت خلال فترة المؤشر. يتكون المتوسط المتحرك من خط واحد مرسوم على الرسم البياني يُظهر السعر المتوسط على مدار عدد الشرائط x الأخيرة.
فيما يلي بناء جملة دالة المتوسط المتحرك:

كود PHP:

double iMA(string Symbol, int Timeframe, int MAPeriod, int MAShift, int MAMethod,
int MAPrice, int Shift) 

الرمز (Symbol) – الرمز المتداول الذي سيتم تطبيق المتوسط المتحرك عليه في الرسم البياني.
الإطار الزمني (Timeframe) – الفترة الزمنية للرسم البياني التي سيتم تطبيق المتوسط المتحرك عليها.

كل وظيفة مؤشر في لغة MQL تبدأ بمعاملين هما:

الرمز (Symbol): يحدد الرمز المتداول الذي سيتم تطبيق المؤشر عليه.
الإطار الزمني (Timeframe): يحدد الفترة الزمنية للرسم البياني التي سيتم تطبيق المؤشر عليها.
بعد ذلك، تأتي المعاملات المحددة لكل مؤشر بشكل خاص. تتوافق هذه المعاملات مع محتويات علامة التبويب “المعلمات” في خصائص المؤشر

MAPeriod – فترة النظر الخلفية للمتوسط المتحرك.

تقريبًا كل مؤشر يحتوي على معامل فترة واحد على الأقل. يتم حساب معظم المؤشرات باستخدام سلسلة من الأسعار المأخوذة من الشرائط السابقة. على سبيل المثال، إعداد فترة 10 يعني أن المؤشر يستخدم بيانات الأسعار من آخر عشرة شرائط لحساب قيمة المؤشر.

MAShift – التحول الأمامي لخط المتوسط المتحرك، بوحدة الشرائط. يختلف هذا عن معامل التحول أدناه.
MAMethod – طريقة حساب المتوسط المتحرك. تشمل الخيارات المتاحة البسيطة (Simple)، التركيبية (Exponential)، المنعمة (Smoothed) أو الوزنية الخطية (Linear Weighted).

أي مؤشر يستخدم متوسطًا متحركًا قد يمنحك خيار اختيار طريقة حساب المتوسط المتحرك. سنتحدث عن طرق حساب المتوسط المتحرك لاحقًا في هذا الفصل.

MAPrice – مجموعة الأسعار المستخدمة عند حساب المتوسط المتحرك.
يمكن أن تكون هذه الأسعار الإغلاق، الافتتاح، الأعلى، الأدنى، أو نوعًا ما من المتوسط مثل المتوسط الوسيط، المتوسط التقليدي، أو المتوسط الوزني. سنناقش ثوابت الأسعار المطبقة لاحقًا في هذا الفصل.

Shift – التحول الخلفي للشريط لإعادة الحساب.
معامل التحول (Shift) هو المعامل النهائي في أي وظيفة مؤشر. يُمثل هذا المؤشر للقيمة المطلوبة للشريط المراد استرجاع قيمة المؤشر له. قيمة 0 تُرجع قيمة المؤشر للشريط الحالي. وقيمة 3 ستعيد قيمة المؤشر منذ 3 شرائط مضت.

يتم رسم المتوسط المتحرك والمؤشرات المشابهة مباشرة على الرسم البياني. يمكنك إنشاء شروط تداول استنادًا إلى العلاقة بين المؤشرات والسعر. عبر المتوسط المتحرك الذي يتقاطع هو مثال على العلاقة بين الأسعار لمؤشرين. عندما يكون سعر مؤشر واحد أكبر من الآخر، يتم توليد إشارة للشراء أو البيع.

يمكنك أيضًا توليد إشارات تداول عندما يتجاوز السعر الحالي خط مؤشر أو يمر تحته. على سبيل المثال، يمكن استخدام مؤشر بولينجر باندز (Bollinger Bands) لتوليد إشارات تداول بناءً على موقع السعر مقارنة بالحزمة العلوية والحزمة السفلية.

المذبذبات
Oscillators

النوع الآخر الرئيسي للمؤشر هو المذبذب (Oscillator). يتم رسم المذبذبات في نافذة منفصلة، وكما يوحي اسمها، فإنها تتذبذب بين أقصى الأسعار العالية والمنخفضة. المذبذبات إما تكون مركزة حول محور محايد (عادة ما يكون 0)، أو محصورة بأقصى علوي أو سفلي (مثل 0 و 100). أمثلة على المذبذبات تشمل الزخم (Momentum)، والاستوكاستيك (Stochastics)، ونسبة القوة النسبية (RSI).

المذبذبات تشير إلى مستويات الشراء المفرط والبيع المفرط. على الرغم من أنها يمكن أن تُستخدم كمؤشر للاتجاه، إلا أنها عمومًا تُستخدم لتحديد مناطق عكس محتملة. يتم استخدامها لإنتاج إشارات تداول مضادة للاتجاه.

لنلقِ نظرة على مذبذب شائع، وهو المؤشر الاستوكاستيك (Stochastics). يتكون الاستوكاستيك من خطين، الخط الاستوكاستيك (المعروف أيضًا باسم خط %K)، والخط الإشاري (المعروف بالخط %D). يتذبذب الاستوكاستيك بين 0 و 100. عندما يكون الاستوكاستيك فوق 70، يقال إنه في حالة شراء مفرط وقد يحدث عكس للاتجاه. إذا كان دون 30، يقال إنه في حالة بيع مفرط.

فيما يلي بناء جملة المؤشر الاستوكاستيك:

كود PHP:

double iStochastic(string Symbol, int Timeframe, int KPeriod, int Dperiod, int Slowing,
int MAMethod, int PriceField, int Mode, int Shift) 

نحن بالفعل على دراية بالمعاملين الأولين، الرمز (Symbol) والإطار الزمني (Timeframe). لنلقِ نظرة على المعاملات الخاصة بالمؤشر:

KPeriod – فترة الخط %K.
DPeriod – فترة الخط %D.
Slowing – قيمة البطء للمؤشر الاستوكاستيك. قيمة أقل تشير إلى استوكاستيك سريع، بينما قيمة أعلى تشير إلى استوكاستيك بطيء.
MAMethod – للخط %D يتم تطبيق طريقة المتوسط المتحرك. هذا هو نفس الإعداد كما هو الحال في المتوسط المتحرك. سنستعرض طرق المتوسط المتحرك في وقت قريب.
PriceField – يحدد بيانات السعر المستخدمة للخط %K. إما 0: الأدنى/الأعلى أو 1: الإغلاق/الإغلاق. قيمة 1 تزيد من احتمالية أن يتداول المؤشر الاستوكاستيك في النطاقات القصوى.
Mode – يحدد الخط الاستوكاستيك الذي يتم حسابه – 1: خط %K، أو 2: خط %D.

لنلقِ لحظة لنتحدث عن معامل الوضع (Mode). بعض المؤشرات ترسم عدة خطوط على الرسم البياني. يحتوي المؤشر الاستوكاستيك على خطين. سنحتاج إلى استدعاء وظيفة iStochastic() لكلا الخطين %K و%D، كما هو موضح أدناه:

كود PHP:

double KLine = iStochastic(NULL,0,KPeriod,DPeriod,Slowing,MAMetho d,Price,0,0);
double DLine = iStochastic(NULL,0,KPeriod,DPeriod,Slowing,MAMetho d,Price,1,0); 

يرجى ملاحظة أن معامل الوضع (Mode) يكون 0 للخط %K و 1 للخط %D. يُدرج في موضوع مرجع MQL “الثوابت القياسية – خطوط المؤشر” الثوابت الصحيحة المقبولة للمؤشرات المختلفة التي تستخدم معامل الوضع.

يمكنك إنشاء إشارات تداول استنادًا إلى العلاقة بين خطوط المؤشر ومستويات مؤشر معينة، مثل مستويات الشراء المفرط والبيع المفرط عند 70 و 30 على التوالي. يمكنك أيضًا تقييم إشارات التداول استنادًا إلى العلاقة بين خطوط المؤشر. على سبيل المثال، قد ترغب في فتح أمر شراء فقط عندما يكون خط %K فوق خط %D. فيما يلي بعض الشروط المثالية:

Buy condition: %K > %D
Sell condition: %K < %D
Overbought condition: %K > 70
Oversold condition: %K < 30
Bullish divergence condition: %K makes a higher low while price makes a lower low
Bearish divergence condition: %K makes a lower high while price makes a higher high
تلك هي بعض الشروط المثالية، ويمكنك تعديلها واستخدامها وفقًا لاحتياجاتك واستراتيجيتك التداولية.

وهكذا يتم صياغتها برمجيا:

كود PHP:

if(KLine < 70) // Buy if stochastic is not overbought
if(KLine > DLine) // Buy if %K is greater than %D 

وظائف المؤشرات المدمجة متوفرة في مرجع MQL تحت مؤشرات تقنية. إذا كنت ترغب في المزيد من المعلومات حول استخدام المؤشر أو طريقة حسابه، يُرجى الاطلاع على قسم التحليل الفني في موقع MQL على العنوان التالي: http://ta.mql4.com/. يحتوي الموقع على معلومات مفصلة وشاملة حول استخدام المؤشرات التقنية وأساليب حسابها، ويمكنك العثور على معلومات إضافية قيمة هناك.

Custom Indicators

متاح المئات من المؤشرات المخصصة لمنصة MetaTrader عبر الإنترنت. إذا قررت استخدام مؤشر مخصص في خبيرك المستشار، سيتعين عليك بذل بعض الجهد. من الأفضل أن تحصل على ملف الشفرة المصدرية .mq4 عند استخدام مؤشر مخصص. على الرغم من أنه من الممكن استخدام مؤشر مخصص بدونه، إلا أن وجود شفرة المصدر سيسهل تحديد مؤشرات المؤشر لمعلمة الوضع (Mode).

لدى لغة MQL وظيفة مدمجة للتعامل مع المؤشرات المخصصة وهي iCustom(). هنا بناء الجملة:

كود PHP:

double iCustom(string Symbol, int Timeframe, string IndicatorName, Indicator Parameters,
int Mode, int Shift); 

المعاملات:

symbol: اسم الرمز أو الأداة المالية التي ترغب في تطبيق المؤشر المخصص عليها.
timeframe: الإطار الزمني أو الفترة الزمنية التي سيتم حساب المؤشر المخصص وعرضها عليها.
name: اسم ملف المؤشر المخصص. يجب أن يشمل امتداد الملف (مثال: “indicator.ex4” أو “indicator.mq4”).
…: معلمات إضافية خاصة بالمؤشر المخصص. تعتمد هذه المعلمات على المؤشر ويجب تقديمها بالترتيب الصحيح كما هو متوقع من المؤشر.
قيمة الإرجاع:
تقوم الوظيفة بإرجاع قيمة من النوع double تمثل القيمة المحسوبة للمؤشر في الشريط المحدد.

من الجدير بالذكر أنه لاستخدام المؤشر المخصص بشكل فعال، من المستحسن أن تكون لديك ملف شفرة المصدر .mq4 للمؤشر.

اضغط على الصورة لعرض أكبر. 

الإسم:	SumatraPDF_dQTfG72at7.png 
مشاهدات:	43 
الحجم:	50.3 كيلوبايت 
الهوية:	951223

طريقة أسهل للعثور على المعلمات هي التحقق من المتغيرات الخارجية (extern variables) في بداية ملف شفرة المصدر للمؤشر. سيتم سرد جميع معلمات المؤشر وأنواع بياناتها وقيمها الافتراضية هنا. يمكنك ببساطة نسخ ولصق هذا الكود إلى قسم المتغيرات الخارجية في خبيرك المستشار.

يجب أن يكون لكل متغير خارجي في المؤشر المخصص معلمة مقابلة في وظيفة iCustom()، ويجب أن تكون في نفس الترتيب الذي يظهرون به في المؤشر. يمكنك استخدام ثابت للمعلمات التي لا يلزم تغييرها (مثل السلاسل الإعلامية أو الإعدادات غير الأساسية).

فيما يلي مثال: المؤشر المخصص الشهير Slope Direction Line يحتوي على هذه المتغيرات الخارجية المدرجة في شفرة المصدر. سنقوم بإنشاء متغيرات خارجية لهذه الإعدادات في خبيرنا المستشار:

كود PHP:

//---- input parameters
extern int period=80;
extern int method=3; // MODE_SMA
extern int price=0; // PRICE_CLOSE 

سنستخدم المعرفات SlopePeriod، SlopeMethod و SlopePrice للمتغيرات الخارجية في خبيرنا المستشار.

كود PHP:

// External variables
extern int SlopePeriod = 80;
extern int SlopeMethod = 3;
extern int SlopePrice = 0; 

هنا كيف ستبدو وظيفة iCustom() لهذا المؤشر المحدد، جنبًا إلى جنب مع المتغيرات الخارجية:

كود PHP:

iCustom(NULL,0,"Slope Direction Line",SlopePeriod,SlopeMethod,SlopePrice,0,0); 

قيمة NULL تشير إلى أننا نستخدم رمز الرسم البياني الحالي، والقيمة 0 تمثل الفترة الزمنية الحالية للرسم البياني. “Slope Direction Line” هو اسم ملف المؤشر. SlopePeriod، SlopeMethod، و SlopePrice هي ثلاثة معلمات للمؤشر. نحن نستخدم فهرس الوضع الافتراضي 0، والشيفت (Shift) هو الشريط الحالي.

على الرغم من أن مؤشر Slope Direction Line يتم رسمه على شكل خط واحد، إلا أنه في الواقع مكون من اثنين من البافرات المختلفة. اعتمادًا على ما إذا كان سعر المؤشر يتحرك لأعلى أو لأسفل، يتغير اللون (والبافر)،

إذا قمت بإلحاق المؤشر برسم بياني وعرض نافذة البيانات في MetaTrader، سترى قيمتين لـ Slope Direction Line. تعرض القيمة الأولى سعرًا عندما يزداد قيمة المؤشر. يكون الخط أزرقًا افتراضيًا. تعرض القيمة الثانية سعرًا عندما تنخفض قيمة المؤشر. يكون هذا الخط أحمرًا افتراضيًا.

نحتاج إلى تحديد فهرس الوضع (Mode index) لكلتا الخطوط. أسهل طريقة للقيام بذلك هي النظر إلى شفرة المصدر. في وظيفة init()، سترى العديد من الأسطر التي تستخدم لإعلان وتعيين الخصائص لبافرات المؤشر.

اضغط على الصورة لعرض أكبر. 

الإسم:	SumatraPDF_JaJDiUchJP.png 
مشاهدات:	58 
الحجم:	48.4 كيلوبايت 
الهوية:	951222

كود PHP:

SetIndexBuffer(0, Uptrend);
SetIndexBuffer(1, Dntrend);
SetIndexBuffer(2, ExtMapBuffer);
...
SetIndexStyle(0,DRAW_LINE,STYLE_SOLID,2);
SetIndexStyle(1,DRAW_LINE,STYLE_SOLID,2); 


تُعيد الدالة SetIndexBuffer() الأولى ضبط بافر المؤشر بفهرس 0، وتستخدم المصفوفة Uptrend. يمكننا التخمين من اسم المصفوفة أن ذلك ينطبق على الخط الأزرق للمؤشر. تفعل الدالة الثانية نفس الشيء للمصفوفة DnTrend. لاحظ الدوال SetIndexStyle() في الأسفل التي تضبط البافرات 0 و 1 لرسم خط صلب.

البافر الثالث، ذو الفهرس 2 والمصفوفة ExtMapBuffer، يستخدم للحساب فقط. وبناءً على ذلك، يمكننا الاستنتاج بأن البافرات 0 و 1 تحتوي على معلومات سعر المؤشر. واستنادًا إلى معرفات المصفوفة، يكون 0 هو خط الاتجاه الصاعد، و 1 هو خط الاتجاه الهابط. فيما يلي كيفية إعلان المؤشرات:

كود PHP:

double SlopeUp = iCustom(NULL,0,"Slope Direction Line",SlopePeriod,SlopeMethod,
SlopePrice,0,1);
double SlopeDown = iCustom(NULL,0,"Slope Direction Line",SlopePeriod,SlopeMethod,
SlopePrice,1,1); 

يرجى ملاحظة أن المعامل Mode – الذي هو قبل الأخير – تم تعيينه ليكون فهرس البافر المناسب – 0 لـ SlopeUp و 1 لـ SlopeDown. وتم تعيين المعامل Shift – الأخير تمامًا – ليكون 1، مما يتحقق من قيمة الإغلاق للشريط الأخير.

من الجيد التأكد المزدوج من استخدام معاملات Mode الصحيحة. أضف دالة Print() إلى خبيرك المستشار، وقم بتشغيل اختبار تاريخي في استراتيجي تستر باستخدام “الأسعار المفتوحة فقط” كنموذج اختبار. تأكد من تعيين المعامل Shift ليكون 1 في وظيفة iCustom().

كود PHP:

Print("Slope Up: "+SlopeUp+", Slope Down: "+SlopeDown+" Time: "+TimeToStr(Time[1])); 

دالة Print() تقوم بطباعة قيمة بافرات المؤشر إلى السجل (الـ log)، جنبًا إلى جنب مع الوقت والتاريخ للشريط السابق. يمكنك عرض السجل تحت علامة التبويب Journal في نافذة استراتيجي تستر.

فيما يلي نتائج دالة Print() في السجل:

كود PHP:

Slope Up: 2147483647.00000000, Slope Down: 1.50483900 Time: 2009.11.26 16:00 


قيمة SlopeUp، 2147483647، هي عدد صحيح كبير جدًا يمثل حالة EMPTY_VALUE لمؤشر مخصص. يمكنك في الواقع استخدام هذا كشرط تداول. تُعيد SlopeDown قيمة المؤشر للشريط السابق. Time يشير إلى الشريط الذي نريد العثور عليه في الرسم البياني.

انقر فوق زر “Open Chart” في نافذة استراتيجي تستر لفتح رسم بياني مع تطبيق المؤشر الخاص بك بالفعل. ابحث عن الشريط المشار إليه في السجل بواسطة الوقت، وتأكد من تطابق قيم المؤشرات في نافذة البيانات مع تلك المطبوعة في السجل. إذا لم يتطابقان، فعليك ضبط معامل Mode في دالة iCustom() حتى تجد البافر الصحيح.

هكذا سنستخدم مؤشر Slope Direction Line في خبيرنا المستشار. إذا كان الاتجاه صعوديًا، فإن SlopeUp ستعيد قيمة السعر، بينما ستعيد SlopeDown EMPTY_VALUE أو 2147483647. والعكس ينطبق عندما يكون الاتجاه هابطًا.

كود PHP:

if(SlopeUp != EMPTY_VALUE && SlopeDown == EMPTY_VALUE) // Buy
if(SlopeUp == EMPTY_VALUE && SlopeDown != EMPTY_VALUE) // Sell 

هذه الشروط تقوم ببساطة بفحص أي من الخطوط هو يساوي EMPTY_VALUE وأي منها ليس كذلك

دورة البرمجة في الفوركس للمبتدئين – الدرس الثامن

دورة البرمجة في الفوركس للمبتدئين – الدرس العاشر

دورة