
Örneklerle formasyonlar (Bölüm I): Çoklu tepe
İçindekiler
- Özet
- Terse dönüş formasyonları hakkında
- Neden çoklu tepe - spesifik özellikleri
- Çift tepe konsepti genişletilebilir mi?
- Çoklu tepe çizmek için kod yazma
- Daha fazla geliştirme fikirleri
- Sonuç
Özet
Formasyonlar birçok yatırımcı tarafından kullanıldığı için internette sıklıkla tartışılmaktadır. Formasyonlar, takip eden fiyatlamanın yönünü belirlemek için görsel analiz kriterleri olarak adlandırılabilir. Algo alım-satım bundan farklıdır. Algoritmik alım-satım için görsel kriterler olamaz. Uzman Danışmanlar ve göstergeler, fiyat serileriyle çalışmak için ayrı yöntemlere sahiptir. Her iki uçta da avantajlar ve dezavantajlar vardır. Kod, insan düşüncesinin genişliğinden ve insan analizinin kalitesinden yoksundur, ancak kodun başka değerli avantajları vardır: kıyaslanamaz hız ve birim zamanda işlenen sayısal veya mantıksal verilerin kıyaslanamaz miktarı. Makineye ne yapması gerektiğini söylemek kolay değildir. Bu biraz pratik gerektirir. Zamanla programcı makineyi, makine de programcıyı anlamaya başlar. Bu makale serisi, düşüncelerini nasıl yapılandıracaklarını ve karmaşık görevleri daha basit adımlara nasıl böleceklerini öğrenecek olan yeni başlayanlar için yararlı olacaktır.
Terse dönüş formasyonları hakkında
Şahsen benim için terse dönüş formasyonlarının çok muğlak bir tanımı var. Dahası, bunların altında yatan herhangi bir matematik yoktur. Dürüst olmak gerekirse, herhangi bir formasyonun altında yatan bir matematik yoktur ve dolayısıyla burada dikkate alınabilecek tek matematik istatistiktir. İstatistikler doğrunun tek ölçütüdür, ancak istatistikler gerçek alım-satıma dayalı olarak derlenir. Açıkçası, çok kesin istatistikler sağlayabilecek hiçbir kaynak bulunmamaktadır. Belirli bir araştırma problemi için bu tür verileri sağlamanın bir anlamı bile yoktur. Buradaki tek çözüm, strateji sınayıcıda geriye dönük test ve görselleştirmedir. Bu yaklaşım daha düşük veri kalitesi sunsa da, veri miktarı ile birlikte hız gibi yadsınamaz bir avantaja sahiptir.
Elbette, terse dönüş formasyonları trend terse dönüşlerini belirlemek için yeterli bir araç değildir, ancak seviyeler veya mum çubuğu analizi gibi diğer analiz yöntemleriyle birlikte kullanıldıklarında istenen sonucu verebilirler. Bu makale serisinde, formasyonlar özellikle ilginç bir analiz yöntemi olarak görülmemektedir, ancak algoritmik alım-satım becerilerini pratik etmek için kullanılabilirler. Pratik yapmanın yanı sıra, ilginç ve kullanışlı bir yardımcı araç elde edeceksiniz - algo alım-satım için değilse de, yatırımcı gözü için. Faydalı göstergelere büyük değer verilmektedir.
Neden çoklu tepe - spesifik özellikleri
Bu formasyon, basitliği nedeniyle internette oldukça popüler hale gelmiştir. Formasyon, farklı alım-satım enstrümanlarında ve çeşitli grafik zaman dilimlerinde oldukça yaygındır, çünkü bu formasyonda karmaşık bir şey yoktur. Ayrıca, formasyona daha yakından bakarsanız, yöntem konseptinin algo alım-satım ve MQL5 dil yetenekleri kullanılarak genişletilebileceğini görebilirsiniz. Sadece çift tepe ile sınırlı kalmayacak bazı genel kodlar oluşturmayı deneyebiliriz. Akıllıca oluşturulmuş bir prototip, tüm formasyon melezlerini ve devam ürünlerini keşfetmek için kullanılabilir.
Çoklu tepenin klasik devamı, çok popüler olan "baş ve omuzlar" formasyonudur. Ne yazık ki, bu formasyonla nasıl alım-satım yapılacağına dair yapılandırılmış bir bilgi yoktur. Bu sorun pek çok popüler strateji için ortaktır - çünkü pek çok güzel kelime vardır ancak istatistik yoktur. Bu makalede bunları algoritmik alım-satım çerçevesinde kullanmanın mümkün olup olmadığını anlamaya çalışacağız. Bir demo veya gerçek hesapta alım-satım yapmadan istatistik toplamanın tek yöntemi, strateji sınayıcının yeteneklerini kullanmaktır. Bu araç olmadan, belirli bir stratejiye ilişkin herhangi bir karmaşık sonuç çıkaramazsınız.
Çift tepe konsepti genişletilebilir mi?
Makalenin konusuyla ilgili olarak, çift tepeden başlayan bir formasyon ağacı olarak bir diyagram çizmeye çalışacağım. Bu, bu konseptin olanaklarının ne kadar geniş olduğunun anlaşılmasına yardımcı olacaktır:
Birkaç formasyon konseptini, yaklaşık olarak aynı fikre dayandıkları varsayımıyla birleştirmeye karar verdim. Bu fikrin basit bir başlangıcı vardır - herhangi bir yönde iyi bir hareket bul ve terse dönmesi gereken yeri doğru bir şekilde belirle. Formasyonu gördükten sonra, yatırımcı, formasyonun belirli kriterleri karşılayıp karşılamadığını değerlendirmenin yanı sıra, piyasaya giriş noktasını ve hedef ve Zararı Durdur seviyesini belirlemede yardımcı olacak bazı yardımcı çizgileri doğru bir şekilde çizmelidir. Burada hedef yerine Kârı al kullanılabilir.
Formasyonlar, bu formasyonların konseptinin birleştirilebileceği bazı ortak yapı ilkelerine sahip olabilir. Bu net tanım, algoritmik yatırımcıları manuel yatırımcılardan ayıran şeydir. Belirsizlik ve aynı ilkelerin birden fazla yorumlanması hayal kırıklığı yaratan sonuçlara yol açabilir.
Temel formasyonlar aşağıdaki gibidir:
- Çift tepe
- Üçlü tepe
- Baş ve omuzlar
Bu formasyonlar benzer yapılara ve kullanım ilkelerine sahiptir. Bunların hepsi terse dönüşleri tespit etmeyi amaçlamaktadır. Her üç formasyon da yardımcı çizgiler konusunda benzer bir mantığa sahiptir. Bunu çift tepe örneği ile açıklayacağım:
Yukarıdaki şekilde, gerekli tüm çizgiler numaralandırılmıştır ve aşağıdaki anlama gelmektedir:
- Trend direnci
- Kötümser bir tepe seviyesi tanımlamak için yardımcı çizgi (kimisi bunun bir boyun olduğunu düşünür)
- Boyun çizgisi
- İyimser hedef (aynı zamanda alım-satım için bir Kârı Al seviyesidir)
- İzin verilen maksimum Zararı Durdur seviyesi (en üste ayarlanır)
- İyimser tahmin çizgisi (önceki trend hareketine eşit)
Kötümser hedef, boyun çizgisinin piyasaya en yakın kenarla kesiştiği noktaya göre belirlenir - "t" olarak gösterilen "1" ve "2" arasındaki mesafeyi alırız ve terse dönüş yönünde aynı mesafeyi ölçeriz. İyimser hedefin minimum değeri de benzer şekilde belirlenir, ancak mesafe "5" ile "3" arasında ölçülür ve "s" olarak gösterilir.
Çoklu tepe çizmek için kod yazma
Bu formasyonları tanımlamak için akıl yürütme mantığını tanımlayarak başlayalım. Bir formasyon bulmak için çubuk mantığına bağlı kalmalıyız, yani tiklere göre değil, çubuklara göre çalışacağız. Bu durumda, gereksiz hesaplamalardan kaçınılacağı için terminal üzerindeki yükü büyük ölçüde azaltacaktır. Öncelikle, formasyonu arayacak bağımsız bir gözlemciyi simgeleyen bir sınıf belirleyelim. Doğru bir formasyon tespiti için gereken tüm çalışma örneğin bir parçası olacaktır, bu nedenle arama bunun içinde gerçekleştirilecektir. Bu çözümü, işlevselliği genişletmemiz veya mevcut özellikleri değiştirmemiz gerektiğinde daha fazla kod değişikliğini mümkün kılmak için seçtim.
Sınıf haritası
Sınıf içeriğini inceleyerek başlayalım:
class ExtremumsPatternFamilySearcher// class simulating an independent pattern search { private: int BarsM;// how many bars on chart to use int MinimumSeriesBarsM;// the minimum number of bars in a row to detect a top int TopsM;// number of tops in the pattern int PointsPessimistM;// minimum distance in points to the nearest target double RelativeUnstabilityM;// maximum excess of the head size relative to the minimum shoulder double RelativeUnstabilityMinM;// minimum excess of the head size relative to the minimum shoulder double RelativeUnstabilityTimeM;// maximum excess of head and shoulders sizes bool bAbsolutelyHeadM;// whether a pronounced head is required bool bRandomExtremumsM;// random selection of extrema struct Top// top data { datetime Datetime0;// time of the candlestick closest to the market datetime Datetime1;// time of the next candlestick int Index0;// index of the candlestick closest to the market int Index1;// index of the next candlestick datetime DatetimeExtremum;// time of the top int IndexExtremum;// index of the top double Price;// price of the top bool bActive;// if the top is active (if not, then it does not exist) }; struct Line// line { double Price0;// price of the candlestick closest to the market, to which the line is bound datetime Time0;// time of the candlestick closest to the market, to which the line is bound double Price1;// price of the farthest candlestick to which the line is bound datetime Time1;// time of the farthest candlestick to which the line is bound datetime TimeX;// time of the X point int Index1;// index of the left edge bool DirectionOfFormation;// direction double C;// free coefficient in the equation double K;// aspect ratio void CalculateKC()// find unknowns in the equation { if ( Time0 != Time1 ) K=double(Price0-Price1)/double(Time0-Time1); else K=0.0; C=double(Price1)-K*double(Time1); } double Price(datetime T)// function of line depending on time { return K*T+C; } }; public: ExtremumsPatternFamilySearcher(int BarsI,int MinimumSeriesBarsI,int TopsI,int PointsPessimistI, double RelativeUnstabilityI, double RelativeUnstabilityMinI,double RelativeUnstabilityTimeI,bool bAbsolutelyHeadI,bool bRandomExtremumsI)// parametric constructor { BarsM=BarsI; MinimumSeriesBarsM=MinimumSeriesBarsI; TopsM=TopsI; PointsPessimistM=PointsPessimistI; RelativeUnstabilityM=RelativeUnstabilityI; RelativeUnstabilityMinM=RelativeUnstabilityMinI; RelativeUnstabilityTimeM=RelativeUnstabilityTimeI; bAbsolutelyHeadM=bAbsolutelyHeadI; bRandomExtremumsM=bRandomExtremumsI; bPatternFinded=bFindPattern(); } int FormationDirection;// direction of the formation (multiple top or bottom, or none at all) ( -1,1,0 ) bool bPatternFinded;// if the pattern was found during formation Top TopsUp[];// required upper extrema Top TopsDown[];// required lower extrema Top TopsUpAll[];// all upper extrema Top TopsDownAll[];// all lower extrema int RandomIndexUp[];// array for the random selection of the tops index int RandomIndexDown[];// array for the random selection of the bottoms index Top StartTop;// where the formation starts (top farthest from the market) Top EndTop;// where the formation ends (top closest to the market) Line Neck;// neck Top FarestTop;// top farthest from the neck (will be used to determine the head or the formation size) or the same as the head Line OptimistLine;// line of optimistic forecast Line PessimistLine;// line of pessimistic forecast Line BorderLine;// line at the edge of the pattern Line ParallelLine;// line parallel to the trend resistance private: void SetTopsSize();// setting sizes for arrays with tops bool SearchFirstUps();// search for tops bool SearchFirstDowns();// search for bottoms void CalculateMaximum(Top &T,int Index0,int Index1);// calculate the maximum price between two bars void CalculateMinimum(Top &T,int Index0,int Index1);// calculate the minimum price between two bars bool PrepareExtremums();// prepare extrema bool IsExtremumsAbsolutely();// control the priority of tops void DirectionOfFormation();// determine the direction of the formation void FindNeckUp(Top &TStart,Top &TEnd);// find neck for the bullish pattern void FindNeckDown(Top &TStart,Top &TEnd);// find neck for the bearish pattern void SearchFarestTop();// find top farthest from the neck bool bBalancedExtremums();// initial balancing of extrema (so that they do not differ much) bool bBalancedExtremumsHead();// if a pattern has more than 2 tops, we can check for a pronounced head bool bBalancedExtremumsTime();// require that the extrema be not very far in time relative to the minimum distance bool bBalancedHead();// balance the head (in other words, require that it be neither the first nor the last one on the list of tops, if there are more than three of them) bool CorrectNeckUpLeft();// adjust the neck so as to find the intersection of price and neck (this creates prerequisites for the previous trend) bool CorrectNeckDownLeft();// similarly for the bottom int CorrectNeckUpRight();// adjust the neck so as to find the intersection of price and neck on the right or at the current price position, which is the same (to determine the entry point) int CorrectNeckDownRight();// similarly for the bottom void SearchLineOptimist();// calculate the optimistic forecast line bool bWasTrend();// determine whether a trend preceded the pattern definition (in this case the optimistic target line is considered as the trend beginning) void SearchLineBorder();// determine trend resistance or support (usually a sloping line) void CalculateParallel();// determine a line parallel to support or resistance (crosses the neck at the pattern low or high) bool bCalculatePessimistic();// calculate the line of the pessimistic target bool bFindPattern();// perform all the above actions int iFindEnter();// find intersection with the neck public: void CleanAll();// clean up objects void DrawPoints();// draw points void DrawNeck();// draw the neck void DrawLineBorder();// line at the border void DrawParallel();// line parallel to the border void DrawOptimist();// line of optimistic forecast void DrawPessimist();// line of pessimistic forecast };
Bir sınıf, bir kişinin bir makinenin yerinde olması durumunda gerçekleştireceği sıralı işlemleri temsil eder. Her halükarda, herhangi bir formasyonun tespiti birbirini takip eden bir dizi basit işleme ayrılabilir. Matematikte bir kural vardır: bir denklemi nasıl çözeceğinizi bilmiyorsanız, basitleştirin. Bu kural sadece matematik için değil, herhangi bir algoritma için de geçerlidir. İlk olarak tespit mantığı net değildir. Ancak tespite nereden başlayacağınızı bilirseniz, görev çok daha basit hale gelir. Bu durumda, tüm formasyonu bulmak için ya tepeleri ya da dipleri ya da aslında her ikisini de ararız.
Tepeleri ve dipleri belirleme
Tepeler ve dipler olmadan tüm formasyon anlamsızdır, çünkü tepelerin ve diplerin varlığı formasyon için gerekli bir koşuldur, ancak bu koşul tek başına yeterli değildir. Tepeleri belirlemenin farklı yolları vardır. En önemli koşul, belirgin bir yarım dalganın varlığıdır; yarım dalga ise iki belirgin zıt hareketle belirlenir; bizim durumumuzda bu hareketlerin tek yönde arka arkaya birkaç çubuk olması gerekir. Bu amaçla, bir yönde hareketin varlığını gösteren minimum çubuk sayısını belirlememiz gerekiyor. Bunun için bir girdi değişkeni sağlayalım.
bool ExtremumsPatternFamilySearcher::SearchFirstUps()// find tops { int NumUp=0;// the number of found tops int NumDown=0;// the number of found bottoms bool bDown=false;// an auxiliary boolean which shows if a segment of bearish candlesticks has been found bool bUp=false;// an auxiliary boolean which shows if a segment of bullish candlesticks has been found bool bNextUp=true;// can we move on to searching for the next top bool bNextDown=true;// can we move on to searching for the next bottom for(int i=0;i<ArraySize(TopsUp);i++)// before search, set all necessary tops to an inactive state { TopsUp[i].bActive=false; } for(int i=0;i<ArraySize(TopsUpAll);i++)// before search, set all tops to an inactive state { if (!TopsUpAll[i].bActive) break; TopsUpAll[i].bActive=false; } for(int i=0;i<BarsM;i++) { if ( i+MinimumSeriesBarsM-1 < BarsM )// if remaining bars are enough to determine the extremum and we can start searching for the next top { if ( bNextUp )// if it is allowed to search for the next top { bDown=true; for(int j=i;j<i+MinimumSeriesBarsM;j++)// determine the first extrema for upper tops { if ( Open[j]-Close[j] < 0 )// if at least one of the selected candlesticks was upward { bDown=false; break; } } if ( bDown ) { TopsUpAll[NumUp].Datetime0=Time[i+MinimumSeriesBarsM-1]; TopsUpAll[NumUp].Index0=i+MinimumSeriesBarsM-1; bNextUp=false; } } } if ( MinimumSeriesBarsM+i < BarsM && bDown )// if the remaining bars are enough to determine the second half of the extremum and the previous half has been found { bUp=true; for(int j=i;j<MinimumSeriesBarsM+i;j++)//determine further candlesticks in the opposite direction { if ( Open[j]-Close[j] > 0 )//if at least one of the selected candlesticks was downward { bUp=false; break; } } if ( bUp ) { TopsUpAll[NumUp].Datetime1=Time[i]; TopsUpAll[NumUp].Index1=i; TopsUpAll[NumUp].bActive=true; bNextUp=false; } } // after that, register the found formation as a top, if it is a top if ( bDown && bUp ) { CalculateMaximum(TopsUpAll[NumUp],TopsUpAll[NumUp].Index0,TopsUpAll[NumUp].Index1);// calculate extremum between two bars bNextUp=true; bDown=false; bUp=false; NumUp++; } } if ( NumUp >= TopsM ) return true;// if the required number of tops have been found else return false; }
Dipler ise tam tersi şekilde tanımlanır:
bool ExtremumsPatternFamilySearcher::SearchFirstDowns()// find bottoms { int NumUp=0; int NumDown=0; bool bDown=false;// an auxiliary boolean which shows if a segment of bearish candlesticks has been found bool bUp=false;// an auxiliary boolean which shows if a segment of bullish candlesticks has been found bool bNextUp=true;// can we move on to searching for the next top bool bNextDown=true;// can we move on to searching for the next bottom for(int i=0;i<ArraySize(TopsDown);i++)// before search, set all necessary bottoms to an inactive state { TopsDown[i].bActive=false; } for(int i=0;i<ArraySize(TopsDownAll);i++)// before search, set all bottoms to an inactive state { if (!TopsDownAll[i].bActive) break; TopsDownAll[i].bActive=false; } for(int i=0;i<BarsM;i++) { if ( i+MinimumSeriesBarsM-1 < BarsM )// if remaining bars are enough to determine the extremum and we can start searching for the next top { if ( bNextDown )// if it is allowed to search for the next bottom { bUp=true; for(int j=i;j<i+MinimumSeriesBarsM;j++)// determine the first extrema for upper tops { if ( Open[j]-Close[j] > 0 )//if at least one of the selected candlesticks was downward { bUp=false; break; } } if ( bUp ) { TopsDownAll[NumDown].Datetime0=Time[i+MinimumSeriesBarsM-1]; TopsDownAll[NumDown].Index0=i+MinimumSeriesBarsM-1; bNextDown=false; } } } if ( MinimumSeriesBarsM+i < BarsM && bUp )// if the remaining bars are enough to determine the second half of the extremum and the previous half has been found { bDown=true; for(int j=i;j<MinimumSeriesBarsM+i;j++)//determine further candlesticks in the opposite direction { if ( Open[j]-Close[j] < 0 )// if at least one of the selected candlesticks was upward { bDown=false; break; } } if ( bDown ) { TopsDownAll[NumDown].Datetime1=Time[i]; TopsDownAll[NumDown].Index1=i; TopsDownAll[NumDown].bActive=true; bNextDown=false; } } // after that, register the found formation as a bottom, if it is a bottom if ( bDown && bUp ) { CalculateMinimum(TopsDownAll[NumDown],TopsDownAll[NumDown].Index0,TopsDownAll[NumDown].Index1);// calculate extremum between two bars bNextDown=true; bDown=false; bUp=false; NumDown++; } } if ( NumDown == TopsM ) return true;//if the required number of bottoms have been found else return false; }
Bu durumda fraktal mantığını kullanmadım. Bunun yerine, tepeleri ve dipleri belirlemek için kendi mantığımı oluşturdum. Fraktallardan daha iyi veya daha kötü olduğunu düşünmüyorum, ancak en azından herhangi bir harici işlevsellik kullanmaya gerek yok. Ayrıca, bazen gerekli olmayan gereksiz yerleşik dil fonksiyonlarını kullanmaya gerek yoktur. Bu fonksiyonlar iyi olabilir, ancak bu durumda gereksizdirler. Fonksiyon, gelecekte birlikte çalışacağımız tüm tepe ve dipleri belirler. Aşağıdaki resim, bu fonksiyonda neler olup bittiğinin görsel bir temsilini sunmaktadır:
İlk olarak, 1. hareketi arar, daha sonra 2. hareketi arar ve son olarak 3. hareket tepe veya dibin belirlenmesi anlamına gelir. 3 için mantık, aşağıdaki gibi görünen iki ayrı fonksiyonda uygulanmaktadır:
void ExtremumsPatternFamilySearcher::CalculateMaximum(Top &T,int Index0,int Index1)// if 2 intermediate points are found, find High between them { double MaxValue=High[Index0]; datetime MaxTime=Time[Index0]; int MaxIndex=Index0; for(int i=Index0;i<=Index1;i++) { if ( High[i] > MaxValue ) { MaxValue=High[i]; MaxTime=Time[i]; MaxIndex=i; } } T.DatetimeExtremum=MaxTime; T.IndexExtremum=MaxIndex; T.Price=MaxValue; } void ExtremumsPatternFamilySearcher::CalculateMinimum(Top &T,int Index0,int Index1)//if 2 intermediate points are found, find Low between them { double MinValue=Low[Index0]; datetime MinTime=Time[Index0]; int MinIndex=Index0; for(int i=Index0;i<=Index1;i++) { if ( Low[i] < MinValue ) { MinValue=Low[i]; MinTime=Time[i]; MinIndex=i; } } T.DatetimeExtremum=MinTime; T.IndexExtremum=MinIndex; T.Price=MinValue; }
Ardından, tüm bunları önceden hazırlanmış bir depoya (container) koyarız. Mantık şu şekildedir: sınıf içinde kullanılan tüm yapılar kademeli olarak veri eklenmesini gerektirir. Tüm adımlar ve aşamalar geçildikten sonra gerekli veri çıktısı alınır. Bu veriler kullanılarak formasyon grafik üzerinde gösterilebilir. Elbette tepe ve dip belirleyici mantık farklı olabilir. Amacım sadece karmaşık şeyler için basit bir algılama mantığı göstermektir.
Çalışmak için tepeleri seçme
Bulduğumuz tepe ve dip noktalar sadece ara seviyelerdir. Bunları bulduktan sonra, omuz görevi görmesi için en uygun olduğunu düşündüğümüz tepeleri seçmemiz gerekir. Bunu kesin olarak belirleyemiyoruz çünkü kod makine görüşüne sahip değil (genel olarak, bu tür karmaşık tekniklerin kullanımının performansa fayda sağlaması pek olası değildir). Şimdilik, piyasaya en yakın tepeleri seçelim:
bool ExtremumsPatternFamilySearcher::PrepareExtremums()// assign the tops with which we will work { int Quantity;// an auxiliary counter for random tops int PrevIndex;// an auxiliary index for maintaining the order of indexes (increment only) for(int i=0;i<TopsM;i++)// simply select the tops that are closest to the market { TopsUp[i]=TopsUpAll[i]; TopsDown[i]=TopsDownAll[i]; } return true; }
Sembol grafiğinde görsel olarak mantık, mor çerçevedeki varyanta eşdeğer olacaktır. Seçim için biraz daha varyant çizeceğim:
Bu durumda, seçim mantığı çok basittir. Seçilen varyantlar 0 ve 1'dir çünkü bunlar piyasaya en yakın olanlardır. Burada her şey çift tepe için geçerlidir. Ancak aynı mantık üçlü veya daha büyük çoklu tepeler için de kullanılacaktır, tek fark seçilen tepelerin sayısında olacaktır.
Bu fonksiyon gelecekte, yukarıdaki resimde mavi renkle gösterildiği gibi tepelerin rastgele seçilebilmesini sağlayacak şekilde genişletilecektir. Bu, formasyon bulucuların birden fazla örneğini simüle edecektir. Bu sayede otomatik modda tüm formasyonlar daha verimli ve daha sık bir şekilde bulunabilecektir.
Formasyon yönünü belirleme
Tepeleri ve dipleri belirledikten sonra, piyasada belirli bir noktada böyle bir formasyon varsa, formasyonun yönünü belirlemeliyiz. Bu aşamada, uç türü piyasaya en yakın olan yöne daha fazla öncelik vermeyi düşünüyorum. Bu mantığa dayanarak, şekildeki 0 varyantını kullanalım, çünkü piyasaya en yakın olan tepe değil diptir (piyasadaki durumun şekildekiyle tamamen aynı olması koşuluyla). Bu kısım kodda basittir:
void ExtremumsPatternFamilySearcher::DirectionOfFormation()// determine whether it is a double top (1) or double bottom (-1) (only if all tops and bottoms are found - if not found, then 0) { if ( TopsDown[0].DatetimeExtremum > TopsUp[0].DatetimeExtremum && TopsDown[ArraySize(TopsDown)-1].bActive ) { StartTop=TopsDown[ArraySize(TopsDown)-1]; EndTop=TopsDown[0]; FormationDirection=-1; } else if ( TopsDown[0].DatetimeExtremum < TopsUp[0].DatetimeExtremum && TopsUp[ArraySize(TopsUp)-1].bActive ) { StartTop=TopsUp[ArraySize(TopsUp)-1]; EndTop=TopsUp[0]; FormationDirection=1; } else FormationDirection=0; }
Daha ileri adımlar için net bir yön belirlenmesi gerekmektedir. Yön, formasyon türüne eşdeğerdir:
- Çoklu tepe
- Çoklu dip
Bu kurallar aynı zamanda baş ve omuz formasyonu ve diğer tüm hibrit formasyonlar için de geçerlidir. Sınıfın bu ailenin tüm formasyonları için ortak olması gerekiyordu - bu genelleştirme halihazırda kısmen çalışıyor.
Geçersiz formasyonları dışlamak için filtreler
Şimdi daha ileri gidelim. Bir yönümüz olduğunu ve tepe ve dipleri seçme yollarından birine sahip olduğumuzu bilerek, çoklu tepe için şunları sağlamalıyız: seçilenler arasındaki tepeler, seçilenlerin en düşüğünden daha düşük olmalıdır. Çoklu dip için, bu dipler seçilenlerin en yükseğinden daha yüksek olmalıdır. Bu durumda, tepeler rastgele seçilirse, seçilen tüm tepeler net bir şekilde ayırt edilebilir. Aksi takdirde, bu kontrol gerekli değildir:
bool ExtremumsPatternFamilySearcher::IsExtremumsAbsolutely()// require the selected extrema to be the most extreme ones { if ( bRandomExtremumsM )// check only if we have a random selection of tops (in other case the check should be considered completed) { if ( FormationDirection == 1 ) { int StartIndex=RandomIndexUp[0]; int EndIndex=RandomIndexUp[ArraySize(RandomIndexUp)-1]; for(int i=StartIndex+1;i<EndIndex;i++)// check all tops between the selected ones { for(int j=0;j<ArraySize(TopsUp);j++) { if ( TopsUpAll[i].Price >= TopsUp[j].Price ) { for(int k=0;k<ArraySize(RandomIndexUp);k++) { if ( i != RandomIndexUp[k] ) return false; } } } } return true; } else if ( FormationDirection == -1 ) { int StartIndex=RandomIndexDown[0]; int EndIndex=RandomIndexDown[ArraySize(RandomIndexDown)-1]; for(int i=StartIndex+1;i<EndIndex;i++)// check all tops between the selected ones { for(int j=0;j<ArraySize(TopsDown);j++) { if ( TopsDownAll[i].Price <= TopsDown[j].Price ) { for(int k=0;k<ArraySize(RandomIndexDown);k++) { if ( i != RandomIndexDown[k] ) return false; } } } } return true; } else return false; } else { return true; } }
Yukarıdaki boolean fonksiyon tarafından gerçekleştirilen rastgele tepe seçiminin doğru ve yanlış varyantını görsel olarak gösterirsek, aşağıdaki gibi görünecektir:
Bu kriterler boğa ve ayı formasyonları için de geçerlidir. Şekilde örnek olarak bir boğa formasyonu gösterilmektedir. İkinci durum kolayca hayal edilebilir.
Tüm hazırlık işlemlerini tamamladıktan sonra boyun araştırmasına geçebiliriz. Farklı yatırımcılar boynu farklı şekillerde çizer. Koşullu olarak birkaç tür belirledim:
- Görsel olarak eğik (gölgelere göre değil)
- Görsel olarak yatay (gölgelere göre değil)
- En yüksek veya en düşük nokta, eğik (gölgelere göre)
- En yüksek veya en düşük nokta, yatay (gölgelere göre)
Güvenlik nedenleriyle ve kâr olasılığını artırmak için en uygun varyantın 4 olduğuna inanıyorum. Bunu aşağıdaki sebeplerden dolayı seçtim:
- Bir terse dönüş hareketinin başlangıcı daha açık bir şekilde görülür
- Bu yaklaşımın kodda uygulanması daha kolaydır
- Eğim kesin olarak belirlenir (yatay olarak)
Belki de yapı açısından bu tamamen doğru değildir, ancak net bir kural bulamadım. Bu, algo alım-satım açısından kritik değildir. Bu formasyonda rasyonel bir şey bulursak, sınayıcı veya görselleştirme bize mutlaka bir şey gösterecektir. Daha ileri bir görev, alım-satım sonuçlarının güçlendirilmesi anlamına gelir, ancak bu kesinlikle farklı bir görevdir.
Boynun gerekli tüm parametrelerini tanımlayan boğa ve ayı formasyonları için iki ayna fonksiyon oluşturdum:
void ExtremumsPatternFamilySearcher::FindNeckUp(Top &TStart,Top &TEnd)// find the neck line based on the two extreme tops (for the classic multiple top) { double PriceMin=Low[TStart.IndexExtremum]; datetime TimeMin=Time[TStart.IndexExtremum]; for(int i=TStart.IndexExtremum;i>=TEnd.IndexExtremum;i--)// define the lowest point { if ( Low[i] < PriceMin ) { PriceMin=Low[i]; TimeMin=Time[i]; } } // define the parameters of the anchor point and all parameters of the line equation Neck.Price0=PriceMin; Neck.TimeX=TimeMin; Neck.Time0=Time[0]; Neck.Price1=PriceMin; Neck.Time1=TStart.DatetimeExtremum; Neck.DirectionOfFormation=true; Neck.CalculateKC(); } void ExtremumsPatternFamilySearcher::FindNeckDown(Top &TStart,Top &TEnd)// find the neck line based on two extreme bottoms (for the classic multiple bottom) { double PriceMax=High[TStart.IndexExtremum]; datetime TimeMax=Time[TStart.IndexExtremum]; for(int i=TStart.IndexExtremum;i>=TEnd.IndexExtremum;i--)// define the lowest point { if ( High[i] > PriceMax ) { PriceMax=High[i]; TimeMax=Time[i]; } } // define the parameters of the anchor point and all parameters of the line equation Neck.Price0=PriceMax; Neck.TimeX=TimeMax; Neck.Time0=Time[0]; Neck.Price1=PriceMax; Neck.Time1=TStart.DatetimeExtremum; Neck.DirectionOfFormation=false; Neck.CalculateKC(); }
Boynun doğru ve basit bir şekilde çizilmesi için, seçilen ailenin tüm formasyonlarının boyun oluşumu için aynı kuralları kullanmak daha iyidir. Bir yandan bu, bizim durumumuzda hiçbir şey sağlamayacak olan gereksiz ayrıntıları ortadan kaldırır. Herhangi bir karmaşıklıktaki bir çoklu tepeye bir boyun oluşturmak için, formasyonun iki uç tepesini kullanmak daha iyidir. Bu tepelerin indeksleri, piyasanın seçilen segmentinde en düşük veya en yüksek fiyatı arayacağımız indeksler olacaktır. Boyun düzgün bir yatay çizgi olacaktır. İlk çapa noktaları tam olarak bu seviyede olmalı, çapa zamanı ise uç tepelerin veya diplerin zamanına tam olarak eşit olmalıdır (hangi formasyonu dikkate aldığımıza bağlı olarak). Resimde şu şekilde görünecektir:
Düşük veya yüksek arama penceresi tam olarak ilk ve son tepe arasındadır. Bu kural, bu ailedeki herhangi bir formasyon için, herhangi bir sayıda tepe ve dip için geçerlidir.
İyimser hedefi belirlemek için öncelikle formasyon büyüklüğünü tanımlamamız gerekir. Formasyon büyüklüğü, puan cinsinden baştan boyuna olan dikey mesafedir. Mesafeyi belirlemek için öncelikle boyundan en uzak olan tepeyi bulmamız gerekir. Bu tepe formasyonun sınırı olacaktır:
void ExtremumsPatternFamilySearcher::SearchFarestTop()// define the farthest top { double MaxTranslation;// temporary variable to determine the highest top if ( FormationDirection == 1 )// if we deal with a multiple top { MaxTranslation=TopsUp[0].Price-Neck.Price0;// temporary variable to determine the highest top FarestTop=TopsUp[0]; for(int i=1;i<ArraySize(TopsUp);i++) { if ( TopsUp[i].Price-Neck.Price0 > MaxTranslation ) { MaxTranslation=TopsUp[i].Price-Neck.Price0; FarestTop=TopsUp[i]; } } } if ( FormationDirection == -1 )// if we deal with a multiple bottom { MaxTranslation=Neck.Price0-TopsDown[0].Price;// temporary variable to determine the lowest bottom FarestTop=TopsDown[0]; for(int i=1;i<ArraySize(TopsDown);i++) { if ( Neck.Price0-TopsDown[i].Price > MaxTranslation ) { MaxTranslation=Neck.Price0-TopsDown[0].Price; FarestTop=TopsDown[i]; } } } }
Tepelerin çok farklı olmadığından emin olmak için ek bir kontrol gereklidir. Yalnızca kontrol başarılı olursa diğer adımlara geçebiliriz. Daha doğrusu, iki kontrol olmalıdır: biri tepenin dikey büyüklüğü için, diğeri yatay büyüklüğü (zaman) için. Eğer tepeler zaman olarak çok uzaksa, böyle bir varyant da uygun değildir. İşte dikey büyüklük için kontrol:
bool ExtremumsPatternFamilySearcher::bBalancedExtremums()// balance the tops { double Lowest;// the lowest top for the multiple top double Highest;// the highest bottom for the multiple bottom double AbsMin;// distance from the neck to the nearest top if ( FormationDirection == 1 )// for the multiple top { Lowest=TopsUp[0].Price; for(int i=1;i<ArraySize(TopsUp);i++)// find the lowest top { if ( TopsUp[i].Price < Lowest ) Lowest=TopsUp[i].Price; } AbsMin=Lowest-Neck.Price0;// determine distance from the lowest top to the neck if ( AbsMin == 0.0 ) return false; if ( ((FarestTop.Price - Neck.Price0)-AbsMin)/AbsMin >= RelativeUnstabilityM ) return false;// if the head is too much bigger than the lowest leverage } else if ( FormationDirection == -1 )// for the multiple bottom { Highest=TopsDown[0].Price; for(int i=1;i<ArraySize(TopsDown);i++)// find the highest top { if ( TopsDown[i].Price > Highest ) Highest=TopsDown[i].Price; } AbsMin=Neck.Price0-Highest;// determine distance from the highest top to the neck if ( AbsMin == 0.0 ) return false; if ( ((Neck.Price0-FarestTop.Price)-AbsMin)/AbsMin >= RelativeUnstabilityM ) return false;// if the head is too much bigger than the lowest leverage } else return false; return true; }
Tepelerin doğru dikey büyüklüğünü belirlemek için iki tepeye ihtiyacımız var. Birincisi boyundan en uzak olanı, ikincisi ise ona en yakın olanıdır. Bu büyüklükler büyük ölçüde farklıysa, bu formasyon geçersiz olabilir ve risk almamak ve geçersiz olarak işaretlemek daha iyidir. Bir önceki boolean fonksiyonda olduğu gibi, tüm bunlara neyin doğru neyin yanlış olduğuna dair uygun bir grafiksel gösterim eşlik edebilir:
Bunları görsel olarak belirlemek kolaydır, ancak kodun nicel bir ölçüme ihtiyacı vardır. Bu durumda, aşağıdaki kadar basittir:
- K = (Max-Min)/Min
- K <= RelativeUnstabilityM
Bu metrik, oldukça fazla sayıda yanlış formasyonu filtrelemek için oldukça etkilidir. En sofistike kod bile gözümüzden daha verimli olamaz. Yapabileceğimiz tek şey mantığı gerçeğe olabildiğince yakın hale getirmektir - ancak burada nerede duracağımızı bilmeliyiz.
Yatay kontrol benzer görünecektir. Tek fark, büyüklük olarak çubuk indekslerini kullanmamızdır (zamanı kullanabilirsiniz, temel bir fark yoktur):
bool ExtremumsPatternFamilySearcher::bBalancedExtremumsTime()// balance the sizes of shoulders and head along the horizontal axis { double Lowest;// minimum distance between the tops double Highest;// maximum distance between the tops if ( FormationDirection == 1 )// for the multiple top { Lowest=TopsUp[1].IndexExtremum-TopsUp[0].IndexExtremum; Highest=TopsUp[1].IndexExtremum-TopsUp[0].IndexExtremum; for(int i=1;i<ArraySize(TopsUp)-1;i++)// find the lowest top { if ( TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum < Lowest ) Lowest=TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum; if ( TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum > Highest ) Highest=TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum; } if ( double(Highest-Lowest)/double(Lowest) > RelativeUnstabilityTimeM ) return false;// if the width of one of the waves differs much } else if ( FormationDirection == -1 )// for the multiple bottom { Lowest=TopsDown[1].IndexExtremum-TopsDown[0].IndexExtremum; Highest=TopsDown[1].IndexExtremum-TopsDown[0].IndexExtremum; for(int i=1;i<ArraySize(TopsDown)-1;i++)// find the lowest top { if ( TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum < Lowest ) Lowest=TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum; if ( TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum > Highest ) Highest=TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum; } if ( double(Highest-Lowest)/double(Lowest) > RelativeUnstabilityTimeM ) return false;// if the width of one of the waves differs much } else return false; return true; }
Bu kontrol için benzer bir metrik kullanabiliriz. Görsel olarak aşağıdaki gibi ifade edilebilir:
Bu durumda, nicel kriterler aynı olacaktır. Ancak bu kez puan yerine indeks veya zaman kullanıyoruz. Karşılaştırma yaptığımız sayıyı ayrı olarak uygulamak daha iyi olabilir, bu da esnek ayarlamalara yer açacaktır:
- K = (Max-Min)/Min
- K <= RelativeUnstabilityTimeM
Boyun çizgisi soldaki fiyatı geçmelidir - bu, formasyonun öncesinde bir trend olduğu anlamına gelir:
bool ExtremumsPatternFamilySearcher::CorrectNeckUpLeft()// next the neck line must be corrected so that it finds an intersection with the price on the left { bool bCrossNeck=false;// indicates if the neck was crossed if ( Neck.DirectionOfFormation )// if the neck is found for a double top { for(int i=StartTop.Index1;i<BarsM;i++)// define the intersection point { if ( High[i] >= FarestTop.Price )// if the movement goes beyond the formation, then the formation is fake { return false; } if ( Close[i] < Neck.Price0 && Open[i] < Neck.Price0 && High[i] < Neck.Price0 && Low[i] < Neck.Price0 ) { Neck.Time1=Time[i]; Neck.Index1=i; return true; } } } return false; } bool ExtremumsPatternFamilySearcher::CorrectNeckDownLeft()// next the neck line must be corrected so that it finds an intersection with the price on the left { bool bCrossNeck=false;// indicates if the neck was crossed if ( !Neck.DirectionOfFormation )// if the neck is found for a double bottom { for(int i=StartTop.Index1;i<BarsM;i++)// define the intersection point { if ( Low[i] <= FarestTop.Price )// if the movement goes beyond the formation, then the formation is fake { return false; } if ( Close[i] > Neck.Price0 && Open[i] > Neck.Price0 && High[i] > Neck.Price0 && Low[i] > Neck.Price0 ) { Neck.Time1=Time[i]; Neck.Index1=i; return true; } } } return false; }
Yine, boğa ve ayı formasyonları için iki ayna fonksiyon vardır. Aşağıda bu boolean fonksiyonun grafiksel bir gösterimi yer almaktadır:
Mavi kutular, kesişimi kontrol ettiğimiz piyasa segmentlerini işaret etmektedir. Her iki segment de formasyonun arkasında, uç tepelerin solunda ve sağındadır.
Sadece iki kontrol kaldı:
- Şu anda (sıfır mumunda) boyun çizgisini kesen bir formasyona ihtiyacımız var
- Formasyondan önce, formasyonun kendisinden daha büyük veya ona eşit bir hareket gelmelidir
İlk madde algoritmik alım-satım için gereklidir. Bu fonksiyon da sağlanmış olmasına rağmen, formasyonları yalnızca görüntülemek için tespit etmeye değeceğini düşünmüyorum. Hem tespit etmeye hem de tam olarak alım-satım yapabileceğimiz noktayı bulmaya ihtiyacımız var - giriş noktasında olduğumuzu bilerek hemen bir pozisyon açabileceğimiz bir yer. İkinci madde gerekli koşullardan biridir, çünkü iyi bir önceki hareket olmadan formasyonun kendisi işe yaramaz.
Sıfır mumun çaprazlanması (sağdaki kesişme noktasının kontrol edilmesi) aşağıdaki gibi belirlenir:
int ExtremumsPatternFamilySearcher::CorrectNeckUpRight()// next the neck line must be corrected so that it finds an intersection with the price on the right bool bCrossNeck=false;// indicates if the neck was crossed if ( Neck.DirectionOfFormation )// if the neck is found for a double top { for(int i=EndTop.IndexExtremum;i>1;i--)// define the intersection point { if ( High[i] > FarestTop.Price || Low[i] < Neck.Price0 )// if the movement goes beyond the formation, then the formation is fake { return -1; } } } if ( Close[0] <= Neck.Price0 ) { Neck.Time0=Time[0]; return 1; } return 0; } int ExtremumsPatternFamilySearcher::CorrectNeckDownRight()// next the neck line must be corrected so that it finds an intersection with the price on the right { bool bCrossNeck=false;// indicates if the neck was crossed if ( !Neck.DirectionOfFormation )// if the neck is found for a double bottom { for(int i=EndTop.IndexExtremum;i>1;i--)// define the intersection point { if ( Low[i] < FarestTop.Price || High[i] > Neck.Price0 )// if the movement goes beyond the formation, then the formation is fake { return -1; } } } if ( Close[0] >= Neck.Price0 ) { Neck.Time0=Time[0]; return 1; } return 0; }
Yine iki ayna fonksiyonumuz var. Fiyat formasyonun ötesine geçip sonra geri dönmüşse sağdaki kesişimin geçerli kabul edilmediğini lütfen unutmayın - bu davranış burada ele alınmıştır ve önceki şekilde gösterilmiştir.
Şimdi, öncül trendin nasıl bulunacağını belirleyelim. Şimdiye kadar bu amaç için iyimser tahmin çizgisini kullanıyorum. Boyun ile iyimser tahmin çizgisi arasında bir piyasa segmenti varsa, bu istenen harekettir. Bu hareket zaman içinde çok uzun olmamalıdır, aksi takdirde net bir hareket değildir:
bool ExtremumsPatternFamilySearcher::bWasTrend()// did we find the movement preceding the formation (also move here the anchor point to the intersection) { bool bCrossOptimist=false;// denotes if the neck is crossed if ( FormationDirection == 1 )// if the optimistic forecast is at the double top { for(int i=Neck.Index1;i<BarsM;i++)// define the intersection point { if ( High[i] > Neck.Price0 )// if the movement goes beyond the neck, then the formation is fake { return false; } if ( Low[i] < OptimistLine.Price0 ) { OptimistLine.Time1=Time[i]; return true; } } } else if ( FormationDirection == -1 )// if the optimistic forecast is at the double bottom { for(int i=Neck.Index1;i<BarsM;i++)// define the intersection point { if ( Low[i] < Neck.Price0 )// if the movement goes beyond the neck, then the formation is fake { return false; } if ( High[i] > OptimistLine.Price0 ) { OptimistLine.Time1=Time[i]; return true; } } } return false; }
Son boolean fonksiyon da grafiksel olarak aşağıdaki gibi gösterilebilir:
Kodu incelemeyi burada bitirelim ve görsel değerlendirmelere geçelim. Yöntemin ana fikirlerinin bu makalede yeterince açıklandığını düşünüyorum. Bu serinin bir sonraki makalesinde daha fazla fikir ele alınacaktır.
MetaTrader 5 Strateji Sınayıcı görselleştiricisinde sonucu kontrol etme
Hızlı, basit ve net olduğu için grafikte her zaman çizgi çizimini kullanıyorum. MQL5 Yardımı, çizgiler de dahil olmak üzere tüm grafiksel nesnelerin kullanımına ilişkin örnekler sağlamaktadır. Çizim kodunu burada vermeyeceğim, ancak yürütme sonucunu görebilirsiniz. Elbette her şey daha iyi yapılabilirdi ama elimizde sadece bir prototip var. Dolayısıyla, burada "gereklilik ve yeterlilik" ilkesini kullanabileceğimize inanıyorum:
İşte üçlü tepeye bir örnek. Bu örnek bana daha ilginç geldi. Çift tepeler de benzer şekilde algılanır - sadece parametrelerde istenen tepe sayısını ayarlamanız gerekir. Kod bu tür formasyonlara pek sık rastlamaz, ancak bu sadece bir gösterimdir. Kod daha da geliştirilebilir (ki bunu daha sonra yapmayı planlıyorum).
Daha fazla geliştirme fikirleri
Daha sonra, bu makalede söylenmemiş olanları ele alacağız ve tüm formasyonlar için arama kalitesini artıracağız. Ayrıca sınıfı, baş ve omuz formasyonlarını tespit etmesini sağlayacak şekilde geliştireceğiz. Ayrıca bu formasyonların olası melez fonksiyonlarını bulmaya çalışacağız; bunlardan biri "N tepeler ve çoklu omuzlar" olabilir. Seri sadece bu formasyon ailesine adanmış değildir ve yeni ilginç ve faydalı materyaller içerecektir. Formasyon aramaya farklı yaklaşımlar vardır ve bu serinin amacı, farklı örnekler kullanarak mümkün olduğunca çok sayıda formasyon göstermek ve böylece karmaşık bir görevi bir dizi daha basit olana ayırmanın farklı olası yollarını kapsamaktır. Seri şunları içerecektir:
- Diğer ilginç formasyonlar
- Farklı formasyon türlerini tespit etmek için diğer yöntemler
- Geçmiş verileri kullanarak alım-satım yapma ve farklı enstrümanlar ve zaman dilimleri için istatistik toplama
- Çok fazla formasyon var ve ben hepsini bilmiyorum (bu yüzden potansiyel olarak sizin formasyonlarınızı dikkate alabilirim)
- Ayrıca seviyeleri de dikkate alacağız (seviyeler genellikle terse dönüşleri tespit etmek için kullanıldığından)
Sonuç
Materyali herkes için basit ve anlaşılır hale getirmeye çalıştım. Umarım herkes burada faydalı bir şeyler bulabilir. Bu makalenin sonucu, görsel strateji sınayıcıdan da görülebileceği gibi, basit bir kodun karmaşık formasyonları bulabilmesidir. Bu yüzden illa sinir ağları kullanmamız ya da bazı karmaşık makine görüşü algoritmaları yazmamız/kullanmamız gerekmiyor. MQL5 dili, en karmaşık algoritmaları bile uygulamak için zengin işlevselliğe sahiptir. Olasılıklar sadece sizin hayal gücünüz ve çalışkanlığınızla sınırlıdır.
MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/9394





- Ücretsiz alım-satım uygulamaları
- İşlem kopyalama için 8.000'den fazla sinyal
- Finansal piyasaları keşfetmek için ekonomik haberler
Gizlilik ve Veri Koruma Politikasını ve MQL5.com Kullanım Şartlarını kabul edersiniz