Linear Hedging Framework for MQL4

Introduction



When opening an order, we are all hoping that our analysis was correct and our position will make a profit. But, when this is not the case, there are in general three possibilities to continue:


  1. Close the order upon a predefined stop-loss, if we were able to close the order on the opening price, then it's even better and no harm was done - The challenge with this approach is to know where to set the stop loss point, not to make too tight so it doesn't get hit quick, or too wide and then the loss is massive.

  2. Let the order run and hope that at some case the trend will reverse and our analysis was correct - Challenging as well, since we will never know with 100% accuracy that this level will be reached again, so in the meantime our P&L gets more and more negative and we make loss on the swap if it is against us.

  3. Hedge the order - meaning opening an opposite order upon a predefined gap and close it once the orders cancel each other profit wise.



I will dedicate this post to the third option and introduce a linear hedging framework I use myself.

Linear Hedging Framework Explained



High level logic



Before diving into the code itself, I will explain the behavior high level:


  1. We open an order and let it run, if it reaches our take profit target, fine, happy days we close it and move on to the next order. If not...

  2. We open a second order with the same direction and size after a predefined pips gap, for example 500 pips. Then, if the 2nd order, which is the hedge in this case, goes up 250 pips, both orders cancel each other and we close both of them. If not...

  3. We open a third order with the same direction, but this time we use twice the lots. Following the logic above, if it climbs up 375 pips, we will close all the three positions.

  4. We continue opening orders according to this series 1x 1x 2x 2x 3x 3x 4x 4x... where x is the original lot size.

  5. The gap between the orders can be a fixed value or according to some indicator such as volatility/standard deviation.



This method is not dangerous as the infamous martingale in that the margin is reasonable and doesn't cause the account to explode. I am running a live account using this system with the gap set to 500 pips and so far, I didn't have any issues whatsoever. You are welcome to use this code as you wish, stick it in your expert adviser and share the results with me.

MQL4 Full Code



The full code is as follows:

const string SYMBOL=Symbol();
const double CLEANUP_TRIGGER=500*MarketInfo(SYMBOL,MODE_POINT);





if(CleanupFlag==true)
{
CleanupFlag=false;

for(int i=OrdersTotal()-1;i>=0;i--)
if(OrderSelect(i,SELECT_BY_POS) && StringCompare(SYMBOL,OrderSymbol())==0 && StringLen(OrderComment())!=0)
{
CleanupFlag=true;

string Tickets=IntegerToString(OrderTicket());
double Profit=OrderProfit()+OrderCommission()+OrderSwap();

while(StringFind(OrderComment(),";")!=-1)
{
string TheCommentDetails[];
StringSplit(OrderComment(),StringGetCharacter(";",0),TheCommentDetails);
if(OrderSelect((int)StringToInteger(TheCommentDetails[1]),SELECT_BY_TICKET) && OrderCloseTime()==0)
{
Tickets=StringConcatenate(IntegerToString(OrderTicket()),";",Tickets);
Profit+=OrderProfit()+OrderCommission()+OrderSwap();
}
else
{
Log(StringConcatenate("Failed selecing ticket #",IntegerToString(OrderTicket())," line ",__LINE__));
ExpertRemove();
return;
}
}

if(Profit>0)
{
string TheTickets[];
if(StringSplit(Tickets,StringGetCharacter(";",0),TheTickets)>1)
for(int j=ArraySize(TheTickets)-1;j>=0;j--)
if(OrderSelect((int)StringToInteger(TheTickets[j]),SELECT_BY_TICKET) && OrderCloseTime()==0 && CloseOrder((int)StringToInteger(TheTickets[j]))==false)
{
Log(StringConcatenate("Failed closing ticket #",IntegerToString(OrderTicket())," line ",__LINE__));
ExpertRemove();
return;
}
}
}
}





