Semi-Automatic Predefined Risk and RR Ratio
Semi-Automatic Predefined Risk and RR Ratio
Introduction
Some traders execute all their trades automatically, and some mix automatic and manual trades based on the
output of several indicators. Being a member of the latter group I needed an interactive tool to asses dynamically
risk and reward price levels directly from the chart.
Having declared maximum risk on my equity I wanted to calculate real-time parameters based on the stop-loss
level I put on the chart and I needed to execute my trade directly from the EA based on calculated SL and TP
levels.
This article will present a way to implement an interactive semi-automatic Expert Advisor with predefined equity risk
and R/R ratio. The Expert Advisor risk, R/R and lot size parameters can be changed during runtime on the EA
panel.
1. Requirements
The requirements for the EA were as follows:
ability to predefine risk level at startup and to change it during runtime to see how it affects position size
ability to predefine risk to reward ratio and change it during runtime
ability to calculate real-time maximum lot size for given risk and stop-loss level
ability to change lot size at runtime to see how it affect equity risk and reward
ability to execute buy/sell market order directly from EA
drag and drop interface to set stop-loss and to see price level for predefined risk to reward level
2. Design
Due to requirements for the EA of displaying and changing parameters during runtime I decided I would use
CChartObject classes and its descendands to display GUI on the chart window and handle incoming chart events
for user interaction. Therefore, the EA needed user interface with labels, buttons and edit fields.
At first I wanted to use CChartObjectPanel object for grouping other objects on panel, but I decided to try a
different approach, I designed a class that holds labels, edit fields and buttons and displays it on an image
background. The background image of the interface was made using GIMP software. MQL5 generated objects are
edit fields, red labels updated real-time and buttons.
I simply put label objects on the chart and recorded their position and constructed CRRDialog class that handles all
functions of displaying calculated output, receiving parameters of CChartObjectEdit fields and recording button
states. Color risk and reward rectangles are objects of CChartObjectRectangle class and draggable stop loss pointer
is a bitmap object of CChartObjectBitmap class.
Figure 1. Visual EA screenshot
I am using CChartObjectBmpLabel object for the background, CChartObjectEdit objects for edit fields,
CChartObjectLabel objects for displaying labels and CChartObjectButton objects for buttons:
class CRRDialog
{
private :
int m_baseX;
int m_baseY;
int m_fontSize;
string m_font;
string m_dialogName;
string m_bgFileName;
double m_RRRatio;
double m_riskPercent;
double m_orderLots;
double m_SL;
double m_TP;
double m_maxAllowedLots;
double m_maxTicksLoss;
double m_orderEquityRisk;
double m_orderEquityReward;
ENUM_ORDER_TYPE m_orderType;
CChartObjectBmpLabel m_bgDialog;
CChartObjectEdit m_riskRatioEdit;
CChartObjectEdit m_riskValueEdit;
CChartObjectEdit m_orderLotsEdit;
CChartObjectLabel m_symbolNameLabel;
CChartObjectLabel m_tickSizeLabel;
CChartObjectLabel m_maxEquityLossLabel;
CChartObjectLabel m_equityLabel;
CChartObjectLabel m_profitValueLabel;
CChartObjectLabel m_askLabel;
CChartObjectLabel m_bidLabel;
CChartObjectLabel m_tpLabel;
CChartObjectLabel m_slLabel;
CChartObjectLabel m_maxAllowedLotsLabel;
CChartObjectLabel m_maxTicksLossLabel;
CChartObjectLabel m_orderEquityRiskLabel;
CChartObjectLabel m_orderEquityRewardLabel;
CChartObjectLabel m_orderTypeLabel;
CChartObjectButton m_switchOrderTypeButton;
CChartObjectButton m_placeOrderButton;
CChartObjectButton m_quitEAButton;
public :
void CRRDialog(); // CRRDialog constructor
void ~CRRDialog(); // CRRDialog destructor
bool CreateCRRDialog( int topX, int leftY);
int DeleteCRRDialog();
void Refresh();
void SetRRRatio( double RRRatio);
void SetRiskPercent( double riskPercent);
double GetRiskPercent();
double GetRRRRatio();
void SetSL( double sl);
void SetTP( double tp);
double GetSL();
double GetTP();
void SetMaxAllowedLots( double lots);
void SetMaxTicksLoss( double ticks);
void SetOrderType( ENUM_ORDER_TYPE );
void SwitchOrderType();
void ResetButtons();
ENUM_ORDER_TYPE GetOrderType();
void SetOrderLots( double orderLots);
double GetOrderLots();
void SetOrderEquityRisk( double equityRisk);
void SetOrderEquityReward( double equityReward);
};
Since get/set variables methods are straightforward, I will concentrate on CreateCRRDialog() and Refresh()
methods. CreateCRRDialog() method initializes background image, labels, buttons and edit fields.
For initializing labels and edit fields I use: Create() method with coordinate parameters to locate the object on the
chart, Font() and FontSize() method to setup font and Description() method to put text on the label.
For buttons: Create() method additional parameters specify button size and BackColor() method specifies
background color of the button.
Refresh() method refreshes all labels and buttons description with the CRRDialog variables and current bid/ask
levels, account equity and equity risk values:
void CRRDialog::Refresh()
{
MqlTick current_tick;
SymbolInfoTick( Symbol (),current_tick);
m_equityLabel.Description( DoubleToString( AccountInfoDouble ( ACCOUNT_EQUITY), 2 ));
m_maxEquityLossLabel.Description( DoubleToString( AccountInfoDouble ( ACCOUNT_EQUITY)*
StringToDouble(m_riskValueEdit.Description())/ 100.0 , 2 ));
m_askLabel.Description( DoubleToString(current_tick.ask, Digits ()));
m_bidLabel.Description( DoubleToString(current_tick.bid, Digits ()));
m_slLabel.Description( DoubleToString(m_SL, Digits ()));
m_tpLabel.Description( DoubleToString(m_TP, Digits ()));
m_maxAllowedLotsLabel.Description( DoubleToString(m_maxAllowedLots, 2 ));
m_maxTicksLossLabel.Description( DoubleToString(m_maxTicksLoss, 0 ));
m_orderEquityRiskLabel.Description( DoubleToString(m_orderEquityRisk, 2 ));
m_orderEquityRewardLabel.Description( DoubleToString(m_orderEquityReward, 2 ));
if (m_orderType== ORDER_TYPE_BUY) m_switchOrderTypeButton.Description( "Order Type: BUY");
else if (m_orderType== ORDER_TYPE_SELL ) m_switchOrderTypeButton.Description( "Order Type: SELL" );
}
4. Chart Events
Since EA is designed to be interactive, it shall handle chart events.
dragging S/L pointer (SL_arrow object of CChartObjectBitmap class) on the chart - this will allow to collect
S/L level and calculate T/P level based on R/R ratio
switching order type (buy/sell) button
pressing 'place market order' button
editing risk, R/R and order lot fields
closing EA after pressing 'Exit' button
Events that are handled are CHARTEVENT_OBJECT_CLICK for pointer selection and
buttons, CHARTEVENT_OBJECT_DRAG for dragging S/L pointer, and CHARTEVENT_OBJECT_ENDEDIT after edit
fields are updated by the trader.
At first implementation of OnChartEvent() function took a few pages of code, but I decided to split it into several
event handlers, this converted the OnChartEvent() function to human readeable form:
Event handlers implementation will be described in more detail in next sections. Worth noticing is the trick I used
for selecting SL_arrow object. Normally, to select an object on the chart one has to click twice on it. But it can be
selected by clicking once and invoking Selected() method of CChartObject object or its descendant in
OnChartEvent() function inside CHARTEVENT_OBJECT_CLICK event handler:
if (clickedChartObject==slButtonID)
SL_arrow.Selected(!SL_arrow.Selected());
Object it is selected or deselected depending on its previous state after one click.
For money management I reused CMoneyFixedRisk class provided by MetaQuotes and implemented
CMoneyFixedRiskExt class.
Original CMoneyFixedRisk class methods return allowed order lot amounts for given price, stop-loss level and equity
risk between minimum and maximum lot size allowed by broker. I changed CheckOpenLong() and
CheckOpenShort() methods to return 0.0 lot size if risk requirements are not met and extended it with four
methods: GetMaxSLPossible(), CalcMaxTicksLoss(), CalcOrderEquityRisk() and CalcOrderEquityReward():
GetMaxSLPossible() method calculates maximum stop-loss price value for given equity risk and minimum allowed
trade size.
For example if account balance is 10 000 of account base currency and risk is 2%, we may put maximum 200 of
account currency at risk. If the minimum trade lot size is 0.1 lot, this method returns price level
for ORDER_TYPE_BUY or ORDER_TYPE_SELL order that will meet value of equity risk for position of 0.1 lot. This
helps to estimate of what is the maximum stop-loss level we can afford for minimum lot size trade. This is a price
level we cannot cross for given equity risk level.
CalcMaxTickLoss() method returns maximum number of ticks we can afford to loose for given risk and minimum
allowed lot size.
At first maximum equity loss is calculated as percentage of the current balance, then tick value loss for change of
one tick for minimum allowed lot size for given symbol is calculated. Then maximum equity loss is divided by tick
value loss and the result is. rounding it to integer value with MathFloor() function:
double CMoneyFixedRiskExt::CalcMaxTicksLoss()
{
double maxEquityLoss, tickValLoss, maxTicksLoss;
double minvol=m_symbol.LotsMin();
if (m_symbol== NULL) return ( 0.0 );
maxEquityLoss = m_account.Balance()*m_percent/ 100.0 ; // max loss
tickValLoss = minvol*m_symbol.TickValueLoss(); // tick val loss
maxTicksLoss = MathFloor(maxEquityLoss/tickValLoss);
return (maxTicksLoss);
}
CalcOrderEquityRisk() method returns equity risk for given price, stop loss level and amount of lots. It is calculated
by multiplying tick loss value by number of lots and price then multiplying by difference between current price and
stop-loss level:
Those methods are sufficient for calculating maximum stop-loss levels and returning real-time equity risk and
reward. CalcMaxTickLoss() method is used to correct drawing of risk rectangle - if trader wants to put a trade that
crosses the boundary of number of ticks he can afford to loose, the rectangle is drawn only to the maximum
number of ticks he can loose.
It makes life easier to see it directly on the chart. You can see it in demo at the end of the article.
void EA_switchOrderType()
{
symbolInfo.RefreshRates();
visualRRDialog.SwitchOrderType();
visualRRDialog.ResetButtons();
visualRRDialog.SetSL( 0.0 );
visualRRDialog.SetTP( 0.0 );
visualRRDialog.SetMaxAllowedLots( 0.0 );
visualRRDialog.SetOrderLots( 0.0 );
visualRRDialog.SetMaxTicksLoss( 0 );
visualRRDialog.SetOrderEquityRisk( 0.0 );
visualRRDialog.SetOrderEquityReward( 0.0 );
if (visualRRDialog.GetOrderType()== ORDER_TYPE_BUY)
SL_arrow.SetDouble( OBJPROP_PRICE ,symbolInfo.Ask());
else if (visualRRDialog.GetOrderType()== ORDER_TYPE_SELL )
SL_arrow.SetDouble( OBJPROP_PRICE ,symbolInfo.Bid());
SL_arrow.SetInteger( OBJPROP_TIME , 0 , TimeCurrent ());
rectReward.Delete();
rectRisk.Delete();
visualRRDialog.Refresh();
}
EA_dragBuyHandle() handler is triggered after SL_arrow object is dragged and dropped on the chart. At first it
reads SL_arrow object drop point time and price parameters from the chart and sets price level as a hypotetical
stop-loss for our trade.
Then it calculates how many lots can we open for given risk on the equity. If stop loss value cannot guarantee risk
objective for the lowest trading lot possible on that symbol, it is automatically moved to the maximum SL level
possible. This helps to assess how much space we have for stop loss for given risk.
After calculating risk and reward, rectangle objects are updated on the chart.
void EA_dragBuyHandle()
{
SL_arrow.GetDouble( OBJPROP_PRICE , 0 ,SL_price);
SL_arrow.GetInteger( OBJPROP_TIME , 0 ,startTime);
symbolInfo.RefreshRates();
currentTime= TimeCurrent ();
// BUY
double allowedLots=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);
Print ( "Allowed lots = " + DoubleToString(allowedLots, 2 ));
double lowestSLAllowed=MM.GetMaxSLPossible(symbolInfo.Ask(), ORDER_TYPE_BUY);
if (SL_price<lowestSLAllowed)
{
SL_price=lowestSLAllowed;
ObjectSetDouble ( 0 ,slButtonID, OBJPROP_PRICE ,lowestSLAllowed);
}
visualRRDialog.SetSL(SL_price);
visualRRDialog.SetTP(symbolInfo.Ask()+(symbolInfo.Ask()-SL_price)*visualRRDialog.GetRRRRatio());
if (visualRRDialog.GetTP()<SL_price)
{
visualRRDialog.SetSL( 0.0 );
visualRRDialog.SetTP( 0.0 );
SL_arrow.SetDouble( OBJPROP_PRICE ,symbolInfo.Ask());
rectReward.Delete();
rectRisk.Delete();
return ;
}
double lotSize=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);
visualRRDialog.SetMaxAllowedLots(lotSize);
visualRRDialog.SetOrderLots(lotSize);
visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());
visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Ask(), SL_price, lotSize));
visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Ask(),
SL_price, lotSize, visualRRDialog.GetRRRRatio()));
visualRRDialog.Refresh();
rectUpdate(visualRRDialog.GetOrderType());
}
Calculations are based on symbolInfo.Bid() price, and rectangles are drawn accordingly, that is green zone marking
profit is below current price level.
void EA_dragSellHandle()
{
SL_arrow.GetDouble( OBJPROP_PRICE , 0 ,SL_price);
SL_arrow.GetInteger( OBJPROP_TIME , 0 ,startTime);
symbolInfo.RefreshRates();
currentTime= TimeCurrent ();
double allowedLots=MM.CheckOpenShort(symbolInfo.Bid(),SL_price);
Print ( "Allowed lots = " + DoubleToString(allowedLots, 2 ));
double maxSLAllowed=MM.GetMaxSLPossible(symbolInfo.Bid(), ORDER_TYPE_SELL );
if (SL_price>maxSLAllowed)
{
SL_price=maxSLAllowed;
SL_arrow.SetDouble( OBJPROP_PRICE , 0 ,maxSLAllowed);
}
visualRRDialog.SetSL(SL_price);
visualRRDialog.SetTP(symbolInfo.Bid()-(SL_price-symbolInfo.Bid())*visualRRDialog.GetRRRRatio());
if (visualRRDialog.GetTP()>SL_price)
{
visualRRDialog.SetSL( 0.0 );
visualRRDialog.SetTP( 0.0 );
SL_arrow.SetDouble( OBJPROP_PRICE ,symbolInfo.Bid());
rectReward.Delete();
rectRisk.Delete();
return ;
}
double lotSize=MM.CheckOpenShort(symbolInfo.Bid(),SL_price);
visualRRDialog.SetMaxAllowedLots(lotSize);
visualRRDialog.SetOrderLots(lotSize);
visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());
visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Bid(), SL_price, lotSize));
visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Bid(),
SL_price, lotSize, visualRRDialog.GetRRRRatio()));
visualRRDialog.Refresh();
rectUpdate(visualRRDialog.GetOrderType());
}
EA_placeOrder() is triggered after m_placeOrderButton object was pressed. It places buy or sell market order for
calculated SL and TP levels and given lot size.
Please notice how easy it is to place market order using CExpertTrade class.
bool EA_placeOrder()
{
symbolInfo.RefreshRates();
visualRRDialog.ResetButtons();
if (visualRRDialog.GetOrderType()== ORDER_TYPE_BUY)
orderPlaced=trade.Buy(visualRRDialog.GetOrderLots(),symbolInfo.Ask(),
visualRRDialog.GetSL(),visualRRDialog.GetTP(), TimeToString ( TimeCurrent ()));
else if (visualRRDialog.GetOrderType()== ORDER_TYPE_SELL )
orderPlaced=trade.Sell(visualRRDialog.GetOrderLots(),symbolInfo.Bid(),
visualRRDialog.GetSL(),visualRRDialog.GetTP(), TimeToString ( TimeCurrent ()));
return orderPlaced;
}
EA_editParamsUpdate() handler is triggered when Enter key is pressed after editing one of the edit
fields: riskRatioEdit, riskValueEdit and orderLotsEdit.
When this happens allowed lot size, TP level, max tick loss, equity risk and reward need to be recalculated:
void EA_editParamsUpdate()
{
MM.Percent(visualRRDialog.GetRiskPercent());
SL_arrow.GetDouble( OBJPROP_PRICE , 0 , SL_price);
SL_arrow.GetInteger( OBJPROP_TIME , 0 , startTime);
symbolInfo.RefreshRates();
currentTime= TimeCurrent ();
double allowedLots=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);
double lowestSLAllowed=MM.GetMaxSLPossible(symbolInfo.Ask(), ORDER_TYPE_BUY);
if (SL_price<lowestSLAllowed)
{
SL_price=lowestSLAllowed;
ObjectSetDouble ( 0 ,slButtonID, OBJPROP_PRICE ,lowestSLAllowed);
}
visualRRDialog.SetSL(SL_price);
visualRRDialog.SetTP(symbolInfo.Ask()+(symbolInfo.Ask()-SL_price)*visualRRDialog.GetRRRRatio());
visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());
visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Ask(),
SL_price, visualRRDialog.GetOrderLots()));
visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Ask(), SL_price,
visualRRDialog.GetOrderLots(), visualRRDialog.GetRRRRatio()));
visualRRDialog.Refresh();
rectUpdate(visualRRDialog.GetOrderType());
ChartRedraw ();
}
EA_onTick() is invoked every time new tick arrives. Calculations are performed only if order was not placed yet and
stop loss level was already chosen by dragging SL_arrow pointer.
After order is placed, risk and reward and TP level as well as redrawing of the risk and reward are not needed.
void EA_onTick()
{
if (SL_price!= 0.0 && orderPlaced==false)
{
double lotSize= 0.0 ;
SL_price=visualRRDialog.GetSL();
symbolInfo.RefreshRates();
if (visualRRDialog.GetOrderType()== ORDER_TYPE_BUY)
lotSize=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);
else if (visualRRDialog.GetOrderType()== ORDER_TYPE_SELL )
lotSize=MM.CheckOpenShort(symbolInfo.Ask(),SL_price);
visualRRDialog.SetMaxAllowedLots(lotSize);
if (visualRRDialog.GetOrderLots()>lotSize) visualRRDialog.SetOrderLots(lotSize);
visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());
if (visualRRDialog.GetOrderType()== ORDER_TYPE_BUY)
{
visualRRDialog.SetTP(symbolInfo.Ask()+(symbolInfo.Ask()-SL_price)*visualRRDialog.GetRRRRatio());
visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Ask(),
SL_price, visualRRDialog.GetOrderLots()));
visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Ask(), SL_price,
visualRRDialog.GetOrderLots(), visualRRDialog.GetRRRRatio()));
}
else if (visualRRDialog.GetOrderType()== ORDER_TYPE_SELL )
{
visualRRDialog.SetTP(symbolInfo.Bid()-(SL_price-symbolInfo.Bid())*visualRRDialog.GetRRRRatio());
visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(
symbolInfo.Bid(), SL_price, visualRRDialog.GetOrderLots()));
visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Bid(), SL_price,
visualRRDialog.GetOrderLots(), visualRRDialog.GetRRRRatio()));
}
visualRRDialog.Refresh();
rectUpdate(visualRRDialog.GetOrderType());
}
ChartRedraw ( 0 );
}
Function rectUpdate() is responsible for redrawing color risk and reward rectangles. The control points are SL_arrow
object start time, current Ask or Bid price value depending on order type, and SL and TP levels. Light pink rectangle
shows price range between current price and SL level and light green rectangle shows price range between current
price and TP level.
Both rectangles are a great tool to observe risk to reward ratio impact on SL and TP price levels and help to adjust
risk before entering the trade.
7. Demo
Please observe below demo of the working Expert Advisor in action. I am doing sell order after big rebounce shortly
after market opened on Monday 01/11/2010.
For best viewing experience please set the video to full screen and quality to 480p. Comments are included in the
video:
Conclusion
In the following article I presented a way to build interactive Expert Advisor for manual trading based on predefined
risk and risk to reward ratio.
I showed how to use standard classes for displaying content on the chart and how to handle chart events for
entering new data and handling drag-and-drop objects. I hope that the ideas I presented will serve as basis for
building other configurable visual tools in MQL5.
Attached files
visualrrea.mq5 (13.84 KB)
crrdialog.mqh (13.95 KB)
visualrrids.mqh (0.8 KB)
moneyfixedriskext.mqh (7.51 KB)
images.zip (159.05 KB)
interactive_expert_mql5_doc.zip (1445.52 KB)
flimboquest:
Hi Nice Expert
It is the CExpert class from Standard Library. Look up it in the include folder.
Hi Nice Expert
#include <Experts\Money\MoneyFixedRiskExt.mqh>
#include <ChartObjects\ChartObjectsShapes.mqh>
#include <ChartObjects\ChartObjectsBmpControls.mqh>
#include <Trade\SymbolInfo.mqh>
#include <CRRDialog.mqh>
#include <VisualRRIDs.mqh>