92 lines
3.4 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| CkRiskBox.mqh |
| |||
//| Copyright 2026, Allan Niñal |
| |||
//| https://www.allanninal.dev/mql5 |
| |||
//+------------------------------------------------------------------+
| |||
//| Chart kit risk/reward box: a loss zone (entry to stop) and a |
| |||
//| profit zone (entry to target) behind the candles, and one tag |
| |||
//| per line at the box's right end ("SL 1.08210 -50.00 USD 1R"). |
| |||
//| The product's lines stay the source of truth: the box is redrawn |
| |||
//| from their prices and never moves them. Draw only; it sends no |
| |||
//| order and needs no trade permission. |
| |||
//+------------------------------------------------------------------+
| |||
#ifndef CK_RISKBOX_MQH
| |||
#define CK_RISKBOX_MQH
| |||
| |||
#include "CkTheme.mqh"
| |||
#include "CkObj.mqh"
| |||
| |||
struct CkBoxSpec
| |||
{
| |||
double entry,sl,tp; // tp 0 = no target
| |||
string tag_entry,tag_sl,tag_tp; // the line tags ("" = none)
| |||
string tip_sl,tip_tp; // zone tooltips
| |||
int bars_back,bars_ahead; // box width around the current bar
| |||
};
| |||
| |||
void CkBoxDefaults(CkBoxSpec &b)
| |||
{
| |||
b.entry=0; b.sl=0; b.tp=0;
| |||
b.tag_entry=""; b.tag_sl=""; b.tag_tp="";
| |||
b.tip_sl=""; b.tip_tp="";
| |||
b.bars_back=24; b.bars_ahead=10;
| |||
}
| |||
| |||
//--- bars visible to the right of the current bar: the chart-shift area (0 with chart shift off)
| |||
int CkBarsAhead(void)
| |||
{
| |||
if(!ChartGetInteger(0,CHART_SHIFT))
| |||
return(0);
| |||
double shift=ChartGetDouble(0,CHART_SHIFT_SIZE); // % of the chart width
| |||
long wb=ChartGetInteger(0,CHART_WIDTH_IN_BARS);
| |||
return((int)MathMax(0,MathFloor(wb*shift/100.0)-1));
| |||
}
| |||
| |||
//--- the box's time span: from `bars_back` bars before the current bar to `bars_ahead` bars after it, cut to what the
| |||
//--- chart shows to the right, so the tags at the right end stay on screen
| |||
void CkBoxTimes(const CkBoxSpec &b,datetime &t1,datetime &t2)
| |||
{
| |||
int sec=PeriodSeconds(_Period);
| |||
datetime t0=iTime(_Symbol,_Period,0);
| |||
if(t0==0)
| |||
t0=TimeCurrent();
| |||
t1=t0-(datetime)(b.bars_back*sec);
| |||
t2=t0+(datetime)(MathMin(b.bars_ahead,CkBarsAhead())*sec);
| |||
}
| |||
| |||
void CkRiskBoxDelete(const string pfx) { CkDeleteAll(pfx); }
| |||
| |||
void CkRiskBoxDraw(const string pfx,const CkTheme &th,const CkBoxSpec &b)
| |||
{
| |||
if(!CkCanDraw())
| |||
return;
| |||
if(b.entry<=0 || b.sl<=0 || b.entry==b.sl)
| |||
{
| |||
CkRiskBoxDelete(pfx);
| |||
return;
| |||
}
| |||
datetime t1,t2;
| |||
CkBoxTimes(b,t1,t2);
| |||
bool buy=b.sl<b.entry;
| |||
CkRect(pfx+"loss",t1,b.entry,t2,b.sl,th.zone_neg,b.tip_sl);
| |||
if(b.tp>0)
| |||
CkRect(pfx+"gain",t1,b.entry,t2,b.tp,th.zone_pos,b.tip_tp);
| |||
else
| |||
CkDelete(pfx+"gain");
| |||
//--- the stop and target tags sit on the outer side of their lines, the entry tag inside the loss zone: no overlap
| |||
if(b.tag_entry!="")
| |||
CkText(pfx+"t_en",t2,b.entry,b.tag_entry,th.acc,buy ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER);
| |||
else
| |||
CkDelete(pfx+"t_en");
| |||
if(b.tag_sl!="")
| |||
CkText(pfx+"t_sl",t2,b.sl,b.tag_sl,th.neg,buy ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER);
| |||
else
| |||
CkDelete(pfx+"t_sl");
| |||
if(b.tp>0 && b.tag_tp!="")
| |||
CkText(pfx+"t_tp",t2,b.tp,b.tag_tp,th.pos,buy ? ANCHOR_RIGHT_LOWER : ANCHOR_RIGHT_UPPER);
| |||
else
| |||
CkDelete(pfx+"t_tp");
| |||
}
| |||
| |||
#endif
| |||
//+------------------------------------------------------------------+
|