forked from rosh/MQL5Book
205 lines
6.5 KiB
MQL5
205 lines
6.5 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| 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));
| |
}
| |
//+------------------------------------------------------------------+
|