English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
preview
Expert Advisor 개발 기초부터(19부): 새로운 주문 시스템(II)

Expert Advisor 개발 기초부터(19부): 새로운 주문 시스템(II)

MetaTrader 5트레이딩 | 6 7월 2023, 15:58
401 0
Daniel Jose
Daniel Jose

소개

이전 글인 Expert Advisor 처음부터 개발하기(18부)에서는 계좌 운용에 차이에 따른 네팅 계좌와 헤징 계좌에서 서로 다른 방식의 거래를 하게 해 주는 시스템을 만드는 것을 목표로 주문 시스템에 몇 가지 수정, 변경 및 조정을 구현했습니다. 네팅 유형의 경우 시스템이 평균 가격을 생성하며 자산에 대해 하나의 오픈 포지션만 보유합니다. 헤징 계좌에서는 여러 개의 오픈 포지션을 보유할 수 있으며, 각각의 포지션에는 개별 한도가 있게 됩니다. 여러분은 동일한 자산을 동시에 매수하고 매도할 수 있습니다. 이는 헤징 계정에서만 할 수 있습니다. 이것이 옵션 거래를 이해할 수 있는 기초입니다.

하지만 이제 우리는 주문 시스템을 완전히 시각화함으로써 메시지 상자를 없애고 메시지 상자 없이도 각 포지션에 어떤 값이 있는지 분석할 수 있어야 합니다. 새로운 주문 시스템을 살펴보는 것만으로도 이것이 가능하게 될 것입니다. 이렇게 하면 우리는 한 번에 여러 가지를 조정할 수 있습니다. 또한 EA가 추가적인 계산 없이 관련 정보를 실시간으로 표시하기 때문에 우리는 OCO 포지션 또는 펜딩 OCO 오더의 손익의 한도를 쉽게 알 수 있게 됩니다.

이것은 구현을 위한 첫 번째 부분이지만 처음부터 만드는 것이 아닙니다; 우리는 거래하는 자산의 차트에 더 많은 객체와 이벤트를 추가하여 기존의 시스템을 수정할 것입니다.


1.0. 계획

사용할 시스템 설계 계획은 특별히 어렵지 않습니다: 차트에 주문을 표시하는 시스템 부분만을 변경하여 기존 시스템을 수정할 것입니다. 이것이 주요 아이디어이고 매우 간단해 보입니다. 하지만 실제로는 많은 창의력이 필요합니다. 데이터를 조작하고 모델링 해서 MetaTrader 5 플랫폼이 모든 작업을 대신 수행하도록 해야 하기 때문입니다.

데이터를 모델링하는 방법에는 여러 가지가 있으며 각각의 방법에는 장단점이 있습니다.

  • 첫 번째 방법은 목록을 사용하는 것입니다. 주기 단일, 주기 이중, 심지어 해싱 시스템일 수도 있습니다. 이러한 접근 방식 중 하나를 사용하면 시스템을 쉽게 구현할 수 있다는 장점이 있습니다. 그러나 단점은 데이터 조작을 못하게 하거나 주문 수를 제한할 수 있다는 단점이 있습니다. 또한 이 경우 목록을 저장하기 위해 모든 추가적인 로직을 만들어야 합니다.
  • 두 번째 방법은 클래스의 배열을 만드는 것인데 클래스는 새로 생성된 모든 객체를 포함하고 유지 관리합니다. 이 경우 배열은 목록처럼 작동하지만 목록을 사용할 경우 코딩해야 하는 몇 가지 사항을 MQL5가 이미 지원하기 때문에 코드를 더 적게 작성하게 됩니다. 그러나 이 상황에서는 이벤트 처리와 같은 다른 문제가 발생할 수 있으며 이는 매우 어려운 문제일 수 있습니다.
  • 세 번째 방법은 우리가 사용할 방법입니다. MQL5에서 생성된 코드가 동적 객체를 지원하도록 강제할 것입니다. 이것은 비현실적인 것처럼 보이지만 사용할 데이터의 올바른 모델링을 수행하면 화면의 오브젝트 수의 제한이 없는 시스템을 MQL5 언어를 통해 만들 수 있습니다. 또한 모든 오브젝트가 이벤트를 생성하고 수신할 수 있게 됩니다. 또한 각각의 특징에도 불구하고 플랫폼에서는 마치 이들이 목록이나 배열 인덱스에 있는 것처럼 모두 연결된 것으로 간주합니다.

