English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
preview
Expert Advisor 개발 기초부터 (파트 14): 가격에 볼륨 추가 (II)

Expert Advisor 개발 기초부터 (파트 14): 가격에 볼륨 추가 (II)

MetaTrader 5트레이딩 시스템 | 17 1월 2023, 10:11
379 0
Daniel Jose
Daniel Jose

소개

우리의 EA에는 이미 거래에 도움이 되는 몇 가지 리소스가 있습니다 - 이 작업은 이전 기사에서 했습니다. 그러나 이 EA에는 시각화 및 크기 조정에 몇 가지 문제가 있습니다. 이 문제들이 거래를 방해하지는 않지만 어떤 시점에서는 강제로 새로 고침할 때까지 화면이 엉망이 됩니다. 또한 우리에게 귀중한 정보를 제공할 수 있는 몇 가지 누락된 것들이 있습니다. 이들은 구체적인 사항이지만 정보가 필요할 수 있습니다.

이제 이러한 새로운 개선 사항을 구현해 보겠습니다. 이 흥미로운 기사는 정보를 표시하는 새로운 아이디어와 방법을 제공합니다. 또한 프로젝트의 사소한 결함을 수정하는 데 도움이 될 수 있습니다.


Volume At Price에서 새로운 함수를 계획하고 및 구현하기

1. 계획

거래에는 흥미로운 점이 있습니다. 우리는 종종 시장이 특정한 가격 영역에 축적되는 것을 볼 수 있으며 매수 또는 매도 측에서 스탑이 트리거 되면 가격의 급격한 움직임이 있습니다. 이 움직임은 Times & Trade에서 볼 수 있습니다. 이전 기사 Times & Trade(I)Times & Trade(II)에서 이를 살펴보았습니다. 기사에서는 실행된 주문의 주문 흐름을 읽고 분석하기 위한 그래픽 시스템을 만드는 방법에 대해 살펴보았습니다. 자세히 살펴보면 어떤 때 가격이 축적 영역으로 되돌아가는 경향이 있음을 알 수 있습니다. 그러나 Volume at Price 지표에서 보면 이 특정 지역에서 최근의 가격이 얼마나 변경되지 않았는지 판단하기 어렵습니다. 이 지표는 "가격에 볼륨 추가하기(I)" 기사에서 구현되었습니다. 이를 이용하여 분석의 시작점을 변경하는 것만으로도 비교적 최근의 움직임을 분석할 수 있으며 이는 아래 그림에 표시된 객체의 값을 조정함으로써 가능합니다.

그러나 이것은 실제로는 비실용적입니다. 왜냐하면 우리는 기본 시간대에 묶여 있기 때문입니다. 즉 60분 시간대 차트가 있는 경우 이 시간대 주기 아래에 있는 가격의 움직임을 분석할 수 없습니다. 분석 포인트를 조정하려면 더 낮은 주기로 전환해야 합니다. 그러나 선물 계약을 거래할 때 대부분의 거래자들은 실제로 5분, 10분 또는 30분과 같은 더 낮은 시간 프레임을 사용하므로 분석 시작점을 조정하는 것은 문제가 없습니다. 그러나 앞서 설명했듯이 스탑이 발동되고 이로 인해 가격이 누적에서 빠져나오는 경우가 있으며 이러한 반환은 일반적으로 5분 이내에 발생합니다. 이러한 경우 긴 위 또는 아래 그림자가 있는 캔들이 차트에 나타납니다. 이러한 경우 Price Action은 마켓의 신호라고 알려줍니다. 이러한 유형의 움직임은 화살표가 나타난 캔들 아래에서 볼 수 있습니다.

   

매수자의 일반적인 테스트 또는 스탑 트리거링

 

매도자의 일반적인 테스트 또는 매수자 측의 스탑 트리거링

이러한 종류의 움직임은 자주 발생하며 각각의 가격 범위에서 생성된 거래량을 분석하는 것은 시장이 테스트를 하는 것인지 아니면 추세가 실제로 반전되고 있는 것인지를 이해할 수 있게 해주기 때문에 매우 중요합니다. 그러나 이전에 제안된 볼륨 지표를 사용하여 이를 적절하고 신속하게 수행하는 것은 불가능합니다.

그러나 지표의 객체 클래스를 약간 변경하면 진행 상황을 더 명확하게 파악할 수 있습니다. 이는 일정 시간 동안 이루어진 거래의 흔적으로 나타나게 됩니다.


2. 구현

