English Русский
preview
MetaTrader 5: Stratejinize Uygun Bir Piyasa Oluşturun - Özel Sembollerde Renko/Aralık/Hacim, Sentetikler ve Stres Testleri

MetaTrader 5: Stratejinize Uygun Bir Piyasa Oluşturun - Özel Sembollerde Renko/Aralık/Hacim, Sentetikler ve Stres Testleri

MetaTrader 5Örnekler |
30 4
MetaQuotes
MetaQuotes

Giriş

Standart MetaTrader 5 grafiği güvenilir bir araçtır, ancak zaman dilimlerine, aracı kurumun fiyat akışına ve takvim çizelgesine sıkı sıkıya bağlıdır. Modern algoritmik alım-satımda bu genellikle yetersizdir: volatilite gürültüsü, geçmişteki boşluklar ve sabit zaman aralıkları gerçek dinamikleri bozar. Terminal yalnızca piyasayı okumanıza değil, aynı zamanda gösterimini belirli bir stratejinin ihtiyaçlarına uyacak şekilde tasarlamanıza da izin verirse ne olur?

Özel semboller API'sinin geliştirilmesiyle MetaTrader 5 mimarisini değiştirdi: yatırımcı artık sadece pasif bir fiyat tüketicisi değil, bir veri mimarı haline geliyor. Özel bir sembol, görselleştirme için çevrimdışı bir grafik değil, kendi tik geçmişi, sözleşme özellikleri ve Strateji Sınayıcıda yerel desteği olan tam teşekküllü bir terminal nesnesidir.

Artık zamansız grafikler (Renko çubukları, Aralık çubukları, Eşit Hacimli çubuklar) oluşturabilir, sentetik enstrümanları ve sepetleri bir araya getirebilir ve makas, durma seviyeleri ve teminat gereksinimlerini doğrudan kod içinde keyfi olarak değiştirerek stres testleri yapabilirsiniz.

Bu makalede, özel enstrümanlarla çalışmanın pratik yönlerini inceleyeceğiz: veri depolama yapısı, temel MQL5 API, tik birleştirme algoritmaları ve geçmiş değiştirme. Emirleri özel bir sembolden gerçek bir işlem enstrümanına yönlendirmek için standart Uzman Danışmanların nasıl uyarlanacağına bakacağız.

Özel veri akışı

Şekil 1. Piyasanızı standart malzemelerden oluşturun


Özel sembol nedir: Mimari, depolama ve API

MetaTrader 5'teki özel bir sembol, platformun önceki nesillerinde uygulandığı gibi görsel gözlem için çevrimdışı bir grafik değildir. Artık kendi tik geçmişi, sözleşme özellikleri ve strateji sınayıcıda yerel desteği olan tam teşekküllü bir terminal nesnesidir.

Bir aracı kurum tarafından sağlanan ve fiyat akışının (fiyat sağlayıcı) kalitesine bağlı olan standart enstrümanların aksine, özel bir sembol yatırımcının veri mimarı olmasına olanak tanır. Hangi fiyatların, ne şekilde ve hangi hassasiyet seviyesinde geçmişe dahil edileceğini siz belirlersiniz. Bu, yalnızca aracı kurumun sahip olmadığı piyasaları (kripto paralar, bankalar arası spread'ler) analiz etmenin değil, aynı zamanda mevcut verileri dönüştürmenin de yolunu açar (Renko, Eşit Hacim, sentetik endeksler).

Özel semboller, aracı kurumun işlem sunucularından ayrı tutulur. Örneğin dosyalar yerel olarak depolanır: “AppData\Roaming\MetaQuotes\Terminal\[InstanceID]\bases\Custom”.

Bu çözümün önemli bir avantajı vardır: bağımsızlık. Aracı kurumunuzu değiştirebilir, terminalinizi güncelleyebilirsiniz, ancak özel sembolleriniz ve birikmiş geçmişiniz yerinde kalacaktır. Piyasa Gözlemi penceresinde, gerçek enstrümanları kendi enstrümanlarınızdan görsel olarak ayırmak için her zaman Özel adlı ayrı bir klasörde bulunurlar.


MQL5 API çekirdeği

Koddaki özel sembollerin işlenmesi, özel API fonksiyonlarına erişim gerektirir. Tam liste dokümantasyondan bulunabilir, ancak ana kullanım durumları için üç fonksiyon grubuna odaklanacağız.

Sembol yaşam döngüsü yönetimi:

  • CustomSymbolCreate() - belirtilen bir grupta belirtilen bir adla özel bir sembol oluşturur,
  • CustomSymbolDelete() - belirtilen ada sahip bir kullanıcı sembolünü kaldırır,
  • SymbolSelect() - Piyasa Gözlemine ekler (bu olmadan sembol grafik arayüzünde görünmeyecektir).

Veri indirme yönetimi:

  • CustomTicksAdd() - özel bir sembolün fiyat geçmişine MqlTick[] türündeki bir diziden veri ekler. Piyasa Gözlemi penceresinde özel bir sembol seçilmelidir,
  • CustomTicksReplace() - özel bir sembolün belirtilen zaman aralığındaki fiyat geçmişini MqlTick[] türündeki bir diziden verilerle tamamen değiştirir.
  • CustomRatesUpdate() - MqlRates[] türündeki bir diziden verilerle özel sembol geçmişine eksik çubukları ekler ve/veya mevcut olanları değiştirir.

Özellik yönetimi:

  • CustomSymbolSetInteger()/CustomSymbolSetDouble()/CustomSymbolSetString() - sözleşme özelliklerini ayarlar (makas, hassasiyet, teminat para birimi vb.).


API fonksiyonlarını doğrudan çağırmak, çeşitli hataları işlemeyi ve durumları kontrol etmeyi gerektirir. Geliştirmeyi hızlandırmak için CiCustomSymbol sarmalayıcı sınıfını kullanıyoruz. Özel sembol oluşturulması, gerçek sembol özelliklerinin klonlanması ve tiklerin toplu olarak yüklenmesi için mantığı kapsüller. CiCustomSymbol sınıfının tam kodu aşağıda ekli CiCustomSymbol.mqh dosyasında yer almaktadır.

Aşağıda özel bir sembolün başlatılmasına ilişkin bir örnek yer almaktadır - bir sembol oluşturuyoruz, EURUSD sembolünden özellikleri kopyalıyoruz ve geri dönüş kodlarını kontrol ediyoruz:

#include <CiCustomSymbol.mqh>

//+------------------------------------------------------------------+
//| inputs                                                           |
//+------------------------------------------------------------------+
input string   CustomSymbolName = "EURUSD_Custom"; // new symbol name
input string   OriginSymbol     = "EURUSD";        // source symbol

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   CiCustomSymbol symb;
   
//--- attempt to create a symbol
//--- return codes: -1 (error), 0 (already exists), 1 (successfully created)
   int res = symb.Create(CustomSymbolName, "", OriginSymbol, 1000000, true);
   
   if(res == -1)
     {
      Print("Error creating symbol: ", GetLastError());
      return;
     }
   
   if(res == 0)
     {
      Print("Symbol ", CustomSymbolName, " already exists.");
     }
   else
     {
      Print("Symbol ", CustomSymbolName, " successfully created.");
      
      //--- set specific properties (for example, fixed spread of 20 points) 
      //--- properties are changed BEFORE loading history
      if(!symb.SetProperty(SYMBOL_SPREAD, 20))
         Print("Failed to set spread. Error: ", GetLastError());
         
      if(!symb.SetProperty(SYMBOL_SPREAD_FLOAT, false))
         Print("Failed to set spread type (fixed). Error: ", GetLastError());
     }
   
//--- symbol added to Market Watch ('true' parameter to Create)
   Print("Symbol created: ", CustomSymbolName);
  }

Kodun tamamını aşağıda ekli CreateCustomSymbol.mq5 dosyasında bulabilirsiniz.

Özel sembolleri kullanırken dokümantasyonda açıklanan çeşitli sınırlamalara dikkat edin:

MQL5 Bulut Ağının devre dışı bırakılması:

Bulut Ağı aracılığıyla özel semboller üzerinde strateji optimizasyonu yasaktır. Nedeni basit: farklı yatırımcıların bilgisayarlarında aynı adlara sahip (örneğin EURUSD_Custom), ancak tamamen farklı geçmişleri olan semboller bulunabilir. Bulut Ağının kullanılması senkronizasyon kaosuna ve aşırı trafik oluşumuna yol açardı. Testler yalnızca yerel olarak veya yerel temsilciler aracılığıyla yapılabilir.

Teminat hesaplama mantığı:

Strateji sınayıcı, teminatı ve karı hesaplamak için çapraz kurları kullanır. AUDCAD.custom sembolünü (AUDUSD/USDCAD temelinde) oluşturduysanız ve test ediyorsanız ve hesabınız USD cinsindeyse, sınayıcının şunları yapması gerekir:

  • AUDUSD'yi bulma (AUD'nin USD cinsinden değeri) - teminatı hesaplamak için
  • USDCAD'i bulma (CAD'i USD'ye çevirme) - karı hesaplamak için

Gerekli tüm döviz paritelerinin Piyasa Gözleminizde olduğundan ve geçmişin yüklendiğinden emin olmanız önemlidir, aksi takdirde sınayıcı finansal göstergeleri hesaplayamayacaktır.

Sembol özellikleri test sırasında değişmez:

Özel sembol özellikleri (örneğin, SYMBOL_TRADE_STOPS_LEVEL durma seviyeleri için minimum mesafedir) teste başlamadan önce ayarlanır. Uzman Danışman çalışırken bunları dinamik olarak değiştirmek (örneğin, haberlerden önce durma seviyesini genişletmek) sınayıcının mevcut sürümünde mümkün değildir. Sınayıcı, başlatma sırasında sembol parametrelerini sabitler.


Analitik ara söz: Geriye dönük testin anlamı

Neden bu kadar karmaşık bir mimariye ihtiyaç duyuluyor? Özel sembollerin kullanılması, alım-satım fikirlerini test etme yaklaşımını değiştirir. Stratejinizi belirli bir aracı kurumun muhtemelen kusurlu verilerine (geçmişte boşluklar veya anormal makaslar) göre uyarlamak yerine, kendi ideal laboratuvar ortamınızı oluşturursunuz.

Stratejinin 50 piplik bir makas ile nasıl davrandığını (scalping'i ayıklamak için) veya zamanın önemli olmadığı grafiklerde (Aralık/Renko) nasıl işlem yaptığını kontrol edebilirsiniz. Bu, bir stratejinin temel mantığını piyasa gürültüsünden izole etmeye yardımcı olur.

Zamandan bağımsız grafikler: Renko, Aralık ve Eşit Hacim

Standart zaman dilimleri kullanışlıdır, ancak piyasaya yapay bir zaman çizelgesi dayatırlar. Örneğin, M1 mumu, tik sayısına bakılmaksızın her 60 saniyede bir kapanır. Düşük likidite koşullarında, böyle bir çubuk çok az bilgi aktarır ve yüksek volatilite anlarında, önemli hareketleri tek bir dikdörtgen içinde gizler.

Zamansız grafikler bu sorunu, zaman geçtikten sonra değil, fiyat veya hacim belirli bir eşiğe ulaştığında yeni bir çubuk oluşturarak çözer. Bu sayede şunlar mümkün olur:

  • Piyasa gürültüsünü filtreleme ve önemli hareketlere odaklanma;
  • Analiz adımını mevcut volatiliteye otomatik olarak uyarlama;
  • Birikme/dağılma bölgelerini zamanlayıcıya göre değil, gerçek (veya tik) hacmine göre belirleme.

Bu grafiklerin üç popüler türüne (Renko, Aralık ve Eşit Hacim) bakalım ve bunları özel sembolleri kullanarak MetaTrader 5'te nasıl oluşturacağımızı görelim. Başlangıç olarak, bu üç çubuk türünü bir tabloda karşılaştıralım ve açıklayalım:

Grafik türü Yeni çubuk oluşturma kriterleri  En iyi kullanım örnekleri 
Renko Belirli bir puan miktarına göre fiyat hareketi Trend stratejileri, yatay hareketleri filtreleme
Aralık Yüksek-Düşük çubuk aralığı belirtilen değere ulaşır Yüksek volatilite koşullarında scalping, gün içi işlem
Eşit Hacim Belirli sayıda tik veya gerçek hacim birikimi Likidite analizi, büyük oyuncuların ilgi alanlarının araştırılması

Not:

MetaTrader 5'teki tüm özel semboller M1 zaman dilimi çizelgesine bağlıdır. Bu, platformun mimari bir sınırlamasıdır: bir çubuk 2 saniye içinde oluşsa bile, geçmişte bir dakikalık bir hassasiyetle bir zaman damgası alacaktır. Volatilitenin yüksek olduğu dönemde, aynı dakika içinde birden fazla çubuk oluşabilir (aynı zamana sahip olabilir) - Uzman Danışman bunu dikkate almalıdır.


Teknik uygulama: Tiklerin çubuklar halinde birleştirilmesi

Genel olarak, zamansız çubuklar oluşturma algoritması her üç tür için de aynıdır:

  1. Sırasıyla CopyTicksRange()/OnTick() fonksiyonları aracılığıyla geçmişten tiklerin okunması veya çevrimiçi olarak alınması;
  2. Arabellekte verileri biriktirme: Açılış, Yüksek, Düşük, Kapanış, Hacim;
  3. Yeni bir çubuk oluşturmak için koşulları kontrol etme (puan/aralık/hacim);
  4. CustomRatesUpdate() aracılığıyla bir çubuğu özel sembol veritabanına kaydetme;
  5. MetaTrader 5 grafiklerinde göstergeleri ve Uzman Danışmanları etkinleştirmek için CustomTicksAdd() aracılığıyla tik uyarlama.

Özel sembollerle çalışmak için temel sınıf: Tüm çubuk türleri için algoritma

//+------------------------------------------------------------------+
//| class-aggregator of ticks into custom bars                       |
//+------------------------------------------------------------------+
class CBarAggregator
  {
private:
   string            symbol_name;
   double            threshold;
   int               bar_type;
   ENUM_VOLUME_MODE  volume_mode;

   MqlRates          rates_buffer[];
   int               buffer_limit;
   int               buffer_idx;

   MqlRates          current_bar;
   double            last_close;
   int               trend;
   bool              is_initialized;
   datetime          last_bar_time;
   int               symbol_digits;

public:
                     CBarAggregator(void);
   bool              Init(const string _symbol_name, double _threshold, int _bar_type, ENUM_VOLUME_MODE _vol_mode = VOLUME_MODE_TICK);
   bool              ProcessTick(const MqlTick &tick);
   void              FlushBuffer(void);
   void              Reset(void);

private:
   void              CreateBar(const MqlTick &tick, double open_price = 0.0);
   void              CloseAndSaveBar(const MqlTick &tick, double forced_close = 0.0);
   void              WriteBatch(void);
  };

//+------------------------------------------------------------------+
//| handle an incoming tick                                          |
//+------------------------------------------------------------------+
bool CBarAggregator::ProcessTick(const MqlTick &tick)
  {
   if(tick.bid <= 0 || tick.ask <= 0)
      return false;
   if(tick.time <= 0)
      return false;  // protection from invalid ticks

   if(!is_initialized)
     {
      CreateBar(tick);
      last_close = tick.bid;
      last_bar_time = tick.time;  // initialize with the first tick time
      is_initialized = true;
      return false;
     }

   bool bar_closed = false;

   if(bar_type == 0) // renko
     {
      double diff = tick.bid - last_close;
      double size = threshold * _Point;
      int req_move = (trend != 0) ? 2 : 1;

      if(diff >= size * req_move)
        {
         int bricks = (int)(diff / size);
         for(int i = 0; i < bricks; i++)
           {
            double target_close = NormalizeDouble(last_close + size, symbol_digits);
            CloseAndSaveBar(tick, target_close);
            last_close = target_close;
            CreateBar(tick, last_close);
            trend = 1;
           }
         bar_closed = true;
        }
      else
         if(diff <= -size * req_move)
           {
            int bricks = (int)(MathAbs(diff) / size);
            for(int i = 0; i < bricks; i++)
              {
               double target_close = NormalizeDouble(last_close - size, symbol_digits);
               CloseAndSaveBar(tick, target_close);
               last_close = target_close;
               CreateBar(tick, last_close);
               trend = -1;
              }
            bar_closed = true;
           }
     }
   else
      if(bar_type == 1) // range bars
        {
         double bid = NormalizeDouble(tick.bid, symbol_digits);
         if(bid > current_bar.high)
            current_bar.high = bid;
         if(bid < current_bar.low)
            current_bar.low = bid;

         if((current_bar.high - current_bar.low) >= threshold * _Point)
           {
            CloseAndSaveBar(tick);
            CreateBar(tick);
            bar_closed = true;
           }
        }
      else
         if(bar_type == 2) // equal volume bars
           {
            if(volume_mode == VOLUME_MODE_REAL)
               current_bar.real_volume += (long)tick.volume_real;
            else
               current_bar.tick_volume++;

            long current_vol = (volume_mode == VOLUME_MODE_REAL) ? current_bar.real_volume : current_bar.tick_volume;
            if(current_vol >= (long)threshold)
              {
               CloseAndSaveBar(tick);
               CreateBar(tick);
               bar_closed = true;
              }
           }

   return bar_closed;
  }

//+------------------------------------------------------------------+
//| close the bar and save to the buffer                             |
//+------------------------------------------------------------------+
void CBarAggregator::CloseAndSaveBar(const MqlTick &tick, double forced_close)
  {
   current_bar.close = (forced_close != 0.0) ? NormalizeDouble(forced_close, symbol_digits) : NormalizeDouble(tick.bid, symbol_digits);

   current_bar.high  = NormalizeDouble(MathMax(current_bar.high, MathMax(current_bar.open, current_bar.close)), symbol_digits);
   current_bar.low   = NormalizeDouble(MathMin(current_bar.low, MathMin(current_bar.open, current_bar.close)), symbol_digits);
   current_bar.spread = MathMax(0, current_bar.spread);

// make sure last_bar_time > 0 (initialized)
   if(last_bar_time > 0 && current_bar.time <= last_bar_time)
     {
      // shift by 1 second instead of 60 to minimize distortion
      current_bar.time = last_bar_time + 1;
     }
   last_bar_time = current_bar.time;

// final validation before the buffer
   if(current_bar.high < current_bar.low ||
      current_bar.high < current_bar.open || current_bar.high < current_bar.close ||
      current_bar.low > current_bar.open || current_bar.low > current_bar.close)
     {
      Print("🟡 Invalid bar skipped: O=", current_bar.open, " H=", current_bar.high, " L=", current_bar.low, " C=", current_bar.close, " Time=", TimeToString(current_bar.time));
      return;
     }

   if(buffer_idx < buffer_limit)
     {
      rates_buffer[buffer_idx] = current_bar;
      buffer_idx++;
     }

   if(buffer_idx >= buffer_limit)
      WriteBatch();
  }

//+------------------------------------------------------------------+
//| save the buffer to the symbol history                            |
//+------------------------------------------------------------------+
void CBarAggregator::WriteBatch(void)
  {
   if(buffer_idx > 0)
     {
      datetime t_from = rates_buffer[0].time;
      datetime t_to   = rates_buffer[buffer_idx - 1].time;

      if(CustomRatesUpdate(symbol_name, rates_buffer) < 1)
         Print("CustomRatesUpdate error: ", GetLastError());

      buffer_idx = 0;
     }
  }
//+------------------------------------------------------------------+

CBarAggregator sınıfının tam kodu CBarAggregator.mqh dosyasında bulunmaktadır.


Evrensel zamansız çubuk göstergesi

CBarAggregator sınıfının pratik uygulaması için evrensel bir gösterge oluşturalım. Girdilerde Renko, Aralık ve Eşit Hacim modları arasında geçiş yapmamızı sağlar ve belirli bir tarih aralığında özel bir sembol oluşturur.

Girdiler:

input ENUM_CUSTOM_CHART_MODE InputMode = CMT_RENKO;      // generation mode
input double InputBoxSize = 10;                          // bar size (points) for renko/range
input long InputVolLimit = 1000;                         // volume limit for equal-volume
input string InputSuffix = "";                           // symbol name suffix
input datetime InputStartTime  = 0;                      // history start (0 - last 7 days)

Çubuk oluşturma modlarını numaralandırma:

enum ENUM_CUSTOM_CHART_MODE
  {
   CMT_RENKO = 0,       // renko
   CMT_RANGE = 1,       // range bars
   CMT_EQVOL_TICK = 2,  // equal tick volumes
   CMT_EQVOL_REAL = 3   // equal real volumes
  };

Gerçek zamanlı çalışma - geçmişi yükledikten sonra, gösterge çevrimiçi olarak çalışmaya devam eder ve yeni tikleri işler:

//+------------------------------------------------------------------+
//| handle a new tick                                                |
//+------------------------------------------------------------------+
void ProcessNewTicks()
  {
   if(last_tick_time_msc==0)
      return;

   MqlTick ticks[];
   int copied=CopyTicksRange(_Symbol,ticks,COPY_TICKS_ALL,last_tick_time_msc,0);

   if(copied>0)
     {
      for(int i=0;i<copied;i++)
        {
         aggregator.ProcessTick(ticks[i]);
         last_tick_time_msc=ticks[i].time_msc;
        }
      aggregator.FlushBuffer();
     }
  }

Bu gösterge kendi başına grafik çizmez. Amacı, özel bir sembol oluşturmak ve bu sembolü verilerle doldurmaktır. Başlamak için göstergeyi herhangi bir grafiğe sürükleyin (örneğin, EURUSD) - bir özellikler penceresi açılacaktır. Bir mod (örn. MODE_RENKO), kutu boyutu (InputBoxSize) ve geçmiş başlangıcı seçin.

Gösterge uygulama sırası:

  • Gösterge çubuklar oluşturmaya başlayacaktır (oluşturma başlangıcı ve tamamlanmasıyla ilgili mesajlar günlükte görünecektir),
  • Oluşturmanın tamamlandığına dair mesaj göründüğünde, Piyasa Gözlemini açın (Ctrl+M),
  • Semboller listesinde oluşturulan enstrümanı bulun ve seçin (örneğin, EURUSD_Renko_10),
  • Yeni bir grafik açın (Ctrl+N),
  • Zaman dilimini M1 olarak ayarlayın.

Göstergenin farklı modlarda çalışmasının sonuçları Şekil 2 - Şekil 4'te sunulmuştur.

Renko

Şekil 2. Renko

Aralık

Şekil 3. Aralık

Eşit Hacim

Şekil 4. Eşit Hacim

Zamansız çubukları kullanmanın özelliklerini ve örneklerini kısaca ele alalım.

Renko - yeni bir çubuk yalnızca fiyat, önceki çubuğun kapanış fiyatından puan olarak belirtilen çubuk boyutunu aştığında oluşturulur. Çubuğun yönü (yükseliş/düşüş) kırılma yönüne göre belirlenir. Gölgeler varsayılan olarak görüntülenmez, ancak çubuk içi volatiliteyi analiz etmemiz gerekirse kullanılabilir. Güçlü bir hareketle, bir dakika içinde birkaç çubuk oluşabilir. Çubuk kapanış fiyatı, dakika kapanış fiyatı değil, kırılma fiyatıdır.

Örnek Renko tabanlı strateji:

  • Giriş - çubuğun kapanışında hızlı MA(9) ve yavaş MA(21)'in çaprazlaması,
  • Filtre - ATR(14) değeri belirtilen eşikten büyükse, yatay harekette yanlış kırılmaları filtrele,
  • Çıkış - Hareketli Ortalamaların ters çaprazlaması veya puan cinsinden sabit durma seviyesi,
  • Buradaki özellik, sıfır (son) Renko çubuğu her zaman tamamlanmamış olduğu için işlemlerin PRICE_CLOSE fiyatından yapılmasıdır.

Aralık - Yüksek ve Düşük arasındaki salınım puan olarak belirtilen değere ulaştığında yeni bir çubuk açılır. Renko'nun aksine, sadece kapanışı değil, çubuk içindeki tüm hareketi dikkate alır. Sabit bir boyut değeri yerine, ATR'a dayalı dinamik bir çubuk boyutu (Aralık) kullanabiliriz.

Aralık boyutu ne anlama geliyor? Aralık boyutu, yeni bir çubuğun oluşmaya başlayacağı eşik fiyat değişim değerini belirler. 1 Aralık, bir minimum fiyat değişimine eşittir. Bu değer aşağıdaki denklemle gösterilebilir: 1 Aralık = Tik Büyüklüğü.

Scalping'de kullanım için avantajlar:

  • Volatilitenin düşük olduğu dönemlerde çubuklar yavaş oluşur - daha az yanlış sinyal,
  • Volatilite yükseldiğinde, çubuklar daha sık hale gelir ve hızlı hareketleri yakalamamızı sağlar,
  • Çubuk sınırlarında net destek/direnç seviyeleri.

Eşit Hacim - belirli sayıda tik (Forex için) veya gerçek hacim (borsa enstrümanları için) biriktikten sonra yeni bir çubuk oluşturulur. Eşit Hacimli çubuklar, hareketleri eşit bir temelde karşılaştırmamızı sağlar:

  • Hızlı çubuk oluşumuna sahip bölgeler yüksek aktiviteye işaret eder ve bu nedenle mevcut aralığın kırılması mümkündür.
  • Grafiğin boş alanları ilgi eksikliğine işaret eder ve bu nedenle bir geri dönüş mümkündür.

Notlar:

Strateji Sınayıcı, standart kurallara göre özel semboller için tikler oluşturur: çubuk yapılandırmasına (OHLC) dayalı olarak. Bununla birlikte, zamansız çubuklar için (özellikle Renko), çubuğun kapanış fiyatı tanımı gereği bir sonraki hareketin tahmin edicisidir. Sınayıcı, sıfır (tamamlanmamış) çubuğun yapılandırmasına bakarak ideal bir giriş yanılsaması yaratabilir.

Çözüm:

  • Sinyalleri her zaman sıfırıncı çubuktan değil, birinci çubuktan (tamamlanmış) alırız;
  • Sınayıcıyı kapanmış çubuklar üzerinde çalışacak şekilde ayarlarız;
  • Gerçek sembol emir yönlendirmesi kullanırız (aşağıya bakın) - bu, sınayıcıdaki tik oluşturma yapaylıklarını ortadan kaldıracaktır.


Sentetik enstrümanlar: Spread'ler, sepetler ve piyasalar arası bağlantılar

Piyasalar nadiren tek başına hareket eder - enstrümanlar genellikle birbiriyle bağlantılıdır. EURUSD, GBPUSD'yi beraberinde çekme eğilimindedir; petrol ise CAD, AUD ve NOK'daki hissiyatı şekillendirir; S&P 500 ve DAX ise genellikle senkronize hareket eder. Peki ya korelasyonları pasif bir şekilde gözlemlemek yerine, bu varlıkları matematiksel olarak birleştiren tek bir araç yaratırsak? MetaTrader 5'teki özel semboller, standart aracı kurum araçlarının ötesine geçmenize ve kendi analitik sisteminizi oluşturmanıza olanak tanır: spread'ler, sepetler, arbitraj pariteleri ve anti-korelasyon endeksleri.

Sentetiklerin matematiği:

Herhangi bir sentetik enstrümanın temeli doğrusal bir kombinasyondur:

Synth = k₁·Asset_A ± k₂·Asset_B ± ... ± kₙ·Asset_N.

Pratikte, iki varlığın spread'i için basitleştirilmiş bir denklem sıklıkla kullanılır:

Spread = Price_A - Ratio · Price_B,

Burada Ratio, varlıkların volatilitesini veya fiyat ölçeğini eşitleyen normalleştirme katsayısıdır. Normalleştirme olmadan, spread daha pahalı enstrümana doğru eğimli olacak ve bu da osilatör sinyallerini ve teminat gereksinimi hesaplamalarını bozacaktır.

Ana teknik görev, tikleri senkronize etmektir. Fiyatların farklı likidite havuzlarından eşzamansız olarak alınması düzensiz makaslara ve yanlış kırılmalara yol açar. MetaTrader 5'te, bunu bir tamponlama zaman penceresi aracılığıyla ve her sentetik bacak için bilinen son fiyatı sabitleyerek çözüyoruz.

Sentetik tik oluşturucu:

//+------------------------------------------------------------------+
//| synthetic tick generator based on two assets                     |
//+------------------------------------------------------------------+
class CSyntheticTickGenerator
  {
private:
   string            symbol_a;
   string            symbol_b;
   string            synth_name;
   double            ratio;
   double            last_price_a;
   double            last_price_b;
   int               symbol_digits;
   double            point;

public:
   //+------------------------------------------------------------------+
   //| initializer                                                      |
   //+------------------------------------------------------------------+
   bool              Init(const string _sym_a, const string _sym_b, const string _synth, double _ratio)
     {
      symbol_a=_sym_a;
      symbol_b=_sym_b;
      synth_name=_synth;
      ratio=_ratio;
      symbol_digits=(int)SymbolInfoInteger(_sym_a, SYMBOL_DIGITS);
      point=SymbolInfoDouble(_sym_a, SYMBOL_POINT);
      last_price_a=0.0;
      last_price_b=0.0;
      return true;
     }

   //+------------------------------------------------------------------+
   //| handle an incoming tick                                          |
   //+------------------------------------------------------------------+
   void              ProcessTick(const MqlTick &tick, const string source_symbol)
     {
      if(tick.bid<=0 || tick.ask<=0)
         return;

   //--- update the last known price for the corresponding leg
      if(source_symbol==symbol_a)
        {
         last_price_a=tick.bid;
        }
      else
         if(source_symbol==symbol_b)
           {
            last_price_b=tick.bid;
           }

   //--- waiting for prices to appear for both assets
      if(last_price_a<=0 || last_price_b<=0)
         return;

   //--- calculate synthetic price with normalization
      double synth_bid=NormalizeDouble(last_price_a-ratio*last_price_b, symbol_digits);

      double base_spread=tick.ask-tick.bid;
      double synth_ask=NormalizeDouble(synth_bid+base_spread, symbol_digits);

   //--- tick structure formation
      MqlTick synth_tick={0};
      synth_tick.time=tick.time;
      synth_tick.time_msc=tick.time_msc;
      synth_tick.bid=synth_bid;
      synth_tick.ask=synth_ask;
      synth_tick.flags=TICK_FLAG_BID | TICK_FLAG_ASK;

   //--- writing a custom symbol to the database
      MqlTick batch[1];
      batch[0]=synth_tick;
      CustomTicksAdd(synth_name, batch);
     }
  };
//+------------------------------------------------------------------+


Örnek strateji - sentetik spread'ler, istatistiksel arbitraj ve ortalamaya dönüş stratejileri için idealdir. Geleneksel yaklaşım, spread'in hareketli ortalamadan sapmasını standart sapma (z-skoru) birimi cinsinden izlemektir.

Sinyal mantığı:

  • Giriş - |z-skoru| > 2.0 ise, spread sapması istatistiksel olarak anormaldir;
  • Çıkış - |z-skoru| < 0.5 ise, ortalamaya dönüş gerçekleşti, pozisyon kapatılır;
  • Risk yönetimi - Zararı Durdur sepet volatilitesi ile bağlantılıdır (örneğin, 2.5 × ATR(20)).

Z-skorunu hesaplama ve sinyal üretme fonksiyonları aşağıda sunulmuştur:

//+------------------------------------------------------------------+
//| z-score calculation for synthetic spread                         |
//+------------------------------------------------------------------+
double CalculateSpreadZScore(const string synth_symbol, int ma_period, int std_dev_period)
  {
   static int ma_handle=INVALID_HANDLE;
   static int std_handle=INVALID_HANDLE;
   
//--- initialize handles on first call
   if(ma_handle == INVALID_HANDLE)
     {
      ma_handle=iMA(synth_symbol, PERIOD_M1, ma_period, 0, MODE_SMA, PRICE_CLOSE);
      if(ma_handle==INVALID_HANDLE)
        {
         Print("Error creating iMA handle: ", GetLastError());
         return 0.0;
        }
     }
   
   if(std_handle == INVALID_HANDLE)
     {
      std_handle = iStdDev(synth_symbol, PERIOD_M1, std_dev_period, 0, MODE_SMA, PRICE_CLOSE);
      if(std_handle==INVALID_HANDLE)
        {
         Print("Error creating iStdDev handle: ", GetLastError());
         return 0.0;
        }
     }
   double ma_buf[], std_buf[], close_buf[];
   ArraySetAsSeries(ma_buf, true);
   ArraySetAsSeries(std_buf, true);
   ArraySetAsSeries(close_buf, true);
   
//--- copy data from the 1st completed bar (index 1, quantity 1)
   if(CopyBuffer(ma_handle, 0, 1, 1, ma_buf) != 1)
      return 0.0;
   if(CopyBuffer(std_handle, 0, 1, 1, std_buf) != 1)
      return 0.0;
   if(CopyClose(synth_symbol, PERIOD_M1, 1, 1, close_buf) != 1)
      return 0.0;
   
   double spread_ma = ma_buf[0];
   double spread_std = std_buf[0];
   double current_spread = close_buf[0];
   
   if(spread_std == 0.0)
      return 0.0;
   return (current_spread - spread_ma) / spread_std;
  }

//+------------------------------------------------------------------+
//| trading signal generator                                         |
//+------------------------------------------------------------------+
int CheckSpreadSignal(const string synth_symbol)
  {
   double z = CalculateSpreadZScore(synth_symbol, 20, 20);
   if(z>2.0)
      return -1; // spread overbought -> spread short
   if(z<-2.0)
      return 1;  // spread oversold -> spread long
   if(MathAbs(z)<0.5)
      return 0;  // close signal
   return 0;
  }

Hedge'in yönü z-skorunun işaretine bağlıdır: sapma pozitifse, aşırı alınmış a bacağını satar ve aşırı satılmış b bacağını alırız; negatifse, bunun tersi geçerlidir. İlgili çapraz kurlar Piyasa Gözleminde mevcutsa, teminat gereksinimleri sınayıcı tarafından otomatik olarak hesaplanır.


Sepet endeksleri ve dinamik yeniden dengeleme

Eğer bir spread iki bacaklı bir enstrüman ise, o zaman bir sepet N sembolden oluşan bir portföydür. Ağırlık faktörleri şu şekilde olabilir:

  • Statik - eşit ağırlıklı (k = 1/N) veya sembolün piyasa değeri/likiditesine göre sabit;
  • Dinamik - ters volatilite veya hareketli hacme göre yeniden hesaplanır.

Dinamik yeniden dengeleme, k katsayılarının periyodik olarak yeniden hesaplanmasını ve CustomSymbolSetDouble() aracılığıyla sembol özelliklerinin ayarlanmasını gerektirir. Strateji sınayıcının mevcut sürümünde, sözleşme özellikleri optimizasyonun başlangıcında sabitlenir, bu nedenle dinamik ağırlıklandırma değişiklikleri, sözleşme özelliklerini değiştirmeden sinyalleri hesaplamak için tik geçmişini değiştirerek veya özel göstergeler kullanarak taklit edilmelidir.


Analitik ara söz: Sentetiklerdeki zorluklar

Korelasyon nedenselliğe eşit değildir - varlıklar arasındaki tarihsel bağlantı makroekonomik şoklar anında kopabilir. Yıllardır sabit olan EURUSD/GBPUSD sentetik spread'i, Merkez Bankası faiz oranı veya jeopolitik haberlerle 50-80 puanlık bir boşluk oluşturabilir. Ortalamaya dönüş stratejisi, varlıklardaki (semboller) yapısal değişiklikleri dikkate almaz.

Sınayıcıda teminatları ve karları hesaplama - strateji sınayıcı, teminatı dönüştürmek ve finansal bir sonuç elde etmek için otomatik olarak çapraz kurları arar. SYNTH_EURGBP.custom'ı bir USD hesabında test ederseniz, terminal pariteleri aşağıdaki sırayla arar:

  • EURUSD.custom / GBPUSD.custom (özel)
  • EURUSD.b / GBPUSD.b (aracı kurum son eki ile)
  • EURUSD / GBPUSD (baz pariteler)

Piyasa Gözleminde eksiklerse, sınayıcı bir teminat hesaplama hatası geri döndürür veya karı sıfıra ayarlar.

MQL5 Bulut Ağının devre dışı bırakılması - bulut temsilcileri aracılığıyla sentetik semboller üzerinde optimizasyon devre dışı bırakılır. Farklı makineler kullanıcı tanımlı sembolleri aynı adlarla ancak farklı geçmişlerle veya normalleştirme faktörleriyle depolayabileceğinden, bu durum sonuçların senkronize olmamasına ve aşırı trafiğe yol açacaktır. Bu nedenle, test yalnızca yerel olarak veya yerel bir ağda mümkündür.

Haberleri filtreleme - istatistiksel spread arbitrajı asimetrik makro olaylara karşı oldukça hassastır (olay sadece bir ülkeyi etkilediğinde). Yüksek etkili verilerin yayınlanmasından 30-60 dakika önce stratejiyi devre dışı bırakan ekonomik takvim veya volatilite filtresinin eklenmesi önerilir.


İş akışı entegrasyon hattı

MQL5 alım-satım sistemi geliştirmede "entegrasyon hattı", harici bir kaynağı terminal işlem arayüzüne bağlayan bir veri ve olay işleme zinciridir.

Başka bir deyişle, ham verilerin (tiklerin) kullanıma hazır bir alım-satım veya analiz aracına dönüştürülene kadar çeşitli işleme aşamalarından geçtiği bir hattır. Pratikte, hat yapısı (verilerden alım-satıma kadar) dört ardışık aşamadan oluşur:

  1. Aracı kurumdan ham tikleri alırken, SymbolInfoTick() fonksiyonlarını ve/veya CopyTicksRange() tik geçmişini kullanırız. Örneğin, EURUSD ve GBPUSD için tikler farklı sunuculardan gelir.
  2. Birleştirici sınıf (CBarAggregator veya CSyntheticTickGenerator) bu tikleri alır, zaman açısından senkronize eder ve matematiksel bir denklem uygular. Örneğin, Price(EUR) - Price(GBP) spread'ini hesaplar.
  3. Sonuç, CustomRatesUpdate() veya CustomTicksAdd() fonksiyonları aracılığıyla terminal veritabanına yazılır. Örneğin, SPREAD_EURGBP.custom özel sembolünün oluşturulması. Artık terminal bu enstrümanı normal bir döviz paritesi gibi işler.
  4. Oluşturulan sembol için platform standart araçlarını kullanma - Strateji Sınayıcıda Uzman Danışman/gösterge başlatma.


Değiştirilmiş geçmiş kullanılarak stres testi

Standart geriye dönük testler genellikle güvenlik yanılsaması yaratır. Mükemmel bir geçmişte, mükemmel bir yürütme ile bir Uzman Danışman çalıştırırsınız ve varlık eğrisinin güzel bir resmini elde edersiniz. Ancak gerçek piyasa toksik tiklerle, haber olayları sırasında makas genişlemeleriyle ve likidite sorunlarıyla dolu bir ortamdır.

MetaTrader 5 size kendi aracı kurumunuz olmanız için eşsiz bir fırsat sunar. Geçmişi yalnızca analiz etmekle kalmaz, aynı zamanda stratejinin gücünü test etmek için işlem koşullarını kasıtlı olarak kötüleştirerek değiştirebilirsiniz. Bir robot ideal verilerden para kazanıyorsa, ancak makas 5 pip genişlediğinde bakiyesini kaybediyorsa, böyle bir sistem uygulanabilir değildir.

Şimdi stres senaryoları oluşturmak için özel sembollerin nasıl kullanılacağına bakalım: işlem maliyetlerini yapay olarak şişirmekten yüksek volatilite dönemlerinde aracı kurum kısıtlamalarını taklit etmeye kadar.

Stres testinin arkasındaki felsefe oldukça basittir: gerçek piyasaya girmeden önce sistemi çökertmeye çalışırız.

Bir sembol geçmişini alır ve bunu aşırı piyasa koşullarını simüle edecek şekilde değiştiririz.

Değiştirme senaryoları:

  • Makas genişletme, scalping ve gün içi işlem için kritik olan maliyet artışlarını simüle eder.
  • Durma Seviyesini/Donma Seviyesini artırma - Zararı Durdur ve Kar Al ayarlamalarına ilişkin aracı kurum kısıtlamalarına karşı direnci test etme.
  • Teminat gereksinimlerini değiştirme - kaldıracı değiştirirken teminat çağrılarına karşı dayanıklılığı test etme;
  • Toksik tik enjeksiyonu - boşluklar veya likidite kesintileri ekleme.

Özel sembol yaklaşımının ana avantajı tekrarlanabilirliktir. Aynı testi farklı stres parametreleriyle çalıştırabilir ve ölçütleri (Kar Faktörü, Maksimum Düşüş, Düzelme Faktörü) tablo biçiminde karşılaştırabilirsiniz.


Senaryo 1: Makasın yapay olarak genişletilmesi

Makas, kısa vadeli stratejilerin ana düşmanıdır. Gerçek dünya koşullarında, makas sabit değildir. Sıfır ile fiilen sonsuz arasında dalgalanır ve önemli makroekonomik haberlerin yayınlanması sırasında dramatik bir şekilde genişleyebilir. Bunu test etmek için, tik geçmişini alabilir, sembolü klonlayabilir ve her Ask'e sabit sayıda pip veya mevcut makasın bir yüzdesini ekleyerek tikleri yeniden yazabiliriz.

Aşağıda, sabit bir yüksek makas ile bir sembolün strese maruz kalmış bir versiyonunu oluşturan örnek bir komut dosyası bulunmaktadır:

//+------------------------------------------------------------------+
//| inputs                                                           |
//+------------------------------------------------------------------+
input string   SourceSymbol        = "EURUSD";          // Source symbol
input string   TargetSymbol        = "EURUSD_Stress";   // New symbol name
input int      StressSpreadPoints  = 50;                // Fixed spread (in points)
input datetime HistoryFromDate     = D'2023.01.01';     // Start loading history
input bool     DeleteOldData       = true;              // Clear the target symbol's history before writing

CiCustomSymbol stressSymb;

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   double point;
   int    digits;

//--- get the properties of the original symbol for calculations
   point = SymbolInfoDouble(SourceSymbol, SYMBOL_POINT);
   digits = (int)SymbolInfoInteger(SourceSymbol, SYMBOL_DIGITS);

   if(point == 0)
     {
      Print("Error: Failed to get point size for ", SourceSymbol);
      return;
     }

   Print("--- Start generating stress symbol ---");
   PrintFormat("Source: %s | Target: %s | Spread: %d pp", SourceSymbol, TargetSymbol, StressSpreadPoints);

//--- create a custom symbol via a class method
//--- return codes: -1 (error), 0 (already exists), 1 (created)
   int createRes = stressSymb.Create(TargetSymbol, "", SourceSymbol, 1000000, true);

   if(createRes == -1)
     {
      Print("Error creating symbol via CiCustomSymbol.Create()");
      return;
     }

   stressSymb.Select(true);

//--- clone the contract properties (Digits, Point, Mode etc.)
   if(!stressSymb.Clone(SourceSymbol))
     {
      Print("Error cloning symbol properties");
      return;
     }

//--- clear old history
   if(DeleteOldData)
     {
      Print("Clear history using CiCustomSymbol...");
      if(stressSymb.TicksDelete(0, LONG_MAX) < 0)
         Print("Failed to clear ticks: ", GetLastError());
     }

//--- cyclic loading and modification of ticks (in 1-day batches)
   datetime current_date = HistoryFromDate;
   datetime stop_date = TimeCurrent();

   if(current_date >= stop_date)
     {
      Print("Error: Invalid history start date.");
      return;
     }

   int total_ticks_added = 0;
   MqlTick ticks[];

//--- calculate the stress spread value
   double stress_spread_value = StressSpreadPoints * point;

   Print("Starting batch history processing...");

   while(current_date < stop_date && !IsStopped())
     {
      //--- define the boundaries of the day
      datetime day_start = current_date;
      datetime day_end = current_date + PeriodSeconds(PERIOD_D1);

      ulong t1 = (ulong)day_start * 1000;
      ulong t2 = (ulong)day_end * 1000;

      //--- read the original symbol ticks
      int copied = CopyTicksRange(SourceSymbol, ticks, COPY_TICKS_ALL, t1, t2);

      if(copied > 0)
        {
         //--- data stress modification
         for(int i = 0; i < copied; i++)
           {
            //--- set Ask = Bid + Fixed Spread
            ticks[i].ask = ticks[i].bid + stress_spread_value;

            //--- mark that both prices have changed
            ticks[i].flags = TICK_FLAG_BID | TICK_FLAG_ASK;
           }

         //--- write to the database via a class
         int added = stressSymb.TicksReplace(t1, t2, ticks);

         if(added != copied)
           {
            PrintFormat("Error recording ticks for %s via class. Registered: %d of %d",
                        TimeToString(day_start, TIME_DATE), added, copied);
           }
         else
           {
            total_ticks_added += added;
           }
        }

      //--- move on to the next day
      current_date = day_end;
      Sleep(10);
     }

   Print("--- Generation complete ---");
   PrintFormat("Total ticks processed: %d", total_ticks_added);
  }
//+------------------------------------------------------------------+

Komut dosyası, özel sembolleri işlemek için CiCustomSymbol sarmalayıcı sınıfını kullanır. Kodun tamamı makaleye ekli StressTest_SpreadModifier.mq5 dosyasında yer almaktadır.


Senaryo 2: Durma Seviyesi ve Donma Seviyesi

Yüksek volatilite dönemlerinde (örneğin, ABD'deki NFP verilerinden önce), birçok aracı kurum Zararı Durdur ayarlanabilmesi için minimum mesafeyi (Durma Seviyesi) ve emir dondurma seviyesini (Donma Seviyesi) artırır. Stratejiniz 5 piplik bir Zararı Durdur ayarlarsa ve aracı kurumunuz girişte minimum 20 pip gerektiriyorsa, emir sunucu tarafından reddedilecek veya pozisyon bir durma seviyesi olmadan kalacaktır.

MetaTrader 5'te bu nasıl kontrol edilir? Sözleşme özellikleri CustomSymbolSetInteger() fonksiyonu tarafından belirtilir. SYMBOL_TRADE_STOPS_LEVEL'in normal değerin 5-10 katına ayarlandığı özel bir sembol oluşturabilirsiniz.

//--- set an extreme stop level (for example, 500 points)
long huge_stop_level = 500;
if(!CustomSymbolSetInteger(stress_symbol, SYMBOL_TRADE_STOPS_LEVEL, huge_stop_level))
   Print("Error setting stop level");
   
//--- set the freeze level (so that orders cannot be modified close to the market)
long huge_freeze_level = 500;
if(!CustomSymbolSetInteger(stress_symbol, SYMBOL_TRADE_FREEZE_LEVEL, huge_freeze_level))
   Print("Error setting freeze level");

Sınayıcıyı böyle bir sembol üzerinde çalıştırdıktan sonra günlüğe dikkat edin. Strateji çok yakın bir durma seviyesi yerleştirmeye çalışırsa, sınayıcı "OrderSend error 130" (invalid stops) geri döndürür. Bu, stratejinin ne kadar sıkı durma seviyeleri yerleştirme yeteneğine dayandığını değerlendirmenize olanak tanır.


Senaryo 3: Teminat gereksinimleri ve kaldıraç

Teminat gereksinimlerini değiştirmek, mevcut kaldıracınız azaldığında stratejinizi teminat çağrılarına karşı dayanıklılık açısından test etmenize olanak tanır. Bu özellikle, yetersiz serbest teminat olduğunda bakiyelerini kaybetme eğiliminde olan grid ve martingale stratejileri için geçerlidir. SYMBOL_MARGIN_INITIAL'ı (başlangıç teminatı) yapay olarak artırabilirsiniz.

//--- increase the margin requirement 2 times
double normal_margin = SymbolInfoDouble(source_symbol, SYMBOL_MARGIN_INITIAL);
double stress_margin = normal_margin * 2.0;

if(!CustomSymbolSetDouble(stress_symbol, SYMBOL_MARGIN_INITIAL, stress_margin))
   Print("Error setting margin");


Analitik ara söz: Bir risk bir yapaylıktan nasıl ayırt edilir?

Stres testi sonuçlarını yorumlarken aşırıya kaçmamak önemlidir:

  • Yumuşak bozulma iyidir. Koşullar kötüleştikçe (makas artar, durma seviyeleri artar) varlık eğrisi kademeli olarak azalıyorsa, stratejinin bir güvenlik marjı vardır.
  • Keskin bir kırılma kötüdür. Bir strateji 20'lik bir makasta pozitif bir sonuç gösteriyorsa, ancak 21'lik bir makasta tam bir kayıp gösteriyorsa, bu, dar koşullara göre ayarlanmış (aşırı optimize edilmiş) kasedir. Gerçek alım-satımda böyle bir robot uzun süre hayatta kalamaz.

Veri kaynakları

Doğru bir stres testi için tik geçmişinin kalitesi önemlidir. Yerleşik dakika çubukları (M1), makas kapanış fiyatına dahil edildiğinden veya ayrı bir unsur olarak mevcut olmadığından makas testi için uygun değildir. CopyTicks() ve tikler üzerine inşa edilmiş özel sembolleri kullanın.

Strateji test aşamaları:

  • Verilerin hazırlanması - yüksek kaliteli tikler indirin (örneğin, Dukascopy'den) veya terminalde tam bir tik geçmişinin yüklü olduğundan emin olun,
  • Bir test ortamı oluşturun - baz sembolü klonlayan (örneğin EURUSD → EURUSD_Stress_50) ve değişiklikleri (makas, durma seviyeleri, teminat) uygulayan bir komut dosyası kullanın,
  • Toplu çalıştırma - strateji sınayıcı optimize edicisini kullanın.

İpucu:

Uzman Danışman kodunu değiştirmek yerine, üzerinde test edildiği sembolü değiştirin. Bir özel sembol kümesi için optimizasyonu çalıştırın: EURUSD_Normal, EURUSD_Spread_30, EURUSD_Spread_50. Sonuçları (Net Kar, Düşüş) bir tabloda veya veritabanında birleştirin. Tüm senaryolarda karlı kalan bir strateji, gerçek bir hesapta kullanılmak üzere düşünülebilir.


İş akışı entegrasyonu: Sınayıcıdan canlı grafiğe

Özel semboller oluşturduğumuzu, bunlara dayalı zamansız grafikler ürettiğimizi (Renko, Aralık) ve stratejiyi sınayıcıda test ettiğimizi varsayalım. Ancak işin püf noktası burada yatıyor: aracı kurumunuzun işlem sunucusu EURUSD_Renko_10'un varlığı hakkında hiçbir şey bilmiyor.

Doğrudan özel bir sembol için bir emir göndermeye çalışırsanız, terminal 4756 (Unknown Symbol) hatasını geri döndürecektir. Özel semboller yalnızca müşteri terminalinde bulunur. O zaman Renko çubuklarına dayalı kararlar veren bir Uzman Danışmanı gerçek EURUSD işlemlerinde nasıl kullanabiliriz?

Bu soruyu yanıtlamak için, emir yönlendirme ile yapılan alım-satımın yapısını analiz edeceğiz. İşlem sorgularındaki sembolleri şeffaf bir şekilde değiştirecek bir mekanizma oluşturacağız; böylece bir enstrümanı analiz edip başka bir enstrümanla işlem yapabileceğiz.

Yönlendirme problemi ve sanal gerçeklik

Bir Uzman Danışman özel bir sembol grafiğine yerleştirildiğinde, _Symbol sistem değişkeni bu özel enstrümanın adını geri döndürür (örneğin XAGUSD_Range_10), ki bu şaşırtıcı değildir. Aynı zamanda, Uzman Danışman genellikle aşağıdaki eylemler için _Symbol alanını veya (aynı olan) Symbol() sistem fonksiyonunu kullanır:

  • Emirlerin gönderilmesi;
  • Fiyatların talep edilmesi (SymbolInfoTick());
  • Açık pozisyonların kontrol edilmesi (PositionSelect vb.).

Gerçek piyasa ile işlem yapmak için, tüm bu çağrıların arasına girmemiz ve _Symbol'ü gerçek bir enstrümanla (örneğin, XAGUSD) değiştirmemiz gerekir. Her Uzman Danışman için kodu manuel olarak yeniden yazmak bizim yaklaşımımız değildir. Çözüm basit - CustomOrder sarmalayıcı sınıfı. MQL5 API'sinin temel fonksiyonlarını kopyalayan bir sınıf oluşturalım. Bu fonksiyonların içinde bir kontrol gerçekleştirilir: geçerli grafik sembolü (özel) isteniyorsa, bunu gerçek olanla değiştiririz.

Uzman Danışman kaynak kodunu değiştirmekten kaçınmak için, önişlemci aşamasında standart çağrıları bizimkilerle değiştirecek olan #define yönergesini kullanacağız.

Özel sembolleri işlemek için sınıfın uygulaması aşağıda verilmiştir:

//+------------------------------------------------------------------+
//| class for routing orders                                         |
//| purpose: replacing a custom symbol with a real one               |
//+------------------------------------------------------------------+
class CustomOrder
  {
private:
   static string     workSymbol; // real symbol name

public:
   //--- set a replacement symbol
   static void       setReplacementSymbol(const string replacement)
     {
      workSymbol = replacement;
     }

   //--- send a trade request
   static bool       OrderSend(MqlTradeRequest &request, MqlTradeResult &result)
     {
      //--- replace a request symbol
      if(request.symbol == _Symbol && workSymbol != "")
        {
         request.symbol = workSymbol;

         //--- price adjustment if it is taken from a custom symbol
         if(request.type == ORDER_TYPE_BUY)
            request.price = SymbolInfoDouble(workSymbol, SYMBOL_ASK);
         else
            if(request.type == ORDER_TYPE_SELL)
               request.price = SymbolInfoDouble(workSymbol, SYMBOL_BID);
        }

      //--- call the original function
      return ::OrderSend(request, result);
     }

   //--- calculate profit
   static bool       OrderCalcProfit(ENUM_ORDER_TYPE action, string symbol, double volume,
                                     double price_open, double price_close, double &profit)
     {
      if(symbol == _Symbol && workSymbol != "")
         symbol = workSymbol;

      return ::OrderCalcProfit(action, symbol, volume, price_open, price_close, profit);
     }

   //--- get the position string property
   static string     PositionGetString(ENUM_POSITION_PROPERTY_STRING property_id)
     {
      string res = ::PositionGetString(property_id);
      //--- if a position symbol is requested, return the chart name, not the actual one
      if(property_id == POSITION_SYMBOL && res == workSymbol)
         return _Symbol;
      return res;
     }

   //--- get the order string property
   static string     OrderGetString(ENUM_ORDER_PROPERTY_STRING property_id)
     {
      string res = ::OrderGetString(property_id);
      if(property_id == ORDER_SYMBOL && res == workSymbol)
         return _Symbol;
      return res;
     }

   //--- select position by symbol
   static bool       PositionSelect(string symbol)
     {
      if(symbol == _Symbol && workSymbol != "")
         return ::PositionSelect(workSymbol);
      return ::PositionSelect(symbol);
     }
  };

//+------------------------------------------------------------------+
//| static initialization                                            |
//+------------------------------------------------------------------+
string CustomOrder::workSymbol = "";

//+------------------------------------------------------------------+
//| macros for transparent integration                               |
//+------------------------------------------------------------------+
#define OrderSend(request, result) CustomOrder::OrderSend(request, result)
#define OrderCalcProfit(action, symbol, volume, open, close, profit) CustomOrder::OrderCalcProfit(action, symbol, volume, open, close, profit)
#define PositionGetString(prop) CustomOrder::PositionGetString(prop)
#define OrderGetString(prop) CustomOrder::OrderGetString(prop)
#define PositionSelect(symbol) CustomOrder::PositionSelect(symbol)
//+------------------------------------------------------------------+

Bir uygulama özelliği, makroların MetaTrader 5 terminalinin standart işlem fonksiyonlarını aynı parametrelerle değiştirmesidir. Kodun tamamı bu makaleye ekli CustomOrder.mqh dosyasında yer almaktadır.


Uzman Danışmana entegrasyon örneği

MQL5 Sihirbazı tarafından oluşturulan standart bir TrendFollower Uzman Danışmanına sahip olduğumuzu düşünelim. Bir Renko grafiğindeyken gerçek bir varlıkla işlem yapabilmesi için üç adım atmamız gerekiyor:

1. Header'ın dahil edilmesi - #include yönergesi standart kütüphaneler dahil edilmeden önce gelmelidir. Bu, makroların kütüphane kodu derlenmeden önce çağrıların yerini almasını sağlar.

//+------------------------------------------------------------------+
//|                                               TrendFollower.mq5  |
//+------------------------------------------------------------------+
#include <CustomOrder.mqh>
#include <Expert\Expert.mqh>
#include <Expert\Signal\MySignals\SignalMACD.mqh>
//--- other declarations...

2. Bir girdi girme - Uzman Danışmana hangi gerçek sembole emir göndereceğini söylemek için bir parametre ekliyoruz:

input string   InpWorkSymbol = "XAGUSD";   // Actual symbol to execute

3. OnInit()'te başlatma - başlatma fonksiyonunda sınıfımıza gerçek sembolü iletiyoruz. Ayrıca görsel sınayıcı için akıllıca bir yöntem kullanıyoruz: grafiğin donmasını önlemek amacıyla, gerçek sembol için fiyat talebini zorlamamız gerekiyor:

int OnInit()
  {
//--- configure the router
   if(InpWorkSymbol != "")
     {
      CustomOrder::setReplacementSymbol(InpWorkSymbol);
      
      //--- we force loading the history of the real symbol, so that the visualization goes smoothly
      MqlRates rates[1];
      CopyRates(InpWorkSymbol, PERIOD_M1, 0, 1, rates);
     }

//--- standard EA initialization...
// ...
   return(INIT_SUCCEEDED);
  }

Bu değişikliklerden sonra Uzman Danışman, XAGUSD_Renko_10 grafiklerini analiz edecek, ancak tüm emirler XAGUSD üzerinde işlenecek ve sembol işlem günlüğünde doğru şekilde görüntülenecektir.


Analitik ara söz: Sınayıcıda ileriye bakma yanlılığının ortadan kaldırılması

Gerçek veriler üzerinde basitçe test yapabiliyorsak neden tüm bunlara ihtiyacımız var? Renko ile ilgili bölümü hatırlayalım. Strateji sınayıcının, özel bir sembol üzerinde test yaparken çubuk yapılandırmasına (OHLC) dayalı tikler oluşturduğundan bahsetmiştik. Renko çubuğu için bu, ideal bir giriş yanılsaması yaratır: Uzman Danışman çubuğun kapandığını görür ve hemen kapanış fiyatından giriş yapar. Ancak gerçekte tuğlanın oluşması biraz zaman alır. Fiyat, çubuk oluşumu aralığı içinde dalgalanabilir.

Yönlendirmeyi etkinleştirdiğinizde (CustomOrder sınıfını kullanarak), aşağıdakiler gerçekleşir:

  1. Uzman Danışman, grafikte özel bir sembolle bir sinyal görür (örneğin, MA'nın Renko üzerinde çaprazlaması),
  2. Bir alış veya satış talebi gönderir,
  3. CustomOrder sınıfı sembolü EURUSD ile değiştirir,
  4. İşlem, gerçek EURUSD'nin mevcut piyasa fiyatından gerçekleştirilir.

Sonuç olarak, gerçekçi sonuçlar elde edersiniz. Giriş fiyatı, kayma veya veri eşzamansızlığı nedeniyle Renko grafiğindeki fiyattan farklı olabilir. Özel sembol testi ile yönlendirme testi arasındaki fark, işlem hipotezinizin maliyetidir.


Özel semboller üzerinde canlı işlem yapmaya başlamadan önce lütfen aşağıdakilerden emin olun:

  • Geçmiş senkronizasyonu - gerçek sembolün terminalde tam bir geçmişe sahip olduğundan emin olun. Veri yoksa CustomOrder teminatı veya giriş fiyatını yanlış hesaplayabilir;
  • Kod 4756 hatası olmamalı - Uzman Danışman günlüğünü kontrol edin. Unknown Symbol hatasını görürseniz, makroların çalışmadığı anlamına gelir (belki de CustomOrder.mqh dosyasını diğer kütüphanelerden sonra dahil ettiniz);
  • Kayma - bir sarmalayıcı aracılığıyla işlem yaparken, piyasa fiyatını elde edersiniz. Uzman Danışman ayarlarında izin verilen kayma değerinin, hızlı hareketler sırasında – Renko sinyallerinin gerçek sembolün tik güncelleme sıklığından daha sık üretildiği durumlarda – emirlerin gerçekleştirilmesini sağlayacak kadar yüksek ayarlandığından emin olun.

Analiz için özel semboller kullanıyorsanız MQL5 Bulut Ağı üzerinde bulut optimizasyonu kullanılamaz. Yerel bir ağ ve/veya yerel bir bilgisayar kullanın.


Sonuç

MetaTrader 5 sessiz ama temel bir evrim geçirdi: terminal pasif bir fiyat okuyucusu olmaktan çıktı ve bir mühendislik laboratuvarı haline geldi. Artık yatırımcılar, aracı kurumun sağladıklarına uyum sağlamak zorunda değiller, bunun yerine belirli bir fikir için en uygun olan kendi analitik ortamlarını oluşturabilirler.

Renko, Aralık ve Eşit Hacim grafikleri aracılığıyla takvim çizelgesinden ayrılmayı, spread'ler ve sepetler aracılığıyla piyasalar arası ilişkileri sentezlemeyi, stres testi için kasıtlı olarak geçmişi değiştirmeyi ve son olarak şeffaf emir yönlendirme aracılığıyla sanal işlemleri gerçek işlemlere bağlamayı öğrendik. Bu aşamaların her biri sistem geliştiricisi için belirli bir sorunu ele alır: zaman dilimi gürültüsü, gerekli araçların eksikliği, mükemmel bir geriye dönük test yanılsaması ve grafik ile sunucu arasındaki boşluklar.

Özel sembollerin ana değeri veriler üzerindeki kontroldür. Yalnızca Uzman Danışmanın parametrelerini değil, aynı zamanda mantığın kendisinin işlem maliyetlerine, tik eşzamansızlığına ve aracı kurum kısıtlamalarına karşı dayanıklılığını da test edersiniz. Bir strateji, 50 piplik sabit bir makas, yapay boşluklar ve dinamik bir durma seviyesi ile pozitif bir beklenen değeri sürdürüyorsa, gerçek piyasa için hazırdır. Aksi takdirde, simülasyon aşamasında bir zayıflık keşfederek bakiyenizi kurtarmış olursunuz.

Küçük adımlarla başlayın: herhangi bir scalping veya trend Uzman Danışmanını alın, bunun için Eşit Hacimli çubuklara sahip bir grafik oluşturun, makası artırarak ile bir stres senaryosu çalıştırın ve CustomOrder aracılığıyla yönlendirmeyi bağlayın. Ölçütleri karşılaştırın.

Zamana dayalı gürültü filtrelendiğinde ve işlem riskleri önceden hesaplandığında varlık eğrisinin nasıl değiştiğine şaşırabilirsiniz. MQL5 dokümantasyonu, Kod Tabanı ve topluluk forumu, kendi piyasa gerçekliğinizi oluştururken ana en büyük destekçilerinizdir.


MetaTrader 5'te özel sembollerle nasıl çalışılacağını öğrenmek için önerilen kaynaklar:


Makaleye ekli dosyaların listesi:

Dosya adı Açıklama
CiCustomSymbol.mqh CiCustomSymbol sınıf kodunu içeren dosya
CreateCustomSymbol.mq5 Özel bir sembol oluşturmaya yönelik örnek bir komut dosyası.
CBarAggregator.mqh CBarAggregator sınıf kodu
CustomChartGenerator.mq5 Üç türde geçmiş ve canlı çubuklar oluşturan bir göstergenin kodunu içeren dosya: Renko, Aralık ve Eşit Hacim
CSyntheticTickGenerator.mqh  CSyntheticTickGenerator sınıf kodunu içeren dosya
StressTest_SpreadModifier.mq5 Geniş bir makasla stres testi için özel bir sembol oluşturmaya yönelik örnek bir komut dosyasının kodunu içeren dosya
CustomOrder.mqh CustomOrder sınıf kodunu içeren dosya



    MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
    Orijinal makale: https://www.mql5.com/ru/articles/22391

    Son yorumlar | Tartışmaya git (4)
    Roman Shiredchenko
    Roman Shiredchenko | 19 May 2026 saat 16:13

    Çok ilginç bir makale – tekrar okuyorum...

    Aleksandr Slavskii
    Aleksandr Slavskii | 21 May 2026 saat 03:44

    Bu makaledeki göstergeyi kullanarak Renko grafiği oluşturmayı başaran var mı?

    Nedense bu gösterge bende bir türlü çalışmıyor.


    Dmitriy Skub
    Dmitriy Skub | 21 May 2026 saat 05:16
    Aleksandr Slavskii #:

    Bu makaledeki göstergeyi kullanarak renko grafiği oluşturmayı başaran var mı?

    Nedense bu gösterge bende bir türlü çalışmıyor.

    Çok büyük bir "tuğla" koymuşlar.

    Aleksandr Slavskii
    Aleksandr Slavskii | 21 May 2026 saat 06:28
    Dmitriy Skub #:

    Gerçekten çok büyük bir "tuğla" koymuşlar.

    Göstergedeki varsayılan ayar neyse, onu ayarladım.

    Teşekkürler, gerçekten de tuğlayı biraz daha küçük yaparsak grafik görünüyor.

    Yeni Raylara Adım Atın: MQL5'te Özel Göstergeler Yeni Raylara Adım Atın: MQL5'te Özel Göstergeler
    Yeni terminalin ve dilin tüm yeni olanaklarını ve özelliklerini listelemeyeceğim. Bunlar sayısızdır ve bazı yenilikler ayrı bir makalede tartışılmaya değerdir. Ayrıca burada nesne yönelimli programlama ile yazılmış bir kod yoktur, geliştiriciler için ek avantajlar olarak bir bağlamda basitçe bahsedilemeyecek kadar ciddi bir konudur. Bu makalede, MQL4'e kıyasla göstergeleri, yapılarını, çizimlerini, türlerini ve programlama ayrıntılarını ele alacağız. Umarım bu makale hem yeni başlayanlar hem de deneyimli geliştiriciler için faydalı olacaktır, belki bazıları yeni bir şeyler bulacaktır.
    MQL5'te Olay Odaklı Mimari: Bir Uzman Danışmanı Tam Teşekküllü Bir Alım-Satım Sistemine Dönüştürme MQL5'te Olay Odaklı Mimari: Bir Uzman Danışmanı Tam Teşekküllü Bir Alım-Satım Sistemine Dönüştürme
    Makale, MQL5'teki olay odaklı mimariye adanmıştır ve tek parça OnTick modelinden dağıtılmış işlemeye geçişi açıklamaktadır. Önceden tanımlanmış ve özel olayları, hizmetleri, programlar arası mesajlaşmayı ve yaygın mimari hataları ele alacağız. Uygulamalı bir örnek, yükü azaltmak, okunabilirliği artırmak ve bakımı basitleştirmek için göstergeler ve Uzman Danışman arasındaki etkileşimlerin nasıl düzenleneceğini göstermektedir.
    İşte Karışınızda Yeni MetaTrader 5 ve MQL5 İşte Karışınızda Yeni MetaTrader 5 ve MQL5
    Bu MetaTrader 5 ile ilgili sadece kısa bir inceleme. Sistemin tüm yeni özelliklerini bu kadar kısa sürede açıklayamam, test süreci 09.09.2009’da başladı. Bu sembolik bir tarihtir ve şanslı sayı olacağına eminim. MetaTrader 5 terminalinin ve MQL5’in beta sürümünü beş gün önce aldım. Tüm özelliklerini deneme şansım olmadı ama şimdiden etkilendim.
    MQL5'te CPU'dan GPU'ya: Araştırma, Optimizasyon ve Formasyon Analizinin Hızlandırılması için Pratik Bir OpenCL Çerçevesi MQL5'te CPU'dan GPU'ya: Araştırma, Optimizasyon ve Formasyon Analizinin Hızlandırılması için Pratik Bir OpenCL Çerçevesi
    OpenCL kullanarak MQL5'te pratik bir CPU'dan GPU'ya geçiş yolunun nasıl oluşturulacağını öğrenin. Bağlam başlatma, arabellek organizasyonu, büyük yığınlar, kernel başlatma ve veri alışverişlerini en aza indirme konularına odaklanacağız. Tipik hatalar ve bunları ortadan kaldırmanın yolları da ele alınacaktır. Mum formasyonlarıyla ilgili bir örnek, yaklaşımın pratik faydasını göstermektedir.