만약 이것이 구현되기가 쉽지 않다고 생각되면 C_HLineTrade 클래스의 다음과 같은 코드 부분을 살펴보시기 바랍니다:

inline void SetLineOrder(ulong ticket, double price, eHLineTrade hl, bool select)
{
        string sz0 = def_NameHLineTrade + (string)hl + (string)ticket, sz1;
                                
        ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, 0, 0, 0);

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

강조 표시된 부분은 원하는 만큼 수평선을 만들 수 있으며 완전히 독립적인 방식으로 이벤트를 수신한다는 것을 명확하게 보여줍니다. 각 줄의 이름은 고유할 것이므로 각 줄의 이름을 기반으로 이벤트를 구현하기만 하면 됩니다. 나머지는 MetaTrader 5 플랫폼이 알아서 처리합니다. 결과는 다음과 같습니다:


이 모델링은 이미 이상적인 것처럼 보이지만 우리에게 실제로 필요한 결과를 얻기에는 충분하지 않습니다. 아이디어는 구현될 수 있습니다. 그러나 현재 EA에서 사용할 수 있는 데이터 모델링은 이상적이지 않습니다. 하나의 이름에 기반한 객체를 무제한으로 가질 수 없기 때문입니다. 상당히 심층적인 코드 수정이 필요한 몇 가지 변경 사항이 필요합니다.

이제 이 새로운 데이터 모델링 방법을 구현하기 시작할 것입니다. 가능한 한 안정적으로 작동해야 하므로 전체 코드는 안정적으로 유지하면서 필요한 부분만 변경할 것입니다. 모든 작업은 MetaTrader 5 플랫폼에서 수행하며 플랫폼이 모델링을 어떻게 이해해야 하는지를 알아볼 것입니다.


2.0. 구현

첫 번째 수정 사항은 C_HLineTrade를 새로운 C_ObjectsTrade 클래스로 변경하는 것입니다. 이 새로운 클래스는 우리가 필요로 하는 것, 즉 객체를 무제한으로 연결할 수 있는 방법을 지원할 수 있습니다.

다음 코드의 원래의 정의를 살펴보는 것으로부터 시작하겠습니다.

