//+------------------------------------------------------------------+ //| 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 //+------------------------------------------------------------------+ //| Complex position for working with the statistical arbitrage. | //+------------------------------------------------------------------+ class CComplexPosition : public CObject { private: CArrayObj m_targets; // The list of tasks CArrayObj m_positions; // The list of open positions for the active position uint m_magic; // The EA magic number bool m_correct; // An indication of a correctly formed position. CTradeControl* m_trade; // Trading module. datetime m_time; // Execution time CLog* Log; // Logging 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); }; //+------------------------------------------------------------------+ //| Creates a copy of current complex position containing a similar | //| set of targets. Active positions are not copied, because | //| there should be only one instance of them. | //| I.e. a complex position returned by the method not always returns| //| false when calling the IsActive method | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Default constructor. | //+------------------------------------------------------------------+ CComplexPosition::CComplexPosition(void) : m_magic(0), m_correct(true) { Log = CLog::GetLog(); m_trade.SetAsyncMode(true); } //+------------------------------------------------------------------+ //| Returns the unique ID of the Expert Advisor | //| the current complex position belongs to. | //+------------------------------------------------------------------+ uint CComplexPosition::ExpertMagic(void) { return m_magic; } //+------------------------------------------------------------------+ //| Sets the unique ID of the Expert Advisor | //| the current complex position belongs to. | //+------------------------------------------------------------------+ void CComplexPosition::ExpertMagic(uint magic) { m_magic = magic; m_trade.SetExpertMagicNumber(magic); } //+------------------------------------------------------------------+ //| Adds a new target to the scenario. | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Checks the correctness of the offered symbol. | //+------------------------------------------------------------------+ bool CComplexPosition::CheckSymbol(string symbol) { datetime times[]; if(CopyTime(symbol, PERIOD_CURRENT, 0, 1, times) < 1) return false; return true; } //+------------------------------------------------------------------+ //| Clears position scenario. | //+------------------------------------------------------------------+ void CComplexPosition::Clear(void) { if(IsActive())return; m_correct = true; m_time = 0; m_magic = 0; m_targets.Clear(); } //+------------------------------------------------------------------+ //| Returns true if the scenario for the current position is | //|correct or false otherwise. | //+------------------------------------------------------------------+ bool CComplexPosition::IsCorrect(void) { return m_correct; } //+------------------------------------------------------------------+ //| Asynchronously executes complex position. | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Asynchronously executes given volume of specified symbol. | //| Negative volume means Sell, positive volume means 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; } //+------------------------------------------------------------------+ //| Checks the correctness of generated position before execution. | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Forms a unique hash for the complex position. | //+------------------------------------------------------------------+ string CComplexPosition::GetHash(void) { uint time = (uint)TimeCurrent(); uint msc = GetTickCount()%1000; string hash = "CP[" + (string)ExpertMagic() + ":" + (string)time + ":" + (string)msc + "]"; return hash; } //+------------------------------------------------------------------+ //| Returns current price of the market neutral position Unlike | //| the usual position, the price of a market-neutral position can | //| be negative | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Returns the weighted average price for the specified | //| 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; } //+------------------------------------------------------------------+ //| Returns true if the position is active. | //| Otherwise it returns false. | //+------------------------------------------------------------------+ bool CComplexPosition::IsActive(void) { return m_positions.Total() > 0; } //+------------------------------------------------------------------+ //| Returns the incoming hash comment of complex position. | //| Returns an empty string if the complex position is not active | //+------------------------------------------------------------------+ string CComplexPosition::EntryComment(void) { if(m_positions.Total() == 0) return ""; CPosition* pos = m_positions.At(0); return pos.EntryComment(); } //+------------------------------------------------------------------+ //| Adds a new active position to the current complex position. | //+------------------------------------------------------------------+ 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)); } //+------------------------------------------------------------------+ //| Returns true if the scenario of the passed position is equal | //| current position scenario | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Returns the complex position execution time. If the position | //| is active, returns the time of the last executed | //| position included in complex position. If a position is | //| pending, but its execution started, returns saved time during | //| execution. If position has not been executed yet, returns zero | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Closes the complex position by market. Closure is performed | //| in asynchronous mode. | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Returns profit of the current complex position | //+------------------------------------------------------------------+ 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; }