가장 먼저 할 일은 60분, 45분, 30분, 19분, 7분, 1분 등 추적하고자 시간을 어떻게 설정할지를 분석하는 것입니다. 이외 추적 시스템이 실제로 유용할 수 있도록 배수인 값을 사용하는 것이 좋습니다. 30분 추적이 실용적이므로 다음과 같은 코드 라인을 정의합니다:

#define def_MaxTrailMinutes     30

근데 왜 30분일까요? 실제로 추적 시스템은 1분마다 실행되지만 최대 추적 시간은 30분입니다. 즉 항상 30분 추적이 가능합니다. 예를 들어 추적이 31분으로 전환되면 첫 거래의 분은 더 이상 표시되지 않습니다. 어떻게 구현될까요? 아래 표시된 캡처 시스템을 사용합니다:

inline void SetMatrix(MqlTick &tick)
{
        int pos;
                                
        if ((tick.last == 0) || ((tick.flags & (TICK_FLAG_BUY | TICK_FLAG_SELL)) == (TICK_FLAG_BUY | TICK_FLAG_SELL))) return;
        pos = (int) ((tick.last - m_Infos.FirstPrice) / Terminal.GetPointPerTick()) * 2;
        pos = (pos >= 0 ? pos : (pos * -1) - 1);
        if ((tick.flags & TICK_FLAG_BUY) == TICK_FLAG_BUY) m_InfoAllVaP[pos].nVolBuy += tick.volume; else
        if ((tick.flags & TICK_FLAG_SELL) == TICK_FLAG_SELL) m_InfoAllVaP[pos].nVolSell += tick.volume;
        m_InfoAllVaP[pos].nVolDif = (long)(m_InfoAllVaP[pos].nVolBuy - m_InfoAllVaP[pos].nVolSell);
        m_InfoAllVaP[pos].nVolTotal = m_InfoAllVaP[pos].nVolBuy + m_InfoAllVaP[pos].nVolSell;
        m_Infos.MaxVolume = (m_Infos.MaxVolume > m_InfoAllVaP[pos].nVolTotal ? m_Infos.MaxVolume : m_InfoAllVaP[pos].nVolTotal);
        m_Infos.CountInfos = (m_Infos.CountInfos == 0 ? 1 : (m_Infos.CountInfos > pos ? m_Infos.CountInfos : pos));
        m_Infos.Momentum = macroGetMin(tick.time);
        m_Infos.Momentum = (m_Infos.Momentum > (def_MaxTrailMinutes - 1) ? m_Infos.Momentum - def_MaxTrailMinutes : m_Infos.Momentum);
        if (m_Infos.memMomentum != m_Infos.Momentum)
        {
                for (int c0 = 0; c0 <= m_Infos.CountInfos; c0++) m_TrailG30[m_Infos.Momentum].nVolume[c0] = 0;
                m_Infos.memMomentum = m_Infos.Momentum;
        }
        m_TrailG30[m_Infos.Momentum].nVolume[pos] += tick.volume;
}

강조 표시된 줄은 객체 클래스의 소스 코드에 추가된 줄입니다. - 이들은 볼륨 추적 캡처를 구현합니다. 아래 줄은 추적이 예상대로 수행됨을 보장합니다.

m_Infos.Momentum = macroGetMin(tick.time);
m_Infos.Momentum = (m_Infos.Momentum > (def_MaxTrailMinutes - 1) ? m_Infos.Momentum - def_MaxTrailMinutes : m_Infos.Momentum);

추적 캡처 시스템이 준비되었습니다. 이제 우리는 새로운 결단을 내려야 합니다. 추적은 1분마다 캡처 된다는 점을 기억하십시오. 이를 통해 1분 이내에 각 가격대의 거래량을 볼 수 습니다. 이러한 방식으로 차트를 작성하는 한 여러분은 아래와 같은 작업을 생각해 볼 수 있을 것입니다.

 

밝은 색조는 근래 만들어진 볼륨을 나타냅니다.

좋은 생각인 것 같지만 볼륨이 작거나 움직임이 매우 빠른 경우나 순간적인 의미 있는 볼륨의 경우에는 실제로는 보이지 않을 수 있습니다. 왜냐하면 지금까지 생긴 최대의 볼륨에 조정되기 때문입니다. 따라서 이 문제를 해결하기 위해 약간 다르게 플로팅 해야 합니다. 그러면 다음과 같이 표시될 것입니다.

각 색상은 볼륨 추적에서 특정 기간을 나타냅니다.