class C_ObjectsTrade
{
//+------------------------------------------------------------------+
#define def_NameObjectsTrade 	"SMD_OT"
#define def_SeparatorInfo       '*'
#define def_IndicatorTicket0    1
//+------------------------------------------------------------------+
        protected:
                enum eIndicatorTrade {IT_NULL, IT_STOP= 65, IT_TAKE, IT_PRICE};
//+------------------------------------------------------------------+

// ... The rest of the class code

여기에 우리가 구현할 초기 기반이 있습니다. 향후 확장될 예정이지만 지금 저는 시스템이 수정되고 새로운 데이터 모델링이 적용되더라도 안정적으로 유지되기를 원합니다.

'protected' 선언 내에서도 우리는 다음과 같은 함수들을 가지고 있습니다:

inline double GetLimitsTake(void) const { return m_Limits.TakeProfit; }
//+------------------------------------------------------------------+
inline double GetLimitsStop(void) const { return m_Limits.StopLoss; }
//+------------------------------------------------------------------+
inline bool GetLimitsIsBuy(void) const { return m_Limits.IsBuy; }
//+------------------------------------------------------------------+
inline void SetLimits(double take, double stop, bool isbuy)
{
        m_Limits.IsBuy = isbuy;
        m_Limits.TakeProfit = (m_Limits.TakeProfit < 0 ? take : (isbuy ? (m_Limits.TakeProfit > take ? m_Limits.TakeProfit : take) : (take > m_Limits.TakeProfit ? m_Limits.TakeProfit : take)));
        m_Limits.StopLoss = (m_Limits.StopLoss < 0 ? stop : (isbuy ? (m_Limits.StopLoss < stop ? m_Limits.StopLoss : stop) : (stop < m_Limits.StopLoss ? m_Limits.StopLoss : stop)));
}
//+------------------------------------------------------------------+
inline int GetBaseFinanceLeveRange(void) const { return m_BaseFinance.Leverange; }
//+------------------------------------------------------------------+
inline int GetBaseFinanceIsDayTrade(void) const { return m_BaseFinance.IsDayTrade; }
//+------------------------------------------------------------------+
inline int GetBaseFinanceTakeProfit(void) const { return m_BaseFinance.FinanceTake; }
//+------------------------------------------------------------------+
inline int GetBaseFinanceStopLoss(void) const { return m_BaseFinance.FinanceStop; }

현재 이러한 함수들은 우리가 향후 구현할 다른 계획을 위한 보안 조치일 뿐입니다. 데이터와 구문 분석은 다른 위치에서 구현할 수 있지만 일부 항목은 상속 체인에서 가능한 한 낮게 남겨두는 것이 좋습니다. 반환값이 파생 클래스에서만 사용되더라도 저는 이를 직접 허용하고 싶지 않습니다: 저는 파생 클래스가 이 C_ObjectsTrade 객체 클래스 내부의 값에 액세스하지 않기를 원합니다. 왜냐하면 파생 클래스가 프로시저 호출을 통해 관련 변경을 수행하지 않고 기본 클래스의 값을 변경하면 객체 클래스 캡슐화 개념이 깨지게 되고 향후 수정을 하거나 버그를 고치는 것이 어려워지기 때문입니다.

호출 중복을 최대한 최소화하기 위해 모든 함수가 인라인으로 선언되므로 실행 파일의 크기가 약간 증가하지만 시스템의 보안은 더욱 강화됩니다.

이제 비공개 선언에 대해 알아보겠습니다.

//+------------------------------------------------------------------+
        private :
                string  m_SelectObj;
                struct st00
                {
                        double  TakeProfit,
                                StopLoss;
                        bool    IsBuy;
                }m_Limits;
                struct st01
                {
                        int     FinanceTake,
                                FinanceStop,
                                Leverange;
                        bool    IsDayTrade;
                }m_BaseFinance;
//+------------------------------------------------------------------+
                string MountName(ulong ticket, eIndicatorTrade it)
                {
                        return StringFormat("%s%c%c%c%d", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket);
                }
//+------------------------------------------------------------------+

가장 중요한 부분은 강조 표시된 조각으로 객체명을 모델링합니다. 여기서 우리는 시스템에서 사용할 수 있는 기본 기능은 그대로 유지하고 있습니다. 모델링을 먼저 생성하고 수정하여 시스템을 안정적으로 유지하기 때문입니다. 그런 다음 새로운 객체를 추가할 것입니다. 이 작업은 매우 쉽고 빠르게 완료할 수 있습니다. 또한 이미 달성한 안정성을 유지할 것입니다.

여기에 표시된 것보다 더 많은 코드가 변경되었지만 여기서는 새로운 함수들과 이전 코드와 비교하여 크게 변경된 함수에만 초점을 맞추겠습니다.

첫 번째 함수는 아래와 같습니다:

inline string CreateIndicatorTrade(ulong ticket, eIndicatorTrade it, bool select)
{
        string sz0 = MountName(ticket, it);
                                
        ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, 0, 0, 0);
        ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_COLOR, (it == IT_PRICE ? clrBlue : (it == IT_STOP ? clrFireBrick : clrForestGreen)));
        ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_WIDTH, 1);
        ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_STYLE, STYLE_DASHDOT);
        ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTABLE, select);
        ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTED, false);
        ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_BACK, true);
        ObjectSetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, (string)ticket + " "+StringSubstr(EnumToString(it), 3, 10));
                                
        return sz0;
}