for(int i=OrdersTotal()-1;i>=0;
if(OrderSelect(i,SELECT_BY_POS) && StringCompare(SYMBOL,OrderSymbol())==0 && (OrderProfit()+OrderSwap()+OrderCommission())<0 && MathAbs(OrderOpenPrice()-iClose(SYMBOL,PERIOD_M1,0))>CLEANUP_TRIGGER)
{
CleanupFlag=true;

bool HedgeExists=false;
int TheTicket=OrderTicket();
int TheTicketType=OrderType();
string TheTicketComment=OrderComment();

for(int j=OrdersTotal()-1;j>=0;j--)
if(OrderSelect(j,SELECT_BY_POS) && StringCompare(SYMBOL,OrderSymbol())==0)
{
string TheCommentDetails[];
if(StringSplit(OrderComment(),StringGetCharacter(";",0),TheCommentDetails)==2)
{
if(StringToInteger(TheCommentDetails[1])==TheTicket)
{
HedgeExists=true;
break;
}
}
}

if(HedgeExists==true)
continue;
else if(StringFind(TheTicketComment,";")==-1)
{
if(OpenOrder(SYMBOL,TheTicketType,LOTS,0,0,StringConcatenate(IntegerToString(2),";",IntegerToString(TheTicket)))==-1)
{
Log(StringConcatenate("Failed opening a cleanup position on ticket #",IntegerToString(TheTicket)," line ",__LINE__));
ExpertRemove();
return;
}
}
else
{
string TheCommentDetails[];
StringSplit(TheTicketComment,StringGetCharacter(";",0),TheCommentDetails);
if(OpenOrder(SYMBOL,TheTicketType,(int)MathCeil((double)(StringToInteger(TheCommentDetails[0])+1)/2)*LOTS,0,0,StringConcatenate(IntegerToString(StringToInteger(TheCommentDetails[0])+1),";",IntegerToString(TheTicket)))==-1)
{
Log(StringConcatenate("Failed opening a cleanup position on ticket #",IntegerToString(TheTicket)," line ",__LINE__));
ExpertRemove();
return;
}
}
}


Breaking It Down



Setting the Gap



As mentioned, the first step is deciding what will be the gap we let an order run until we hedge it. This is the same like having a stop loss at the point, but instead, we open another trade with the same direction. In my case, following some exhaustive testing, I realized that a fixed gap works the best for me:

const string SYMBOL=Symbol();
const double CLEANUP_TRIGGER=500*MarketInfo(SYMBOL,MODE_POINT);


In this case, I set the cleanup trigger to 500 pips, so every 500 pips against the order type original direction a new hedge will open.

Hedging



The acutal code to open a new hedge is straightforward. Simply go through the open orders and check if any of their open price is remote from the current price with more than the predefined gap:

for(int i=OrdersTotal()-1;i>=0;i--) 
if(OrderSelect(i,SELECT_BY_POS) && StringCompare(SYMBOL,OrderSymbol())==0 && (OrderProfit()+OrderSwap()+OrderCommission())<0 && MathAbs(OrderOpenPrice()-iClose(SYMBOL,PERIOD_M1,0))>CLEANUP_TRIGGER)
{


Then, go through all the open orders and check if there is already a hedge open for this order.

CleanupFlag=true; 
bool HedgeExists=false;
int TheTicket=OrderTicket();
int TheTicketType=OrderType();
string TheTicketComment=OrderComment();
for(int j=OrdersTotal()-1;j>=0;j--)
if(OrderSelect(j,SELECT_BY_POS) && StringCompare(SYMBOL,OrderSymbol())==0)
{
string TheCommentDetails[]; if(StringSplit(OrderComment(),StringGetCharacter(";",0),TheCommentDetails)==2)
{
if(StringToInteger(TheCommentDetails[1])==TheTicket)
{
HedgeExists=true; break;
}
}
}


Notice that I am using the order comment to tag the order being hedged by a two size array:


  • TheCommentDetails[0] will hold the hedge level we have at the moment, the higher the level, the bigger the lot size we will open.

  • TheCommentDetails[1] will hold the order number being hedged, so when the loop is running, we compare if the current prolematic order was hedged already or not.



If there is no hedge for this order, we will open one:

if(HedgeExists==true)
continue;
else if(StringFind(TheTicketComment,";")==-1)
{
if(OpenOrder(SYMBOL,TheTicketType,LOTS,0,0,StringConcatenate(IntegerToString(2),";",IntegerToString(TheTicket)))==-1)
{
Log(StringConcatenate("Failed opening a cleanup position on ticket #",IntegerToString(TheTicket)," line ",__LINE__));
ExpertRemove();
return;
}
}
else
{
string TheCommentDetails[];
StringSplit(TheTicketComment,StringGetCharacter(";",0),TheCommentDetails);
if(OpenOrder(SYMBOL,TheTicketType,(int)MathCeil((double)(StringToInteger(TheCommentDetails[0])+1)/2)*LOTS,0,0,StringConcatenate(IntegerToString(StringToInteger(TheCommentDetails[0])+1),";",IntegerToString(TheTicket)))==-1)
{
Log(StringConcatenate("Failed opening a cleanup position on ticket #",IntegerToString(TheTicket)," line ",__LINE__));
ExpertRemove();
return;
}
}


Closing Hedges



Finally, we go over a loop and check for all the orders, if the hedges sum > 0. If yes, we close them all:

if(CleanupFlag==true)
{
CleanupFlag=false;

for(int i=OrdersTotal()-1;i>=0;i--)
if(OrderSelect(i,SELECT_BY_POS) && StringCompare(SYMBOL,OrderSymbol())==0 && StringLen(OrderComment())!=0)
{
CleanupFlag=true;

string Tickets=IntegerToString(OrderTicket());
double Profit=OrderProfit()+OrderCommission()+OrderSwap();

while(StringFind(OrderComment(),";")!=-1)
{
string TheCommentDetails[];
StringSplit(OrderComment(),StringGetCharacter(";",0),TheCommentDetails);
if(OrderSelect((int)StringToInteger(TheCommentDetails[1]),SELECT_BY_TICKET) && OrderCloseTime()==0)
{
Tickets=StringConcatenate(IntegerToString(OrderTicket()),";",Tickets);
Profit+=OrderProfit()+OrderCommission()+OrderSwap();
}
else
{
Log(StringConcatenate("Failed selecing ticket #",IntegerToString(OrderTicket())," line ",__LINE__));
ExpertRemove();
return;
}
}

if(Profit>0)
{
string TheTickets[];
if(StringSplit(Tickets,StringGetCharacter(";",0),TheTickets)>1)
for(int j=ArraySize(TheTickets)-1;j>=0;j--)
if(OrderSelect((int)StringToInteger(TheTickets[j]),SELECT_BY_TICKET) && OrderCloseTime()==0 && CloseOrder((int)StringToInteger(TheTickets[j]))==false)
{
Log(StringConcatenate("Failed closing ticket #",IntegerToString(OrderTicket())," line ",__LINE__));
ExpertRemove();
return;
}
}
}
}





Stay tuned for my next webinar "FX Algotraind Q&A" scheduled for Thursday 16th August, 19:00-20:00 GMT+2.

For any inquiries, please don’t hesitate to contact me per email or mobile.

Comments