MQL5Book/Scripts/p7/CustomSymbolRandomRates.mq5

206 lines
6.5 KiB
MQL5
Raw Permalink Normal View History

2025-05-30 16:09:41 +02:00
//+------------------------------------------------------------------+
//| CustomSymbolRandomRates.mq5 |
//| Copyright 2022, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property description "Create custom symbol with randomized quotes based on current chart's symbol."
#property script_show_inputs
#include "..\..\Include\PRTF.mqh"
enum RANDOMIZATION
{
ORIGINAL,
RANDOM_WALK,
FUZZY_WEAK,
FUZZY_STRONG,
};
input string CustomPath = "MQL5Book\\Part7"; // Custom Symbol Folder
input RANDOMIZATION RandomFactor = RANDOM_WALK;
input datetime _From; // From (default: 120 days back)
input datetime _To; // To (default: up to now)
input uint RandomSeed = 0; // Random Seed (0 means random)
datetime From;
datetime To;
const string CustomSymbol = _Symbol + "." + EnumToString(RandomFactor)
+ (RandomSeed ? "_" + (string)RandomSeed : "");
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
From = _From == 0 ? TimeCurrent() - 60 * 60 * 24 * 120 : _From; // 120 days by default
To = _To == 0 ? TimeCurrent() / 60 * 60 : _To; // till current time
if(From > To)
{
Alert("Date range must include From <= To");
return;
}
if(RandomSeed != 0) MathSrand(RandomSeed);
bool custom = false;
if(PRTF(SymbolExist(CustomSymbol, custom)) && custom)
{
if(IDYES == MessageBox(StringFormat("Delete custom symbol '%s'?", CustomSymbol),
"Please, confirm", MB_YESNO))
{
if(CloseChartsForSymbol(CustomSymbol))
{
Sleep(500); // wait changes to take effect (unreliable)
// we need to wipe out rates because otherwise the rates
// remain in the base file on the disk for some time
// and can be picked up by the following creation procedure
PRTF(CustomRatesDelete(CustomSymbol, 0, LONG_MAX));
PRTF(SymbolSelect(CustomSymbol, false));
PRTF(CustomSymbolDelete(CustomSymbol));
}
}
}
if(IDYES == MessageBox(StringFormat("Create new custom symbol '%s'?", CustomSymbol),
"Please, confirm", MB_YESNO))
{
if(PRTF(CustomSymbolCreate(CustomSymbol, CustomPath, _Symbol)))
{
if(RandomFactor == RANDOM_WALK)
{
CustomSymbolSetInteger(CustomSymbol, SYMBOL_DIGITS, 8);
}
CustomSymbolSetString(CustomSymbol, SYMBOL_DESCRIPTION, "Randomized quotes");
const int n = GenerateQuotes();
Print("Bars M1 generated: ", n);
if(n > 0)
{
SymbolSelect(CustomSymbol, true);
ChartOpen(CustomSymbol, PERIOD_M1);
}
}
}
}
//+------------------------------------------------------------------+
//| Chart management function |
//+------------------------------------------------------------------+
bool CloseChartsForSymbol(const string symbol)
{
long id = ChartFirst();
long match = -1;
while(id != -1)
{
if(ChartSymbol(id) == symbol)
{
if(id == ChartID())
{
Alert("Can't close itself: start this script on another chart");
return false;
}
else
{
match = id;
}
}
id = ChartNext(id);
if(match != -1)
{
ChartClose(match);
match = -1;
}
}
ResetLastError(); // clear CHART_NOT_FOUND (4103)
return true;
}
//+------------------------------------------------------------------+
//| Get value mangled with nonuniform random noice |
//+------------------------------------------------------------------+
double RandomWalk(const double p)
{
const static double factor[] = {0.0, 0.1, 0.01, 0.05};
const static double f = factor[RandomFactor] / 100;
const double r = (rand() - 16383.0) / 16384.0; // [-1,+1]
const int sign = r >= 0 ? +1 : -1;
if(r != 0)
{
return p + p * sign * f * sqrt(-log(sqrt(fabs(r))));
}
return p;
}
//+------------------------------------------------------------------+
//| Request, modify, commit MqlRates for custom symbol |
//+------------------------------------------------------------------+
int GenerateQuotes()
{
MqlRates rates[];
MqlRates zero = {};
datetime start;
double price;
if(RandomFactor != RANDOM_WALK)
{
// NB: terminal settings are in effect here, including bar limit
if(PRTF(CopyRates(_Symbol, PERIOD_M1, From, To, rates)) <= 0)
{
return 0; // error
}
if(RandomFactor == ORIGINAL)
{
return PRTF(CustomRatesReplace(CustomSymbol, From, To, rates));
}
price = rates[0].open;
start = rates[0].time;
}
else
{
ArrayResize(rates, (int)((To - From) / 60) + 1);
price = 1.0;
start = From - 60;
}
const int size = ArraySize(rates);
double hlc[3];
for(int i = 0; i < size; ++i)
{
if(RandomFactor == RANDOM_WALK)
{
rates[i] = zero;
rates[i].time = start += 60;
rates[i].open = price;
hlc[0] = RandomWalk(price);
hlc[1] = RandomWalk(price);
hlc[2] = RandomWalk(price);
}
else
{
double delta = 0;
if(i > 0)
{
delta = rates[i].open - price; // cumulative correction
}
rates[i].open = price;
hlc[0] = RandomWalk(rates[i].high - delta);
hlc[1] = RandomWalk(rates[i].low - delta);
hlc[2] = RandomWalk(rates[i].close - delta);
}
ArraySort(hlc);
rates[i].high = fmax(hlc[2], rates[i].open);
rates[i].low = fmin(hlc[0], rates[i].open);
rates[i].close = price = hlc[1];
rates[i].tick_volume = 4;
}
return PRTF(CustomRatesReplace(CustomSymbol, From, To, rates));
}
//+------------------------------------------------------------------+