이 함수는 현재 수평선만 생성합니다. 이름 생성 코드에 주목하세요; 이제 색상은 사용자가 아닌 코드에 의해 내부적으로 정의된다는 점에 유의하세요.

그런 다음 아래에서 볼 수 있듯이 동일한 함수를 오버로드합다.

inline string CreateIndicatorTrade(ulong ticket, double price, eIndicatorTrade it, bool select)
{
        if (price <= 0)
        {
                RemoveIndicatorTrade(ticket, it);
                return NULL;
        }
        string sz0 = CreateIndicatorTrade(ticket, it, select);
        ObjectMove(Terminal.Get_ID(), sz0, 0, 0, price);
                                
        return sz0;
}

이 두 함수는 동일해 보이지만 실제로는 다르므로 혼동하지 마세요. 간단한 함수를 만든 다음 새로운 매개변수를 추가하여 특정 유형의 모델링을 축적하는 방식 등과 같이 오버로드는 매우 일반적입니다. 만약 오버로딩을 통해 구현하지 않으면 동일한 코드 시퀀스를 반복해야 하는 경우가 있습니다. 이는 선언하는 것을 잊어버릴 수 있기 때문에 위험합니다. 또한 그리 실용적이지 않습니다. 그러므로 우리는 여러 번 호출하는 대신 한 번만 호출하도록 함수를 오버로드 합니다.

여기서 한 가지 말씀드리고자 하는 것은 이번 두 번째 버전에서 강조된 부분입니다. 여기서 만들 필요가 없습니다. 다른 곳에서 할 수 있습니다. 제로 가격으로 어떤 객체를 만들려고 할 때 객체는 실제로는 파괴되어야 합니다.

실제로 이런 일이 발생하는 순간을 보려면 아래 코드를 살펴보시기 바랍니다:

class C_Router : public C_ObjectsTrade
{

// ... Internal class code ....

