Помогите правильно написать класс канала - страница 2

 
Andrei:

У как эти коэффициенты извлекаются из класса?

Соответствующими функциями.

Вот мой заголовочный файл класса МНК:

/*
CLSMCore - класс, осуществляющий метод наименьших квадратов. Ядро.
Для использования - необходимо пронаследовать свой класс от ядра и перегрузить чисто виртуальные функции.
*/

#include <MyLib\DebugOrRelease\DebugSupport.mqh>

enum ELSMType
{
   LSM_FLAT       = 0,
   LSM_TREND      = 1,
   LSM_PARABOLIC  = 2,
   LSM_CUBIC      = 3
};

struct SLSMPowers
{
   double   m_dThirdPower;
   double   m_dSecondPower;
   double   m_dFirstPower;
   double   m_dNullPower;
};

enum EQntExtremums
{
   QE_NO_EXTREMUMS = 0,
   QE_MAXIMUM = 1,
   QE_MINIMUM = 2,
   QE_BOTH = 3
};

struct SLSMExtremums
{
   EQntExtremums  m_qeExtremums;
   double         m_dMinX;
   double         m_dMinY; 
   double         m_dMaxX;
   double         m_dMaxY; 
};


class CLSMCore
{
protected:
// Степени многочленов
static const uint LSM_FLAT_POWER;
static const uint LSM_TREND_POWER;
static const uint LSM_PARABOLIC_POWER;
static const uint LSM_CUBIC_POWER;
static const int  LSM_DEFAULT_POINT_WEIGHT;

   
   // Функции, которые обязательно следует перегрузить
   // Именно эти функции возвращают число точек и их координаты.
   virtual uint   _N() = 0;      // Число точек
   virtual double _X(uint uiIdx) = 0;  // Значение X точки с индиксом uiIdx
   virtual double _Y(uint uiIdx) =0;  // Значение Y точки с индиксом uiIdx
   
   // Функци, которые можно не перегружать, если необходимо значение по умолчанию.
   // Это функции для указания веса и полярной точки.
   virtual int    _W(uint uiIdx) { return(LSM_DEFAULT_POINT_WEIGHT); };  // Значение веса точки с индексом uiIdx
   virtual int    _PolarIdx()    { return(WRONG_VALUE); }            // Индекс точки, через которую должна проходить прямая, при отрицательном значении - такой точки нет.     

   // Функция, проверяющаяя, что МНК указанного типа возможен
   // (Количество точек ненулевого веса должно быть на один больше, чем степень многочлена)
   virtual bool _LSMIsAvailible(ELSMType ltType);

   // Расчетные функции (во всех случаях учитываются веса точек и отнимается полярная точка, если ее индекс валиден, обозначается "с" - корректированное).
   double _SummXc();
   double _SummXc2();
   double _SummXc3();
   double _SummXc4();
   double _SummXc5();
   double _SummXc6();
   double _SummX2c2();
   double _SummX3c2();
   double _SummXcYc();
   double _SummX2cXc();
   double _SummX2cYc();
   double _SummXcX2c();
   double _SummX3cX2c();
   double _SummX3cXc();
   double _SummX3cYc();
   double _SummYc();
   int    _SummW();


   // Функциии расчета для аппроксимированных значений.
   // Функции возвращают коэффициенты определенных кривых. 
   // Последняя функция возвращает сразу все коэффициенты в структуре, ее чаще всего и имеет смысл использовать.
   // Функции вынесены в protected-секцию, чтобы можно было организовать различные способы доступа к данным
   // Для расчетов используются перегруженные функции: _N(); _X(); _Y(); _W(); _PolarIdx().


   // Функция расчета горизонтальной прямой (по сути среднего арифметического)
   double _CountFlatLevel();         

   // Функция расчета тренда для случая, когда есть точка-полюс, через которую должна лежать прямая
   // Предполагается, что _PolarIdx() != WRONG_VALUE (проверка по ASSERT)
   double _CountPolarTrend(double & rdPolarLevel);    
   
   // Функция расчета обычной прямой c трендом
   // Предполагается, что _PolarIdx() == WRONG_VALUE (проверка по ASSERT)
   double _CountTrend(double & rdLevel);   
   
