//+------------------------------------------------------------------+
//| SwapMonitor.mq5 |
//| Copyright (c) 2024, Marketeer |
//| https://www.mql5.com/en/users/marketeer |
//+------------------------------------------------------------------+
#property service
#property copyright "Copyright (c) 2024 Marketeer"
#property link "https://www.mql5.com/en/users/marketeer"
#property version "1.0"
#property script_show_inputs
#include <MQL5Book/ArrayUtils.mqh>
#include <MQL5Book/Defines.mqh>
//+------------------------------------------------------------------+
//| I N P U T S |
//+------------------------------------------------------------------+
input string SymbolList = ""; // SymbolList (comma,separated)
input string Folder = "Swaps";
input uint Beat = 10; // Beat (seconds)
input bool AutoSelect = false;
//+------------------------------------------------------------------+
//| Global definitions |
//+------------------------------------------------------------------+
const string Program = EnumToString((ENUM_PROGRAM_TYPE)MQLInfoInteger(MQL_PROGRAM_TYPE)) + " " + MQLInfoString(MQL_PROGRAM_NAME);
string SymbolArray[];
struct Swaps // could be null-ed on creation by compiler if static and global (not the case)
{
datetime dt;
double L;
double S;
int N;
Swaps()
{
ZeroMemory(this);
}
Swaps(const datetime t, const double l, const double s, const int n = 0): dt(t), L(l), S(s), N(n) { }
string csv() const
{
return StringFormat("%s,%g,%g", TimeToString(dt, TIME_DATE | TIME_SECONDS), L, S);
}
};
struct Position
{
ulong ticket;
double swap;
};
Swaps cache[];
Position positions[];
//+------------------------------------------------------------------+
//| Service program start function |
//+------------------------------------------------------------------+
void OnStart()
{
if(StringSplit(SymbolList, ',', SymbolArray) < 1)
{
Print("No symbols given");
if(AutoSelect)
{
for(int i = 0; i < SymbolsTotal(true); i++)
{
const string symbol = SymbolName(i, true);
if(!SymbolInfoInteger(symbol, SYMBOL_CUSTOM)
&& SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE) != SYMBOL_TRADE_MODE_DISABLED)
{
PUSH(SymbolArray, symbol);
}
}
Print("MarketWatch symbols autoselected:");
ArrayPrint(SymbolArray);
}
}
for(int i = 0; i < ArraySize(SymbolArray); i++)
{
if(!SymbolInfoInteger(SymbolArray[i], SYMBOL_EXIST))
{
Print("Unknown symbol: ", SymbolArray[i]);
SymbolArray[i] = "";
}
else if(!SymbolInfoInteger(SymbolArray[i], SYMBOL_VISIBLE))
{
if(AutoSelect && SymbolSelect(SymbolArray[i], true))
{
// ok
}
else
{
PrintFormat("Can't select symbol: %s [%d]", SymbolArray[i], _LastError);
SymbolArray[i] = "";
}
}
}
ArrayPurge(SymbolArray, "");
ArrayResize(cache, ArraySize(SymbolArray));
Print(Program + " started");
bool active = true;
for(; !IsStopped() ;)
{
// wait for connected and logged in state
if(!(TerminalInfoInteger(TERMINAL_CONNECTED) && AccountInfoInteger(ACCOUNT_LOGIN)))
{
if(active)
{
Print(Program + " is waiting for connection");
active = false;
}
Sleep(1000);
continue;
}
if(!active)
{
Print(Program + " connected");
active = true;
}
// check swaps in symbols properties and save changes (if any)
const string filename = GetTimeStamp();
for(int i = 0; i < ArraySize(SymbolArray); i++)
{
if(StringLen(SymbolArray[i]) && !SaveSymbolSwap(SymbolArray[i], filename, cache[i]))
{
SymbolArray[i] = "";
}
}
// check if positions still exist
for(int i = 0; i < ArraySize(positions); i++)
{
if(!PositionSelectByTicket(positions[i].ticket))
{
Alert(StringFormat("Position %llu closed with swap %g", positions[i].ticket, positions[i].swap));
positions[i].ticket = 0;
}
}
ArrayPurger<Position> purger(positions, IsClosedPosition);
// compare positions swaps and alert changes (if any)
const int n = PositionsTotal();
for(int i = 0; i < n; i++)
{
const ulong ticket = PositionGetTicket(i);
if(ticket)
{
FindPositionSwap(ticket);
}
}
Sleep(Beat * 1000);
}
Print(Program + " stopped");
}
//+------------------------------------------------------------------+
//| Auxiliary functions |
//+------------------------------------------------------------------+
string GetTimeStamp()
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
return StringFormat("%04d%02d", dt.year, dt.mon) + ".csv";
}
template<typename T>
bool Contains(const T &a[], const T v)
{
for(int i = 0; i < ArraySize(a); i++)
{
if(a[i] == v) return true;
}
return false;
}
string Escape(const string s)
{
uchar a[];
uchar forbidden[] = {'<', '>', '/', '\\', '"', ':', '|', '*', '?'};
int n = StringToCharArray(s, a);
for(int i = 0; i < n - 1; i++)
{
if(a[i] < 32 || Contains(forbidden, a[i]))
{
a[i] = '_';
}
}
return CharArrayToString(a);
}
bool IsClosedPosition(const Position &p)
{
return !p.ticket;
}
void FindPositionSwap(const ulong ticket)
{
const double swap = PositionGetDouble(POSITION_SWAP);
const double volume = PositionGetDouble(POSITION_VOLUME);
const string symbol = PositionGetString(POSITION_SYMBOL);
const int digits = (int)MathLog10(1.0 / SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP));
const string type = PositionGetInteger(POSITION_TYPE) ? "Sell" : "Buy";
for(int i = 0; i < ArraySize(positions); i++)
{
if(positions[i].ticket == ticket)
{
if(positions[i].swap != swap)
{
Alert(StringFormat("Position %llu (%s %.*f %s) swap: %g -> %g",
ticket, type, digits, volume, symbol, positions[i].swap, swap));
positions[i].swap = swap;
}
return;
}
}
Position p = {ticket, swap};
PUSH(positions, p);
Alert(StringFormat("New position %llu (%s %.*f %s) swap: %g",
ticket, type, digits, volume, symbol, swap));
}
//+------------------------------------------------------------------+
//| Persistent storage (CSV-files) |
//+------------------------------------------------------------------+
bool SaveSymbolSwap(const string &s, const string &f, Swaps &last)
{
const Swaps now
(
TimeCurrent(),
SymbolInfoDouble(s, SYMBOL_SWAP_LONG),
SymbolInfoDouble(s, SYMBOL_SWAP_SHORT),
last.N
);
if(now.L == last.L && now.S == last.S) return true; // up to date
const string name = Folder + "/" + Escape(s) + "/" + f;
int h = FileOpen(name, FILE_ANSI | FILE_TXT | FILE_READ | FILE_WRITE, ",", CP_UTF8);
if(h == INVALID_HANDLE)
{
PrintFormat("Can't open file '%s' [%d]", name, _LastError);
return false;
}
if(!FileSize(h))
{
FileWrite(h, "T,L,S");
}
else
{
FileSeek(h, 0, SEEK_END);
}
FileWrite(h, now.csv());
FileClose(h);
PrintFormat("%s swap changed %d-th time", s, last.N + 1);
if(last.dt)
{
PrintFormat("- Old: %s L=%g S=%g", TimeToString(last.dt, TIME_DATE | TIME_SECONDS), last.L, last.S);
}
PrintFormat("+ New: %s L=%g S=%g", TimeToString(now.dt, TIME_DATE | TIME_SECONDS), now.L, now.S);
Alert(StringFormat("%s swaps: L=%g S=%g", s, now.L, now.S));
last = now;
last.N++;
return true;
}
//+------------------------------------------------------------------+
Comments