                void UpdatePosition(int iAdjust = -1)
                        {

// ... Internal function code ...

                                for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
                                {
                                        ul = PositionGetInteger(POSITION_TICKET);
                                        m_bContainsPosition = true;
                                        CreateIndicatorTrade(ul, PositionGetDouble(POSITION_PRICE_OPEN), IT_PRICE, false);
                                        CreateIndicatorTrade(ul, take = PositionGetDouble(POSITION_TP), IT_TAKE, true);
                                        CreateIndicatorTrade(ul, stop = PositionGetDouble(POSITION_SL), IT_STOP, true);

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

EA가 Ontrade 이벤트를 수신할 때마다 위의 함수를 실행하고 선택한 지점에 지표를 생성하려고 시도하지만 사용자가 한도를 제거하면 0이 됩니다. 따라서 이 함수를 호출하면 지표가 차트에서 삭제되어 메모리에 쓸모없는 객체가 생기지 않습니다. 따라서 생성 시점에서 바로 확인이 이루어지기 때문에 우리에게는 좋습니다.

그러나 일부 사람들은 실제 코드에서 어떻게 사용되는지 완전히 이해하지 못할 수 있기 때문에 여전히 오버로딩의 문제가 있습니다. 이를 이해하려면 아래의 두 코드를 살펴보시기 바랍니다:

class C_OrderView : public C_Router
{
        private  :
//+------------------------------------------------------------------+
        public   :
//+------------------------------------------------------------------+
                void InitBaseFinance(int nContracts, int FinanceTake, int FinanceStop, bool b1)
                        {                       
                                SetBaseFinance(nContracts, FinanceTake, FinanceStop, b1);
                                CreateIndicatorTrade(def_IndicatorTicket0, IT_PRICE, false);
                                CreateIndicatorTrade(def_IndicatorTicket0, IT_TAKE, false);
                                CreateIndicatorTrade(def_IndicatorTicket0, IT_STOP, false);
                        }
//+------------------------------------------------------------------+

// ... Rest of the code...
class C_Router : public C_ObjectsTrade
{

// ... Class code ...

                void UpdatePosition(int iAdjust = -1)
                        {
// ... Function code ....
                                for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
                                {
                                        ul = PositionGetInteger(POSITION_TICKET);
                                        m_bContainsPosition = true;
                                        CreateIndicatorTrade(ul, PositionGetDouble(POSITION_PRICE_OPEN), IT_PRICE, false);

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

두 경우 모두 사용 중인 함수의 이름이 동일하다는 점에 유의하세요. 또한 둘 다 동일한 C_ObjectsTrade 클래스에 속해 있습니다. 그러나 이 경우에도 컴파일러는 매개 변수의 수로 인해 이들을 구분할 수 있습니다. 자세히 살펴보면 유일한 차이점은 추가된 'price' 매개변수 뿐이지만 다른 매개변수도 있을 수 있습니다. 보시다시피 한 번의 호출로 오버로드 된 버전 중 하나에 있는 모든 코드를 복사하는 것이 훨씬 쉬우므로 결국 유지 관리가 더 쉬운 깔끔한 코드를 갖게 됩니다.

이제 C_ObjectsTrade 클래스로 돌아가 보겠습니다. 다음으로 이해해야 할 함수는 다음과 같습니다:

bool GetInfosOrder(const string &sparam, ulong &ticket, double &price, eIndicatorTrade &it)
{
        string szRet[];
        char szInfo[];
                                
        if (StringSplit(sparam, def_SeparatorInfo, szRet) < 2) return false;
        if (szRet[0] != def_NameObjectsTrade) return false;
        StringToCharArray(szRet[1], szInfo);
        it = (eIndicatorTrade)szInfo[0];
        ticket = (ulong) StringToInteger(szRet[2]);
        price = ObjectGetDouble(Terminal.Get_ID(), sparam, OBJPROP_PRICE);
                                
        return true;
}

사실 이는 새로운 시스템 전체의 심장이자 정신이며 몸통입니다. 이 함수는 매우 단순해 보이지만 우리의 새로운 모델링 시스템이 요구하는 대로 전체 EA가 작동하도록 하는 데 필수적인 작업을 수행합니다.

강조 표시된 코드, 특히 StringSplit 함수를 주의 깊게 살펴보세요. 만약 이 함수가 MQL5에 존재하지 않는다면 코딩을 해야 합니다. 다행히 MQL5에는 이 함수가 있으므로 우리는 이 함수를 최대한 활용할 것입니다. 이 함수가 하는 일은 객체의 이름을 필요한 데이터로 분해하는 것입니다. 객체 이름이 생성되면 매우 특정한 방식으로 모델링 되며 이 때문에 우리는 이 코딩 모델을 되돌릴 수 있으므로 StringSplit은 StringFormat 함수가 수행하는 작업을 원래대로 되돌릴 수 있습니다.

나머지 함수는 객체 이름에 있는 데이터를 캡처하여 테스트하고 나중에 사용할 수 있도록 합니다. 즉 MetaTrader 5가 데이터를 생성하고 우리는 데이터를 분해하여 무슨 일이 일어났는지 파악한 다음 MetaTrader 5에 어떤 단계를 거쳐야 하는지를 알려줍니다. 우리의 목적은 MetaTrader 5가 제대로 작동하도록 하는 것입니다. 저는 모델을 처음부터 새로 만드는 것이 아닙니다; 인터페이스와 EA를 처음부터 모델링하는 것입니다. 따라서 외부의 솔루션을 찾는 대신 MetaTrader 5가 제공하는 자원을 활용해야 합니다.

아래 코드에서는 우리가 위에서 수행한 작업과 매우 유사한 작업을 수행합니다:

inline void RemoveAllsIndicatorTrade(bool bFull)
{
        string sz0, szRet[];
        int i0 = StringLen(def_NameObjectsTrade);
                                
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        for (int c0 = ObjectsTotal(Terminal.Get_ID(), -1, -1); c0 >= 0; c0--)
        {
                sz0 = ObjectName(Terminal.Get_ID(), c0, -1, -1);
                if (StringSubstr(sz0, 0, i0) == def_NameObjectsTrade)
                {
                        if (!bFull)
                        {
                                StringSplit(sz0, def_SeparatorInfo, szRet);
                                if (StringToInteger(szRet[2]) == def_IndicatorTicket0) continue;
                        }
                }else continue;                                         
                ObjectDelete(Terminal.Get_ID(), sz0);
        }
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
}

차트에서 라인을 제거할 때마다 차트에서 EA를 제거할 때와 마찬가지로 청산할 포지션이나 제거할 한도의 레벨의 해당 객체를 제거해야 합니다. 객체를 삭제해야 하지만 꼭 필요한 경우가 아니면 삭제해서는 안 되는 라인도 있습니다(예: Ticket0). 이들이 삭제되지 않도록 하려면 강조 표시된 코드를 사용하세요. 이 티켓은 나중에 설명할 다른 코드 부분에서 매우 중요하기 때문에 이 기능이 없으면 매번 이 Ticket0을 새로 만들어야 합니다.

다른 모든 경우에는 특정 항목을 삭제해야 합니다. 이를 위해 아래에 표시된 또 다른 제거 함수를 사용합니다.

inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        if ((it != NULL) && (it != IT_PRICE))
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, it));
        else
        {
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, IT_PRICE));
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, IT_TAKE));
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, IT_STOP));
        }
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
}