이렇게 하면 부피가 매우 좁은 밴드를 분석하는 데 도움이 될 수 있으며 첫 번째 경우에 나타나는 간헐적인 문제를 해결할 수 있습니다. 그러나 여전히 볼륨이 다른 시점의 전체 볼륨에 비해 나타내 주는 것이 없을 때 발생하는 조정의 문제가 있습니다. 또한 매우 활발한 거래 시 분석이 혼동되지 않도록 각 기간의 색상을 신중하게 선택해야 합니다.

따라서 다른 기간의 움직임을 분석하기 위해 다시 조정할 수 있는 더 간단한 모델을 사용합니다. 그러나 위에서 언급한 문제를 염두에 두십시오. 여러분이 하기 나름입니다. 그러면 추적이 다음과 같이 표시될 것입니다:

우리는 여기서 순전히 추적만을 봅니다. 그런 일이 발생하면 무슨 일이 일어나고 있는지 이해하기 위해 Times & Trade와 Price Action을 모두 분석해야 합니다.

어쨌든 볼륨 표시를 변경하기 위해 수정해야 할 유일한 함수는 다음과 같은 함수입니다.

void Redraw(void)
{
        uint            x, y, y1, p;
        double  reason = (double) (m_Infos.MaxVolume > m_WidthMax ? (m_WidthMax / (m_Infos.MaxVolume * 1.0)) : 1.0);
        double  desl = Terminal.GetPointPerTick() / 2.0;
        ulong           uValue;
                                
        Erase();
        p = m_WidthMax - 8;
        for (int c0 = 0; c0 <= m_Infos.CountInfos; c0++)
        {
                if (m_InfoAllVaP[c0].nVolTotal == 0) continue;
                ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, m_Infos.FirstPrice + (Terminal.GetPointPerTick() * (((c0 & 1) == 1 ? -(c0 + 1) : c0) / 2)) + desl, x, y);
                y1 = y + Terminal.GetHeightBar();
                FillRectangle(p + 2, y, p + 8, y1, macroColorRGBA(m_InfoAllVaP[c0].nVolDif > 0 ? m_Infos.ColorBuy : m_Infos.ColorSell, m_Infos.Transparency));
                FillRectangle((int)(p - (m_InfoAllVaP[c0].nVolTotal * reason)), y, p, y1, macroColorRGBA(m_Infos.ColorBars, m_Infos.Transparency));
                uValue = 0;
                for (int c1 = 0; c1 < def_MaxTrailMinutes; c1++) uValue += m_TrailG30[c1].nVolume[c0];
                FillRectangle((int) (p - (uValue * reason)), y, p, y1, macroColorRGBA(clrRoyalBlue, m_Infos.Transparency));
        }
        C_Canvas::Update();
};

정확히 말하면 강조 표시된 코드만 수정하면 됩니다. 원하는 결과를 얻을 때까지 이리 저리 계속 해 볼 수 있습니다. 강조 표시된 부분을 제외하고 클래스의 다른 항목을 수정할 필요는 없습니다. 프로그램을 컴파일하고 차트에서 실행하면 다음과 같은 내용이 표시됩니다.



렌더링 문제 해결

코드에 특별한 문제가 없었지만 차트의 크기를 조정할 때 사소한 결함이 있습니다. 최대화된 차트의 크기를 다른 크기로 조정한 다음 다시 최대화하면 일부 개체가 없어지고 예상대로 작동하지 않습니다. 혹은 잘못된 위치에 배치됩니다. 그러나 고칠 것이 많지 않습니다. 문제는 아래 코드에 있습니다. - 이전 기사에서 사용한 것입니다.

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        Chart.DispatchMessage(id, lparam, dparam, sparam);
        VolumeAtPrice.DispatchMessage(id, sparam);
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
                        TimesAndTrade.Resize();
        break;
        }
        ChartRedraw();
}

매우 간단한 수정이 있지만 여러분은 "아무것도 보이지 않습니다. 코드가 정확합니다."라고 생각할 수도 있습니다. 언뜻 보기에도 잘못된 것이 보이지 않으며 코드에 런타임 오류가 남아 있습니다. 그러나 저는 몇 가지 추가적인 기능을 추가할 때 위에서 설명한 것과 정확히 같은 문제를 발견했습니다. 이 문제를 해결하려면 다음과 같이 코드를 변경해야 합니다:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
                        TimesAndTrade.Resize();
        break;
        }
        Chart.DispatchMessage(id, lparam, dparam, sparam);
        VolumeAtPrice.DispatchMessage(id, sparam);
        ChartRedraw();
}

