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

دورة البرمجة في الفوركس للمبتدئين الحلقة العاشرة
ثوابت المؤشرات
ضمن دورة البرمجة هناك العديد من الوظائف في MQL، بما في ذلك وظائف المؤشرات والأسعار، تقبل معامل إطار الزمن. كما تم الإشارة إليه سابقًا، إذا استخدمنا معامل إطار الزمن بقيمة 0، سيتم استخدام إطار الزمن الحالي للرسم البياني. إذا كنا نرغب في استخدام إطار زمن مختلف، يتعين علينا تحديد إطار الزمن بالدقائق. على سبيل المثال، M5 يعادل 5 دقائق، H1 يعادل 60 دقيقة، و H4 يعادل 240 دقيقة. يمكننا أيضًا استخدام ثوابت للإشارة إلى إطار الزمن:
PERIOD_M1 – دقيقة واحدة.
PERIOD_M5 – 5 دقائق.
PERIOD_M15 – 15 دقيقة.
PERIOD_M30 – 30 دقيقة.
PERIOD_H1 – ساعة واحدة (60 دقيقة).
PERIOD_H4 – 4 ساعات (240 دقيقة).
PERIOD_D1 – يوميًا (1440 دقيقة).
سعر التطبيق
Applied Price
يشير معامل المؤشر “Applied Price” إلى سلسلة الأسعار التي سيتم استخدامها عند حساب قيمة المؤشر. عادةً ما تستخدم القيمة الأخيرة (Close) لحساب قيم المؤشرات، على الرغم من أنه قد ترغب أيضًا في استخدام قيم أخرى. فيما يلي قائمة بسلاسل الأسعار وثوابتها المرتبطة، جنبًا إلى جنب مع القيمة العددية المرتبطة بها:
PRICE_CLOSE – 0: سعر الإغلاق
PRICE_OPEN – 1: سعر الفتح
PRICE_HIGH – 2: أعلى سعر
PRICE_LOW – 3: أدنى سعر
PRICE_MEDIAN – 4: سعر الوسط، (الأعلى + الأدنى) / 2
PRICE_TYPICAL – 5: سعر النموذج، (الأعلى + الأدنى + الإغلاق) / 3
PRICE_WEIGHTED – 6: سعر موزون، (الأعلى + الأدنى + الإغلاق + الإغلاق) / 4
طرق المتوسط المتحرك
Moving Average Methods
المؤشرات التي تستخدم المتوسط المتحرك كجزء من حساباتها قد تحتوي على معامل لضبط طريقة حساب المتوسط المتحرك. سيتم رسم خط المتوسط المتحرك بشكل مختلف اعتمادًا على طريقة الحساب. فيما يلي ثوابت طرق المتوسط المتحرك مع القيم العددية المقابلة:
MODE_SMA – 0: متوسط حسابي بسيط. يحسب المتوسط للبيانات السعرية.
MODE_EMA – 1: متوسط حسابي مكرر. يعطي وزنًا أكبر للبيانات السعرية الحديثة ووزنًا أقل بشكل تصاعدي للبيانات السعرية القديمة. وهو متوسط حسابي شائع جدًا.
MODE_SMMA – 2: متوسط حسابي ملسّن. متوسط حسابي بسيط يتم حسابه باستخدام معادلة تمليس. ينتج خطًا سلسًا ولكنه أقل استجابة.
MODE_LWMA – 3: متوسط حسابي ذو وزن خطي. يشبه المتوسط المتحرك المكرر، ولكنه يعطي وزنًا أكبر للسعر الأحدث.
تقييم شروط التداول
Evaluating Trade Conditions
نستخدم المشغلات الشرطية if و else لتقييم شروط التداول. لقد رأيت بالفعل استخدام هذه المشغلات في هذا الكتاب، ولكن للمبرمجين الجدد، فإنه من الضروري إجراء مراجعة سريعة.
يقوم المشغل if بتقييم شرط صحيح أو خاطئ. إذا كان الشرط صحيحًا، يتم تنفيذ الكود فورًا بعد عبارة if. إذا كان الشرط خاطئًا، سيتجاوز الكود وينتقل إلى الكود الذي يلي الكتلة if:
كود :
if(BuyCondition == true)
{
OpenBuyOrder(...);
}
إذا كان هناك عبارة واحدة فقط تلي المشغل if، يمكن كتابتها بهذا الشكل:
كود :
if(BuyCondition == true) OpenBuyOrder(...);
يجب أن يتم وضع العبارات المتعددة داخل الأقواس الزوجية.
يقوم المشغل else بتقييم شرط بديل، شريطة أن يكون الشرط السابق (أو الشروط) للعبارة if خاطئًا. يمكنك دمج else و if لإنشاء شرط بديل يتم تنفيذه فقط إذا كان الشرط صحيحًا.
على سبيل المثال، يقوم هذا الكود بتقييم ثلاثة شروط بالترتيب. إذا كان أحد هذه الشروط صحيحًا، فسيتم تنفيذ الكود الخاص بهذا الشرط فقط. إذا لم يكن أي منها صحيحًا، فلن يتم تنفيذ أي منها:
كود :
if(Condition1 == true)
// Execute condition 1
else if(Condition2 == true)
// Execute condition 2
else if(Condition3 == true)
// Execute condition 3
يمكن استخدام المشغل else بمفرده في نهاية تسلسل if-else للإشارة إلى شرط سيتم تنفيذه بشكل افتراضي إذا كانت جميع المشغلات الأخرى لـ if خاطئة. كما ذكر أعلاه، سيتم تنفيذ شرط واحد فقط:
كود :
if(Condition1 == true)
// Execute condition 1
else if(Condition2 == true)
// Execute condition 2
else
{
// Execute condition 3 if 1 and 2 are false
}
إذا كان لديك عدة مشغلات if بدون أي مشغلات else، فسيتم تنفيذ كل منها إذا كانت صحيحة – بغض النظر عما إذا كانت العبارة if التالية صحيحة أو خاطئة:
كود :
if(Condition1 == true)
// Execute condition 1
if(Condition2 == true)
// Execute condition 2
عمليات العلاقة
Relation Operations
نبدأ بتقييم الشروط الصحيحة والخاطئة عن طريق مقارنة القيم باستخدام عمليات أكبر من، أقل من، مساوي ل، ليس مساوي لإلخ. فيما يلي قائمة بعمليات العلاقة:
• == مساوي لـ – إذا كانت x == y ، فإن الشرط صحيح.
• > أكبر من – إذا كانت x < y ، فإن الشرط صحيح.
• < أقل من – إذا كانت x > y ، فإن الشرط صحيح.
• =< أكبر من أو يساوي – إذا كانت x >= y ، فإن الشرط صحيح.
• => أقل من أو يساوي – إذا كانت x <= y ، فإن الشرط صحيح.
• =! ليس مساوي لـ – إذا كانت x != y ، فإن الشرط صحيح.
يرجى ملاحظة أن عامل المساواة (==) ليس نفسه عامل التعيين (=)، فعامل التعيين يستخدم عند تعيين قيمة لمتغير، بينما يستخدم عامل المساواة لتقييم شرط صحيح/خاطئ. هذا خطأ بناء الجملة شائع، ويجب أن تكون حذرًا منه.
يمكنك مقارنة أي قيمتين طالما أنهما من نفس نوع البيانات. يمكنك مقارنة قيمة منطقية مع الثوابت true أو false. يمكنك مقارنة متغير من نوع السلسلة النصية أو العدد الصحيح أو العدد العشري مع قيمة ثابتة مناسبة، أو مع متغير آخر من نفس النوع.
العمليات المنطقية
Boolean Operations
نستخدم المشغلات المنطقية AND (&&) و OR (||) لدمج عمليات العلاقة. يقوم المشغل AND بتقييم ما إذا كانت جميع الشروط صحيحة. إذا كانت كل الشروط صحيحة، فإن البيان بأكمله صحيح. إذا كان أي من الشروط خاطئًا، فإن البيان بأكمله خاطئ.
كود :
if(BooleanVar1 == true && Indicator1 > Indicator2)
{
// Open order
}
إذا كانت قيمة BooleanVar1 تساوي true، وكانت قيمة Indicator1 أكبر من Indicator2، فإن البيان يقيم إلى true ويتم تنفيذ الشفرة بين الأقواس المنحنية. إذا كان أيًا من هذه الشروط خاطئًا، فإن البيان بأكمله يقيم إلى false ولا يتم تنفيذ الشفرة بين الأقواس المنحنية. يمكن أن يكون هناك أي عدد من الشروط المدمجة معًا باستخدام المشغل &&، ويجب أن تقيم جميعها إلى true.
المشغل OR يقيم ما إذا كان أي من الشروط صحيحًا. إذا كانت واحدة على الأقل من الشروط صحيحة، فإن البيان بأكمله يقيم إلى true. إذا كانت جميع الشروط خاطئة، فإن البيان يقيم إلى false.
كود :
if(BooleanVar1 == true || Indicator1 > Indicator2)
إذا كانت قيمة BooleanVar1 مساوية لـ true، أو إذا كانت قيمة Indicator1 أكبر من Indicator2، فإن البيان يقيم إلى true. إذا كان كلتا الشروطين خاطئتين، فإن البيان يقيم إلى false.
يمكنك دمج عمليات AND و OR لإنشاء شروط تداول أكثر تعقيدًا. عند القيام بذلك، استخدم الأقواس لتحديد ترتيب العمليات.
كود :
if((BooleanVar1 == true && Indicator1 > Indicator2) || BooleanVar1 == false)
يتم تقييم البيان (BooleanVar1 == true && Indicator1 > Indicator2) أولاً. إذا كانت كلتا الشروط صحيحتين، فإن البيان يقيم إلى true، ويتبقى لدينا عملية OR:
كود :
if(true || BooleanVar1 == false)
هذا البيان يقيم تلقائياً إلى true، لأن واحدة من الشروط بالفعل صحيحة. ولكن ماذا لو أن (BooleanVar1 == true && Indicator1 > Indicator2) يقيم إلى false؟
كود :
if(false || BooleanVar1 == false)
إذا قيمة الشرط BooleanVar1 == false تقيم إلى true، فإن البيان بأكمله يكون صحيحًا. (بمعنى آخر، إذا تم تعيين BooleanVar1 إلى false، فإن هذا الشرط يقيم إلى true). وإلا، فإن البيان يكون خاطئًا.
من الممكن إنشاء عمليات منطقية معقدة باستخدام المشغلات AND و OR والأقواس للتحكم في ترتيب العمليات. تأكد من مراقبة مواقع الأقواس الخاصة بك، حيث أن قوسًا واحدًا خاطئًا يمكن أن يؤدي إلى تقييم البيان بشكل مختلف، وقد يؤدي نقص الأقواس إلى بعض عمليات التصحيح المملة.
تشغيل وإيقاف المؤشر
Turning An Indicator On and Off
يمكنك استخدام مثال AND/OR في الجزء السابق لتشغيل وإيقاف مؤشر. لنفترض أنّ الخبير الاستشاري الخاص بك يستخدم عدة مؤشرات، وترغب في تمكين إمكانية تشغيل وإيقاف المؤشرات. هنا كيف نقوم بذلك. أولاً، دعنا نعلن متغيرًا منطقيًا خارجيًا لاستخدامه كمفتاح تشغيل/إيقاف. سنستخدم مؤشر Stochastic في هذا المثال:
كود :
extern bool UseStochastic = true;
نحدد مجموعتين من الشروط لمؤشرنا – حالة التشغيل وحالة الإيقاف. حالة التشغيل تتألف من ضبط المتغير التشغيل/الإيقاف على القيمة true، جنبًا إلى جنب مع شرط فتح الأمر. أما حالة الإيقاف، فتتألف ببساطة من ضبط المتغير التشغيل/الإيقاف على القيمة false.
كود :
if((UseStochastic == true && Kline > Dline) || UseStochastic == false)
{
// Buy order
}
العبارة (UseStochastic == true && Kline > Dline) تمثل حالة “التشغيل”. إذا تم ضبط المتغير الخارجي UseStochastic على true وكانت الشروط التداول Kline > Dline صحيحة، فإن شرط طلب الأمر الاستوكاستيك سيكون صحيحًا.
أما UseStochastic == false، فتمثل حالة “الإيقاف”. إذا تم ضبط المتغير الخارجي UseStochastic على false، فإن (UseStochastic == true && Kline > Dline) ستقيم إلى قيمة false، بينما UseStochastic == false ستقيم إلى قيمة true.
نظرًا لأن حالتي التشغيل والإيقاف مرتبطتين بعامل OR، يكفي أن تكون إحدى الحالتين صحيحة لجعل العبارة ككل صحيحة. لذلك، طالما إما a.) المؤشر مشغّل وتتحقق شرط وضع الأمر، أو b.) المؤشر موقوف، فسيكون العبارة ككل صحيحة، ويمكن تقييم أي شروط أمر أخرى.
دعونا نضيف شرط تداول ثاني إلى شرط الاستوكاستيك – عبور المتوسط المتحرك:
كود :
if(((UseStochastic == true && Kline > Dline) || UseStochastic == false)
&& FastMA > SlowMA)
في هذا المثال، قمنا بإضافة شرط المتوسط المتحرك المتقاطع، FastMA > SlowMA. يجب ملاحظة أننا قمنا بإضافة مجموعة أخرى من الأقواس حول الشرط المتعلق بالاستوكاستيك، لأن البيان بأكمله داخل الأقواس يحتاج إلى تقييم أولاً.
أولاً، نقوم بتقييم البيان داخل مجموعة الأقواس الأكثر داخلية: (UseStochastic == true && Kline > Dline). إذا تم تعيين المعلمة UseStochastic على true، وتقييم Kline > Dline يعطي قيمة true، فإن الجزء الأول من البيان يكون صحيحًا.
كود :
if((true || UseStochastic == false) && FastMA > SlowMA)
شرط UseStochastic == false يقيم إلى قيمة false. نتبقى مع عملية OR، وبما أن أحد الشروط بالفعل صحيح، يقيم الشرط الاستوكاستيك بأكمله إلى true.
كود :
if((true || false) && FastMA > SlowMA)
if(true && FastMA > SlowMA)
إذا تقييم FastMA > SlowMA يكون صحيحًا، فإن الشرط التجاري بأكمله يكون صحيحًا، ويتم وضع الطلب.
إذا كانت القيمة خاطئة، فإن التعبير يقيم إلى قيمة خاطئة، ولا يتم وضع الطلب.
الآن، ماذا يحدث إذا كان شرط التداول الاستوكاستيك خاطئًا؟ إذا تم تعيين UseStochastic على true، وتقييم Kline > Dline يعطي قيمة خاطئة، فإن الشرط بأكمله يصبح خاطئًا.
كود :
if((false || UseStochastic == false) && FastMA > SlowMA)
if((false || false) && FastMA > SlowMA)
if(false && FastMA > SlowMA)
بغض النظر عن كيفية تقييم FastMA > SlowMA، فإن الشرط التجاري بأكمله يكون خاطئًا.
الآن لنفترض أن UseStochastic معينة على false. في هذه الحالة، يتم تقييم البيان (UseStochastic == true && Kline > Dline) على أنه خاطئًا.
كود :
if((false || UseStochastic == false) && FastMA > SlowMA)
بما أن البيان UseStochastic == false يعتبر صحيحًا، فإن الشرط الاستوكاستيك يقيم إلى true.
كود :
if((false || true) && FastMA > SlowMA)
if(true && FastMA > SlowMA)
مما يعني أنه إذا كانت قيمة FastMA > SlowMA صحيحة أيضًا، سيتم وضع الطلب. في هذه الحالة، لم يتم النظر حتى في الشرط الاستوكاستيك، بخلاف تقييم حالة تشغيل/إيقاف المؤشر.
مقارنة قيم المؤشر عبر الأشرطة
Comparing Indicator Values Across Bars
في بعض الأحيان قد تحتاج إلى مقارنة قيمة المؤشر للشريطة الحالية أو الأخيرة المغلقة بقيمة المؤشر لشريطة سابقة. على سبيل المثال، لنفترض أنك ترغب في معرفة ما إذا كانت المتوسطة المتحركة ترتفع أم تنخفض. للقيام بذلك، نقارن قراءة المؤشر للشريطة الحالية بتلك الشريطة السابقة.
نستخدم معامل التحويل (Shift) في وظيفة المؤشر لتحديد أي شريطة سيتم إرجاع قيمة المؤشر لها. يكون معامل التحويل هو آخر معامل داخل وظيفة المؤشر. تحمل الشريطة الحالية قيمة تحويل (Shift) قدرها 0، والشريطة السابقة تحمل قيمة تحويل (Shift) قدرها 1، وهكذا. تعيد وظائف المتوسط المتحرك أدناه قيمة المتوسط المتحرك للشريطة الحالية والسابقة:
كود :
double MA = iMA(NULL,0,MAPeriod,0,MAMethod,MAPrice,0);
double LastMA = iMA(NULL,0,MAPeriod,0,MAMethod,MAPrice,1);
في هذا المثال، MA هي المتغير الذي يحمل قيمة المؤشر للشريطة الحالية، بينما يحمل LastMA قيمة المؤشر للشريطة السابقة. يرجى ملاحظة أن معامل التحويل (Shift) هو 0 للشريطة الحالية و 1 للشريطة السابقة.
فيما يلي الشيفرة لتحديد ما إذا كانت خط المتوسط المتحرك يتحرك صعودًا أم هبوطًا:
كود :
if(MA > LastMA)
{
// MA is going up
}
else if(MA < LastMA)
{
// MA is going down
}
إذا كانت قيمة المؤشر للشريطة الحالية (MA) أكبر من قيمة الشريطة السابقة (LastMA)، يمكننا الاستنتاج بأن المؤشر يتحرك صعودًا. والعكس صحيح عندما تكون قيمة المؤشر للشريطة الحالية أقل من قيمة المؤشر للشريطة السابقة.
من خلال مقارنة قيمة المؤشر لشريطة سابقة بالشريطة الحالية، يمكننا تحديد ما إذا كان المؤشر قد عبر مؤخرًا فوق أو تحت قيمة معينة، مثل مستويات الشراء المفرط/البيع المفرط لمؤشر مذبذب، أو خط مؤشر آخر.
على سبيل المثال، لنفترض أن نظام التداول الخاص بك يعطي إشارة تداول عندما يتجاوز مؤشر الاستوكاستيك قيمة 30 أو تحت 70. فيما يلي الشيفرة للتحقق من ذلك:
كود :
double Stoch = iStochastic(NULL,0,KPeriod,DPeriod,Slowing,MAMetho d,Price,0,0);
double LastStoch = iStochastic(NULL,0,KPeriod,DPeriod,Slowing,MAMetho d,Price,0,1);
if(Stoch > 30 && LastStoch < 30)
{
// Open buy order
}
if(Stoch < 70 && LastStoch > 70)
{
// Open sell order
}
Stoch هي قيمة المؤشر للشريطة الحالية، بينما LastStoch هي قيمة المؤشر للشريطة السابقة. إذا كانت قيمة Stoch أكبر من 30 وكانت قيمة LastStoch أقل من 30، يمكننا الاستنتاج بأن المؤشر قد تجاوز مستوى البيع المفرط خلال الشريطة الأخيرة. عن طريق عكس مشغلات المقارنة، يمكننا التحقق من تجاوز مؤخرًا أسفل قيمة ثابتة، مثل مستوى الشراء المفرط 70.
فيما يلي مثال آخر باستخدام المتوسطات المتحركة. سننشئ شرطًا لفتح أمر فقط عندما يتجاوز FastMA و SlowMA في الشريطة الأخيرة:
كود :
double FastMA = iMA(NULL,0,FastMAPeriod,0,0,0,0);
double SlowMA = iMA(NULL,0,SlowMAPeriod,0,0,0,0);
double LastFastMA = iMA(NULL,0,FastMAPeriod,0,0,0,1);
double LastSlowMA = iMA(NULL,0,SlowMAPeriod,0,0,0,1);
if(FastMA > SlowMA && LastFastMA <= LastSlowMA
&& BuyMarketCount(Symbol(),MagicNumber) == 0)
{
// Open buy order
}
if(FastMA < SlowMA && LastFastMA >= LastSlowMA
&& SellMarketCount(Symbol(),MagicNumber) == 0)
{
// Open sell order
}
في هذا المثال، نقوم بمقارنة علاقة اثنين من المؤشرات مع بعضهما البعض. LastFastMA و LastSlowMA تُرجع قيم المتوسط المتحرك للشريطة السابقة. إذا كانت قيمة LastFastMA أقل من (أو تساوي) LastSlowMA، وتكون قيمة FastMA أكبر من SlowMA في الوقت الحالي، فإننا نعلم أن خط المتوسط المتحرك السريع قد تجاوز خط المتوسط المتحرك البطيء في الشريطة الأخيرة.
يوفر ذلك إشارة تداول موثوقة، حيث يمكننا تحديد وضع أمرنا فورًا بعد حدوث التقاطع. يمكنك تغيير قيمة Shift لوظائف LastFastMA و LastSlowMA إذا كنت ترغب في زيادة عدد الشرائط التي سيتم البحث فيها عن تقاطع المؤشر.
لقد قمنا بإضافة مقارنة LastFastMA و LastSlowMA إلى شروط طلب الشراء والبيع في مستشارنا الخبير. يمكننا الآن إزالة التحقق من BuyTicket و SellTicket، حيث أن هذه الطريقة أكثر موثوقية من التحقق من رقم تذكرة الطلب المخزن. كما أننا لا نحتاج إلى القلق بشأن وضع الطلبات بعد فترة طويلة من وقت حدوث التقاطع.
شيفرة المستشار الخبير المرفق للاطلاع على جميع التغييرات.
تم تعريف الوظائف في IncludeExample.mqh، والمحتوى المفصل له مدرج في الدروس السابقة.
كود :
// 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);
}
العمل مع الوقت والتاريخ
Working with Time and Date
متغيرات التاريخ
Datetime Variables
داخليًا، يتم تمثيل المتغير الزمني (datetime) كعدد الثواني المنقضية منذ الأول من يناير 1970. على سبيل المثال، فإن يوم 15 يونيو 2009 في الساعة 00:00 (منتصف الليل) سيكون بقيمة 1245024000. ميزة تنسيق datetime هي أنه يسهل مقارنة الأوقات في الماضي والمستقبل والتلاعب الرياضي بها بشكل سهل جدًا.
على سبيل المثال، إذا كنت ترغب في التحقق مما إذا كان تاريخ ما يأتي قبل أو بعد تاريخ آخر، يمكنك القيام بعملية نسبية بسيطة. لنفترض أن تاريخ البدء (StartDate) هو 15 يونيو 2009 في الساعة 14:00، وتاريخ الانتهاء (EndDate) هو 16 يونيو 2009 في الساعة 5:00.
كود :
if(StartDate < EndDate) // Result is true
if(StartDate > EndDate) // Result is false
ميزة أخرى هي أنه يمكنك إضافة أو طرح الوقت من تاريخ محدد، ببساطة عن طريق إضافة أو طرح العدد المناسب من الثواني. إذا كنت ترغب في إضافة 24 ساعة إلى StartDate، ما عليك سوى إضافة عدد الثواني في يوم واحد:
كود :
datetime AddDay = StartDate + 86400;
إذا كنت تخطط للقيام بالعديد من العمليات الرياضية باستخدام المتغيرات الزمنية (datetime)، قد يكون من الجيد تعريف بعض الثوابت الصحيحة (integer constants) لتمثيل وحدات زمنية معينة:
كود :
#define SEC_H1 3600
// Seconds in an hour
#define SEC_D1 86400
// Seconds in a day
عيب تنسيق datetime هو أنه غير قابل للقراءة بشكل جيد. لا يمكنك النظر إلى قيمة مثل 1245024000 وأن تستطيع أن تعرف تلقائيًا أنه يمثل يونيو 15، 2009 في الساعة 00:00. لهذا الغرض، نستخدم وظائف التحويل لتحويل datetime إلى شكل أكثر قراءةً والعكس.
ثوابت التاريخ
Datetime Constants
ثابت datetime هو تاريخ ووقت معروض بالتنسيق النصي التالي: yyyy.mm.dd hh:mm.
على سبيل المثال، سيكون يوم 15 يونيو 2009 في الساعة 00:00 بالتنسيق 2009.06.15 00:00. هناك تنسيقات أخرى قابلة للقبول.
تنسيقات الثوابت datetime: موضوع مرجع MQL الأساسي – أنواع البيانات – الثوابت Datetime يحتوي على مزيد من المعلومات. سنستخدم التنسيق المقدم أعلاه، حيث أنه التنسيق الوحيد الذي يمكن تحويله بسهولة.
لتحويل متغير datetime إلى ثابت سلسلة نصية، استخدم وظيفة TimeToStr(). هنا هو الصيغة:
كود :
string TimeToStr(datetime Time, int Output = TIME_DATE|TIME_MINUTES);
المتغير “Time” هو المتغير “datetime” المعبر عنه بعدد الثواني المنقضية منذ الأول من يناير 1970.
المعامل “Output” هو معامل اختياري يخرج الثابت بصورة تاريخ فقط، أو ساعة ودقيقة فقط، أو ساعة ودقيقة وثوانٍ، أو أي تركيبة من تاريخ ووقت. القيم الصحيحة للمدخلات هي:
TIME_DATE: يخرج التاريخ فقط، على سبيل المثال، 2009.06.15
TIME_MINUTES: يخرج الساعة والدقيقة، على سبيل المثال، 05:30
TIME_SECONDS: يخرج الساعة والدقيقة والثواني، على سبيل المثال، 05:30:45
لإخراج الثابت النصي في التنسيق الافتراضي yyyy.mm.dd hh:mm، اترك المعامل “Output” فارغًا. إذا كنت ترغب فقط في التاريخ أو الساعة والدقيقة (أو الثواني)، استخدم الوسيطة المناسبة. في هذا المثال، سنفترض أن StartTime يساوي 2009.06.15 05:30:45.
كود :
TimeToStr(StartTime,TIME_DATE)
// Returns "2009.06.15"
TimeToStr(StartTime,TIME_SECONDS)
// Returns "05:30:45"
TimeToStr(StartTime,TIME_MINUTES)
// Returns "05:30"
TimeToStr(StartTime,TIME_DATE|TIME_SECONDS) // Returns "2009.06.15 05:30:45"
TimeToStr(StartTime)
// Returns "2009.06.15 05:30"
يمكننا إنشاء ثابت datetime باستخدام اتصال السلاسل (string concatenation)، وتحويله إلى متغير datetime باستخدام وظيفة StrToTime(). الصيغة مطابقة لـ TimeToStr() أعلاه، ولكن بدون المعامل Output. يجب أن يكون الثابت النصي في التنسيق yyyy.mm.dd hh:mm ليتم التحويل بشكل صحيح.
فيما يلي مثال لكيفية تجميع ثابت datetime باستخدام أعداد صحيحة، ثم تحويل تلك الأعداد الصحيحة إلى تنسيق سلسلة نصية، وتحويل السلسلة إلى متغير datetime. أولاً، سنعلن بعض المتغيرات الخارجية لتعيين وقت وتاريخ:
كود :
extern int UseMonth = 6;
extern int UseDay = 15;
extern int UseHour = 5;
extern int UseMinute = 30;
بعد ذلك، نقوم بإنشاء الثابت النصي باستخدام وظيفة StringConcatenate()، وأخيرًا نقوم بتحويل السلسلة إلى تنسيق datetime باستخدام وظيفة StrToTime().
كود :
string DateConstant = StringConcatenate(Year(),".",UseMonth,".",UseDay," ",
UseHour,":",UseMinute);
// DateConstant is "2009.6.15 05:30"
datetime StartTime = StrToTime(DateConstant);
// StartTime is "1245043800"
يرجى ملاحظة أنه في وظيفة StringConcatenate()، نستخدم Year() لإرجاع السنة الحالية بدلاً من استخدام متغير خارجي. يمكنك استخدام وظائف مثل Month() و Day() وغيرها لإدراج قيم الوقت الحالية. سنتناول هذه الوظائف في القسم التالي ان شاء الله.
وظائف التاريخ والوقت
Date and Time Functions
هناك وظيفتان تعيدان الوقت الحالي: TimeCurrent() تعيد وقت الخادم الحالي، بينما TimeLocal() تعيد وقت جهاز الكمبيوتر المحلي الخاص بك. يمكنك استخدام أيهما تفضل. قد ترغب في إنشاء متغير خارجي منطقي للاختيار بين الوظيفتين.
كود :
extern bool UseLocalTime = true;
فيما يلي الشيفرة لتعيين إما الوقت المحلي الحالي أو وقت الخادم الحالي لمتغير يسمى “CurrentTime”:
كود :
if(UseLocalTime == true) datetime CurrentTime = TimeLocal();
// Local time
else CurrentTime = TimeCurrent();
// Server time
في بعض الأحيان قد تحتاج فقط إلى استرداد جزء من الوقت الحالي، مثل الساعة أو اليوم. فيما يلي قائمة بالوظائف الأكثر فائدة التي يمكنك استخدامها لاسترداد قيم الوقت الحالي. جميع هذه الوظائف تستخدم وقت الخادم – وليس وقت جهاز الكمبيوتر المحلي الخاص بك. قيمة العودة هي من نوع integer (عدد صحيح):
Year() – السنة الحالية المكونة من أربعة أرقام، على سبيل المثال، 2009.
Month() – الشهر الحالي من السنة من 1 إلى 12.
Day() – اليوم الحالي من الشهر من 1 إلى 31.
DayOfWeek() – عدد صحيح يمثل اليوم الحالي من الأسبوع. الأحد هو 0، الاثنين هو 1، الجمعة هو 5 وهكذا.
Hour() – الساعة الحالية في نظام الوقت بتنسيق 24 ساعة، من 0 إلى 23. على سبيل المثال، الساعة 3 صباحًا هي 3، والساعة 3 مساءً هي 15.
Minute() – الدقيقة الحالية من 0 إلى 59.
يمكنك أيضًا استرداد هذه القيم من أي متغير datetime باستخدام مجموعة مختلفة من الوظائف. تتطلب هذه الوظائف متغير datetime كمعامل وحيد، ولكنها تعمل بنفس طريقة الوظائف المذكورة أعلاه. إذا كنت ترغب في استرداد قيمة الوقت من TimeLocal()، استخدم ناتج وظيفة TimeLocal() كوسيط للوظائف أدناه:
TimeYear() – السنة المكونة من أربعة أرقام للقيمة datetime المحددة.
TimeMonth() – الشهر للقيمة datetime المحددة من 1 إلى 12.
TimeDay() – اليوم من الشهر للقيمة datetime المحددة من 1 إلى 31.
TimeDayOfWeek() – عدد صحيح يمثل اليوم من الأسبوع للقيمة datetime المحددة. الأحد هو 0، الاثنين هو 1، الجمعة هو 5 وهكذا.
TimeHour() – الساعة للقيمة datetime المحددة في نظام الوقت بتنسيق 24 ساعة، من 0 إلى 23.
TimeMinute() – الدقيقة للقيمة datetime المحددة من 0 إلى 59.
فيما يلي بعض الأمثلة على استخدام هذه الوظائف. دعنا نفترض أن TimeLocal() يساوي 2009.06.15 05:30.
كود :
datetime CurrentTime = TimeLocal();
int GetMonth = TimeMonth(CurrentTime);
// Returns 6
int GetHour = TimeHour(CurrentTime);
// Returns 5
int GetWeekday = TimeDayOfWeek(CurrentTime);
// Returns 1 for Monday
إنشاء مؤقت بسيط
شيء مفيد جدًا يمكننا القيام به باستخدام الوقت والتاريخ في MQL هو إضافة مؤقت إلى خبيرنا المستشار. بعض التجار يفضلون تحديد تداولهم في أوقات النشاط الأكثر لليوم، مثل جلسات لندن ونيويورك. قد يرغب آخرون في تجنب التداول خلال أحداث السوق العنيفة، مثل تقارير الأخبار والتقرير الشهري للتوظيف. لإنشاء مؤقت، نحتاج إلى تحديد وقت البدء ووقت الانتهاء. سنستخدم متغيرات صحيحة خارجية لإدخال معلمات الوقت. سنقوم بإنشاء سلسلة ثابتة من التاريخ والوقت، وتحويلها إلى متغير datetime. ثم سنقارن بين أوقات البدء والانتهاء مع الوقت الحالي. إذا كان الوقت الحالي أكبر من وقت البدء ولكنه أقل من وقت الانتهاء، سيتم السماح بالتداول.
فيما يلي المتغيرات الخارجية التي سنستخدمها. سنضبط متغيرًا لتشغيل وإيقاف المؤقت، وكذلك لتحديد الوقت الحالي (الخادم أو المحلي). لدينا إعدادات لشهر ويوم وساعة ودقيقة لكل من وقتي البدء والانتهاء:
كود :
extern bool UseTimer = true;
extern bool UseLocalTime = false;
extern int StartMonth = 6;
extern int StartDay = 15;
extern int StartHour = 7;
extern int StartMinute = 0;
extern int EndMonth = 6;
extern int EndDay = 15;
extern int EndHour = 2;
extern int EndMinute = 30;
وفيما يلي الشفرة للتحقق مما إذا كان يسمح بالتداول أم لا. المتغير TradeAllowed يحدد ما إذا كان سيتم فتح صفقات جديدة. إذا تم ضبط UseTimer على قيمة false، يتم ضبط TradeAllowed تلقائيًا على true. وإلا، نقوم بتقييم أوقات البدء والانتهاء بالمقارنة مع الوقت الحالي لمعرفة ما إذا كان سيسمح بالتداول أم لا.
كود :
if(UseTimer == true)
{
// Convert start time
string StartConstant = StringConcatenate(Year(),".",StartMonth,".",StartD ay," ",
StartHour,":",StartMinute);
datetime StartTime = StrToTime(StartConstant);
if(StartMonth == 12 && StartDay == 31 && EndMonth == 1) int EndYear = Year() + 1;
else EndYear = Year();
// Convert end time
string EndConstant = StringConcatenate(EndYear,".",EndMonth,".",EndDay, " ",
EndHour,":",EndMinute);
datetime EndTime = StrToTime(EndConstant);
// Choose local or server time
if(UseLocalTime == true) datetime CurrentTime = TimeLocal();
else CurrentTime = TimeCurrent();
// Check for trade condition
if(StartTime <= CurrentTime && EndTime > CurrentTime)
{
bool TradeAllowed = true;
}
else TradeAllowed = false;
}
else TradeAllowed = true;
نبدأ بتحويل وقت البدء إلى متغير datetime بواسطة StartTime. يتحقق البيان if (StartMonth == 12 && StartDay == 31 && EndMonth == 1) مما إذا كانت تاريخ البدء هو آخر يوم في العام، وإذا كان تاريخ الانتهاء بعد الأول من العام التالي. إذا كان الأمر كذلك، فإنه يزيد تلقائيًا السنة النهائية بمقدار 1. وإلا، سنستخدم السنة الحالية لـ EndYear.
بعد ذلك، نحول وقت الانتهاء إلى المتغير datetime بواسطة EndTime ونختار أي CurrentTime نرغب في استخدامه، سواءً كان الخادم أو المحلي. يتحقق الكتلة النهائية if للتحقق مما إذا كان الوقت الحالي بين وقت البدء ووقت الانتهاء. إذا كان الأمر كذلك، يتم ضبط TradeAllowed على القيمة true.
الآن نحتاج إلى إضافة الشفرة للتحكم في تنفيذ التداول. أسهل طريقة للقيام بذلك هي إضافة كتلة if حول روتينات فتح الأوامر الخاصة بنا.
كود :
// Begin trade block
if(TradeAllowed == true)
{
// Buy Order
if(FastMA > SlowMA && BuyTicket == 0 && BuyOrderCount(Symbol(),MagicNumber) == 0)
{
// Buy order code omitted for brevity
}
// Sell Order
if(FastMA < SlowMA && SellTicket == 0 && SellOrderCount(Symbol(),MagicNumber) == 0)
{
// Sell order code omitted for brevity
}
} // End trade block
هناك العديد من الطرق الأخرى لإنشاء المؤقتات – على سبيل المثال، يمكنك استخدام يوم الأسبوع بدلاً من الشهر واليوم، أو تعيين أوقات التداول بالنسبة لليوم الحالي. سنترك لك، القارئ، إنشاء مؤقت يناسب احتياجاتك.
تنفيذ عند افتتاح الشمعة
Execute On Bar Open
بشكل افتراضي، تعمل الخبراء الاستشاريون في الوقت الحقيقي، على كل نقطة (Tick). ولكن في بعض الحالات، قد يكون من الأفضل التحقق من شروط التداول مرة واحدة فقط في كل شريط (Bar). عن طريق انتظار إغلاق الشريط الحالي، يمكننا التأكد من حدوث الشرط وصحة الإشارة. وبالمقارنة، من خلال تنفيذ الصفقات في الوقت الحقيقي، قد نكون أكثر عرضة للإشارات الزائفة.
التداول مرة واحدة في كل شريط يعني أيضًا أن النتائج في محاكي الاستراتيجية ستكون أكثر دقة وصلة. نظرًا للقيود الأساسية لمحاكي استراتيجية ميتاتريدر، استخدام “كل نقطة” كنموذج للاختبار سينتج نتائج اختبار سابق غير موثوق بها، نظرًا لأن النقاط المتمثلة غالبًا في نموذج المحاكاة
من بيانات M1. فإن الصفقات التي تحدث في التداول الحي لا تتطابق بالضرورة مع الصفقات التي تمت في محاكي الاستراتيجية.
ولكن من خلال وضع الصفقات عند إغلاق الشريط واستخدام “أسعار الافتتاح فقط” كنموذج للاختبار، يمكننا الحصول على نتائج اختبار تعكس بدقة أكبر التداولات في الوقت الحقيقي. عيب التداول مرة واحدة في كل شريط هو أن الصفقات قد يتم تنفيذها بشكل متأخر، خاصة إذا كان هناك الكثير من حركة الأسعار على مدار الشريط. إنها أساسًا تجارة متبادلة بين الاستجابة والموثوقية.
للتحقق من شروط التداول مرة واحدة في كل شريط، يجب علينا فحص الطابع الزمني للشريط الحالي. سنقوم بحفظ هذا الطابع الزمني في متغير عالمي. عند كل تنفيذ للخبير المستشار، سنقارن الطابع الزمني المحفوظ بالطابع الزمني الحالي. عندما يتغير طابع الزمن للشريط الحالي، مما يشير إلى فتح شريط جديد، سنقوم بذلك بالتحقق من شروط التداول.
يجب أيضًا ضبط معامل الانتقال (shift parameter) في وظائف المؤشرات ووظائف الأسعار والمصفوفات ليعيد قيمة الشريط السابق. إذا تم تعيين وظيفة المؤشر أو مصفوفة الأسعار لفحص الشريط الحالي، فسنقوم بتحويل مؤشر الشريط بزيادة 1 لفحص الشريط السابق بدلاً من ذلك. يجب زيادة معامل الانتقال بمقدار 1 لجميع المؤشرات ومصفوفات الأسعار.
من الناحية التقنية، نحن نقوم بفحص شروط التداول على النقطة الأولى من شمعة جديدة، بينما نفحص قيمة الإغلاق للشمعة السابقة. لا نقوم بفحص الشمعة المفتوحة حاليًا عند التنفيذ مرة واحدة في كل شريط.
فيما يلي الشفرة للتحقق من فتح شريط جديد. أولاً، نعلن عن متغير خارجي بالاسم CheckOncePerBar لتشغيل وإيقاف هذه الميزة. ثم نعلن عن متغير عالمي من نوع datetime لتخزين الطابع الزمني للشريط الحالي – وسيتم تسميته CurrentTimeStamp.
في وظيفة init()، سنعين الطابع الزمني للشريط الحالي لـ CurrentTimeStamp. وهذا سيؤخر فحص شرط التداول حتى فتح الشريط التالي:
كود :
// External variables
extern bool CheckOncePerBar = true;
// Global variables
datetime CurrentTimeStamp;
// Init function
int init()
{
CurrentTimeStamp = Time[0];
}
فيما يلي الشفرة التي تأتي في بداية وظيفة start()، مباشرة بعد المؤقت. المتغير الصحيح (integer) BarShift سيحدد ما إذا كان سيتم تعيين قيمة التحول (Shift value) لوظائف المؤشر والأسعار للشريط الحالي أم الشريط السابق. المتغير البولياني (boolean) NewBar سيحدد ما إذا كنا سنتحقق من شروط التداول:
كود :
if(CheckOncePerBar == true)
{
int BarShift = 1;
if(CurrentTimeStamp != Time[0])
{
CurrentTimeStamp = Time[0];
bool NewBar = true;
}
else NewBar = false;
}
else
{
NewBar = true;
BarShift = 0;
}
إذا تم تعيين CheckOncePerBar على القيمة الصحيحة (true)، سنقوم أولاً بتعيين قيمة BarShift إلى 1. وهذا سيعين قيمة التحول (Shift parameter) لجميع وظائف المؤشر والأسعار/المصفوفات للشريط السابق.
بعد ذلك، سنقارن قيمة المتغير CurrentTimeStamp بقيمة Time[0]، والتي هي الطابع الزمني للشريط الحالي. إذا لم تتطابق القيمتين، سنقوم بتعيين قيمة Time[0] لـ CurrentTimeStamp وتعيين NewBar إلى القيمة الصحيحة (true). ستتم التحقق من شروط التداول بعد وقت قصير من ذلك.
في التشغيلات التالية، ستتطابق قيمة CurrentTimeStamp و Time[0]، وهذا يعني أن NewBar سيتم تعيينه إلى القيمة الخاطئة (false). لن يتم التحقق من شروط التداول حتى يتم فتح شريط جديد. بمجرد فتح شريط جديد، ستكون قيمة Time[0] مختلفة عن قيمة CurrentTimeStamp، وسيتم تعيين NewBar إلى القيمة الصحيحة (true) مرة أخرى.
إذا تم تعيين CheckOncePerBar على القيمة الخاطئة (false)، سيتم تعيين NewBar تلقائيًا إلى القيمة الصحيحة (true)، وسيتم تعيين BarShift إلى 0. وهذا سيتحقق من شروط التداول في كل نقطة (tick)، كما كان سابقًا.
سيحتاج متغير BarShift إلى أن يتم تعيينه إلى معامل التحول (Shift parameter) في أي وظائف المؤشرات، وظائف الأسعار أو المصفوفات التي تشير إلى الشريط الأخير. فيما يلي بعض الأمثلة على كيفية تطبيق ذلك:
كود :
يجب أن تتعرف على هذه الأمثلة من قبل. بدلاً من التحقق من الشريط الحالي، سنتحقق من الشريط الذي تم إغلاقه للتو، أي الشريط السابق. إذا كنت بحاجة إلى الإشارة إلى شريط قبل الشريط السابق المغلق، ما عليك سوى إضافة معامل التحول الحالي إلى BarShift.
كود :
double LastFastMA = iMA(NULL,0,FastMAPeriod,0,0,0,BarShift+1);
إذا كنت لا تتوقع الحاجة إلى تشغيل خبيرك المستشار مرة واحدة في كل شريط، فلن تحتاج إلى إضافة هذه الشفرة. ولكن بالنسبة للعديد من أنظمة التداول المستندة إلى المؤشرات، يمكن أن يجعل ذلك نتائج التداول واختبار الرجوع أكثر موثوقية.
للتحكم في تنفيذ الصفقات، نحتاج إلى التحقق من قيمة NewBar قبل روتينات وضع الأوامر. يمكننا القيام بذلك باستخدام كتلة if التي وضعناها سابقًا للمؤقت:
كود :
// Begin trade block
if(TradeAllowed == true && NewBar == true)
{
// Buy Order
if(FastMA > SlowMA && BuyTicket == 0 && BuyOrderCount(Symbol(),MagicNumber) == 0)
{
// Buy order code omitted for brevity
}
// Sell Order
if(FastMA < SlowMA && SellTicket == 0 && SellOrderCount(Symbol(),MagicNumber) == 0)
{
// Sell order code omitted for brevity
}
} // End trade block
نصائح وحيل ( 1 )
Tips and Tricks
في هذا الدرس سنغطي الميزات الإضافية التي قد تكون مفيدة في مستشاري الخبراء الخاصة بك.
حروف الهروب
Escape Characters
إذا كنت ترغب في إضافة علامات اقتباس أو حرف علامة خط إلى ثابت سلسلة النص، ستحتاج إلى تهريب
الحرف باستخدام حرف الهروب الخلفي (\). على سبيل المثال، إذا كنت بحاجة لإدراج علامة اقتباس مزدوجة، فسيكون حرف الهروب
هو “. أما بالنسبة للفتحة المفردة، فإن حرف الهروب هو ‘. وبالنسبة للشرطة الخلفية، استخدم شرطتين خلفية كحرف هروب: \\.
كود :
string EscQuotes = "This string has "escaped double quotes"";
// Output: This string has "escaped double quotes"
string EscQuote = "This string has \'escaped single quotes\'";
// Output: This string has 'escaped single quotes'
string EscSlash = "This string has an escaped backslash \";
// Output: This string has an escaped backslash \
إذا كنت بحاجة إلى سلسلة نصية تمتد عبر عدة أسطر، استخدم حرف الهروب \n لإضافة سطر جديد:
كود :
string NewLine = "This string has \n a newline";
// Output: This string has
a newline
استخدام التعليقات على الرسم البياني
Using Chart Comments
يمكنك طباعة نص في الزاوية اليسرى العلوية من الرسم البياني باستخدام وظيفة Comment(). يمكن استخدام ذلك لطباعة معلومات الحالة، إعدادات المؤشر أو أي معلومات أخرى قد تجدها مفيدة.
طريقة واحدة لعرض التعليقات على الرسم البياني هي تعريف عدة متغيرات نصية ودمجها معًا باستخدام حروف سطر جديدة. يمكن استخدام سلسلة نصية واحدة لعرض الإعدادات، وأخرى لعرض رسائل المعلومات أو حالة الطلبات، وما إلى ذلك. سيتم تمرير السلسلة المدمجة إلى وظيفة Comment().
ضع وظيفة Comment() في نهاية وظيفة start() لتحديث تعليق الرسم البياني.
كود :
string SettingsComment = "FastMAPeriod: "+FastMAPeriod+" SlowMAPeriod: "+SlowMAPeriod;
string StatusComment = "Buy order placed";
Comment(SettingsComment+"\n"+StatusComment);
نعلن ونقوم بتعيين قيم المتغيرات SettingsComment وStatusComment داخل وظيفة start().
في نهاية وظيفة start، نستدعي وظيفة Comment() ونستخدمها لطباعة التعليقات على الرسم البياني. نستخدم حرف سطر جديد (\n) لفصل التعليقات إلى سطرين.
فحص الإعدادات
Check Settings
هناك عدة خصائص لمستشار الخبراء يجب تمكينها قبل أن يُسمح للمستشار الخبير بالتداول. توجد هذه الإعدادات تحت علامة “مشترك” في نافذة خصائص المستشار الخبير.
يجب تمكين إعداد “السماح بالتداول الحي” قبل أن يتمكن من التداول. إذا لم يتم تمكينه، ستظهر وجه حزين في الزاوية اليمنى العلوية من الرسم البياني، بجوار اسم المستشار الخبير.
يمكنك التحقق من هذا الشرط في برنامج المستشار الخبير الخاص بك باستخدام وظيفة IsTradeAllowed(). إذا كانت تعيد قيمة خاطئة، فإن إعداد “السماح بالتداول الحي” معطل.
إذا كنت ترغب في عرض رسالة للمستخدم تشير إلى ضرورة تفعيل هذا الإعداد، يمكنك فعل ذلك على النحو التالي:
كود :
if(IsTradeAllowed() == false) Alert("Enable the setting \'Allow live trading\' in the
Expert Properties!");
إذا كان مستشارك الخبير يستخدم مكتبة .ex4 خارجية، يجب تمكين إعداد “السماح باستيراد خبراء خارجيين”
كود :
if(IsLibrariesAllowed() == false) Alert("Enable the setting \'Allow import of external
experts\' in the Expert Properties!");
يمكن القيام بالشيء نفسه بالنسبة للملفات ذات الامتداد DLL باستخدام وظيفة IsDllsAllowed().
كود :
if(IsDllsAllowed() == false) Alert("Enable the setting \'Allow DLL imports\' in the
Expert Properties!");
يمكنك عرض جميع وظائف فحص النظام في المرجع MQL تحت فئة “Checkup”.
قيود النسخة التجريبية أو الحساب
Demo or Account Limitations
قد تقرر في وقت ما بيع مستشار الخبراء الرابح الخاص بك للتجار الآخرين. قد ترغب أيضًا في توفير نسخة تجريبية للمشترين المحتملين للاختبار. لمنع انتشار مستشارك الخبير بحرية أو تداوله بواسطة أشخاص غير مصرح لهم، سترغب في تضمين نوع من قيود الحساب التي تحد من استخدام المستشار الخبير للمشترين المصرح لهم. قد ترغب حتى في تحديد استخدامه لوسيط معين.
لتحديد استخدامه لحساب تجريبي، استخدم وظيفة IsDemo() للتحقق مما إذا كان الحساب النشط حاليًا هو حساب تجريبي. إذا كان الحساب الحالي ليس حسابًا تجريبيًا، فسنقوم بعرض تنبيه وإيقاف تنفيذ المستشار الخبير.
كود :
if(IsDemo() == false)
{
Alert("This EA only for use on a demo account!");
return(0);
}
يمكنك استخدام وظائف الحساب AccountName() و AccountNumber() و AccountBroker() للتحقق من اسم الحساب ورقمه والوسيط على التوالي. تحديد الاستخدام بواسطة رقم الحساب هو طريقة شائعة وسهلة التنفيذ للحماية:
كود :
int CustomerAccount = 123456;
if(AccountNumber() != CustomerAccount)
{
Alert("Account number does not match!");
return(0);
}
يمكنك استخدام AccountName() أو AccountBroker() بنفس الطريقة. بالنسبة لـ AccountBroker()، ستحتاج أولاً إلى استخدام تعليمة Print() لاسترداد قيمة الإرجاع الصحيحة من الوسيط. ستتم طباعة هذه القيمة في سجل المستشار الخبير.
إذا قررت بالفعل بيع مستشار خبير تجاريًا، فاعلم أن ملفات MQL سهلة التفكيك. هناك أساليب مختلفة يمكنك استخدامها لتعقيد مهمة القراصنة في كسر المستشار الخبير الخاص بك، مثل وضع الوظائف في مكتبات خارجية أو ملفات DLL. ولكن في النهاية، ليس هناك الكثير من الحماية ضد قراصنة مصممين على اختراق الملف.
نصائح وخدع ( 2 )
Tips and Tricks
حتى الآن، قمنا باستخدام وظيفة Alert() المدمجة لعرض رسائل الخطأ. ولكن ماذا لو أردت تخصيص مربعات الحوار الخاصة بك، أو طلب إدخال من المستخدم؟ تسمح لك وظيفة MessageBox() بإنشاء مربع حوار مخصص باستخدام وظائف Windows API.
لاستخدام وظيفة MessageBox()، يجب علينا أولاً تضمين ملف WinUser32.mqh الذي يتم تثبيته مع MetaTrader. يقوم هذا الملف باستيراد وظائف من ملف Windows user32.dll وتعريف الثوابت اللازمة لعمل وظيفة MessageBox(). فيما يلي بناء الجملة الصحيح لوظيفة MessageBox():
كود :
int MessageBox(string ****, string Title, int Flags);
لاستخدام وظيفة MessageBox()، يجب علينا تعريف النص الذي سيظهر في مربع الحوار المنبثق، جنبًا إلى جنب مع عنوان يظهر في شريط العنوان. سيكون من الضروري أيضًا تحديد الأعلام التي تشير إلى الأزرار والرموز التي يجب أن تظهر في مربع الحوار. إذا لم يتم تحديد أي أعلام، فإن زر OK سيكون الافتراضي. يجب فصل الأعلام بواسطة الحرف العمود (|).
فيما يلي مثال لمربع رسالة يحتوي على أزرار نعم/لا ورمز علامة استفهام:
كود :
// Preprocessor directives
#include <WinUser32.mqh>
// start() function
int YesNoBox = MessageBox("Place a Trade?","Trade Confirmation",
MB_YESNO|MB_ICONQUESTION);
if(YesNoBox == IDYES)
{
// Place Order
}
العلم MB_YESNO يحدد أننا سنستخدم أزرار نعم/لا في مربع الرسالة الخاص بنا، في حين يضع العلم MB_ICONQUESTION رمز علامة الاستفهام في مربع الحوار. المتغير الصحيح YesNoBox يحتوي على قيمة العودة لوظيفة MessageBox()، التي ستشير إلى الزر الذي تم الضغط عليه.
إذا تم الضغط على زر نعم، ستكون قيمة YesNoBox هي IDYES، وسيتم وضع أمر. إذا تم الضغط على زر لا، فسيكون العلم المُرجَع هو IDNO. يمكنك استخدام قيمة العودة من وظيفة MessageBox() كإدخال لتحديد مسار العمل، مثل وضع أمر.
ما يلي هو قائمة جزئية من الأعلام التي يمكن استخدامها في مربعات الرسالة الخاصة بك. للحصول على قائمة كاملة، يرجى الاطلاع على موضوع Standard Constants – MessageBox في مرجع MQL.
أعلام الأزرار
تحدد هذه الأعلام الأزرار التي تظهر في مربع الرسالة الخاص بك.
•
MB_OKCANCEL – أزرار “موافق” و “إلغاء”.
•
MB_YESNO – أزرار “نعم” و “لا”.
•
MB_YESNOCANCEL – أزرار “نعم” و “لا” و “إلغاء”.
أعلام الرموز
تحدد هذه الأعلام الرموز التي تظهر بجوار النص في مربع الرسالة.
•
MB_ICONSTOP – رمز لوحة توقف (إشارة توقف).
•
MB_ICONQUESTION – رمز علامة استفهام.
•
MB_ICONEXCLAMATION – رمز نقطة تعجب (علامة تعجب).
•
MB_ICONINFORMATION – رمز معلومات.
أعلام العودة
هذه الأعلام هي قيمة العودة لوظيفة MessageBox() وتشير إلى الزر الذي تم الضغط عليه.
•
IDOK – تم الضغط على زر “موافق”.
•
IDCANCEL – تم الضغط على زر “إلغاء”.
•
IDYES – تم الضغط على زر “نعم”.
•
IDNO – تم الضغط على زر “لا”.
التنبيهات عبر البريد الإلكتروني
Email Alerts
يمكن لمستشار الخبراء الخاص بك أن يُنبهك عبر البريد الإلكتروني بشأن الصفقات المُفتوحة، وإعدادات الصفقات المحتملة وغيرها. ستقوم وظيفة SendMail() بإرسال بريد إلكتروني بعنوان ونص حسب اختيارك إلى عنوان البريد الإلكتروني المُدرج في حوار أدوات – الخيارات تحت علامة البريد الإلكتروني.
في علامة البريد الإلكتروني، يجب أن تحدد أولاً خادم البريد SMTP مع رقم المنفذ – على سبيل المثال: mail.yourdomain.com:25 – جنبًا إلى جنب مع اسم المستخدم وكلمة المرور، إن لزم الأمر. تحقق من ذلك مع مزود خدمة الإنترنت الخاص بك أو مزود الاستضافة للحصول على هذه المعلومات.
يمكنك استخدام أي عنوان بريد إلكتروني في حقل “من”. حقل “إلى” هو عنوان البريد الإلكتروني الذي يتم إرسال الرسائل إليه. تأكد من تحقيق إعداد التمكين في الأعلى لتمكين إرسال الرسائل.
تحتوي وظيفة SendMail() على معاملين: الأول هو سطر موضوع البريد الإلكتروني، والثاني هو محتوى البريد الإلكتروني نفسه. يمكنك استخدام سطور جديدة، وحروف مهربة، ومتغيرات وثوابت داخل جسم بريدك الإلكتروني.
إليك مثال على كيفية استخدام وظيفة SendMail():
كود :
string EmailSubject = "Buy order placed";
string EmailBody = "Buy order "+Ticket+" placed on "+Symbol()+" at "+Ask;
// Sample output: "Buy order 12584 placed on EURDUSD at 1.4544"
SendMail(EmailSubject,EmailBody);
نصائح وخدع ( 3 )
Tips and Tricks
إعادة المحاولة في حالة الخطأ
طوال هذا الكتاب، حاولنا التحقق من معلمات الطلب قبل محاولة وضع الطلب، لتجنب ظهور رسائل الخطأ الشائعة بسبب الإعدادات أو الأسعار الغير صحيحة. ومع ذلك، قد تحدث أخطاء لا يمكن تجنبها بسبب إعادة الأسعار (requotes) أو انشغال سياق التداول (trade con**** busy) أو مشاكل في الخادم. قد لا يمكن تجنب هذه الأخطاء دائمًا، ولكن يمكننا محاولة وضع الطلب مرة أخرى عند حدوثها.
لإعادة المحاولة في حالة حدوث خطأ في الطلب، سنضع دالة OrderSend() داخل حلقة while. إذا لم تعيد دالة OrderSend() رقم تذكرة (ticket number)، سنقوم بإعادة محاولة وضع الطلب مرة أخرى:
كود :
int Ticket = 0;
while(Ticket <= 0)
{
Ticket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSli ppage,
BuyStopLoss,BuyTakeProfit);
}
نقوم أولاً بتعريف المتغير الخاص برقم التذكرة، وفي هذه الحالة نسميه Ticket. طالما أن قيمة Ticket ليست أكبر من 0، ستقوم حلقة while التي تحتوي على دالة OrderSend() بالتنفيذ مرارًا وتكرارًا. هناك مشكلة واحدة مع هذه الحلقة، وهي أنه في حالة حدوث خطأ في البرمجة أو خطأ تداول آخر غير مصحح، فإن الحلقة ستستمر بالتكرار بشكل لا نهائي وسيتعلق خبيرك المستشار (Expert Advisor). يمكننا التخفيف من هذا الموضوع عن طريق إضافة عدد أقصى لمحاولات إعادة الطلب:
كود:
int Retries = 0;
int MaxRetries = 5;
int Ticket = 0;
while(Ticket <= 0)
{
Ticket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSli ppage,BuyStopLoss,
BuyTakeProfit);
if(Retries <= MaxRetries) Retries++;
else break;
}
نحن نعلن عن متغير يستخدم كعداد لإعادة المحاولات (Retries)، وإعداد أقصى عدد للمحاولات (MaxRetries). طالما أننا لم نتجاوز قيمة MaxRetries، سيتم زيادة قيمة المتغير Retries وسيتم تكرار الحلقة مرة أخرى. فور الوصول إلى MaxRetries، سيقوم عامل الإنهاء (break operator) بإنهاء الحلقة. بعد ذلك، يمكنك تنبيه المستخدم بحالة الخطأ عند الاقتضاء.
إذا كنت ترغب في جعل حلقة المحاولة المتكررة تعتمد على حالة خطأ معينة، يمكننا التحقق من رمز الخطأ في قائمة وإرجاع قيمة صحيحة إذا توافقت. تحتوي هذه الوظيفة على بعض رموز الأخطاء الشائعة التي تشير إلى حالة يمكن فيها إعادة المحاولة بنجاح للتداول:
كود :
bool ErrorCheck(int ErrorCode)
{
switch(ErrorCode)
{
case 128:
// Trade timeout
return(true);
case 136:
// Off quotes
return(true);
case 138:
// Requotes
return(true);
case 146:
// Trade con**** busy
return(true);
default:
return(false);
}
}
تستخدم هذه الوظيفة عامل التبديل (switch operator). نبحث عن تسمية حالة (case label) تتطابق قيمتها مع التعبير المسند إلى عامل التبديل (في هذا المثال، ErrorCode). إذا تم العثور على حالة مطابقة، يتم تنفيذ الشيفرة بعد الحالة (case). إذا لم يتم العثور على تسمية حالة متطابقة، فإن الشيفرة بعد التسمية الافتراضية (default label) ستتم تنفيذها.
عند العثور على تطابق للحالة، يجب الخروج من كتلة التبديل بواسطة عامل الإنهاء (break) أو عامل الإرجاع (return). في هذا المثال، نستخدم عامل الإرجاع (return) لإرجاع قيمة صحيحة/غير صحيحة إلى الوظيفة المستدعاة. يمكن أن يكون عامل التبديل مفيدًا لتقييم تطابق ثابت العدد الصحيح، ولكن فائدته محدودة نوعًا ما.
فيما يلي كيفية استخدام ErrorCheck() لإعادة محاولة وضع الطلب بشكل مشروط:
(لاحظ أن النص المتوفر لدي لا يحتوي على الوظيفة ErrorCheck() المذكورة في النص السابق، لذلك لا يمكنني تقديم تفاصيل حول كيفية استخدامها لإعادة محاولة وضع الطلب بشكل مشروط. إذا كنت بحاجة إلى ترجمة أو شرح أجزاء معينة من الكود، يُرجى توضيح ذلك.)
كود :
int Retries;
int MaxRetries = 5;
int Ticket;
while(Ticket <= 0)
{
Ticket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSli ppage,BuyStopLoss,
BuyTakeProfit);
if(Ticket == -1) int ErrCode = GetLastError();
if(Retries <= MaxRetries && ErrorCheck(ErrCode) == true) Retries++;
else break;
}
إذا عادت قيمة Ticket بقيمة -1، مما يشير إلى حدوث خطأ، نقوم باسترجاع رمز الخطأ باستخدام دالة GetLastError(). ثم نمرر رمز الخطأ إلى الوظيفة ErrorCheck() التي ذُكرت أعلاه. إذا تطابق رمز الخطأ مع أي من الأخطاء الموجودة في وظيفة فحص الأخطاء ErrorCheck()، ستُرجع هذه الوظيفة قيمة صحيحة (true)، وبالتالي سيتم إعادة محاولة دالة OrderSend() مرة أخرى لمدة تصل إلى 5 مرات.
نصائح وخدع ( 4 )
Tips and Tricks
استخدام التعليقات على الأوامر كمعرف
Using Order Comments As an Identifier
لقد قمنا باستخدام “الرقم السحري” كمعرف للطلبات يميز بشكل فريد بين الطلبات التي تم وضعها بواسطة خبير مستشار محدد. إذا كان خبيرك المستشار يضع عدة طلبات في وقت واحد، وترغب في التعامل مع كل من تلك الطلبات بشكل مختلف، يمكنك استخدام التعليق على الطلب كمعرف اختياري.
على سبيل المثال، لنفترض أن خبير مستشارك سيضع نوعين من الطلبات، وترغب في تعديل أو إغلاق هذه الطلبات بشكل منفصل. ستحتاج إلى استخدام وظيفة “OrderSend()” مرتين ووضع تعليق طلب مختلف لكل منهما. ثم، عند تحديد الطلبات باستخدام حلقة الطلبات في الفصل 5، ستستخدم “OrderComment()” كواحدة من الشروط لتحديد الطلبات التي ترغب في تعديلها أو إغلاقها.
كود :
string OrderComment1 = "First order";
string OrderComment2 = "Second order";
// Order placement
int Ticket1 = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSli ppage,BuyStopLoss,
BuyTakeProfit,OrderComment1,MagicNumber,0,Green);
int Ticket2 = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSli ppage,BuyStopLoss,
BuyTakeProfit,OrderComment2,MagicNumber,0,Green);
// Order modification
for(int Counter = 0; Counter <= OrdersTotal()-1; Counter++)
{
OrderSelect(Counter,SELECT_BY_POS);
if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol()
&& OrderComment() == OrderComment1)
{
// Modify first order
}
else if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol()
&& OrderComment() == OrderComment2)
{
// Modify second order
}
}
نقوم بتعريف متغيرين من نوع السلسلة (String) لاستخدامهما كتعليقات على الطلب. تُستخدم وظائف “OrderSend()” لوضع طلبين، وكل منهما يحمل تعليق طلب مختلف. في الحلقة المثالية لتعديل الطلبات التي تلي ذلك، يتم استخدام وظيفة “OrderComment()” كشرط عند اختيار الطلبات المراد تعديلها.
يمكنك استخدام فحص “OrderComment()” لإغلاق الطلبات بشكل مستقل عن غيرها من الطلبات، أو استخدام إعدادات مختلفة لوقف الخسارة المتحركة (Trailing Stop)، أو أي ما يتطلبه نظام التداول الخاص بك.
نصائح وخدع ( 5 )
Tips and Tricks
التحقق من الهوامش
Margin Check
يأتي MetaTrader مع وظائف تتيح لك التحقق من الهوامش الحرة الحالية أو مستوى الإيقاف قبل وضع أمر. مستوى الإيقاف هو النسبة المئوية أو مبلغ الهامش الحر تحت الذي لن تكون قادرًا على وضع أوامر. إلا أن التحقق يدويًا من الهامش الحر أو مستوى الإيقاف قبل وضع الأمر ليس ضروريًا حقًا، حيث سيحدث خطأ إذا حاولت وضع أمر بمبلغ هامش غير كافٍ.
فكرة أكثر فائدة ستكون تحديد مستوى خاص بك للإيقاف، وتعليق التداول إذا انخفض حسابك الحالي أدناه ذلك المستوى. دعنا نبدأ بتعريف متغير خارجي يُسمى “MinimumEquity”، وهو الحد الأدنى للأموال الخاصة المطلوبة في حسابنا قبل أن نتمكن من وضع أمر. سنقوم بمقارنة “MinimumEquity” بحسابنا الحالي. إذا كان حسابنا الحالي أقل من الحد الأدنى للأموال، لن يتم وضع الأمر، وسيظهر رسالة تنبيه للمستخدم بالشرط. دعنا نفترض أن لدينا رصيد حساب قدره 10,000 دولار. إذا فقدنا أكثر من 20% من هذه الأموال، فنحن لا نرغب في وضع الأمر. هذا هو الكود الذي يتحقق من الحد الأدنى للأموال:
كود :
// External variables
extern int MinimumEquity = 8000;
// Order placement
if(AccountEquity() > MinimumEquity)
{
// Place order
}
else if(AccountEquity() <= MinimumEquity)
{
Alert("Current equity is less than minimum equity! Order not placed.");
}
المتغير الخارجي “MinimumEquity” يوضع في بداية الملف. بقية الكود يأتي قبل وبعد وظيفة وضع الأمر. إذا كانت الأموال الحالية، كما هو مشار إليه بواسطة “AccountEquity()”، أكبر من “MinimumEquity”، سيتم وضع الأمر. وإلا، لن يتم وضع الأمر وسيتم عرض رسالة تنبيه.
فحص الانتشار
Spread Check
قد تفضل تجنب وضع الصفقات خلال الفترات التي يكون فيها الانتشار قد اتسع بشكل كبير عن المعتاد. يمكننا تعيين حد أقصى للانتشار والتحقق من الانتشار الحالي قبل التداول. سنقوم بتعريف متغير خارجي يُسمى “MaximumSpread”، ونستخدم دالة MarketInfo() لفحص الانتشار الحالي.
سيكون الكود مشابهًا جدًا للقسم السابق حيث قمنا بإضافة التحقق من الهامش الأدنى. سنقوم بتضمين الكود من القسم السابق لنُظهر كيفية عمل هذه التحققات المختلفة معًا:
كود :
// External variables
extern int MaximumSpread = 5;
extern int MinimumEquity = 8000;
if(AccountEquity() > MinimumEquity && MarketInfo(Symbol(),MODE_SPREAD) < MaximumSpread)
{
// Place order
}
else
{
if(AccountEquity() <= MinimumEquity) Alert("Current equity is less than minimum
equity! Order not placed.");
if(MarketInfo(Symbol(),MODE_SPREAD) > MaximumSpread) Alert("Current spread is
greater than maximum spread! Order not placed.");
}
يرجى ملاحظة أننا نقوم بفحص كل من التحقق من الهامش الأدنى وفحص الانتشار قبل وضع الأمر. إذا كان أحد الشروط غير صحيح، سننتقل إلى الكتلة الأخرى (else) ونتحقق لمعرفة أي من الشروط أدى إلى عدم وضع الأمر. سنقوم بعرض إنذار واحد أو أكثر اعتمادًا على أي شرط صحيح.
نصائح وخدع ( 6 )
Tips and Tricks
طلبات متعددة
Multiple Orders
قد ترغب في وضع طلبات متعددة لكل وضعية مع مستويات مختلفة لوقف الخسارة والربح المأمول، وكذلك أحجام العقود. هناك طرق عدة لتحقيق ذلك. طريقة واحدة هي استخدام بيان OrderSend() مختلف لكل طلب ترغب في وضعه. وذلك في افتراض أنك تخطط لوضع نفس عدد الطلبات في كل مرة.
طريقة أخرى هي استخدام حلقة تكرارية (for loop) لوضع الطلبات. بهذه الطريقة، يمكنك ضبط عدد الطلبات التي تُرغب في وضعها في آن واحد. يمكنك تحميل مستويات وقف الخسارة والربح المأمولة في مصفوفات مسبقاً، وزيادة الترقيم خلال الحلقة التكرارية (for loop).
لنبدأ بتعريف متغيرات خارجية لثلاث مستويات لوقف الخسارة والربح المأمولة. أي طلبات إضافية تزيد عن ثلاثة لن تكون لها مستويات لوقف الخسارة أو الربح المأمولة. سنضيف أيضًا متغيرًا خارجيًا لضبط عدد الطلبات التي سيتم وضعها.
كود :
extern int StopLoss1 = 20;
extern int StopLoss2 = 40;
extern int StopLoss3 = 60;
extern int TakeProfit1 = 40;
extern int TakeProfit2 = 80;
extern int TakeProfit3 = 120;
extern int MaxOrders = 3;
بعد ذلك، سنقوم بتعريف مصفوفاتنا، وحساب وقف الخسارة والربح المأمول، وتحميل الأسعار المحسوبة في المصفوفة:
كود :
double BuyTakeProfit[3];
double BuyStopLoss[3];
BuyTakeProfit[0] = CalcBuyTakeProfit(Symbol(),TakeProfit1,Ask);
BuyTakeProfit[1] = CalcBuyTakeProfit(Symbol(),TakeProfit2,Ask);
BuyTakeProfit[2] = CalcBuyTakeProfit(Symbol(),TakeProfit3,Ask);
BuyStopLoss[0] = CalcBuyStopLoss(Symbol(),StopLoss1,Ask);
BuyStopLoss[1] = CalcBuyStopLoss(Symbol(),StopLoss2,Ask);
BuyStopLoss[2] = CalcBuyStopLoss(Symbol(),StopLoss3,Ask);
نبدأ بتعريف المصفوفات التي ستحمل أسعار وقف الخسارة والربح المأمولة، وهي BuyTakeProfit و BuyStopLoss. يجب تحديد عدد عناصر المصفوفة عند تعريفها. تبدأ فهارس المصفوفة من الصفر، لذا عندما نعرف حجم البُعد كـ 3، فإن فهرس البداية هو 0، وأكبر فهرس هو 2.
بعد ذلك، نقوم بحساب أسعار وقف الخسارة والربح المأمولة باستخدام الدوال التي قمنا بتعريفها في الفصل الرابع – CalcBuyStopLoss() و CalcBuyTakeProfit(). ثم نخصص قيمة وقف الخسارة أو الربح المأمول المحسوبة لعنصر المصفوفة المناسب. يُلاحظ أن فهرس المصفوفة الأول هو 0 وفهرس المصفوفة الثالث هو 2.
وفيما يلي الحلقة التكرارية (for loop) لوضع الطلبات:
كود :
for(int Count = 0; Count <= MaxOrders - 1; Count++)
{
int OrdInt = Count + 1;
OrderSend(Symbol(),OP_BUY,LotSize,Ask,UseSlippage, BuyStopLoss[Count],
BuyTakeProfit[Count],"Buy Order "+OrdInt,MagicNumber,0,Green);
}
المتغير “Count” يبدأ من 0، ليتوافق مع عنصر المصفوفة الأول. عدد المرات التي ستتكرر فيها الحلقة (أي عدد الطلبات المراد وضعها) يتم تحديدها باستخدام “MaxOrders – 1”. في كل تكرار للحلقة، نقوم بزيادة محتوى مصفوفتي وقف الخسارة والربح بمقدار واحد.
نستخدم المتغير “OrdInt” لزيادة عداد الطلبات في تعليق الطلب. سيكون تعليق الطلب الأول “Buy Order 1″، والتالي سيكون “Buy Order 2″، وهكذا. تقوم وظيفة “OrderSend()” بوضع الطلب بقيمة وقف الخسارة والربح المناسبة، باستخدام المتغير “Count” لتحديد عنصر المصفوفة الملائم.
هذه طريقة واحدة فقط للتعامل مع طلبات متعددة، وربما تكون الأكثر كفاءة. العيب الرئيسي لهذا النهج هو أننا يمكن أن نحسب أسعار وقف الخسارة والربح لعدد محدود من الطلبات فقط. بالبديل، يمكننا تعيين قيم وقف الخسارة والربح بنسبة معينة، ووضع عدد غير محدود من الطلبات:
كود :
extern int StopLossStart = 20;
extern int StopLossIncr = 20;
extern int TakeProfitStart = 40;
extern int TakeProfitIncr = 40;
extern int MaxOrders = 5;
في المثال أعلاه، سيكون وقف الخسارة لأول طلب لدينا 20 نقطة. سنزيد وقف الخسارة بمقدار 20 نقطة لكل طلب إضافي. نفس الأمر بالنسبة للربح المأمول، باستثناء أننا سنبدأ من 40 ونزيد بمقدار 40. بدلاً من استخدام المصفوفات، سنقوم بحساب وقف الخسارة والربح المأمول في حلقة التكرار (for loop):
في المثال أعلاه، سيكون وقف الخسارة لأول طلب لدينا 20 نقطة. سنزيد وقف الخسارة بمقدار 20 نقطة لكل طلب إضافي. نفس الأمر بالنسبة للربح المأمول، باستثناء أننا سنبدأ من 40 ونزيد بمقدار 40. بدلاً من استخدام المصفوفات، سنقوم بحساب وقف الخسارة والربح المأمول في حلقة التكرار (for loop):
كود :
for(int Count = 0; Count <= MaxOrders - 1; Count++)
{
int OrdInt = Count + 1;
int UseStopLoss = StopLossStart + (StopLossIncr * Count);
int UseTakeProfit = TakeProfitStart + (TakeProfitIncr * Count);
double BuyStopLoss = CalcBuyStopLoss(Symbol(),UseStopLoss,Ask);
double BuyTakeProfit = CalcBuyTakeProfit(Symbol(),UseTakeProfit,Ask);
OrderSend(Symbol(),OP_BUY,LotSize,Ask,UseSlippage, BuyStopLoss,
BuyTakeProfit,"Buy Order "+OrdInt,MagicNumber,0,Green);
}
نحدد مستوى وقف الخسارة والربح بالنقاط من خلال ضرب المتغير StopLossIncr أو TakeProfitIncr في المتغير Count، وإضافته إلى قيمة المتغير StopLossStart أو TakeProfitStart. بالنسبة للطلب الأول، سيكون مستوى وقف الخسارة أو الربح المأمول مساويًا لقيمة StopLossStart أو TakeProfitStart.
بعد ذلك، نقوم بحساب سعر وقف الخسارة والربح للطلب باستخدام الدوال التي قمنا بتعريفها في الدروس السابقة. وأخيراً، نقوم بوضع الطلب باستخدام OrderSend(). ستستمر الحلقة التكرارية حتى يتم وضع عدد الطلبات المحدد بواسطة متغير MaxOrders. تسمح لنا هذه الطريقة بتحديد عدد الطلبات الذي نرغب فيه باستخدام المتغير MaxOrders، مما يضمن أن كل طلب نقوم بوضعه سيكون له وقف خسارة وربح مأمول.
نصائح وخدع ( 7 )
Tips and Tricks
في الدروس السابقة، كنا نشير إلى المتغيرات ذات النطاق العام باسم “المتغيرات العامة”. تحتوي منصة MetaTrader على مجموعة من الوظائف لضبط المتغيرات على مستوى المحطة، مما يعني أن هذه المتغيرات متاحة لكل مستشار خبير يعمل حاليًا، بشرط أن نعرف اسم المتغير للبداية.
تشير وثائق MQL إلى هذه المتغيرات باسم “المتغيرات العامة”، على الرغم من أن الاسم الأكثر مناسب قد يكون “المتغيرات على مستوى المحطة”. نستخدم وظائف المتغيرات العامة في مرجع MQL تحت عنوان المتغيرات العامة للعمل مع هذا النوع من المتغيرات. يمكن عرض قائمة المتغيرات العامة الحالية في المحطة عن طريق اختيار “المتغيرات العامة” من قائمة الأدوات، أو عن طريق الضغط على مفتاح F3 على لوحة المفاتيح.
أحد الطرق لاستخدام هذه المتغيرات هو تخزين بعض المتغيرات ذات النطاق العام أو الثابتة على مستوى المحطة، بحيث إذا تم إيقاف مستشار الخبراء، يمكننا أن نعاود من حيث توقفنا. ليس كل مستشار خبير يحتاج إلى ذلك، ولكن المستشارات الخبيرة ذات التعقيدات الأكثر تعقيدًا ستحتفظ بحالة معينة، وإذا تم توقفها فجأة، فقد تؤثر ذلك على عمل المستشار الخبير.
أفضل طريقة لتجنب ذلك هي تجنب إنشاء مستشارات خبراء تتطلب مستوى من التعقيد العالي. ولكن إذا لم يكن بالإمكان تجنبه، فقد يكون استخدام وظائف المتغيرات العامة لتخزين الحالة الحالية على مستوى المحطة مفيدًا في حالة الإغلاق العرضي. يجب ملاحظة أن هذا الأسلوب ليس مضمونًا بشكل كامل، ولكنه على الأرجح أفضل طريقة لتحقيق ذلك.
لتعريف متغير عام (على مستوى المحطة)، استخدم وظيفة GlobalVariableSet(). الوسيطة الأولى هي سلسلة تشير إلى اسم المتغير العام، والوسيطة الثانية هي قيمة من نوع double لتعيينها له.
كود :
GlobalVariableSet(GlobalVariableName,DoubleValue);
للحفاظ على أسماء المتغيرات الخاصة بك فريدة، قد ترغب في إنشاء بادئة للمتغير العام. قم بتعريف متغير ذو نطاق عام في مستشارك الخبير، وقم بتعيين القيمة في وظيفة init()، باستخدام الرمز الحالي، والفترة، واسم مستشار الخبير، والرقم السحري لإنشاء بادئة فريدة للمتغير.
كود :
// Global variables
string GlobalVariablePrefix;
int init()
{
GlobalVariablePrefix = Symbol()+Period()+"_"+"ProfitBuster"+"_"+MagicNumb er+"_";
}
نستخدم الرمز الحالي والفترة، جنبًا إلى جنب مع معرف لمستشار الخبير ومتغير MagicNumber الخارجي. الآن، عندما نقوم بتعيين متغير عام باستخدام GlobalVariableSet()، نستخدم البادئة التي قمنا بتعريفها أعلاه، جنبًا إلى جنب مع اسم المتغير الفعلي:
كود :
GlobalVariableSet(GlobalVariablePrefix+Counter,Cou nter);
إذا كنا نتداول على زوج العملات EURUSD على إطار زمني 15 دقيقة باستخدام مستشار الخبير المسمى “ProfitBuster”، باستخدام 11 كرقم سحري، وكان اسم المتغير “Counter”، فإن اسم المتغير العام لدينا سيكون EURUSD15_ProfitBuster_11_Counter. يمكنك استخدام أي اتفاقية ترغب فيها لتسمية متغيراتك العامة، ولكن من الأمور الموصى بها بشدة هو تضمين المعلومات أعلاه.
لاسترجاع قيمة متغير عام، استخدم وظيفة GlobalVariableGet() مع اسم المتغير كوسيطة:
كود :
Counter = GlobalVariableGet(GlobalVariablePrefix+Counter);
لحذف متغير عام، استخدم وظيفة GlobalVariableDel() مع اسم المتغير كوسيطة. لحذف جميع المتغيرات العامة التي تم وضعها بواسطة مستشارك الخبير، استخدم وظيفة GlobalVariableDeleteAll() مع البادئة الخاصة بك كوسيطة.
كود :
GlobalVariableDel(GlobalVariablePrefix+Counter);
GlobalVariableDeleteAll(GlobalVariablePrefix);
لمزيد من المعلومات حول وظائف المتغيرات العامة، يرجى الاطلاع على موضوع المتغيرات العامة في دليل MQL المرجعي.
نصائح وخدع ( 8 )
Tips and Tricks
التحقق من ربح الطلب
Check Order Profit
في بعض الأحيان قد يكون من المفيد التحقق من الربح الحالي لأمر معين، أو التحقق من الربح الإجمالي لأمر قد أغلق بالفعل. هناك طريقتان للتحقق من الربح. للحصول على الربح بالعملة التي تم إيداعها، استخدم وظيفة OrderProfit(). يجب عليك في البداية اختيار الطلب باستخدام OrderSelect().
كود :
نتيجة وظيفة OrderProfit() يجب أن تكون متطابقة تمامًا مع الربح الإجمالي أو الخسارة المدرجة في تاريخ الطلب المحدد.
لاسترجاع الربح أو الخسارة بوحدات “البيبس”، ستحتاج إلى حساب الفرق بين سعر فتح الطلب وسعر إغلاق الطلب. ستحتاج أيضًا إلى استخدام وظيفة OrderSelect() لاسترجاع أسعار الفتح والإغلاق.
كود :
OrderSelect(Ticket,SELECT_BY_TICKET);
if(OrderType() == OP_BUY) double GetProfit = OrderClosePrice() - OrderOpenPrice();
else if(OrderType() == OP_SELL) GetProfit = OrderOpenPrice() - OrderClosePrice();
GetProfit /= PipPoint(Symbol());
بالنسبة لأوامر الشراء، نقوم بحساب الربح عن طريق طرح سعر الفتح من سعر الإغلاق. بالنسبة لأوامر البيع، نقوم بالعملية المعكوسة. بعد أن نقوم بحساب الفرق، يمكننا تحويل الربح أو الخسارة إلى رقم صحيح عن طريق قسمته على نقطة البيب، باستخدام وظيفتنا PipPoint().
على سبيل المثال، إذا كان سعر فتح أمر الشراء الخاص بنا هو 1.4650 وكان سعر الإغلاق هو 1.4700، فإن الفرق بين OrderClosePrice() و OrderOpenPrice() هو 0.0050. عندما نقسم ذلك على وحدتنا PipPoint()، نحصل على نتيجة تبلغ 50. لذلك، بالنسبة لهذا الطلب، نحقق ربحًا قدره 50 بيب. إذا كان سعر الإغلاق للطلب هو 1.4600 بدلاً من ذلك، فسنكون قد تكبدنا خسارة بقيمة -50 بيب.
نصائح وخدع ( 9 )
Tips and Tricks
المارتينجال او المضاعفات
Martingale
المارتينجال هو نظام مراهنات يُستخدم بشكل شائع في لعبة الروليت والبلاك جاك، حيث يتم مضاعفة حجم الرهان بعد كل خسارة متتالية. الفكرة هي أن رهانًا واحدًا فائزًا سيعيد الرصيد إلى التعادل. السلبية في المارتينجال هي أنك تحتاج إلى رأس مال كبير لتحمل مخاطر الخسائر المتتالية. على سبيل المثال، إذا كان حجم الرهان الأولي الخاص بك هو 0.1 لوت، بعد 4 خسائر متتالية سيكون حجم الرهان الخاص بك 1.6 لوت – 16 مرة حجم الرهان الأصلي. بعد 7 خسائر متتالية، سيكون حجم الرهان الخاص بك 12.8 لوت – 128 مرة حجم الرهان الأصلي! ستقوم موجة خسائر طويلة بمحو حسابك قبل أن تتمكن من إعادته إلى التعادل.
ومع ذلك، قد ترغب في دمج نظام زيادة أحجام الرهانات بناءً على الانتصارات أو الخسائر المتتالية، ويمكن القيام بذلك دون محو حسابك. أسهل طريقة هي وضع حد لعدد مرات زيادة حجم الرهان. يجب ألا يكون لدى نظام تداول صحي أكثر من 3 أو 4 خسائر متتالية كحد أقصى. يمكنك تحديد ذلك عن طريق فحص عدد الخسائر المتتالية القصوى في علامة التقرير في نافذة اختبار الاستراتيجية.
طريقة أخرى هي زيادة حجم الرهان بواسطة مضاعف أصغر. استراتيجية المارتينجال الكلاسيكية تضاعف حجم الرهان بعد كل خسارة متتالية. قد ترغب في استخدام مضاعف أصغر من 2.
هناك أيضًا استراتيجية مضادة للمارتينجال، حيث يتم زيادة حجم الرهان بعد كل انتصار متتالي. دعونا نفحص روتينًا حيث نحسب عدد الانتصارات أو الخسائر المتتالية ونزيد حجم الرهان وفقًا لذلك. تعمل استراتيجية المارتينجال بشكل أفضل عندما تقوم بوضع أمر واحد في كل مرة، لذلك سنفترض أن كل موقف يتألف من معاملة واحدة فقط.
سيكون بإمكان المستخدم اختيار استراتيجية المارتينجال (للخسائر) أو استراتيجية مضادة للمارتينجال (للانتصارات). سيتم تضمين إعداد لتحديد الحد الأقصى لعدد زيادات الحجم المتتالية، وسيكون من الممكن تعديل مضاعفة الرهان. أولاً، دعونا نحسب عدد الانتصارات أو الخسائر المتتالية. سنحتاج إلى القيام بذلك عن طريق تكرار تاريخ الطلبات بشكل عكسي، بدءًا من الطلب الأخير المغلق بالفعل. سنقوم بزيادة عداد لكل انتصار أو خسارة. طالما تم الحفاظ على نمط من الانتصارات أو الخسائر المتتالية، سنستمر في التكرار. بمجرد كسر النمط (عندما يتم العثور على انتصار بعد خسارة واحدة أو أكثر، أو العكس)، سيتم الخروج من الحلقة.
كود :
int WinCount;
int LossCount;
for(int Count = OrdersHistoryTotal()-1; ; Count--)
{
OrderSelect(Count,SELECT_BY_POS,MODE_HISTORY);
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
if(OrderProfit() > 0 && LossCount == 0) WinCount++;
else if(OrderProfit() < 0 && WinCount == 0) LossCount++;
else break;
}
}
نبدأ بتعريف المتغيرات الخاصة بعدادات الانتصارات والخسائر. في عامل التكرار (for)، يرجى ملاحظة أننا نستخدم OrdersHistoryTotal() لتحديد الموقف الابتدائي لنا. OrdersHistoryTotal() تُرجع عدد الأوامر في تاريخ الأوامر. نقوم بطرح 1 لتحديد موقع الفهرس لأحدث أمر، والذي يتم تخزينه في المتغير Count.
يرجى ملاحظة أننا قمنا بتجاهل التعبير الثاني في حلقة for – الذي يحدد شرط الإيقاف عن التكرار. يجب أن يبقى الفاصلة المنقوطة لأي تعبيرات تم حذفها. سنقوم بتنقيص المتغير Count في كل تكرار للحلقة.
نحن نستخدم MODE_HISTORY كالعنصر الثالث في دالة OrderSelect() للإشارة إلى أننا نقوم بتصفح مجموعة الأوامر التي تم إغلاقها. بشكل افتراضي، تستخدم OrderSelect() مجموعة الأوامر المفتوحة، لذا يجب علينا تحديد MODE_HISTORY عند فحص مجموعة الأوامر المغلقة.
نتحقق للتأكد من أن الأمر المحدد حاليًا يتطابق مع رمز الرسم البياني الخاص بنا ورقمه السحري. ثم نفحص ربح الأمر باستخدام دالة OrderProfit(). إذا كانت قيمة الإرجاع تشير إلى ربح (أي أنها أكبر من صفر)، فإننا نزيد قيمة المتغير WinCount. إذا كانت خسارة، فإننا نزيد قيمة المتغير LossCount.
نظرًا لأننا نبحث عن الانتصارات أو الخسائر المتتالية، فإننا نحتاج إلى إنهاء الحلقة بمجرد العثور على شرط متناوب. لفعل ذلك، نتحقق من المتغير WinCount أو LossCount عند فحص ربح الأمر. على سبيل المثال، إذا كان لدينا 2 خسارة متتالية – وهذا يعني أن LossCount = 2 – وكان أمرنا التالي فائزًا، فإن كلا عبارات الـ if ستكونان كاذبتين، وسيتم التحكم في تنفيذ العامل break الذي ينهي الحلقة.
الميزة في هذا الأسلوب هي أنه قوي ولن يفشل إذا تم إغلاق الخبير الاستشاري بشكل غير مقصود. سيقوم الخبير الاستشاري بالاستمرار بالعمل من حيث توقف. بالطبع، هذا يعني أنه عند بدء الخبير الاستشاري لأول مرة، سيستخدم أي سلسلة انتصار/خسارة سابقة عند تحديد حجم الرهان. ولكن كما يمكنك رؤية، فإن المزايا تتفوق على السلبيات.
إما أن يحتوي المتغير WinCount أو المتغير LossCount على عدد الانتصارات أو الخسائر المتتالية. إذا كنا نريد تنفيذ استراتيجية المارتينجال، فإننا نستخدم LossCount لتحديد العامل الذي يجب به زيادة حجم الرهان. إذا كنا نقوم بتنفيذ استراتيجية مضادة للمارتينجال، فإننا نستخدم WinCount بدلاً من ذلك.
سنستخدم متغيرًا صحيًا خارجيًا بنوع العدد الصحيح يسمى MartingaleType لتحديد ذلك. إذا تم تعيين MartingaleType إلى 0، سنستخدم استراتيجية المارتينجال. إذا تم تعيينه إلى 1، سنستخدم استراتيجية مضادة للمارتينجال. سنعلن أيضًا عن متغيرات خارجية لمضاعف الرهان (LotMultiplier)، والحد الأقصى لعدد مرات زيادة حجم الرهان (MaxMartingale)، وحجم الرهان الأولي (****LotSize).
كود :
// External variables
extern int MartingaleType = 0;
// 0: Martingale, 1: Anti-Martingale
extern int LotMultiplier = 2;
extern int MaxMartingale = 4;
extern double ****LotSize = 0.1;
// Lot size calculation
if(MartingaleType == 0) int ConsecutiveCount = LossCount;
else if(MartingaleType = 1) ConsecutiveCount = WinCount;
if(ConsecutiveCount > MaxMartingale) ConsecutiveCount = MaxMartingale;
double LotSize = ****LotSize * MathPow(LotMultiplier,ConsecutiveCount);
نقوم بتعيين قيمة ConsecutiveCount إلى إما WinCount أو LossCount، اعتمادًا على إعداد MartingaleType. سنقارن ذلك بإعداد MaxMartingale الخاص بنا. إذا كان عدد الأوامر المتتالية لدينا أكبر من MaxMartingale، سنقوم بإعادة تحجيمه ليكون مساويًا لـ MaxMartingale. (يمكنك أيضًا إعادة تحجيمه لحجم الرهان الافتراضي إذا كنت تفضل.) سيظل حجم الرهان عند هذا الحجم حتى يتمكن فوز أو خسارة من كسر سلسلة الأوامر المتتالية لدينا.
يتم تحديد حجم الرهان عن طريق ضرب حجم الرهان الأولي الخاص بنا (****LotSize) بواسطة مضاعف الرهان (LotMultiplier)، والذي يتزايد بشكل تناسبي بواسطة ConsecutiveCount. تقوم دالة MathPow() برفع عدد معين إلى القوة المحددة. الوسيطة الأولى هي القاعدة، والوسيطة الثانية هي السالب. على سبيل المثال، إذا كان حجم الرهان الأولي لدينا هو 0.1، ومضاعف الرهان هو 2، وكان لدينا أربعة أوامر متتالية، فإن المعادلة تكون 0.1 * 2^4 = 1.6.
من خلال ضبط مضاعفة الرهان واستخدام استراتيجيات المارتينجال واستراتيجيات مضادة للمارتينجال، ستمنحك هذه الخيارات الكافية لتجربة استخدام تحجيم الرهان التناسبي. يمكنك بسهولة تعديل الشيفرة أعلاه لاستخدام تغييرات أخرى. على سبيل المثال، يمكنك تغيير حجم الرهان بالعكس، من الأكبر إلى الأصغر. أو يمكنك استخدام عداد خارجي بدلاً من ConsecutiveCount.
نصائح وخدع ( 10 )
Tips and Tricks
تصحيح مستشارك الخبير
Debugging Your Expert Advisor
على عكس معظم بيئات تطوير البرمجة، لا يدعم MetaEditor نقاط الانقطاع أو أي نوع آخر من تقنيات التصحيح الحديثة. ستحتاج إلى استخدام تعليمات Print() وسجلات لتصحيح أخطاء مشوريك الخبير.
لقد تم تقديم وظيفة Print() بالفعل. في الختام، سيتم طباعة أي سلسلة مرفقة بالوظيفة في السجل. من خلال طباعة محتوى المتغيرات والوظائف في السجل، يمكنك فحص مخرجات الكود وإصلاح أي أخطاء.
سترغب في استخدام مختبر الاستراتيجية لتشغيل محاكاة للتداول وفحص سجل الإخراج. يتم عرض سجل اختبار الاستراتيجية تحت علامة اليومية في نافذة مختبر الاستراتيجية. هناك حد لكمية المعلومات المدرجة في علامة اليومية، لذا قد ترغب في عرض السجل الفعلي.
يتم تخزين سجلات مختبر الاستراتيجية في مجلد \tester\logs. انقر بزر الماوس الأيمن في أي مكان في نافذة اليومية وحدد “فتح” من القائمة المنبثقة. ستفتح نافذة مستكشف Windows، وتعرض محتويات مجلد السجل. أسماء الملفات تأتي في تنسيق yyyymmdd.log، حيث yyyy هو السنة من أربعة أرقام، و mm هو الشهر من رقمين، و dd هو التاريخ من رقمين. يمكنك عرض السجلات في Notepad أو أي محرر نصي آخر.
لنقم بتوضيح مثال على كيفية استخدام السجل للعثور على مشكلة في البرمجة. الشيفرة أدناه تحتوي على خطأ فيها، وهي لا تعمل كما كنا نتوقع. لكي نتمكن من تشخيص المشكلة،
نحتاج إلى التحقق من إدخال أو إخراج الوظيفة. دعونا نقوم بإنشاء تعليمة Print() ونقوم بطباعة محتوى جميع المتغيرات ذات الصلة في السجل.
سنقوم بتشغيل الخبير الخاص بنا في مختبر الاستراتيجية، باستخدام أسعار الافتتاح فقط كنموذج اختبار لنا. تأكد من أنك تختبر الخبير على مدى فترة زمنية كافية حتى يتمكن من تنفيذ عدد كافٍ من الصفقات لنقوم بتحليلها. إذا كنت بحاجة إلى التحقق من الأسعار على الرسم البياني، انقر على زر “فتح الرسم البياني” لفتح رسم بياني يظهر فيه الصفقات المحاكاة.
بعد ذلك، سننتقل إلى علامة اليومية ونتحقق من المعلومات التي نحتاجها. إذا كنا بحاجة إلى عرض السجل بالكامل، أو إذا كانت هناك صفقات لا تظهر في علامة اليومية، يمكننا النقر بزر الماوس الأيمن واختيار “فتح” من القائمة المنبثقة، وفتح ملف السجل مباشرة.
هذا الشيفرة يعطينا خطأ 130: “توقفات غير صالحة” في كل مرة نقوم فيها بوضع أمر شراء. نعلم أن الخطأ 130 يعني أن توقف الخسارة أو الربح المأمون غير صحيح. هل يمكنك التعرف على الخطأ؟
كود :
if(Close[0] > MA && BuyTicket == 0)
{
double OpenPrice = Ask;
double BuyStopLoss = OpenPrice + (StopLoss * UsePoint);
double BuyTakeProfit = OpenPrice + (TakeProfit * UsePoint);
BuyTicket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSli ppage,
BuyStopLoss,BuyTakeProfit,"Buy Order",MagicNumber,0,Green);
SellTicket = 0;
}
سنستخدم وظيفة Print() للتحقق من المعلمات التي يتم تمريرها إلى وظيفة OrderSend(). سنركز على سعر فتح الأمر، ووقف الخسارة، والربح المأمون.
كود :
Print("Price:"+OpenPrice+" Stop:"+BuyStopLoss+" Profit:"+BuyTakeProfit);
إليك الإخراج عند تشغيل الخبير الخاص بنا في مختبر الاستراتيجية. يُفترض أن هناك توقف عن الخسارة وربح مأمون بقيمة 50 نقطة:
كود :
11:52:12 2009.11.02 02:00 Example EURUSD,H1: OrderSend error 130
11:52:12 2009.11.02 02:00 Example EURUSD,H1: Price:1.47340000 Stop:1.47840000
Profit:1.47840000
نعلم أن يجب أن يكون توقف الخسارة أدنى من سعر الفتح في حالة أمر الشراء. هنا، يكون أعلى من السعر. في الواقع، إنه نفس السعر الخاص بربح مأمون. بنظرة سريعة على شيفرتنا، ندرك أننا أدخلنا علامة زائد بالخطأ في معادلة توقف الخسارة في حالة الشراء. إليك الشيفرة الصحيحة:
إذا كنت تتلقى رسالة خطأ عند محاولة وضع أمر، أو إغلاقه، أو تعديله، فالمركز الرئيسي ينبغي أن يكون تركيز جهودك على المشكلة المشار إليها في رسالة الخطأ. إليك بعض من أشهر رسائل الخطأ الناتجة عن أخطاء برمجية:
• خطأ 129: السعر غير صالح – سعر الافتتاح غير صالح. بالنسبة للأوامر السوقية، تأكد من أن سعر العرض الحالي أو السعر الطلب يتم تمريره وفقًا لنوع الأمر. بالنسبة للأوامر المعلقة، تأكد من أن السعر أعلى أو أدنى من السعر الحالي، حسب متطلبات نوع الأمر. تحقق أيضًا من أن سعر الأمر المعلق ليس قريبًا جدًا من السعر الحالي (مثل داخل مستوى التوقف).
• خطأ 130: التوقفات غير صالحة – إما أن سعر توقف الخسارة أو سعر الربح المأمون غير صحيح. تحقق من أن أسعار توقف الخسارة والربح المأمون تم وضعها أعلى أو أدنى من السعر الحالي، وذلك حسب ما إذا كان نوع الأمر هو شراء أم بيع. تحقق أيضًا من أن سعر توقف الخسارة أو سعر الربح المأمون ليسا قريبين جدًا من السعر الحالي (مثل داخل مستوى التوقف).
• خطأ 131: حجم التداول غير صالح – حجم اللوت غير صحيح. تأكد من أن حجم اللوت لا يتجاوز الحد الأدنى أو الحد الأقصى المطلوب من قبل الوسيط، وتحقق من أن حجم اللوت قد تم توحيده إلى القيمة الصحيحة (0.1 أو 0.01 في معظم الوسطاء).
يمكن العثور على وصف لجميع رسائل الأخطاء في دليل MQL تحت الثوابت القياسية – رموز الأخطاء. إذا كنت بحاجة إلى مساعدة إضافية بخصوص خطأ تواجهه، تحقق من المنتديات على MQL4.com.
نصائح وخدع ( 11 )
Tips and Tricks
حل مشكلات أخطاء التداول المتقطعة
Troubleshooting Intermittent Trading Errors
على الرغم من أن معظم الأخطاء الجادة يمكن العثور عليها ببساطة من خلال اختبار العودة، إلا أن بعضها قد يحدث فقط أثناء التداول في الوقت الحقيقي. يمكن أن تؤدي أخطاء في المنطق إلى عدم وضع الصفقات بشكل صحيح، ويمكن أن تستغرق هذه الأخطاء بعض الجهد للعثور عليها. إذا كانت هناك صفقات تُوضع بشكل غير صحيح أثناء التداول التجريبي أو الحي، فإننا نحتاج إلى أكبر قدر ممكن من المعلومات لحل المشكلة.
سنقوم بإضافة ميزة اختيارية لتسجيل معلومات الصفقات والحالة في الوقت الحقيقي، بحيث نكون لدينا سجل لها عند حل مشكلات التداول. سنستخدم تعليمات Print() كما في السابق، ولكننا سنسجل قيم المؤشرات والأسعار – أي معلومات تكون مفيدة في عملية تصحيح الأخطاء. سنقوم أيضًا بإضافة متغير خارجي لتشغيل وإيقاف التسجيل.
كود:
// External variables
extern bool Debug = true;
// Place near the end of the start() function
if(Debug == true) Print(StringConcatenate("Bid:",Bid," Ask:",Ask," MA:",MA,
" BuyTicket:",BuyTicket," SellTicket:",SellTicket));
سيتم تسجيل معلومات الأسعار والمؤشرات، بالإضافة إلى محتوى المتغيرات BuyTicket و SellTicket في الشيفرة أعلاه. إذا كانت هناك أي أسئلة حول كيفية فتح صفقة أو لماذا لم تتم فتح صفقة معينة، ستظهر السجل في تلك اللحظة معلومات حالة جميع شروط الصفقة ذات الصلة. يمكنك تشغيل أو إيقاف تسجيل المعلومات باستخدام المتغير الخارجي Debug.
يجب وضع تعليمة الـ Print() للتصحيح بالقرب من نهاية الدالة start()، بعد جميع دوال التداول. إذا كنت تستخدم مؤقتًا و/أو ميزة تنفيذ عند فتح شريط، يجب وضع تعليمة الـ Print() للتصحيح داخل كتلة المؤقت بحيث تعمل فقط عند الضرورة. وإلا فإن الخطأ سيظهر في السجل في كل انقضاء زمني (تيك)، مما يمكن أن يؤدي إلى إنشاء ملف سجل ضخم.
حل أخطاء التجميع
Fixing Compilation Errors
عند تجميع مستشار الخبير الخاص بك، سيقوم المترجم بفحص الصيغة الصحيحة وضمان أن جميع الوظائف والمتغيرات المخصصة قد تم إعلانها بشكل صحيح. إذا نسيت شيئًا ما، سيتوقف المترجم، وستظهر أي أخطاء في التجميع في علامة الأخطاء في نافذة الأدوات.
عندما تواجه قائمة طويلة من أخطاء التجميع، دائمًا ابدأ من الخطأ الأول. انقر نقرًا مزدوجًا على الخطأ في القائمة، وسيقفز المحرر مباشرةً إلى السطر الذي يحتوي على الخطأ. قم بتصحيح الخطأ وأعد التجميع. في بعض الأحيان، قد ينتج خطأ بسيط في الصيغة عن العديد من أخطاء غير مرتبطة، على الرغم من أن الأول فقط كان صحيحًا.
إليك قائمة بأخطاء التجميع الشائعة وحلولها:
المتغير غير معرف( Variable not defined ): نسيت أن تعلن عن متغير بنوع البيانات. إذا كان متغيراً عاماً أو خارجياً، قم بإعلانه في الجزء العلوي من الملف. إذا كان متغيرًا محليًا، ابحث عن أول حدوث وضع تصريح النوع أمامه. في حالة الشك، تحقق من الإملاء أو حالة (أعلى/أدنى) اسم المتغير.
المتغير تم تعريفه بالفعل( Variable already defined ): قمت بتعريف نفس المتغير مرتين. احذف تصريح النوع من جميع التصريحات المتكررة.
الدالة غير معرفة( Function is not defined ): إذا كانت الدالة في سياق تضمين أو مكتبة، تأكد من أن التوجيه #include أو #import موجود في الجزء العلوي من الملف وصحيح. في حالة عدم وجوده، تحقق من الإملاء أو حالة اسم الدالة وتأكد من وجودها إما في الملف الحالي أو في الملفات المتضمنة أو المكتبات ذات الصلة.
استخدام تعيين غير قانوني( Illegal assignment used ): غالبًا ما يكون ذلك مرتبطًا برمز اليساوي (=). تذكر أن العلامة اليساوي الواحدة تُستخدم لتعيين المتغير، وعلامتي اليساوي (==) هما عامل مقارنة. قم بتصحيح عامل التعيين إلى العامل المقارن المناسب.
المتوقع تعيينه( Assignment expected ): هذا يكون عادةً متعلقًا بعامل المقارن “يساوي” (==). لقد استخدمت اثنين من علامات اليساوي بدلاً من واحدة في تعيين المتغير. قم بتصحيح العامل إلى علامة اليساوي الواحدة.
قوس الإغلاق اليميني غير متوازن( Unbalanced right parenthesis ): هذه الأخطاء عادة تحدث في عبارة if عند استخدام الأقواس متداخلة. انتقل إلى السطر الذي يشير إليه الخطأ الأول وأدخل قوس الإغلاق اليميني في المكان المناسب.
قوس الفتح اليساري غير متوازن( Unbalanced left parenthesis ): هذا أمر معقد. الخطأ عادةً ما يشير إلى نهاية سطر البرنامج. ببساطة نسيت قوس الإغلاق اليميني في مكان ما. قم بمراجعة الشيفرة التي قمت بتحريرها مؤخرًا وابحث عن قوس الإغلاق اليميني المفقود. قد تضطر إلى تعليق أجزاء من الشيفرة للعثور على المشكلة.
عدد معلمات خاطئ( Wrong parameters count ): لديك عدد قليل أو كثير جدًا من الوسائط في دالة. قم بالتحقق المضاعف من بنية الدالة في مرجع MQL وقم بتصحيح الوسائط.
المتوقع نص الفاصلة( Semicolon expected ): ربما نسيت وضع فاصلة في نهاية السطر. قم بوضع فاصلة في نهاية السطر السابق. علمًا أن نسيان الفاصلة يمكن أن يتسبب في أي من الأخطاء أعلاه أيضًا، لذا تأكد من وضع تلك الفواصل!
المؤشرات المخصصة والنصوص
Custom Indicators and Scripts
لن تكتمل هذه السلسلة دون تغطية المؤشرات المخصصة والنصوص. المؤشرات المدمجة في منصة التداول MetaTrader محدودة إلى حد ما، ولكن لحسن الحظ، يسمح MQL بإمكانية للمبرمجين بإنشاء مؤشراتهم الخاصة. إذا كنت تبحث عن مؤشر شائع غير متضمن في MT4، فمن الممكن أن يكون شخص ما قد قام بإنشاء واحد بالفعل.
سنلقي نظرة عامة أساسية على إنشاء المؤشرات المخصصة. معظم المؤشرات تستخدم صيغًا رياضية معقدة، وبالتالي تعتبر من مجال المبرمجين ذوي الخبرة. ومع ذلك، لا يجب أن تكون المؤشرات معقدة بالضرورة. سنقوم بإنشاء مؤشر مخصص في هذا الدرس يستخدم فقط عدد قليل من الأسطر.
البوفرز
Buffers
البوفرز هي مصفوفات تخزن قيم المؤشر والحسابات. يمكن لمؤشر مخصص أن يحتوي على ما يصل إلى 8 بوفرز. تستخدم البوفرز مؤشرات، تمامًا مثلما تفعل المصفوفات، وتتراوح من 0 إلى 7. عند استدعاء مؤشر مخصص في خبير مستشار باستخدام وظيفة iCustom()، يكون المعامل قبل الأخير في الوظيفة هو بوفر المؤشر.
للعثور على البوفر المناسب لخط مؤشر ما، عادة ما تفحص الشيفرة المصدرية، إذا كانت متاحة. إذا كانت الشيفرة المصدرية منظمة بوضوح بأسماء متغيرة واضحة، يجب أن تكون قادرًا على تحديد البوفر المناسب بسهولة. سنتناول في هذا الدرس مسألة تسمية بوفرات المؤشر بالشكل الصحيح.
إنشاء مؤشر مخصص
Creating A Custom Indicator
لنقم بإنشاء مؤشر مخصص باستخدام مؤشرين مدمجين في منصة التداول MetaTrader لحساب خطوطنا. سنقوم بإنشاء مؤشر معدل لنطاق بولينجر المعدل. تتألف بولينجر باند من 3 خطوط – خط مركزي هو متوسط حسابي بسيط، بالإضافة إلى خط علوي وخط سفلي تحدد قيمتهما بالانحراف المعياري.
يمكننا إنشاء مؤشر بولينجر باند الخاص بنا باستخدام مؤشر المتوسط المتحرك ومؤشر الانحراف المعياري. نرغب في إنشاء مؤشر يستخدم متوسط حسابي تنقلي لحساب الخطوط، بدلاً من متوسط حسابي بسيط.
نبدأ باستخدام معالج لإنشاء ملف المؤشر الخاص بنا. حدد “جديد” من قائمة الملف أو شريط الأدوات لفتح المعالج وإنشاء مؤشر مخصص. قم بملء اسم المؤشر وأضف المعلمات إذا كنت ترغب. في الصفحة النهائية، قمنا بإضافة ثلاثة خطوط مؤشر بنفس اللون. إليك نتيجة المعالج. لقد تركنا وظيفة start() في هذا الوقت:
كود :
//+------------------------------------------------------------------+
//| EMA Bollinger.mq4 |
//| Mohammad Al turk |
//| http://www.arabicbroker.com |
//+------------------------------------------------------------------+
#property copyright "Mohammad Al turk"
#property link "http://www.arabicbroker.com"
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_color1 DeepSkyBlue
#property indicator_color2 DeepSkyBlue
#property indicator_color3 DeepSkyBlue
//---- buffers
double ExtMapBuffer1[];
double ExtMapBuffer2[];
double ExtMapBuffer3[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int init()
{
//---- indicators
SetIndexStyle(0,DRAW_LINE);
SetIndexBuffer(0,ExtMapBuffer1);
SetIndexStyle(1,DRAW_LINE);
SetIndexBuffer(1,ExtMapBuffer2);
SetIndexStyle(2,DRAW_LINE);
SetIndexBuffer(2,ExtMapBuffer3);
//----
return(0);
}
لنلتفت إلى العناصر المُدرجة بالخط العريض. تعيينات #property تعيّن المعلمات لبوفرات المؤشر الخاص بنا. تعيين خاصية indicator_chart_window ترسم مؤشرنا في نافذة الرسم الرئيسية. إذا كنا نقوم بإنشاء مؤشر تذبذب ونرغب في رسم المؤشر في نافذة منفصلة، فسنستخدم بدلاً من ذلك خاصية indicator_separate_window.
تعيين خاصية indicator_buffers يحدد عدد البوفرات لمؤشرنا. في هذه الحالة، نستخدم ثلاث بوفرات. تعيينات indicator_color تحدد لون الخطوط الثلاثة باللون الأزرق السماوي (DeepSkyBlue).
الآن نأتي إلى التعريفات لمصفوفات البوفر. لدينا ثلاث بوفرات تحمل أسماء ExtMapBuffer(1-3). سنقوم قريبًا بتغيير معرفات هذه المصفوفات إلى شيء أكثر وصفًا.
تتم محاكمة الوظيفة init() هي الوظيفة التي نقوم فيها بتعيين الخصائص لبوفرات المؤشر الخاص بنا. تقوم وظيفة SetIndexBuffer() بربط مصفوفة البوفر بفهرس البوفر. الفهرس البوفر هو ما نشير إليه عند تعيين الخصائص لخط مؤشر، وأيضًا عند استدعاء خط مؤشر من خلال خبير مستشار باستخدام وظيفة iCustom(). الباراميتر الأول هو عدد صحيح يتراوح من 0 إلى 7، والباراميتر الثاني هو اسم مصفوفة البوفر.
خصائص الرسم
Drawing Properties
تقوم وظيفة SetIndexStyle() بتعيين نوع الخط الذي سيتم رسمه، بالإضافة إلى خصائص هذا الخط. ستحتوي كل خط مؤشر على وظيفة SetIndexStyle() المقابلة. إليك بنية الجملة:
كود :
void SetIndexStyle(int BufferIndex, int LineType, int LineStyle = EMPTY,
int LineWidth = EMPTY, color LineColor = CLR_NONE)
• فهرس البوفر (BufferIndex) – فهرس المصفوفة، من 0 إلى 7.
• نوع الخط (LineType)- يعيّن نوع الخط الذي سيتم رسمه. DRAW_LINE يرسم خطًا واحدًا، DRAW_HISTOGRAM يرسم هستوغرامًا رأسيًا (انظر إلى مؤشري OsMA أو مؤشر الرسم البياني الرائع لمثال)، DRAW_ARROW يرسم رمزًا، وDRAW_NONE لا يرسم خطًا.
• نمط الرسم (LineStyle)- معلمة اختيارية تشير إلى نمط الرسم. يُستخدم بشكل أساسي للخطوط من نوع DRAW_LINE. افتراضيًا، يتم رسم خط ثابت (STYLE_SOLID). يمكنك أيضًا رسم خطوط متقطعة (STYLE_DASH) ونقاط (STYLE_DOT).
• عرض الخط (LineWidth)- معلمة اختيارية تشير إلى عرض الخط بالبكسل. القيمة الافتراضية هي 1.
• لون الخط (LineColor)- معلمة اختيارية تشير إلى لون الخط. إذا كنت تستخدم المعالم #property، يتم تعيين اللون باستخدامها، ولكن يمكنك أيضًا تعيين اللون هنا.
إذا كنت تستخدم DRAW_ARROW كنوع للخط، فإن وظيفة SetArrow() تسمح لك بتعيين رمز الخط في خط الرسم باستخدام خطوط Wingdings. الباراميتر الأول هو فهرس البوفر، والثاني هو ثابت صحيح يمثل الرمز الذي سيتم رسمه. يمكن العثور على الرموز في دليل MQL تحت الثوابت القياسية – رموز السهم.
قد ترغب في إضافة وصف لخطوط المؤشر التي ستتم عرضها في الإشارة المنبثقة أو في نافذة البيانات. للقيام بذلك، استخدم وظيفة SetIndexLabel(). الباراميتر الأول هو فهرس البوفر، والثاني هو وصف نصي. سنقوم بإضافة هذه المعلومات إلى مؤشرنا قريبًا.
إذا كان المؤشر الخاص بك يتم رسمه في نافذة منفصلة (مثل المؤشرات التذبذبية)، وترغب في إضافة مستويات للإشارة إلى مستويات البيع الزائد أو الشراء الزائد (مثل مؤشر الستوكاستيك أو RSI)، أو المستوى الصفر (مثل مؤشر CCI)، يمكنك استخدام وظائف SetLevelStyle() و SetLevelValue(). انظر إلى دليل MQL تحت عنوان المؤشرات المخصصة لمزيد من المعلومات.
قد ترغب أيضًا في تحديد اسم مختصر للمؤشر ليتم عرضه في الزاوية اليسرى العلوية لنافذة المؤشر. استخدم وظيفة IndicatorShortName() لتعيين هذه القيمة. الباراميتر الوحيد هو سلسلة نصية ستظهر في الزاوية اليسرى العلوية لنافذة المؤشر، بالإضافة إلى نافذة البيانات.
استخدام أسماء المؤشرات الوصفية
Using Descriptive Buffer Names
إليك رمز المؤشر المحدث لدينا. لاحظ أننا قمنا بإعادة تسمية مصفوفات البوفر لتكون أكثر وصفًا لوظيفتها الفعلية. لقد قمنا بتغيير الباراميتر الثاني لوظائف SetIndexBuffer() ليعكس أسماء البوفر الجديدة. لقد قمنا أيضًا بإضافة وظيفة SetIndexLabel() لكل خط لعرض أسماء وصفية في نافذة البيانات.
كود :
//---- buffers
double EMA[];
double UpperBand[];
double LowerBand[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int init()
{
//---- indicators
SetIndexStyle(0,DRAW_LINE);
SetIndexBuffer(0,EMA);
SetIndexLabel(0,"EMA");
SetIndexStyle(1,DRAW_LINE);
SetIndexBuffer(1,UpperBand);
SetIndexLabel(1,"UpperBand");
SetIndexStyle(2,DRAW_LINE);
SetIndexBuffer(2,LowerBand);
SetIndexLabel(2,"LowerBand");
//----
return(0);
}
قمنا بإعادة تسمية مصفوفات البوفر الخاصة بنا من الأسماء الافتراضية (ExtMapBuffer) إلى أسماء أكثر وصفًا. EMA[] سيكون بوفرنا للخط المركزي، وUpperBand[] وLowerBand[] ستكونان الخطوط العلوية والسفلية على التوالي.
تقوم وظائف SetIndexBuffer() بربط فهارس البوفر بمصفوفات البوفر الخاصة بنا. EMA هو 0، UpperBand هو 1، وLowerBand هو 2. لاحظ أن الأقواس تُترك خارج اسم مصفوفة الهوية في البرميتر الثاني لوظائف SetIndexBuffer().
تقوم وظائف SetIndexLabel() بتعيين اسم وصفي لكل من بوفرات المؤشر. في هذه الحالة، أسماء الخطوط هي نفس أسماء الهويات لدينا. ستظهر هذه الأسماء على تلميح الماوس وكذلك في نافذة البيانات. إذا قرر مبرمج آخر استخدام هذا المؤشر في مستشار خبير، ستجعل التنسيقات أعلاه واضحًا بالضبط أي فهرس بوفر يجب عليهم استخدامه لكل خط.
المؤشرات المخصصة والنصوص
Custom Indicators and Scripts
وظيفة start() في المؤشر
The Indicator start() Function
المعالج يقوم بإدراج تعبير واحد فقط في وظيفة start().
كود :
int counted_bars = IndicatorCounted();
دالة IndicatorCounted() تُرجع عدد الشرائط على الرسم البياني التي قام المؤشر بحسابها بالفعل. عند تشغيل الخبير المستشار لأول مرة، ستكون قيمة هذه الدالة هي 0. سيتم حساب المؤشر لكل شريط على الرسم البياني. في الشرائط التالية، سنقوم بفحص دالة IndicatorCounted() لمعرفة عدد الشرائط التي تم حسابها بالفعل، حتى نعرف بالضبط كم عدد الشرائط الجديدة التي يجب حسابها.
عمليات حساب المؤشر ستحدث داخل حلقة for. نقطة البداية ستكون الشريط الأول غير المحسوب، ونقطة النهاية ستكون الشريط الحالي. سنقارن قيمة دالة IndicatorCounted() بالمتغير Bars المعرف مسبقًا، الذي يُرجع عدد الشرائط على الرسم البياني الحالي. هذا سيحدد نقطة البداية لدينا. إليك الشيفرة المصدرية لحلقة الـ for:
كود :
int counted_bars = IndicatorCounted();
if(counted_bars > 0) counted_bars--;
int CalculateBars = Bars - counted_bars;
for(int Count = CalculateBars; Count >= 0; Count--)
{
// Indicator calculations
}
البيان الشرطي الأول سيقلل قيمة counted_bars بمقدار 1 عند حساب الشرائط الجديدة. سنقوم دائمًا بحساب ما لا يقل عن الشريطين السابقين. ذلك بسبب شرط حيث يمكن أن لا يتم حساب العلامة النهائية للشريط في بعض الحالات. بعد ذلك، نقوم بتحديد عدد الشرائط التي سنقوم بحسابها، من خلال طرح قيمة counted_bars من المتغير المعرف مسبقًا Bars. هذا يُخزن في المتغير CalculateBars.
في حلقتنا for، يتم تعيين المتغير المتزايد Count إلى قيمة CalculateBars، وشرط الإنهاء هو عندما يكون Count أقل من 0، ويتم تنقيص المتغير Count في كل تكرار. سيتم ذلك بحيث يتم حساب كل شريط على الرسم البياني من اليسار إلى اليمين.
إليك الشيفرة المصدرية لحساب الشرائط البولنجر. سنقوم بتعريف المتغير الخارجي BandsPeriod في بداية الملف. وحلقة الـ for هي تلك التي قمنا بإنشائها أعلاه:
كود :
// External parameters
extern int BandsPeriod = 20;
// start() function
for(int Count = CalculateBars; Count >= 0; Count--)
{
EMA[Count] = iMA(NULL,0,BandsPeriod,0,MODE_EMA,0,Count);
double StdDev = iStdDev(NULL,0,BandsPeriod,0,MODE_EMA,0,Count);
UpperBand[Count] = EMA[Count] + StdDev;
LowerBand[Count] = EMA[Count] - StdDev;
}
أولاً، نقوم باستدعاء المؤشر المتحرك المتوسط المدمج باستخدام دالة iMA()، ونسند قيمة العائد إلى EMA[Count]. يجب ملاحظة أن مؤشر الفهرس للمصفوفة ومعلمة الانتقال لمؤشر المتوسط المتحرك تستخدمان قيمة الـ Count الحالية.
بعد ذلك، نستدعي مؤشر الانحراف المعياري باستخدام iStdDev(). لحساب الشريط العلوي، كل ما علينا القيام به هو إضافة الانحراف المعياري إلى خط المتوسط المتحرك. يتم تخزين هذا في مصفوفة البيانات UpperBand[]. لحساب LowerBand[]، نقوم بطرح الانحراف المعياري من المتوسط المتحرك.
دعونا نوسع مؤشرنا قليلاً بمنحه مجموعة كاملة من الإعدادات. سنضيف إعدادات لضبط التأخير الأمامي، وطريقة المتوسط المتحرك، ومعلمات السعر المطبقة، بالإضافة إلى تعديل الانحراف المعياري.
كود :
// External parameters
extern int BandsPeriod = 20;
extern int BandsShift = 0;
extern int BandsMethod = 1;
extern int BandsPrice = 0;
extern int Deviations = 1;
// start() function
for(int Count = CalculateBars; Count >= 0; Count--)
{
EMA[Count] = iMA(NULL,0,BandsPeriod,BandsShift,BandsMethod,Band sPrice,Count);
double StdDev = iStdDev(NULL,0,BandsPeriod,BandsShift,BandsMethod, BandsPrice,Count);
UpperBand[Count] = EMA[Count] + (StdDev * Deviations);
LowerBand[Count] = EMA[Count] - (StdDev * Deviations);
}
لقد قمنا بإضافة متغيرات خارجية لضبط المعلمات المتبقية لدوال iMA() و iStdDev(). كما قمنا أيضًا بإضافة معلمة لضبط عدد الانحرافات المعيارية. لحساب هذا، نقوم ببساطة بضرب StdDev في Deviations. الآن لدينا مؤشر Bollinger Bands قابل للتعديل بالكامل وأكثر مرونة من مؤشر MetaTrader القياسي.
يمكنك القيام بالمزيد مع المؤشرات المخصصة بخلاف إعادة حساب المؤشرات المدمجة. اعتمادًا على مستوى معرفتك الرياضية، يمكنك كتابة مؤشرات ليست متضمنة في MetaTrader، أو حتى إنشاء مؤشرات خاصة بك. يمكنك أيضًا رسم وتلاعب الكائنات أيضًا. إذا كنت ترغب في معرفة المزيد حول إنشاء المؤشرات المخصصة، انظر إلى مواضيع الإشارة المرجعية MQL Custom indicators و Object functions و Math & Trig.
السكريبتات
“سكريبت” هو برنامج MQL يُشغّل مرة واحدة فقط عندما يتم تعليقه لأول مرة على الرسم البياني. يمكن استخدام السكريبتات لأتمتة سلسلة من الإجراءات التداولية، مثل إغلاق جميع الطلبات على الرسم البياني أو إرسال أمر معلق. بعض السكريبتات، مثل سكريبت period_converter الذي يأتي مع منصة التداول MetaTrader، يمكنها إعادة رسم الرسم البياني بناءً على فترة زمنية مخصصة.
يجب أن يحتوي ملف شيفرة المصدر للسكريبت على إما توجيه خاصية show_confirm أو توجيه خاصية show_inputs. توجيه خاصية show_confirm يطلب من المستخدم تأكيد تشغيل السكريبت، في حين أن توجيه خاصية show_inputs يعرض نافذة حوار لخصائص السكريبت للمستخدم.
كود :
#property show_confirm
// shows confirm dialog
#property show_inputs
// shows properties dialog
إذا كان لديك معلمات في سكريبتك تحتاج إلى تعديلها، فيجب استخدام خاصية show_inputs. وإلا، يمكنك استخدام خاصية show_confirm.
مثلما يحدث مع الخبراء المستشارين والمؤشرات، تستخدم السكريبتات الوظائف init() و deinit() و start(). تذكر أن كل وظيفة ستُشغل مرة واحدة فقط – تعمل الوظائف init() و start() عند بدء تشغيل السكريبت، وتعمل الوظيفة deinit() عندما يتم إزالة السكريبت. يمكن تعليق سكريبت واحد فقط على الرسم البياني في نفس الوقت.
تأتي منصة التداول MetaTrader مع العديد من السكريبتات العينية. يتم حفظ جميع السكريبتات في مجلد \experts\scripts.
وهكذا أكون قد أنهيت بعون الله وحمده دورة لغة الـ MQL4 وسأبدء إن شاء الله دورة لغة MQL5 باقرب فرصة.