이후 새로운 루틴은 아래에서 확인할 수 있습니다:

inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy)
{
        double ad = Terminal.GetAdjustToTrade() / (Leverange * Terminal.GetVolumeMinimal());
        ObjectMove(Terminal.Get_ID(), MountName(ticket, it), 0, 0, price);
        if (it == IT_PRICE)
        {
                ObjectMove(Terminal.Get_ID(), MountName(ticket, IT_TAKE), 0, 0, price + Terminal.AdjustPrice(FinanceTake * (isBuy ? ad : (-ad))));
                ObjectMove(Terminal.Get_ID(), MountName(ticket, IT_STOP), 0, 0, price + Terminal.AdjustPrice(FinanceStop * (isBuy ? (-ad) : ad)));
        }
}

가격 축에 객체를 배치합니다. 하지만 여러 가지 이유로 곧 중단될 예정이므로 너무 애착을 갖지 마세요. 그 중에는 이 시리즈의 다른 기사에서 논의한 내용도 있습니다: 하나의 차트에 여러개의 지표 넣기(파트 05): MetaTrader 5를 RAD(I) 시스템으로 변환하기. 이 문서에는 직교 좌표를 사용하여 위치를 지정할 수 있는 객체를 보여주는 표가 있으며 이러한 좌표는 X와 Y입니다. 가격 및 시간의 좌표는 경우에 따라 유용하지만 항상 편리한 것은 아닙니다. 화면의 특정 지점에 배치해야 하는 요소를 배치하려는 경우 가격 및 시간 좌표를 사용하여 개발하는 것이 더 빠르지만 X 및 Y 시스템보다 작업하기가 훨씬 더 어렵습니다.

다음에는 변경할 예정이지만 지금은 지금까지 사용하던 시스템을 대체할 수 있는 시스템을 만드는 것이 목적입니다.

