93 lines
3.5 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| VtExposure.mqh |
| |||
//| Copyright 2026, Allan Niñal |
| |||
//| https://www.allanninal.dev/mql5 |
| |||
//+------------------------------------------------------------------+
| |||
//| Open positions: long / short / total, risk at the stops, the |
| |||
//| positions without a stop, and the net exposure per currency in |
| |||
//| account money. Pure aggregation of numbers the live layer |
| |||
//| (VtLive.mqh) read from the terminal: the same function runs in |
| |||
//| the tests (tests/reference.py twins it). |
| |||
//| Exposure of a position = +/- its notional (account money): |
| |||
//| long = + base currency, - profit currency. When base == |
| |||
//| profit currency (an index CFD) the symbol is the "currency". |
| |||
//+------------------------------------------------------------------+
| |||
// SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 (see LICENSE)
| |||
#ifndef VTEXPOSURE_MQH
| |||
#define VTEXPOSURE_MQH
| |||
#include "VtCore.mqh"
| |||
| |||
struct VtPos
| |||
{
| |||
long magic;
| |||
string symbol,comment;
| |||
int dir; // 0 long, 1 short
| |||
double volume,profit,pips,sl,risk,value; // profit incl. swap; risk = OrderCalcProfit at the stop (<=0 is a loss)
| |||
string base,ccy; // base and profit currency
| |||
};
| |||
| |||
void VtSummarize(CVtEngine &e,const VtPos &pos[],const int n,const double balance)
| |||
{
| |||
for(int k=0; k<3; k++)
| |||
{ e.o_n[k]=0; e.o_lots[k]=0; e.o_pl[k]=0; e.o_pips[k]=0; }
| |||
e.o_risk=0; e.o_nostop=0; e.o_nostop_first=""; e.o_nex=0;
| |||
ArrayResize(e.ex_name,0,8);
| |||
ArrayResize(e.ex_net,0,8);
| |||
for(int i=0; i<n; i++)
| |||
{
| |||
if(!e.PassPos(pos[i].symbol,pos[i].magic,pos[i].comment))
| |||
continue;
| |||
for(int k=0; k<3; k++)
| |||
if(k==2 || k==pos[i].dir)
| |||
{
| |||
e.o_n[k]++;
| |||
e.o_lots[k]+=pos[i].volume;
| |||
e.o_pl[k]+=pos[i].profit;
| |||
e.o_pips[k]+=pos[i].pips;
| |||
}
| |||
if(pos[i].sl>0)
| |||
e.o_risk+=pos[i].risk;
| |||
else
| |||
{
| |||
e.o_nostop++;
| |||
if(e.o_nostop_first=="")
| |||
e.o_nostop_first=StringFormat("%s %s %.2f",pos[i].symbol,pos[i].dir==0 ? "long" : "short",pos[i].volume);
| |||
}
| |||
double s=(pos[i].dir==0 ? 1.0 : -1.0);
| |||
string key[2];
| |||
double val[2];
| |||
int nk=0;
| |||
if(pos[i].base==pos[i].ccy)
| |||
{ key[0]=pos[i].symbol; val[0]=s*pos[i].value; nk=1; }
| |||
else
| |||
{ key[0]=pos[i].base; val[0]=s*pos[i].value; key[1]=pos[i].ccy; val[1]=-s*pos[i].value; nk=2; }
| |||
for(int q=0; q<nk; q++)
| |||
{
| |||
int f=-1;
| |||
for(int j=0; j<e.o_nex; j++)
| |||
if(e.ex_name[j]==key[q])
| |||
{ f=j; break; }
| |||
if(f<0)
| |||
{
| |||
f=e.o_nex++;
| |||
ArrayResize(e.ex_name,e.o_nex,8);
| |||
ArrayResize(e.ex_net,e.o_nex,8);
| |||
e.ex_name[f]=key[q]; e.ex_net[f]=0;
| |||
}
| |||
e.ex_net[f]+=val[q];
| |||
}
| |||
}
| |||
e.o_risk_pct=(balance>0 ? 100.0*e.o_risk/balance : 0.0);
| |||
for(int i=1; i<e.o_nex; i++) // |net| descending, then name
| |||
{
| |||
string nm=e.ex_name[i];
| |||
double v=e.ex_net[i];
| |||
int j=i-1;
| |||
while(j>=0 && (MathAbs(e.ex_net[j])<MathAbs(v) ||
| |||
(MathAbs(e.ex_net[j])==MathAbs(v) && StringCompare(e.ex_name[j],nm)>0)))
| |||
{ e.ex_name[j+1]=e.ex_name[j]; e.ex_net[j+1]=e.ex_net[j]; j--; }
| |||
e.ex_name[j+1]=nm; e.ex_net[j+1]=v;
| |||
}
| |||
}
| |||
| |||
#endif
|