risk-position-tool/mql5/Include/ChartKit/CkRiskBox.mqh
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
//+------------------------------------------------------------------+