어리석게 들릴 수 있지만 그 이유를 이해하려면 함수 코드 전체와 강조 표시된 부분을 보십시오. 이제 시스템이 수정되었으므로 우리는 다음 단계로 넘어갈 수 있습니다.


추가 리소스

지금 추가할 함수는 매우 간단하고 구현해야 할 이유가 많지 않을 지 모르나 구현하게 되면 가격 지표에서 포지셔닝, 이동 또는 볼륨 표시를 포함하여 주문 작업을 할 때 많은 도움이 될 것입니다.

가장 먼저 해야 할 일은 가격 라인 조정 코드가 포함될 클래스를 변경하는 것입니다. 이 코드는 C_OrderView 클래스에서 나와 C_Terminal 클래스로 들어가지만 이를 위해 클래스 자체의 변수로 작업을 시작하기 때문에 약간의 변경도 거칩니다. 다음은 새 코드의 모습입니다.

double AdjustPrice(const double arg)
{
        double v0, v1;
                                
        if(m_Infos.TypeSymbol == OTHER) return arg;
        v0 = (m_Infos.TypeSymbol == WDO ? round(arg * 10.0) : round(arg));
        v1 = fmod(round(v0), 5.0);
        v0 -= ((v1 != 0) || (v1 != 5) ? v1 : 0);
        return (m_Infos.TypeSymbol == WDO ? v0 / 10.0 : v0);
};

그렇게 함으로써 우리는 새로운 EA 클래스를 생성할 수 있습니다 - 그것은 C_Mouse 클래스가 될 것입니다. 이 클래스 객체는 마우스 이벤트를 담당하기도 하고 마우스 이벤트의 기반이 됩니다. 그러나 그전에 먼저 아래 그림과 같이 Expert Advisor의 현재 클래스의 구조를 살펴보겠습니다.

다음 시스템을 구현하려면 새로운 구조를 도입해야 합니다...

따라서 위의 구조가 주어 졌을때 아래에서 볼 수 있는 변수 선언부터 C_Mouse 객체 클래스의 코드까지 분석해 보겠습니다.

class C_Mouse
{
        private  :
                struct st00
                {
                color   cor01,
                        cor02,
                        cor03;
                string  szNameObjH,
                        szNameObjV,
                        szNameObjT,
                        szNameObjI,
                        szNameObjB;
                }m_Infos;
                struct st01
                {
                        int      X,
                                 Y;
                        datetime dt;
                        double   price;
                        uint     ButtonsStatus;
                }Position;

이 개발 단계에서 필요한 것이 거의 없으며 다음 지점으로 이동하겠습니다.

~C_Mouse()
{
// ... Internal code ...
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_MOUSE_MOVE, false);
        ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, true);
}

이 코드는 십자선CHART_CROSSHAIR_TOOL을 복원하고 차트에서 마우스 이벤트의 사용을 비활성화합니다. 즉 플랫폼 자체에서 처리하기 때문에 MT5가 더 이상 이러한 이벤트를 차트로 보내는 데 신경 쓸 필요가 없습니다.

또한 마우스를 제어할 때 사용되는 매우 일반적인 두 가지 함수가 있습니다.

inline void Show(void)
{
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjH, OBJPROP_COLOR, m_Infos.cor01);
}
//+------------------------------------------------------------------+
inline void Hide(void)
{
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjH, OBJPROP_COLOR, clrNONE);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_COLOR, clrNONE);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, clrNONE);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_COLOR, clrNONE);
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjB, 0, 0, 0);
}

흥미로운 점은 마우스가 실제로 사라지는 것이 아니라 우리가 만든 객체만 화면에서 사라지고 마우스를 "켜면" 가격선만 실제로 표시된다는 것입니다. 이것은 이상하게 보일 수 있지만 EA에서 쓸모가 있습니다. 이러한 점 중 하나는 아래 코드에서 강조 표시된 부분의 C_OrderView 클래스 객체입니다.