그런 다음 C_ObjectsTrade 클래스의 마지막 중요한 함수가 있습니다. 다음 줄과 같습니다:

inline double GetDisplacement(const bool IsBuy, const double Vol, eIndicatorTrade it) const
{
        int i0 = (it == IT_TAKE ? m_BaseFinance.FinanceTake : m_BaseFinance.FinanceStop),
            i1 = (it == IT_TAKE ? (IsBuy ? 1 : -1) : (IsBuy ? -1 : 1));
        return (Terminal.AdjustPrice(i0 * (Vol / m_BaseFinance.Leverange) * Terminal.GetAdjustToTrade() / Vol) * i1);
}

이 함수는 펜딩 오더가 접수될 위치와 시장가로 진입할 포지션에 대해 차트 트레이더에서 지정한 값을 시장별로 변환합니다.

이러한 모든 변경 사항은 C_HLineTrade 함수를 C_ObjectsTrade로 변환하기 위해 구현되었습니다. 하지만 이러한 변경에는 몇 가지 다른 변경 사항도 필요했습니다. 예를 들어 상당히 많이 변경된 클래스는 C_ViewOrder입니다. 이 클래스의 일부 부분은 존재할 필요가 없기 때문에 존재하지 않게 되었으며 나머지 기능은 변경되었습니다. 특별히 주의해야 할 함수들은 아래에 강조 표시되어 있습니다.

첫 번째는 차트 트레이더에서 오는 데이터를 초기화하는 함수입니다.

void InitBaseFinance(int nContracts, int FinanceTake, int FinanceStop, bool b1)
{                       
        SetBaseFinance(nContracts, FinanceTake, FinanceStop, b1);
        CreateIndicatorTrade(def_IndicatorTicket0, IT_PRICE, false);
        CreateIndicatorTrade(def_IndicatorTicket0, IT_TAKE, false);
        CreateIndicatorTrade(def_IndicatorTicket0, IT_STOP, false);
}

강조 표시된 부분이 실제로 Ticket0이 생성되는 부분입니다. 이 티켓은 마우스와 키보드를 사용하여 대기 주문을 하는 데 사용됩니다: (SHIFT) 키를 누르면 매수, (CTRL) 키를 누르면 매도가 됩니다. 이전에는 이 지점에 라인을 생성하여 주문의 위치를 표시하는 데 사용했습니다. 이제 상황이 훨씬 더 간단해졌습니다. 주문이 접수되는 것이 보여지는 것과 마찬가지로 펜딩 오더나 오픈 포지션도 볼 수 있습니다. 이는 우리가 항상 시스템을 체크하고 있다는 것을 의미합니다. 마치 자동차를 조립할 때 브레이크를 항상 점검하여 실제로 사용해야 할 때 브레이크가 어떻게 작동할지를 알 수 있다는 것과 같습니다.

긴 코드의 가장 큰 문제점은 우리가 함수를 실제로 사용할 때만 함수가 작동하는지를 알 수 있다는 점입니다. 하지만 이제는 모든 함수를 사용하지 않더라도 다른 부분에서 코드를 재사용하고 있기 때문에 시스템이 항상 점검되고 있습니다.

이 글에서 마지막으로 말씀드리고자 하는 루틴은 아래와 같습니다. 펜딩 오더를 낼 것입니다. 이전 기사의 동일한 기능에 비해 매우 컴팩트 해진 것을 확인할 수 있습니다.