   // Функция расчета параболы для случая, когда есть точка полюс, через которую парабола должна проходить.
   // Заполняет соответствующие коэффициенты
   // Предполагается, что _PolarIdx() != WRONG_VALUE (проверка по ASSERT)
   void _CountPolarParabola(double & rdSecondPower,double & rdFirstPower,double & rdNullPower);    

   // Функция расчета параболы.
   // Заполняет соответствующие коэффициенты
   // Предполагается, что _PolarIdx() == WRONG_VALUE (проверка по ASSERT)
   void _CountParabola(double & rdSecondPower,double & rdFirstPower,double & rdNullPower);    


   // Функция расчета кубической параболы для случая, когда есть точка полюс, через которую парабола должна проходить.
   // Заполняет соответствующие коэффициенты
   // Предполагается, что _PolarIdx() != WRONG_VALUE (проверка по ASSERT)
   void _CountPolarCubic(double & rdThirdPower,double & rdSecondPower,double & rdFirstPower,double & rdNullPower);

   // Функция расчета кубической параболы.
   // Заполняет соответствующие коэффициенты
   // Предполагается, что _PolarIdx() == WRONG_VALUE (проверка по ASSERT)
   void _CountCubic(double & rdThirdPower,double & rdSecondPower,double & rdFirstPower,double & rdNullPower);

   // Функция расчета указанной кривой.
   // Заполняет соответствующие коэффициенты (несуществующие коэффициента заполняются нулями
   // В зависимости от величины, возвращаемой _PolarIdx(), вызываются полярные или обычные функции
   SLSMPowers _CountLSM(ELSMType ltType);

   // Статические функции для матричных вычислений

   // Функция вычисляет детерменант матрицы размера 3х3.
   // Значения передаются по строкам.
   static double _Determinant_3(double dX11,double dX12,double dX13,double dX21,double dX22,double dX23,double dX31,double dX32,double dX33) { return(dX11*dX22*dX33 - dX11*dX23*dX32 - dX12*dX21*dX33 + dX12*dX23*dX31 + dX13*dX21*dX32 - dX13*dX22*dX31);};
       
   // Функция вычисляет детерменант матрицы размера 4х4.
   // Значения передаются по строкам.
   static double _Determinant_4(double dX11,double dX12,double dX13,double dX14,double dX21,double dX22,double dX23,double dX24,double dX31,double dX32,double dX33,double dX34,double dX41,double dX42,double dX43,double dX44);
       
public:
   CLSMCore() {};
   ~CLSMCore() {};

   // Заполнение данной структуры одинаковыми значениями степеней.
   static void SetPowers(SLSMPowers & lpPowers,double dPower) { lpPowers.m_dNullPower = dPower; lpPowers.m_dFirstPower = dPower; lpPowers.m_dSecondPower = dPower; lpPowers.m_dThirdPower = dPower; };
   
   // Расчет многочлена с данными степенями и аргументом.   
   static double CountLSMPolynom(double dX,SLSMPowers & lpPowers) { return(dX*(dX*(dX*lpPowers.m_dThirdPower + lpPowers.m_dSecondPower)+lpPowers.m_dFirstPower)+lpPowers.m_dNullPower); };
   
   // Расчет числа экстремумов для указанных степеней и, если они есть - заполняет их значения.
   // Возвращает структуру количеством экстремумов и их данными.
   // В структуре данных заполняется:
   // Для флета - в обоих экстремумах устанавливается Y, абсцисса у обоих устанавливается на EMPTY_VALUE
   // Для тренда - все значения устанавливаются на EMPTY_VALUE
   // Для параболы - существующий экстремум устанавливается на реальный экстремум, несуществующий на EMPTY_VALUE
   // Для кубики - устанавливаются оба экстремума (если они есть), либо все устанавливается на EMPTY_VALUE (если экстремумов нет).
   static SLSMExtremums GetExtremums(SLSMPowers & lpPowers);   
};

В данном классе - есть все функции для вычисления коэффициентов МНК, и для дальнейшего построения кривой степени от 0 до 3.

А сами данные - отсутствуют, они получаются с помощью виртуальных функций.

Для того, чтобы использовать данный класс - надо пронаследоваться от него, и определить эти функции получения исходных данных.

 
Andrei:

Вот именно организацию данных внутри класса и нужно знать для процесса отладки и написания кода, так как это постоянно меняется по ходу дела.

Это - пока ты отлаживаешь работу самого класса.

Как только класс отлажен - ты забываешь про его внутреннее содержание.

