370 lines
29 KiB
Plaintext
370 lines
29 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| ComplexPosition.mqh |
|
|
//| Copyright 2015, Vasiliy Sokolov. |
|
|
//| http://www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2015, Vasiliy Sokolov."
|
|
#property link "http://www.mql5.com"
|
|
#include <Object.mqh>
|
|
#include <Arrays\ArrayObj.mqh>
|
|
#include <Strategy\TradeControl.mqh>
|
|
#include <Strategy\Logs.mqh>
|
|
#include <Strategy\Message.mqh>
|
|
#include <Strategy\Position.mqh>
|
|
#include <Strategy\Target.mqh>
|
|
//+------------------------------------------------------------------+
|
|
//| 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;
|
|
} |