210 lines
7.2 KiB
MQL5
210 lines
7.2 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| PseudoMarketBook.mq5 |
|
|
//| Copyright 2022, MetaQuotes Ltd. |
|
|
//| https://www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2022, MetaQuotes Ltd."
|
|
#property link "https://www.mql5.com"
|
|
#property description "Generate pseudo market book from ticks for a custom symbol based on a symbol without market books."
|
|
|
|
#include "..\..\Include\PRTF.mqh"
|
|
#include "..\..\Include\MqlError.mqh"
|
|
#include "..\..\Include\Defines.mqh"
|
|
#include "..\..\Include\CustomSymbolMonitor.mqh"
|
|
|
|
input string CustomPath = "MQL5Book\\Part7"; // Custom Symbol Folder
|
|
input uint CustomBookDepth = 20;
|
|
|
|
string CustomSymbol;
|
|
int depth;
|
|
double contract;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
CustomSymbol = _Symbol + ".Pseudo";
|
|
|
|
bool custom = false;
|
|
if(!PRTF(SymbolExist(CustomSymbol, custom)))
|
|
{
|
|
if(PRTF(CustomSymbolCreate(CustomSymbol, CustomPath, _Symbol)))
|
|
{
|
|
CustomSymbolSetString(CustomSymbol, SYMBOL_DESCRIPTION, "Pseudo book generator");
|
|
CustomSymbolSetString(CustomSymbol, SYMBOL_FORMULA, "\"" + _Symbol + "\"");
|
|
}
|
|
}
|
|
else if(!custom)
|
|
{
|
|
Alert("Standard symbol already exists");
|
|
return INIT_FAILED;
|
|
}
|
|
else
|
|
{
|
|
if(IDYES == MessageBox(StringFormat("Delete existing custom symbol '%s'?", CustomSymbol),
|
|
"Please, confirm", MB_YESNO))
|
|
{
|
|
PRTF(MarketBookRelease(CustomSymbol));
|
|
PRTF(SymbolSelect(CustomSymbol, false));
|
|
PRTF(CustomRatesDelete(CustomSymbol, 0, LONG_MAX));
|
|
PRTF(CustomTicksDelete(CustomSymbol, 0, LONG_MAX));
|
|
if(!PRTF(CustomSymbolDelete(CustomSymbol)))
|
|
{
|
|
Alert("Can't delete ", CustomSymbol, ", please, check up and delete manually");
|
|
}
|
|
return INIT_PARAMETERS_INCORRECT;
|
|
}
|
|
}
|
|
|
|
if(PRTF(SymbolInfoInteger(_Symbol, SYMBOL_TICKS_BOOKDEPTH)) != CustomBookDepth
|
|
&& PRTF(SymbolInfoInteger(CustomSymbol, SYMBOL_TICKS_BOOKDEPTH)) != CustomBookDepth)
|
|
{
|
|
Print("Adjusting custom market book depth");
|
|
PRTF(CustomSymbolSetInteger(CustomSymbol, SYMBOL_TICKS_BOOKDEPTH, CustomBookDepth));
|
|
}
|
|
|
|
PRTF(SymbolSelect(CustomSymbol, true));
|
|
|
|
SymbolMonitor sm;
|
|
CustomSymbolMonitor csm(CustomSymbol, &sm);
|
|
int props[] = {SYMBOL_TRADE_TICK_VALUE, SYMBOL_TRADE_TICK_SIZE};
|
|
const int d1 = csm.verify(props);
|
|
if(d1)
|
|
{
|
|
Print("Number of found descrepancies: ", d1);
|
|
if(csm.verify(props)) // check again
|
|
{
|
|
Alert("Custom symbol can not be created, internal error!");
|
|
return INIT_FAILED;
|
|
}
|
|
Print("Fixed");
|
|
}
|
|
|
|
|
|
depth = (int)PRTF(SymbolInfoInteger(CustomSymbol, SYMBOL_TICKS_BOOKDEPTH));
|
|
contract = PRTF(SymbolInfoDouble(CustomSymbol, SYMBOL_TRADE_CONTRACT_SIZE));
|
|
|
|
return INIT_SUCCEEDED;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Helper function to accumulate volumes by price levels |
|
|
//+------------------------------------------------------------------+
|
|
void Place(double &array[], const int index, const double value = 1)
|
|
{
|
|
const int size = ArraySize(array);
|
|
if(index >= size)
|
|
{
|
|
ArrayResize(array, index + 1);
|
|
for(int i = size; i <= index; ++i)
|
|
{
|
|
array[i] = 0;
|
|
}
|
|
}
|
|
array[index] += value;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Simulate market book based on previous ticks |
|
|
//| - what has been bought, will be sold |
|
|
//| - what has been sold, will be bought |
|
|
//+------------------------------------------------------------------+
|
|
bool GenerateMarketBook(const int count, MqlBookInfo &book[])
|
|
{
|
|
MqlTick tick; // center of the book
|
|
if(!SymbolInfoTick(_Symbol, tick)) return false;
|
|
|
|
double buys[]; // buy volumes be price levels
|
|
double sells[]; // sell volumes be price levels
|
|
|
|
MqlTick ticks[];
|
|
CopyTicks(_Symbol, ticks, COPY_TICKS_ALL, 0, count); // get tick history
|
|
for(int i = 1; i < ArraySize(ticks); ++i)
|
|
{
|
|
// consider ask was moved up by buy
|
|
int k = (int)MathRound((tick.ask - ticks[i].ask) / _Point);
|
|
if(ticks[i].ask > ticks[i - 1].ask)
|
|
{
|
|
// already bought, will not buy again, probably will take profit by selling
|
|
if(k <= 0)
|
|
{
|
|
Place(sells, -k, contract / sqrt(sqrt(ArraySize(ticks) - i)));
|
|
}
|
|
}
|
|
|
|
// bid was moved down by sell
|
|
k = (int)MathRound((tick.bid - ticks[i].bid) / _Point);
|
|
if(ticks[i].bid < ticks[i - 1].bid)
|
|
{
|
|
// already sold, will not sell again, probably will take profit by buying
|
|
if(k >= 0)
|
|
{
|
|
Place(buys, k, contract / sqrt(sqrt(ArraySize(ticks) - i)));
|
|
}
|
|
}
|
|
}
|
|
|
|
for(int i = 0, k = 0; i < ArraySize(sells) && k < depth; ++i) // upper half of the book
|
|
{
|
|
if(sells[i] > 0)
|
|
{
|
|
MqlBookInfo info = {};
|
|
info.type = BOOK_TYPE_SELL;
|
|
info.price = tick.ask + i * _Point;
|
|
info.volume = (long)sells[i];
|
|
info.volume_real = (double)(long)sells[i];
|
|
PUSH(book, info);
|
|
++k;
|
|
}
|
|
}
|
|
|
|
for(int i = 0, k = 0; i < ArraySize(buys) && k < depth; ++i) // bottom half of the book
|
|
{
|
|
if(buys[i] > 0)
|
|
{
|
|
MqlBookInfo info = {};
|
|
info.type = BOOK_TYPE_BUY;
|
|
info.price = tick.bid - i * _Point;
|
|
info.volume = (long)buys[i];
|
|
info.volume_real = (double)(long)buys[i];
|
|
PUSH(book, info);
|
|
++k;
|
|
}
|
|
}
|
|
|
|
return ArraySize(book) > 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Tick event handler |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
MqlBookInfo book[];
|
|
if(GenerateMarketBook(2000, book))
|
|
{
|
|
ResetLastError();
|
|
if(!CustomBookAdd(CustomSymbol, book))
|
|
{
|
|
Print("Can't add market books, ", E2S(_LastError));
|
|
ExpertRemove();
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int)
|
|
{
|
|
// this call is required because CustomBookAdd implicitly locks
|
|
// the symbol for which the books are generated,
|
|
// without this call user will not be able to remove the symbol
|
|
// from the Market Watch (where it is internally selected from
|
|
// inside CustomBookAdd, even if it's not visible)
|
|
// and delete it
|
|
PRTF(MarketBookRelease(CustomSymbol));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|