Я уже приводил свой класс торговой позиции:

// СTradePositionI переносимый интерфейс торговой позиции 

// Позиция состоит из компонент-наследников интерфейса CTradePosComponentI
// 
// Реально для МТ4 имплементацией интерфейса является объект CMT4PositionInfo, для МТ5 - объект CMT5PositionInfo
// CMT4PositionInfo представляет из себя массив объектов CMT4OrderInfo, наследников интерфейса CTradePosComponentI.
// Фактически это массив МТ4-ордеров.
// CMT5PositionInfo представляет из себя массив объектов CMT5PositionInfoCore, наследников интерфейса CTradePosComponentI.
// Объект CMT5PositionInfoCore не имеет прямого аналога в МТ5-терминах, это два массива ордеров и сделок, а также структура данных,
// имеющуая идентификатор позиции (m_lPosID или POSITION_IDENTIFIER), тикет магик, вид позиции, и так далее, по аналогии с МТ4-ордером,
// фактически, массивы ордеров и сделок - это аналоги ордеров и сделок обычной неттинговой МТ5-позиции, а структура данных - относится
// к хеджевой позиции, и имеет аналог МТ4-ордера. 
//
// Реально при запросе у позиции компоненты CTradePosComponentI в МТ4 мы получаем указатель на ордер (класс CMT4OrderInfo),
// а в МТ5 - на ядро позиции (класс CMT5PositionInfoCore) 



#include <MyLib\DebugOrRelease\DebugSupport.mqh>
#include <MyLib\Common\MyObject.mqh>
#include <MyLib\Common\CurSymEnum.mq5>
#include <MyLib\Common\TrendEnums.mqh>
#include <MyLib\Trade\TradePosComponentI.mqh>

class CTradePositionI: public CMyObject
{
public:
   void CTradePositionI() {    SetMyObjectType(MOT_TRADE_POSITION_I); };
   virtual void ~CTradePositionI() {};
   
   // Выбор существующей позиции. 
   // Указывается магик и символ, по которому выбираются действующие ордера.
   // Если ulMagic = 0 - выбираются все позиции по всем магикам.
   // Если ECurrencySymbol = CS_UNKNOWN - выбираются все позиции по всем символам
   // Если ECurrencySymbol = CS_CURRENT - запрашивается функция Symbol(), и выбираются все позиции по этому символу
   // Возвращает число компонент позиции внутри позиции (может быть нулевым если позиции нет) или WRONG_VALUE в случае ошибок
   // Каждая фабрика (наследник CEAPartsFactoryT) имеет одну позицию, которая на каждом CEAPartsFactoryT::OnRefresh()
   // обновляется в соответствии с магиком и рабочим символом фабрики. 
   virtual int Select(ulong ulMagic = 0,ECurrencySymbol csSymbol = CS_CURRENT) = 0;

   virtual uint GetTotalComponents() const = 0;  // Получение общего числа компонент
   virtual uint GetNumOfComponentsOnDirection(ENUM_POSITION_TYPE etDirection) const = 0; // Получение числа компонент указанного направления (если tdDirection = TD_FLAT - то всех компонент)  и интерфейс отдельной компоненты
   virtual CTradePosComponentI* GetComponent(uint uiComponentIdx) const = 0;
   
   // Расширенный интерфейс
   
   // Функция исследует позицию, и возвращает ее направление:
   // Если все компоненты - лонги, то вверх.
   // Если все компоненты - шорты, то вниз.
   // Если компонент нет - то флет. 
   // Если компоненты обоих типов, то смотрим на флаг bFlatIfBoth. 
   // Если этот флаг установлен - то возвращаем флет.
   // Если этот флаг сброшен - то смотрим на флаг bConsiderVolume.
   // Если этот флаг установлен - то сравниваем общие объемы лонгов и шортов. Если сброшен - сравниваем количество лонгов и шортов.
   // Каких позиций (или объемов) больше - то направление и возвращаем. 
   // Если позиций (или объемов) одинаково - возвращаем флет.
   // NOTE !!! Функция не проверяет магик и символ компонент !
   virtual ETrendDirection GetDirection(bool bFlatIfBoth = true,bool bConsiderVolume = true) const = 0;
   
