//+------------------------------------------------------------------+ //| ComplexPosition.mqh | //| Copyright 2015, Vasiliy Sokolov. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, Vasiliy Sokolov." #property link "http://www.mql5.com" #include #include #include #include #include #include #include //+------------------------------------------------------------------+ //| Комплексная позиция для работы со статистическим арбитражем. | //+------------------------------------------------------------------+ class CComplexPosition : public CObject { private: CArrayObj m_targets; // Список заданий CArrayObj m_positions; // Список открытых позиций для активной позиции uint m_magic; // Магический номер эксперта bool m_correct; // Признак того, что позиция сформирована корректно. CTradeControl* m_trade; // Торговый модуль. datetime m_time; // Время выполения CLog* Log; // Логирование bool CheckSymbol(string symbol); bool CheckValid(void); bool Execute(string symbol, double volume, string ts); double Price(string symbol, double volume); string GetHash(void); public: CComplexPosition(void); uint ExpertMagic(void); void ExpertMagic(uint magic); bool Execute(void); datetime ExecuteTime(void); bool AddTarget(string symbol, double volume); void Clear(void); double Price(void); bool IsActive(void); string EntryComment(void); bool AddPosition(CPosition* pos); CComplexPosition* Clone(); bool EqualTargets(CComplexPosition* cp); bool IsCorrect(void); bool CloseAtMarket(void); double Profit(void); int PositionsTotal(); CPosition* PositionAt(int); }; //+------------------------------------------------------------------+ //| Создает копию текущей комплексной позиции содержащую аналогичный | //| набор таргетов. Активные позиции не копируются, т.к. по | //| определению находятся в единственном экземпляре. | //| Т.е. возвращемая этим методом комплексная позиция всегда вернет | //| false при вызове метода IsActive | //+------------------------------------------------------------------+ CComplexPosition* CComplexPosition::Clone(void) { CComplexPosition* cp = new CComplexPosition(); for(int i = 0; i < m_targets.Total(); i++) { CTarget* ct = m_targets.At(i); cp.m_targets.Add(new CTarget(ct.Symbol(), ct.Volume())); } cp.m_magic = m_magic; cp.m_correct = m_correct; return cp; } //+------------------------------------------------------------------+ //| Конструктор по-умолчанию. | //+------------------------------------------------------------------+ CComplexPosition::CComplexPosition(void) : m_magic(0), m_correct(true) { Log = CLog::GetLog(); m_trade.SetAsyncMode(true); } //+------------------------------------------------------------------+ //| Возвращает уникальный идентификатор эксперата, которому | //| принадлежит текущая комплексная позиция. | //+------------------------------------------------------------------+ uint CComplexPosition::ExpertMagic(void) { return m_magic; } //+------------------------------------------------------------------+ //| Устанавливает уникальный идентификатор эксперата, которому | //| принадлежит текущая комплексная позиция. | //+------------------------------------------------------------------+ void CComplexPosition::ExpertMagic(uint magic) { m_magic = magic; m_trade.SetExpertMagicNumber(magic); } //+------------------------------------------------------------------+ //| Добавляет новый таргет к сценарию. | //+------------------------------------------------------------------+ bool CComplexPosition::AddTarget(string symbol,double volume) { if(m_correct == false) { string text = "With the formation of an integrated position error. Adding new characters is not possible." + "Clean the position of its formation and try again."; CMessage* msg = new CMessage(MESSAGE_ERROR, __FUNCTION__, text); Log.AddMessage(msg); return m_correct; } if(!CheckSymbol(symbol)) { string text = "Symbol " + symbol + " unavailable. Check correct symbol"; CMessage* msg = new CMessage(MESSAGE_ERROR, __FUNCTION__, text); Log.AddMessage(msg); return m_correct = false; } m_correct = m_targets.Add(new CTarget(symbol, volume)); return m_correct; } //+------------------------------------------------------------------+ //| Проверяет предложенный символ на корректность. | //+------------------------------------------------------------------+ bool CComplexPosition::CheckSymbol(string symbol) { datetime times[]; if(CopyTime(symbol, PERIOD_CURRENT, 0, 1, times) < 1) return false; return true; } //+------------------------------------------------------------------+ //| Очищает сценарий позиции. | //+------------------------------------------------------------------+ void CComplexPosition::Clear(void) { if(IsActive())return; m_correct = true; m_time = 0; m_magic = 0; m_targets.Clear(); } //+------------------------------------------------------------------+ //| Возвращает истину если сценарий для текущей позиции составлен | //| корректно и ложь в противном случае. | //+------------------------------------------------------------------+ bool CComplexPosition::IsCorrect(void) { return m_correct; } //+------------------------------------------------------------------+ //| Асинхронно исполняет комплексную позицию. | //+------------------------------------------------------------------+ bool CComplexPosition::Execute(void) { if(!CheckValid())return false; m_trade.SetExpertMagicNumber(m_magic); m_trade.SetAsyncMode(true); string hash = GetHash(); bool res = true; int total = m_targets.Total(); for(int i = 0; i < total; i++) { CTarget* target = m_targets.At(i); if(!Execute(target.Symbol(), target.Volume(), hash)) res = false; } m_time = TimeCurrent(); return res; } //+------------------------------------------------------------------+ //| Асинхронно исполняет указанный объем по указанному символу. | //| Отрицательный объем означает Sell, положительный - Buy | //+------------------------------------------------------------------+ bool CComplexPosition::Execute(string symbol, double volume, string ts) { bool res = false; string op = volume > 0.0 ? "Buy" : "Sell"; if(volume > 0.0) res = m_trade.Buy(volume, symbol, ts); else res = m_trade.Sell(MathAbs(volume), symbol, ts); if(!res) { string vol = DoubleToString(MathAbs(volume), 2); string text = op + " " + vol + " failed. Reason " + m_trade.ResultRetcodeDescription() + " (" + (string)m_trade.ResultRetcode()+ ")"; CMessage* msg = new CMessage(MESSAGE_ERROR, __FUNCTION__, text); msg.Retcode(m_trade.ResultRetcode()); Log.AddMessage(msg); } return res; } //+------------------------------------------------------------------+ //| Проверяет корректность сформированной позиции перед выполнением. | //+------------------------------------------------------------------+ bool CComplexPosition::CheckValid(void) { bool res = true; if(m_magic == 0) { string text = "Magic number of complex position not set. Set magic and try again"; CMessage* msg = new CMessage(MESSAGE_ERROR, __FUNCTION__, text); Log.AddMessage(msg); res = false; } if(!m_correct) { string text = "Position is not formed correctly. Execution is not possible"; CMessage* msg = new CMessage(MESSAGE_ERROR, __FUNCTION__, text); Log.AddMessage(msg); res = false; } if(m_targets.Total() == 0) { string text = "Position has no targets. Add at least one target"; CMessage* msg = new CMessage(MESSAGE_ERROR, __FUNCTION__, text); Log.AddMessage(msg); res = false; } return res; } //+------------------------------------------------------------------+ //| Формирует уникальный хеш для комплексной позиции. | //+------------------------------------------------------------------+ string CComplexPosition::GetHash(void) { uint time = (uint)TimeCurrent(); uint msc = GetTickCount()%1000; string hash = "CP[" + (string)ExpertMagic() + ":" + (string)time + ":" + (string)msc + "]"; return hash; } //+------------------------------------------------------------------+ //| Возвращает текущую цену рыночно-нейтральной позиции. В отличии | //| от обычной позиции, цена у рыночно-нейтральной позиции может | //| быть отрицательной | //+------------------------------------------------------------------+ double CComplexPosition::Price(void) { double price = 0.0; for(int i = 0; i < m_targets.Total(); i++) { CTarget* target = m_targets.At(i); price += Price(target.Symbol(), target.Volume()); } return price; } //+------------------------------------------------------------------+ //| Возвращает средневзвешенную на объем цену по заданному | //| инструменту symbol. | //+------------------------------------------------------------------+ double CComplexPosition::Price(string symbol,double volume) { double close[]; double price = 0.0; if(volume > 0) price = SymbolInfoDouble(symbol, SYMBOL_ASK); else price = SymbolInfoDouble(symbol, SYMBOL_BID); price *= volume; return price; } //+------------------------------------------------------------------+ //| Возвращает истину, если текущая позиция является активной. | //| Возвращает ложь в противном случае. | //+------------------------------------------------------------------+ bool CComplexPosition::IsActive(void) { return m_positions.Total() > 0; } //+------------------------------------------------------------------+ //| Возвращает входящий комментарий-хеш комплексной позиции. | //| Возвращает пустую строку если комплексная позиция не активна | //+------------------------------------------------------------------+ string CComplexPosition::EntryComment(void) { if(m_positions.Total() == 0) return ""; CPosition* pos = m_positions.At(0); return pos.EntryComment(); } //+------------------------------------------------------------------+ //| Добавляет в текущую комплексную позицию новую активную позицию. | //+------------------------------------------------------------------+ bool CComplexPosition::AddPosition(CPosition *pos) { if(m_positions.Total() > 0) { if(EntryComment() != pos.EntryComment()) { string text = "The added position #" + (string)pos.ID() + " is not compatible with the current comment complex position. Adding impossible."; CMessage* msg = new CMessage(MESSAGE_WARNING, __FUNCTION__, text); Log.AddMessage(msg); return false; } if(ExpertMagic() != pos.ExpertMagic()) { string text = "The added position #" + (string)pos.ID() + " is not compatible with the current expert number complex position. Adding impossible."; CMessage* msg = new CMessage(MESSAGE_WARNING, __FUNCTION__, text); Log.AddMessage(msg); return false; } } m_magic = pos.ExpertMagic(); m_positions.Add(pos); double vol = pos.Volume(); if(pos.Direction() == POSITION_TYPE_SELL) vol *= (-1); return m_targets.Add(new CTarget(pos.Symbol(), vol)); } //+------------------------------------------------------------------+ //| Возвращает истину, если сценарий переданной позиции равен | //| сценарию текущей позиции. | //+------------------------------------------------------------------+ bool CComplexPosition::EqualTargets(CComplexPosition *cp) { if(cp.m_targets.Total() != m_targets.Total())return false; for(int i = 0; i < m_targets.Total(); i++) { CTarget* me = m_targets.At(i); CTarget* tg = cp.m_targets.At(i); if(me != tg)return false; } return true; } //+------------------------------------------------------------------+ //| Вовзвращает время исполнения комплексной позиции. Если позиция | //| является активной, возвращается время последней исполненной | //| позиции входящей в комплексную позицию. Если позиция является | //| отложенной, но запущенной на выполнение - запомненное время | //| при выполнении. Если позиция еще не выполнялась - возвращает ноль| //+------------------------------------------------------------------+ datetime CComplexPosition::ExecuteTime(void) { if(m_time == 0 && m_positions.Total() > 0) { CPosition* pos = m_positions.At(m_positions.Total()-1); return pos.TimeOpen(); } return m_time; } //+------------------------------------------------------------------+ //| Закрывает комплексную позицию по рынку. Закрытие происходит | //| в асинхронном режиме. | //+------------------------------------------------------------------+ bool CComplexPosition::CloseAtMarket(void) { bool res = true; for(int i = 0; i < m_positions.Total(); i++) { CPosition* pos = m_positions.At(i); if(!pos.CloseAtMarket(pos.Volume(), 0, "exit from cp", true)) res = false; } m_time = TimeCurrent(); return res; } //+------------------------------------------------------------------+ //| Возвращает профит текущей комплексной позиции. | //+------------------------------------------------------------------+ double CComplexPosition::Profit(void) { double profit = 0; for(int i = 0; i < m_positions.Total(); i++) { CPosition* pos = m_positions.At(i); profit += pos.Profit(); } return profit; }