inline void MoveTo(uint Key)
{
        static double local = 0;
        int w = 0;
        datetime dt;
        bool bEClick, bKeyBuy, bKeySell;
        double take = 0, stop = 0, price;
                                
        bEClick  = (Key & 0x01) == 0x01;    //Left click
        bKeyBuy  = (Key & 0x04) == 0x04;    //SHIFT pressed
        bKeySell = (Key & 0x08) == 0x08;    //CTRL pressed
        Mouse.GetPositionDP(dt, price);
        if (bKeyBuy != bKeySell) Mouse.Hide(); else Mouse.Show();
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLinePrice, 0, 0, price = (bKeyBuy != bKeySell ? price : 0));
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLineTake, 0, 0, take = price + (m_Infos.TakeProfit * (bKeyBuy ? 1 : -1)));
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLineStop, 0, 0, stop = price + (m_Infos.StopLoss * (bKeyBuy ? -1 : 1)));
        if((bEClick) && (bKeyBuy != bKeySell) && (local == 0)) CreateOrderPendent(bKeyBuy, m_Infos.Volume, local = price, take, stop, m_Infos.IsDayTrade); else local = 0;
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLinePrice, OBJPROP_COLOR, (bKeyBuy != bKeySell ? m_Infos.cPrice : clrNONE));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineTake, OBJPROP_COLOR, (take > 0 ? m_Infos.cTake : clrNONE));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineStop, OBJPROP_COLOR, (stop > 0 ? m_Infos.cStop : clrNONE));
};

강조 표시된 부분 위의 줄에 주의하십시오.

Mouse.GetPositionDP(dt, price);

이 줄은 마우스 위치의 값을 캡처합니다. 다음은 이러한 값을 보고하는 코드입니다:

inline void GetPositionDP(datetime &dt, double &price)
{
        dt = Position.dt;
        price = Position.price;
}

하지만 그게 다가 아닙니다. 경우에 따라 화면 위치를 고려하면 차트의 직각 좌표계가 필요합니다. 관련 값을 얻을 수 있는 또 다른 함수가 있습니다. 이 코드는 다음과 같이 표시됩니다:

inline void GetPositionXY(int &X, int &Y)
{
        X = Position.X;
        Y = Position.Y;
}

C_OrderView 클래스로 돌아가 보면 주목해야 할 흥미로운 점이 있습니다:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong           ticket;
        double          price, pp, pt, ps;
        eHLineTrade     hl;
                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        MoveTo(Mouse.GetButtonStatus());
                        break;

// ... The rest of the code ...

}

MoveTo 함수는 이 함수에서 조금 더 일찍 볼 수 있습니다. C_OrderView 클래스의 일부이기도 합니다. 하지만 더 중요한 것은Mouse.GetButtonsStatus 함수입니다. 이 함수는mouse event와 관련된 버튼 및 키의 상태를 반환합니다.

이 함수Mouse.GetButtonStatus는 다음과 같습니다:

inline uint GetButtonStatus(void) const
{
        return Position.ButtonsStatus;
}

이것은 마지막 마우스 이벤트 이후에 기록된 값을 포함하는 변수를 반환하는 한 줄입니다. 이제 이 값을 기록하는 코드로 이동합니다. 그러나 먼저 마우스 초기화 코드를 살펴보겠습니다. 왜냐하면 이 코드는 EA에게 우리가 마우스를 초기화하고 싶고 이제부터 EA가 다양한 마우스 관련 작업을 처리할 것임을 알리기 때문입니다. 이를 담당하는 코드는 다음과 같습니다:

// ... Other things ....

input group "Mouse"
input color     user50 = clrBlack;      //Price line
input color     user51 = clrDarkGreen;  //Positive move
input color     user52 = clrMaroon;     //Negative move
//+------------------------------------------------------------------+

// ... General information ...

//+------------------------------------------------------------------+
int OnInit()
{
        static string   memSzUser01 = "";
        
        Terminal.Init();
        WallPaper.Init(user10, user12, user11);
        Mouse.Init(user50, user51, user52);

// ... The rest of the code ...

따라서 시스템에서 사용할 세 가지 색상을 정의해야 합니다. 이러한 색상은 데이터가 차트에서 명확하고 잘 보이도록 선택해야 합니다. 이에 대해 더 알고자 한다며 Mouse.Init의 코드를 살펴보십시오. 아래에서 볼 수 있습니다:

void Init(color c1, color c2, color c3)
{
        m_Infos.cor01 = c1;
        m_Infos.cor02 = c2;
        m_Infos.cor03 = c3;
        if (m_Infos.szNameObjH != NULL) return;
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, false);
        m_Infos.szNameObjH = "H" + (string)MathRand();
        m_Infos.szNameObjV = "V" + (string)MathRand();
        m_Infos.szNameObjT = "T" + (string)MathRand();
        m_Infos.szNameObjB = "B" + (string)MathRand();
        m_Infos.szNameObjI = "I" + (string)MathRand();
//---
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjH, OBJ_HLINE, 0, 0, 0);
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjV, OBJ_VLINE, 0, 0, 0);
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjT, OBJ_TREND, 0, 0, 0);
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjB, OBJ_BITMAP, 0, 0, 0);
        ObjectCreate(Terminal.Get_ID(), m_Infos.szNameObjI, OBJ_TEXT, 0, 0, 0);