   // Функция ищет внутри позиции компоненту с указанным тикетом. 
   // В случае, если ее нет - возвращается false.
   // Если компонента найдена - возвращается true, и uiComponentIdx устанавливается на индекс компоненты внутри позиции.
   virtual bool FindComponentByTicket(long lTicket,uint &uiComponentIdx) const = 0;
};

Как видишь, в нем все функции виртуальны - ты, получая позицию - даже не знаешь, где ты - то ли в МТ4, то ли в МТ5 (а, может быть, и еще где-то).

Ты просто получаешь указатель на CTradePositionI, и с его помощью - можешь перебрать все компоненты позиции.

Вызываешь у него функцию GetTotalComponents() - узнаешь, сколько их.

Потом можешь вызывать фукнцию GetComponent() - чтобы получать отдельные компоненты позиции.

Вызывая эту функцию - ты опять же, получаешь не данные, а интерфейс:

class CTradePosComponentI: public CMyObject
{
public:
   void CTradePosComponentI() {    SetMyObjectType(MOT_TRADEPOS_COMPONENT_I); };
   virtual void ~CTradePosComponentI() {};
   
   // Основной интерфейс
   virtual long               GetTPCTicket()       const = 0;
   virtual long               GetTPCMagic()        const = 0;
   virtual ECurrencySymbol    GetTPCSymbol()       const = 0;
   virtual ENUM_POSITION_TYPE GetTPCType()         const = 0;
   virtual datetime           GetTPCOpenTime()     const = 0;
   virtual double             GetTPCVolume()       const = 0;
   virtual double             GetTPCOpenPrice()    const = 0;
   virtual double             GetTPCStopLoss()     const = 0;
   virtual double             GetTPCTakeProfit()   const = 0;
   virtual string             GetTPCCommentary()   const = 0;
   
   // NOTE !!! Функция IsTPCInUnloss может работать неверно из-за ошибок округления.
   // Для надежности безубыток надо ставить на пункт больше, чем dUnlossPriceDistance
   virtual bool               IsTPCInUnloss(double dUnlossPriceDistance = 0) const { if(GetTPCStopLoss() <= 0 || GetTPCStopLoss() == EMPTY_VALUE || dUnlossPriceDistance == EMPTY_VALUE) return(false); if(GetTPCType() == POSITION_TYPE_BUY) { if(GetTPCStopLoss() >= GetTPCOpenPrice() + dUnlossPriceDistance) return(true); } else { if(GetTPCStopLoss() <= GetTPCOpenPrice()- dUnlossPriceDistance) return(true); }; return (false); };
   virtual double             GetTPDistance() const { if(GetTPCTakeProfit() == 0 || GetTPCTakeProfit() == EMPTY_VALUE) return(EMPTY_VALUE); if(GetTPCType() == POSITION_TYPE_BUY) return(GetTPCTakeProfit() - GetTPCOpenPrice()); return(GetTPCOpenPrice() - GetTPCTakeProfit());  };
   virtual double             GetSLDistance() const { if(GetTPCStopLoss() == 0 || GetTPCStopLoss() == EMPTY_VALUE) return(EMPTY_VALUE); if(GetTPCType() == POSITION_TYPE_BUY) return(GetTPCOpenPrice()- GetTPCStopLoss()); return(GetTPCStopLoss() - GetTPCOpenPrice());  };
};

Можешь получить все данные по любому ордеру МТ4 или хедж-позиции МТ5 (при этом тебе даже неинтересно, что это реально).

Классы с данными - они отлажены, и работают. Пользователь - никакого доступа к ним не имеет. Он может получить только вот эти интерфейсы, и работать с позициями.

Если надо совершить торговое действие - опять же, для этого существует интерфейс торгового процессора:

class CTradeProcessorI : public CMyObject
{
public:
   void CTradeProcessorI() {    SetMyObjectType(MOT_TRADE_PROCESSOR_I); };
   virtual void ~CTradeProcessorI() {};
   
   // Настроечный интерфейс
   virtual void SetSlippage(uint uiSlippage) = 0;
   
   // Торговый интерфейс
   // Все функции возвращают код возврата торгового сервера
   virtual int Buy(long & lTicket,ECurrencySymbol csSymbol,double dVolume,double dTP=0,double dSL=0,ulong ulMagic = 0,string strComment = NULL) = 0;               // Всегда по цене Ask, если успешно - возвращается lTicket
   virtual int Sell(long & lTicket,ECurrencySymbol csSymbol,double dVolume,double dTP=0,double dSL=0,ulong ulMagic = 0,string strComment = NULL) = 0;              // Всегда по цене Bid, если успешно - возвращается lTicket  