inline void MoveTo(uint Key)
{
        static double local = 0;
        datetime dt;
        bool    bEClick, bKeyBuy, bKeySell, bCheck;
        double  take = 0, stop = 0, price;
                                
        bEClick  = (Key & 0x01) == 0x01;    //Let mouse button click
        bKeyBuy  = (Key & 0x04) == 0x04;    //Pressed SHIFT
        bKeySell = (Key & 0x08) == 0x08;    //Pressed CTRL  
        Mouse.GetPositionDP(dt, price);
        if (bKeyBuy != bKeySell)
        {
                Mouse.Hide();
                bCheck = CheckLimits(price);
        } else Mouse.Show();
        PositionAxlePrice((bKeyBuy != bKeySell ? price : 0), def_IndicatorTicket0, IT_PRICE, (bCheck ? 0 : GetBaseFinanceTakeProfit()), (bCheck ? 0 : GetBaseFinanceStopLoss()), GetBaseFinanceLeveRange(), bKeyBuy);
        if((bEClick) && (bKeyBuy != bKeySell) && (local == 0)) CreateOrderPendent(bKeyBuy, local = price);
        local = (local != price ? 0 : local);
}

그 이유는 이제 시스템에 새로운 규칙이 적용되어 함수가 "약간의 무게를 잃고" 더 컴팩트 해 졌기 때문입니다.


결론

다음 글에서 사용할 몇 가지 변경 사항을 여기에 제시했습니다. 이 모든 것의 목적은 더 단순하게 만들고 시간에 따라 달라질 수 있는 것들을 보여주기 위한 것입니다. 저는 모든 사람이 운영에 도움이 되는 EA를 프로그래밍하는 방법을 따라하고 배우도록 하고 싶습니다. 그래서 완성된 바로 사용할 수 있는 시스템만을 제시하고 싶지는 않습니다. 저는 해결해야 할 문제가 있다는 것을 보여주고 개발 과정에서 발생하는 문제와 이슈를 해결하기 위해 어떤 경로를 밟았는지를 제시하고 싶었습니다. 이 점을 이해하실 거라 생각합니다. 시스템을 만들어서 바로 사용하도록 하는 것이 목적이라면 그렇게 해서 시스템을 판매하는 것도 좋겠지만 그것은 제가 하고자 하는 것이 아니기 때문입니다...

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

파일 첨부됨 |
EA.zip (12023.87 KB)
포스 인덱스 지표로 트레이딩 시스템을 설계하는 방법 알아보기 포스 인덱스 지표로 트레이딩 시스템을 설계하는 방법 알아보기
가장 인기 있는 보조지표로 트레이딩 시스템을 설계하는 방법을 소개하는 시리즈의 새로운 글에 오신 것을 환영합니다. 이 글에서는 새로운 기술 지표와 포스 인덱스 지표를 사용하여 트레이딩 시스템을 만드는 방법에 대해 알아보겠습니다.
Expert Advisor 개발 기초부터(18부): 새로운 주문 시스템(I) Expert Advisor 개발 기초부터(18부): 새로운 주문 시스템(I)
이것이 새로운 주문 시스템의 첫 번째 부분입니다. 기사를 통해 EA를 문서화하기 시작한 이후 우리는 차트 주문 시스템 모델을 그대로 유지하면서 다양한 변경 및 개선을 추가했습니다.
베어스 파워 보조 지표로 트레이딩 시스템을 설계하는 방법 알아보기 베어스 파워 보조 지표로 트레이딩 시스템을 설계하는 방법 알아보기
이번 글은 가장 인기 있는 보조지표로 트레이딩 시스템을 설계하는 방법을 알아보는 시리즈의 새로운 글입니다. 이번에는 베어스 파워 보조지표로 트레이딩 시스템을 설계하는 방법을 알아봅니다.
모집단 최적화 알고리즘: 파티클 스웜(PSO) 모집단 최적화 알고리즘: 파티클 스웜(PSO)
이 글에서는 널리 사용되는 파티클 스웜 최적화(PSO) 알고리즘에 대해 살펴보겠습니다. 이전에는 수렴, 수렴 속도, 안정성, 확장성과 같은 최적화 알고리즘의 중요한 특성에 대해 알아보고 테스트 스탠드를 개발했으며 가장 간단한 RNG 알고리즘에 대해 알아보았습니다.