//---
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjH, OBJPROP_TOOLTIP, "\n");
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_TOOLTIP, "\n");
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_TOOLTIP, "\n");
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_TOOLTIP, "\n");
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_TOOLTIP, "\n");
//---
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_WIDTH, 2);
//---
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_BMPFILE, "::" + def_Fillet);
//---
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_FONT, "Lucida Console");
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_FONTSIZE, 10);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_BACK, false);
        Hide();
        Show();
}

이 코드에는 특별한 것이 없습니다. 클래스에서 사용할 일부 객체를 만드는 것뿐입니다. 하지만 강조 표시된 부분은 다소 혼란스러울 수 있습니다. 클래스에서 찾아보면 선언된 곳을 찾을 수 없기 때문입니다. 다른 리소스의 선언과 함께 EA 파일의 코드에 선언되어 있기 때문입니다. 나중에 이 모든 것을 파일로 그룹화할 것이지만 지금은 이 상태로 유지됩니다. 따라서 EA의 코드를 보면 다음 행을 찾을 수 있습니다:

#define def_Resource    "Resources\\SubSupport.ex5"
#define def_Fillet      "Resources\\Fillet.bmp"
//+------------------------------------------------------------------+
#resource def_Resource
#resource def_Fillet

라인은 마우스 초기화 코드에서 강조 표시된 리소스를 보여줍니다.

우리는 다음 조각에 의해 호출되는 이 클래스 내에서 정점에 도달했습니다.

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        Mouse.DispatchMessage(id, lparam, dparam, sparam);
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
                        TimesAndTrade.Resize();
        break;
        }
        Chart.DispatchMessage(id, lparam, dparam, sparam);
        VolumeAtPrice.DispatchMessage(id, sparam);
        ChartRedraw();
}

그런 다음 EA 코드에서 강조 표시된 줄이 다음 코드를 호출합니다:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        int     w = 0;
        uint    key;
        static int b1 = 0;
        static double memPrice = 0;
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        Position.X = (int)lparam;
                        Position.Y = (int)dparam;
                        ChartXYToTimePrice(Terminal.Get_ID(), Position.X, Position.Y, w, Position.dt, Position.price);
                        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjH, 0, 0, Position.price = Terminal.AdjustPrice(Position.price));
                        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjV, 0, Position.dt, 0);
                        key = (uint) sparam;
                        if ((key & 0x10) == 0x10)
                        {
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_COLOR, m_Infos.cor01);
                                b1 = 1;
                        }
                        if (((key & 0x01) == 0x01) && (b1 == 1))
                        {
                                ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false);
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, m_Infos.cor01);
                                ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 0, Position.dt, memPrice = Position.price);
                                b1 = 2;
                        }
                        if (((key & 0x01) == 0x01) && (b1 == 2))
                        {
                                ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 1, Position.dt, Position.price);
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
                                ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjB, 0, Position.dt, Position.price);
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
                                ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_TEXT, StringFormat("%.2f ", Position.price - memPrice));
                                ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjI, 0, Position.dt, Position.price);
                                ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
                        }
                        if (((key & 0x01) != 0x01) && (b1 == 2))
                        {
                                b1 = 0;
                                ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
                                Hide();
                                Show();
                        }
                        Position.ButtonsStatus = (b1 == 0 ? key : 0);
                        break;
        }
}

위의 코드는 채우기 구현 코드가 아닙니다. 위의 코드는 현 개발 단계까지의 EA의 주요 작업만을 지원하고 해결합니다. 이를 이해하려면 마우스 초기화 코드의 한 부분을 주의 깊게 살펴보시기 바랍니다. 다음과 같은 라인이 있습니다:

ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, false);

이 선은 마우스의 가운데 버튼을 클릭할 때 십자선이 표시되는 것을 방지합니다. 그런데 왜 십자선이 생성되지 않도록 해야 할까요? 이를 이해하기 위해 다음 gif를 살펴보겠습니다.

이것은 WDO 차트이며 0.5에서 0.5로 이동합니다. 그러나 우리는 분석을 하려고 할 때 우리 자신이 그다지 정밀하지 않다는 것을 깨닫게 됩니다. 분석을 수행하기 위해서는 어느 정도의 정밀도를 갖는 것이 중요합니다. 그러나 MetaTrader 5의 십자형 도구는 특정 경우에 충분하지 않습니다. 이 경우 새로운 시스템에 의존해야 하므로 EA가 실행 중일 때 MetaTrader 5가 십자형을 생성하지 않도록 강제해야 합니다. 대신 분석을 수행하기 위해 자체 십자선을 만듭니다. 이를 통해 우리는 더 관련 있는 데이터를 추가하고 가장 적절하다고 생각하는 방식으로 이들 데이터를 나타낼 수 있습니다. 이는 EA를 실행한 상태에서 데이터 모델링 시스템을 사용한 결과를 보여주는 아래 그림에서 확인할 수 있습니다.


보시다시피 지정된 값은 정확히 이동 값에 해당하게 됩니다. 또한 시각적 표현도 있습니다: 값이 양수이면 표시가 녹색으로 바뀌고 음수이면 표시가 빨간색으로 바뀝니다. 추적이 생성되고 시작점과 끝점도 표시됩니다. 그러나 이미 언급했듯이 이 시스템은 아직 완성되지 않았습니다. 여러분이 원하는대로 필요한대로 여전히 개선 사항을 추가 할 수 있습니다. C_Mouse 클래스의 새 버전이 나올 때까지 여러분은 이 버전을 더 개선하고 필요한 더 많은 데이터를 가질 수 있습니다. 그러나 이를 위해서는 모든 것이 어떻게 작동하는지를 이해해야 합니다. 그러기 위해 C_Mouse 클래스 메시지 코드를 자세히 살펴 보겠습니다. 


C_Mouse 클래스의 DispathMessage 코드 이해하기

코드는 마우스 위치 변수의 값을 캡처하고 조정하는 것부터 시작합니다. 아래 코드에서 수행됩니다:

Position.X = (int)lparam;
Position.Y = (int)dparam;
ChartXYToTimePrice(Terminal.Get_ID(), Position.X, Position.Y, w, Position.dt, Position.price);

위치 값은 MetaTrader 5 플랫폼이 보고 하지만 실제로는 이 값은 운영 체제에서 가져오는 것이고 화면 좌표(예: X 및 Y)에 있습니다. 우리는 이를 차트 좌표로 변환해야 하며 이를 위해 ChartXYToTimePrice함수를 사용합니다. 이러한 MQL5의 기능은 작업을 훨씬 단순화 시켜 줍니다.

이 작업이 완료되면 가격과 타임 라인을 이동합니다.

ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjH, 0, 0, Position.price = Terminal.AdjustPrice(Position.price));
ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjV, 0, Position.dt, 0);

하지만 타임라인은 처음에는 보이지 않기 때문에 처음에는 차트에서 볼 수 없습니다. 그후 마우스의 상태를 캡처합니다.

key = (uint) sparam;

지금까지 잘 되고 있을 것입니다. 이제 다음 작업을 할 차례입니다: 가운데 버튼이 눌린 상태인지 확인합니다. 그렇다면 타임 라인이 차트에 표시됩니다. 이것은 다음의 코드에서 구현됩니다:

if ((key & 0x10) == 0x10)
{
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjV, OBJPROP_COLOR, m_Infos.cor01);
        b1 = 1;
}

이를 위해 정적 변수를 사용하여 이 이벤트를 저장합니다. 그러므로 이제부터 다른 이벤트는 EA에서 처리되지 않습니다. 실제로는 마우스 왼쪽 버튼을 누를 때만 시작됩니다. 다시 말해 저는 MetaTrader 5 플랫폼의 모든 사용자에게 이미 알려진 동일한 작업 모드를 사용한 것입니다. 사용자가 리서치 라인을 수행하는 새로운 방법을 배워야 하는 경우 작업을 포기할지도 모르므로 이것이 가장 적절한 방법입니다. 그런 다음 EA는 다음의 코드에 의해 수행되는 왼쪽 클릭을 기다립니다.

if (((key & 0x01) == 0x01) && (b1 == 1))
{
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, m_Infos.cor01);
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 0, Position.dt, memPrice = Position.price);
        b1 = 2;
}

클릭이 발생하면 차트 이동 시스템이 잠깁니다. 그런 우리는 분석 지점을 나타내는 추세선을 얻게 됩니다. 그런 다음 시스템은b1의 새로운 값에서 볼 수 있는 다음 단계로 전환됩니다. 이제 실제로 더 많은 정보를 추가하거나 가장 관련성이 있다고 생각되는 것을 추가 할 수 있는 부분에 왔습니다. 저는 여기에서 시스템을 시연하고 있지만 여러분이 원하는 것을 자유롭게 입력할 수 있습니다. 이 작업은 아래와 같이 수행되어야 합니다:

if (((key & 0x01) == 0x01) && (b1 == 2))
{
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjT, 1, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjT, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjB, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjB, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
        ObjectSetString(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_TEXT, StringFormat("%.2f ", Position.price - memPrice));
        ObjectMove(Terminal.Get_ID(), m_Infos.szNameObjI, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szNameObjI, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
}

강조 표시된 선에 주의하십시오. 왜냐하면 여러분이 차트 화면에서 보게되는 값이 표시되고 계산되는 곳이기 때문입니다. 여러분은 거기에 더 유용한 정보를 추가할 수 있습니다. 이 부분이 작동함으로써 왼쪽 버튼을 누르고 있는 동안 데이터가 계산되고 표시됩니다. 이것은 MetaTrader 5의 기본 설정과 같은 동작이지만 값은 사용자의 필요에 따라 조정되고 모델링됩니다.

이제 우리는 아래와 같은 테스트를 하나 더 수행해야 합니다.

if (((key & 0x01) != 0x01) && (b1 == 2))
{
        b1 = 0;
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
        Hide();
        Show();
}

마우스 왼쪽 버튼을 놓으면 차트가 해제되고 끌 수 있으며 분석을 만드는 데 사용된 모든 요소가 숨겨지고 프라이스 라인만 다시 표시됩니다. 마지막으로 다음과 같은 마지막 코드 부분이 있습니다.

Position.ButtonsStatus = (b1 == 0 ? key : 0);

스터디가 호출되지 않은 경우 마우스 버튼 상태가 저장되어 EA의 다른 곳에서 사용할 수 있지만 스터디가 호출된 경우 NULL 값이 상태 데이터로 사용되므로 주문을 생성할 수 없거나 포지션을 변경할 수 없습니다.

아래 비디오에서 이러한 추적이 실제로 어떻게 작동하는지, 화면의 볼륨을 어떻게 조정하는지를 확인할 수 있습니다. 지표는 많은 도움이 되며 올바르게 사용하는 방법을 배우면 큰 도움이 됩니다. Times & Trade와 함께 지표는 시장에서 가장 발전된 거래 방법 중 하나인 이중 노이즈 분석 도구를 구성합니다.




MetaQuotes 소프트웨어 사를 통해 포르투갈어가 번역됨
원본 기고글: https://www.mql5.com/pt/articles/10419

파일 첨부됨 |
EA_-_Mouse.zip (5986.31 KB)
MFI로 거래 시스템을 설계하는 방법 알아보기 MFI로 거래 시스템을 설계하는 방법 알아보기
가장 인기 있는 기술 지표를 기반으로 거래 시스템을 설계하는 방법에 대한 시리즈 중 이번 기사에서는 새로운 기술 지표인 MFI(Money Flow Index)를 사용할 것입니다. 우리는 MFI에 대해 자세히 알아보고 MetaTrader 5에서 실행할 수 있도록 MQL5로 간단한 거래 시스템을 개발할 것입니다.
Expert Advisor 개발 기초부터 (파트 13): Time and Trade (II) Expert Advisor 개발 기초부터 (파트 13): Time and Trade (II)
오늘은 시장 분석을 위한 Times & Trade 시스템의 두 번째 부분입니다. 이전 기사 "Times & Trade (I)"에서 우리는 시장에서 실행된 거래에 대해 가능한 가장 빠른 해석을 가능하게 하는 지표를 가질 수 있는 차트 구성 시스템에 대해 알아보았습니다.
Expert Advisor 개발 기초부터 (파트 15): 웹에서 데이터 액세스 하기(I) Expert Advisor 개발 기초부터 (파트 15): 웹에서 데이터 액세스 하기(I)
MetaTrader 5를 통해 온라인 데이터에 어떻게 액세스할 수 있을까요? 웹에는 엄청난 양의 정보를 제공하는 많은 웹사이트가 있습니다. 여러분이 알아야 할 것은 어디에서 이 정보를 가장 잘 사용할 수 있을까 하는 점입니다.
볼륨으로 거래 시스템을 설계하는 방법 알아보기 볼륨으로 거래 시스템을 설계하는 방법 알아보기
이 글은 인기 있는 기술 지표를 기반으로 거래 시스템을 설계하는 방법과 관련한 시리즈의 새로운 글입니다. 이 기사에서는 볼륨 지표에 대해 설명합니다. 볼륨의 개념은 금융 시장 거래에서 매우 중요한 요소 중 하나이며 우리 모두 주의를 기울여야 할 요소입니다. 이 글을 통해 볼륨 지표로 간단한 거래 시스템을 설계하는 방법에 대해서 알아보겠습니다.