   virtual int ModifyTPSL(CTradePosComponentI* ptpcComponent,double dTP=0,double dSL=0) = 0;       
   virtual int ModifyTPSL(long lTPCTicket,double dTP=0,double dSL=0) = 0;       

   virtual int CloseTradeComponent(CTradePosComponentI* ptpcComponent,double dCloseVolume=EMPTY_VALUE) = 0;              // dCloseVolume - закрываемый объем. Если равен EMPTY_VALUE или равен или больше, чем объем торговой компоненты с указанным тикетом - закрывается вся торговая компонента.   
   virtual int CloseTradeComponent(long lTPCTicket,double dCloseVolume=EMPTY_VALUE) = 0;              // dCloseVolume - закрываемый объем. Если равен EMPTY_VALUE или равен или больше, чем объем торговой компоненты с указанным тикетом - закрывается вся торговая компонента.   
   
   virtual int PendingSet(long & lTicket,ECurrencySymbol csSymbol,ENUM_ORDER_TYPE otOrderType,double dSetPrice,double dVolume,double dTP=0,double dSL=0,ulong ulMagic = 0,string strComment = NULL) = 0; // если успешно - возвращается lTicket
   virtual int PendingDelete(long lTicket) = 0; 
   virtual int PendingDelete(COrderInfoCore* poiOrder) = 0; 
   
   virtual int PendingModify(long lTicket,double dSetPrice,double dTP=0,double dSL=0) = 0;       
   virtual int PendingModify(COrderInfoCore* poiOrder,double dSetPrice,double dTP=0,double dSL=0) = 0;       
   
   // Проверка кода возврата
   virtual bool TradeOperationWasSuccessful(int iTradeResCode) const = 0;
   
   // Если функция возвращает true, то при возникновении пользовательских ошибок (iTradeResCode > ERR_USER_ERROR_FIRST) советник прекратит работу
   // В случае false - работа будет продолжена
   virtual bool IsUserErrorsFalal() const = 0;
   
   // TODO: Для МТ4 был зарегистрирован код ошибки 128 - сделать не фатальным.. 
};


Заметь, снова - все функции виртуальные, и пользователь никакого доступа к данным торгового процессора не имеет.

Хочешь совершить торговое действие - вызывай соответствующую виртуальную функцию, а лезть во внутренние структуры ты не можешь.

Инкапсуляция - очень полезная штука, позволяющая сосредоточить внимание на конкретном действии, и не думать о внешних объектах, ограничиваясь только лишь интерфейсом взаимодействия.

 
Georgiy Merts:

А сами данные - отсутствуют, они получаются с помощью виртуальных функций.

Для того, чтобы использовать данный класс - надо пронаследоваться от него, и определить эти функции получения исходных данных.

А без наследования напрямую нельзя данные взять?

 
Andrei:

А без наследования напрямую нельзя данные взять?

Можно.

Можно даже вобще с классами не заморачиваться.

Но только, когда возвратишься к коду спустя некоторое время - любая система, написанная без инкапсуляции - требует куда больше ресурсов на модификацию. Фактически, приходится писать все заново.

Вот этот код с реализацией МНК - годится для любого места, где он нужен. А если бы данные были доступны извне (особенно для модификации) - я не поручусь, что где-то я случайно не изменю их, а в результате - начнут неверно работать и прочие места.

Наследование - это просто эффективный механизм реюза кода. Если код нам нужен только один раз - возиться с ООП-оформлением не имеет смысла. Но, если код нам может понадобиться во многих местах - ООП-оформление с лихвой себя окупает.

 

Georgiy Merts:

А если бы данные были доступны извне (особенно для модификации) - я не поручусь, что где-то я случайно не изменю их, а в результате - начнут неверно работать и прочие места.

Вот поэтому каждый человек должен для себя решить - насколько его забывчивость прогрессирует, так как если программист может сесть и случайно перепортить все свои коды, даже не обратив на это внимание, то скорее всего ему ООП очень даже подойдет, а в остальных случаях наверно эти страхи не сбудутся и смысла для него в ООП нет.

Как вариант лечения забывчивости и от случайного написания кода там где не надо - можно использовать документирование кода и создание резервных копий.

Также полезно не пить много крепких напитков во время программирования так как это сильно увеличивает забывчивость и количество случайно написанного или стертого кода. ))

 
Также полезно не пить много крепких напитков во время программирования так как это сильно увеличивает забывчивость и количество случайно написанного или стертого кода. ))


Это пожалуй самый ценный совет...

Вообще конечно всем спасибо за разъяснения. Но наверно мне проще в процедурном варианте писать. Тем более что сама логика не самая сложная. 

 
Andrei:

Вот поэтому каждый человек должен для себя решить - насколько его забывчивость прогрессирует, так как если программист может сесть и случайно перепортить все свои коды, даже не обратив на это внимание, то скорее всего ему ООП очень даже подойдет, а в остальных случаях наверно эти страхи не сбудутся и смысла для него в ООП нет.

Как вариант лечения забывчивости и от случайного написания кода там где не надо - можно использовать документирование кода и создание резервных копий.

Также полезно не пить много крепких напитков во время программирования так как это сильно увеличивает забывчивость и количество случайно написанного или стертого кода. ))

Да-да, есть тут такой Peter Konov - гигант запоминания с резко ослабленными способностями к забыванию.

Но я не раз был самому себе благодарен за то, что в любом месте кода у меня доступны исключительно только те данные, которые в этом месте нужны, а к другим - доступ ограничен. Неоднократно я сталкивался с ситуациями, когда смотрю - да какого хрена здесь я не могу получить необходимые данные ? А начинаю разбираться - опа... Не учел (забыл) важные тонкости, эти данные надо получать иначе, через другие интерфейсы, а иначе - я могу их получить с ошибкой, и даже не сразу это обнаружить.

При этом - я вобще не пью, и вроде как память имею вполне надежную. Но, только программы сейчас имеют настолько сложную структуру, что упомнить все - нереально.

Поэтому я очень широко использую ООП.

Но, в простейших случаях, когда надо что-то "проверить на коленке" - я обхожусь почти без ООП.

"Почти" - потому, что все равно, даже когда ООП не надо - мне удобно использовать старые наработки, которые выполнены в ООП-стиле.

Скажем, вот код инициализации LSM-канала:

   CDataProviderT dpDataProvider(true);   // Объявление дата-провайдера вне функций

   /// ------------------------------------- Функция Init()

   CLSMChannel_IParams  lpParams;  // Объявляем параметры нужного индикатора, и далее - заполняем их
   

   lpParams.SetCommonParameters(_Symbol2CurrencyEnum(Symbol()),_GetMainTimeframe(),uiICanalLength);
   lpParams.SetSpecialParameters(lpcParams);
   
   
   piLSMCanal =  dpDataProvider.GetIndicator(GetPointer(lpParams),uiICanalLength);  // Получаем индикатор

   /// ------------------------------------ Функция OnCalculate()

   dpDataProvider.Refresh(); // Рефрешим дата-провайдер

   // Дорисовываем канал
   for(int iCurBar = 0; iCurBar<iBarsToCount; ++iCurBar)
      {
      adMiddleBuffer[iCurBar] = piLSMCanal.GetValue(iCurBar);
      adHiBuffer[iCurBar] = piLSMCanal.GetValue(iCurBar,1);
      adLoBuffer[iCurBar] = piLSMCanal.GetValue(iCurBar,2);
      adHiOCBuffer[iCurBar] = piLSMCanal.GetValue(iCurBar,3);
      adLoOCBuffer[iCurBar] = piLSMCanal.GetValue(iCurBar,4);
      };   	

Объект CDataProviderT - производит все действия по созданию индикатора, возвращает указатель на него,  и далее, при своем рефреше - рефрешит и этот индикатор. 

По полученному указателю - можно брать нужные данные и нарисовать красивый канал:

https://www.mql5.com/ru/charts/8629095/eurusd-h1-alpari-international-limited

График EURUSD, H1, 2018.05.01 09:28 UTC, Alpari International Limited, MetaTrader 4, Real
График EURUSD, H1, 2018.05.01 09:28 UTC, Alpari International Limited, MetaTrader 4, Real
  • www.mql5.com
Символ: EURUSD. Период графика: H1. Брокер: Alpari International Limited. Торговая платформа: MetaTrader 4. Режим торговли: Real. Дата: 2018.05.01 09:28 UTC.
Причина обращения: