913 lines
No EOL
66 KiB
Text
913 lines
No EOL
66 KiB
Text
//+------------------------------------------------------------------+
|
|
//| CustomCharts.mqh |
|
|
//| Copyright 2019, Guilherme Santos. |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2019, Guilherme Santos."
|
|
#property link "fishguil@gmail.com"
|
|
#property version "1.1"
|
|
//+------------------------------------------------------------------+
|
|
//| defines |
|
|
//+------------------------------------------------------------------+
|
|
#define TIME_MIN D'1970.01.01 00:00'
|
|
#define TIME_MAX D'3000.12.31 23:59'
|
|
#define CUSTOM_SYMBOL_PATH "CustomCharts"
|
|
//+------------------------------------------------------------------+
|
|
//| types |
|
|
//+------------------------------------------------------------------+
|
|
enum ENUM_CUSTOM_TYPE
|
|
{
|
|
RENKO_TYPE_TICKS, //Renko Ticks
|
|
RENKO_TYPE_PIPS, //Renko Pips
|
|
RENKO_TYPE_POINTS, //Renko Points
|
|
RENKO_TYPE_R, //Renko R (-1 Tick)
|
|
RENKO_TYPE_ATR, //Renko ATR
|
|
POINTS_TYPE_TICKS, //Point Ticks
|
|
POINTS_TYPE_PIPS, //Point Pips
|
|
POINTS_TYPE_POINTS, //Point
|
|
POINTS_TYPE_P, //Point R (-1 Tick)
|
|
POINTS_TYPE_ATR //Point ATR
|
|
};
|
|
|
|
enum ENUM_CUSTOM_WINDOW
|
|
{
|
|
CUSTOM_NO_WINDOW, //No Window
|
|
CUSTOM_CURRENT_WINDOW, //Current Window
|
|
CUSTOM_NEW_WINDOW //New Window
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| class |
|
|
//+------------------------------------------------------------------+
|
|
class CustomCharts
|
|
{
|
|
//Internal Variables
|
|
private:
|
|
//Buffers
|
|
MqlTick ticks[], //Ticks buffer
|
|
custom_ticks[]; //Custom Ticks buffer
|
|
MqlRates rates[], //Rates buffer
|
|
custom_rates[]; //Custom Rates buffer
|
|
datetime real_time[]; //Real time buffer
|
|
//Cursor
|
|
long last_tick; //Tick cursor
|
|
datetime last_rate; //Rate cursor
|
|
//Counters
|
|
int rates_count, //Rates Count
|
|
ticks_count, //Ticks Count
|
|
custom_rates_count, //Custom Rates Count
|
|
custom_ticks_count; //Custom Ticks Count
|
|
//Config
|
|
ENUM_CUSTOM_TYPE custom_type; //Custom Type
|
|
string chart_symbol, //Original symbol
|
|
custom_symbol; //Custom symbol
|
|
double chart_size, //Chart size
|
|
brick_size, //Brick size
|
|
tick_size, //Tick size
|
|
up_wick, //Upper wick size
|
|
down_wick; //Down wick size
|
|
bool show_wicks, //Show wicks
|
|
asymetric_reversal, //Asymetric Reversal
|
|
gaps, //Gaps
|
|
level; //Level
|
|
ENUM_TIMEFRAMES open_time; //Open Time
|
|
//History
|
|
datetime rates_history; //Rates History
|
|
bool real_ticks, //Real Ticks
|
|
clear_history, //Clear History
|
|
advance; //Ajust time
|
|
//Internals
|
|
long tick_volumes, //Tick Volumes
|
|
volumes; //Volumes
|
|
int hATR, hMA; //Handles
|
|
//Tick counters
|
|
datetime calc_tick_time;
|
|
long calc_tick_volume, calc_tick_volume_real;
|
|
//Methods
|
|
public:
|
|
CustomCharts();
|
|
~CustomCharts();
|
|
bool Setup(string symbol, ENUM_CUSTOM_TYPE type, double size, bool wicks, bool asymetric, bool gaps, bool level, bool advance
|
|
, datetime rates_history, bool real_ticks, bool clear_history
|
|
);
|
|
double GetValue(int buffer, int index);
|
|
double GetValueAsSeries(int buffer, int index);
|
|
MqlRates GetRate(int index);
|
|
MqlRates GetRateAsSeries(int index);
|
|
int UpdateRates(datetime from);
|
|
int UpdateTicks(datetime from, bool last);
|
|
//Custom Symbol Methods
|
|
string GetSymbolName();
|
|
bool CreateCustomSymbol(string name);
|
|
bool DeleteCustomSymbol();
|
|
bool CheckCustomSymbol();
|
|
int ClearCustomRates(datetime from);
|
|
int ClearCustomTicks(datetime from);
|
|
int ReplaceCustomSymbol();
|
|
long OpenCustomSymbol();
|
|
void SetCustomSymbol(long chart_id);
|
|
bool ValidateSymbol(string &name);
|
|
int UpdateCustomRates();
|
|
int AddCustomTicks();
|
|
//Event Methods
|
|
void Start(ENUM_CUSTOM_WINDOW window, int event_timer, bool event_book);
|
|
void Stop();
|
|
void Refresh();
|
|
//Internal Methods
|
|
private:
|
|
int AddOne(datetime time, double price);
|
|
int CloseUp(double points, datetime time, int spread);
|
|
int CloseDown(double points, datetime time, int spread);
|
|
int OnCalculate(double price, datetime time, long tick_volume, long volume, int spread);
|
|
int OnCalculate(const MqlRates &price);
|
|
int OnCalculate(const MqlTick &price);
|
|
int OnCalculateOHLC(const MqlRates &price);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| methods |
|
|
//+------------------------------------------------------------------+
|
|
//Default Constructors
|
|
CustomCharts::CustomCharts()
|
|
{
|
|
return;
|
|
}
|
|
|
|
CustomCharts::~CustomCharts()
|
|
{
|
|
ArrayFree(rates);
|
|
ArrayFree(custom_rates);
|
|
ArrayFree(ticks);
|
|
ArrayFree(custom_ticks);
|
|
SymbolSelect(custom_symbol,false);
|
|
CustomSymbolDelete(custom_symbol);
|
|
}
|
|
|
|
//Setup Renko
|
|
bool CustomCharts::Setup(
|
|
string symbol = "", // Symbol (Default = current)
|
|
ENUM_CUSTOM_TYPE type = RENKO_TYPE_TICKS, // Type
|
|
double size = 14, // Brick Size (Ticks, Pips, Points or ATR)
|
|
bool wicks = true, // Show Wicks
|
|
bool asymetric = true, // Asymetric Reversals (Artificial open on reversal. RENKO ONLY!)
|
|
bool p_gaps = false, // Gaps
|
|
bool p_level = true, // Level Bricks
|
|
bool p_advance = true, // Advance in time
|
|
datetime p_rates_history = 0, // History: Rates (Default is 7 Days 1M OHLC rates.)
|
|
bool p_real_ticks = false, // History: Real Ticks (History based on real ticks. SLOWER!)
|
|
bool p_clear_history = true // History: Clear Custom Symbol
|
|
)
|
|
{
|
|
//Check Symbol
|
|
if(symbol == "" || symbol == NULL)
|
|
{
|
|
Print("Line:",__LINE__," - Invalid symbol selected.");
|
|
return(false);
|
|
}
|
|
if(SymbolInfoInteger(symbol, SYMBOL_CUSTOM))
|
|
{
|
|
if (MQL5InfoInteger(MQL5_TESTER) || MQL5InfoInteger(MQL5_DEBUG) || MQL5InfoInteger(MQL5_DEBUGGING) || MQL5InfoInteger(MQL5_OPTIMIZATION) || MQL5InfoInteger(MQL5_VISUAL_MODE))
|
|
{
|
|
Print("Line:",__LINE__," - symbol selected is custom.");
|
|
}
|
|
else
|
|
{
|
|
Print("Line:",__LINE__," - Custom Symbol selected. Please Change for the original symbol.");
|
|
return(false);
|
|
}
|
|
}
|
|
//Select Symbol
|
|
if(SymbolSelect(symbol, true) == false)
|
|
{
|
|
Print("Line:",__LINE__," - Symbol selection error.");
|
|
return(false);
|
|
}
|
|
//Buffers
|
|
this.rates_count = this.custom_rates_count = ArrayResize(custom_rates, 0, 1000);
|
|
this.ticks_count = this.custom_ticks_count = ArrayResize(custom_ticks, 0, 1000);
|
|
this.last_rate = 0;
|
|
this.last_tick = 0;
|
|
//Chart setup
|
|
this.chart_symbol = symbol;
|
|
this.custom_type = type;
|
|
this.chart_size = size;
|
|
this.show_wicks = wicks;
|
|
this.asymetric_reversal = (custom_type >= RENKO_TYPE_TICKS && custom_type <= RENKO_TYPE_ATR) ? asymetric : false;
|
|
this.gaps = p_gaps;
|
|
this.level = p_level;
|
|
//History
|
|
if(p_rates_history == 0)
|
|
p_rates_history = TruncateTime(TimeTradeServer(), PERIOD_D1) - 7 * 86400; //Default is 7 days
|
|
this.rates_history = p_rates_history;
|
|
this.real_ticks = p_real_ticks;
|
|
this.clear_history = p_clear_history;
|
|
this.advance = p_advance;
|
|
//Brick size
|
|
int digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
|
double points = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
this.tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
|
double pip_size = (digits == 5 || digits == 3) ? points * 10 : points;
|
|
if(custom_type == POINTS_TYPE_TICKS || custom_type == RENKO_TYPE_TICKS) brick_size = chart_size * tick_size;
|
|
else if(custom_type == POINTS_TYPE_PIPS || custom_type == RENKO_TYPE_PIPS) brick_size = chart_size * pip_size;
|
|
else if(custom_type == POINTS_TYPE_P || custom_type == RENKO_TYPE_R) brick_size = chart_size * tick_size - tick_size;
|
|
else brick_size = chart_size;
|
|
brick_size = NormalizeDouble(brick_size, digits);
|
|
//Invalid brick size
|
|
if(brick_size <= 0)
|
|
{
|
|
Print("Line:",__LINE__," - Invalid brick size. Value of ", brick_size, " selected.");
|
|
return(false);
|
|
}
|
|
//Minimum brick size
|
|
if(brick_size < tick_size)
|
|
{
|
|
Print("Line:",__LINE__," - Invalid brick size. Minimum value of ", tick_size, " will be used.");
|
|
brick_size = tick_size;
|
|
}
|
|
//Success
|
|
return(true);
|
|
}
|
|
|
|
//Add one to buffer array
|
|
int CustomCharts::AddOne(datetime time = 0, double price = 0)
|
|
{
|
|
//Resize buffers
|
|
int index = ArrayResize(custom_rates, ArraySize(custom_rates) + 1, 1000) - 1;
|
|
ArrayResize(real_time, ArraySize(custom_rates), 1000);
|
|
if(index <= 0) return 0;
|
|
//Time
|
|
if(time == 0) time = TimeTradeServer();
|
|
real_time[index] = time;
|
|
//M1 Rates indexing
|
|
time = TruncateTime(time, PERIOD_M1);
|
|
if(time <= custom_rates[index-1].time)
|
|
custom_rates[index].time = custom_rates[index-1].time + 60;
|
|
else
|
|
custom_rates[index].time = time;
|
|
//Defaults
|
|
if(price == 0) price = custom_rates[index-1].close;
|
|
custom_rates[index].open = custom_rates[index].high = custom_rates[index].low = custom_rates[index].close = price;
|
|
custom_rates[index].tick_volume = custom_rates[index].real_volume = 0;
|
|
custom_rates[index].spread = 0;
|
|
return index;
|
|
}
|
|
|
|
//Add positive points bar
|
|
int CustomCharts::CloseUp(double points, datetime time=0, int spread=0)
|
|
{
|
|
int index = ArraySize(custom_rates) -1;
|
|
//OHLC
|
|
double price = custom_rates[index].open; //custom_rates[index-1].close;
|
|
if(asymetric_reversal) custom_rates[index].open = price + points - brick_size;
|
|
else custom_rates[index].open = price;
|
|
custom_rates[index].high = custom_rates[index].close = price + points;
|
|
//Wicks
|
|
if(show_wicks) custom_rates[index].low = down_wick;
|
|
else custom_rates[index].low = custom_rates[index].open;
|
|
down_wick = custom_rates[index].close;
|
|
//Volumes
|
|
custom_rates[index].tick_volume = tick_volumes;
|
|
custom_rates[index].real_volume = volumes;
|
|
custom_rates[index].spread = spread;
|
|
tick_volumes = volumes = 0;
|
|
//Add one
|
|
return AddOne(time);
|
|
}
|
|
|
|
//Add negative points bar
|
|
int CustomCharts::CloseDown(double points, datetime time=0, int spread=0)
|
|
{
|
|
int index = ArraySize(custom_rates) -1;
|
|
//OHLC
|
|
double price = custom_rates[index].open; //custom_rates[index-1].close;
|
|
if(asymetric_reversal) custom_rates[index].open = price - points + brick_size;
|
|
else custom_rates[index].open = price;
|
|
custom_rates[index].low = custom_rates[index].close = price - points;
|
|
//Wicks
|
|
if(show_wicks) custom_rates[index].high = up_wick;
|
|
else custom_rates[index].high = custom_rates[index].open;
|
|
up_wick = custom_rates[index].close;
|
|
//Volumes
|
|
custom_rates[index].tick_volume = tick_volumes;
|
|
custom_rates[index].real_volume = volumes;
|
|
custom_rates[index].spread = spread;
|
|
tick_volumes = volumes = 0;
|
|
//Add one
|
|
return AddOne(time);
|
|
}
|
|
|
|
//Load price information
|
|
datetime calc_rates_time;
|
|
long calc_rates_volume, calc_rates_volume_real;
|
|
int CustomCharts::OnCalculate(double price, datetime time=0, long tick_volume=0, long volume=0, int spread=0)
|
|
{
|
|
//Wicks
|
|
up_wick = MathMax(up_wick, price);
|
|
down_wick = MathMin(down_wick, price);
|
|
if(down_wick <= 0) down_wick = price;
|
|
//Time
|
|
datetime current_time = TruncateTime(SymbolInfoInteger(chart_symbol, SYMBOL_TIME), PERIOD_M1);
|
|
if(time != calc_rates_time)
|
|
{
|
|
calc_rates_time = time;
|
|
tick_volumes += calc_rates_volume;
|
|
volumes += calc_rates_volume_real;
|
|
}
|
|
//Volume
|
|
calc_rates_volume = tick_volume;
|
|
calc_rates_volume_real = volume;
|
|
//Buffer
|
|
int size = ArraySize(custom_rates);
|
|
int index = size-1;
|
|
//First bricks
|
|
if(size==0)
|
|
{
|
|
//1st Buffers
|
|
ArrayResize(custom_rates, 2, 1000);
|
|
|
|
custom_rates[0].time = time - 120;
|
|
custom_rates[0].open = NormalizeDouble(MathFloor(price/brick_size) * brick_size,_Digits) - brick_size;
|
|
custom_rates[0].high = custom_rates[0].low = custom_rates[0].close = custom_rates[0].open;
|
|
custom_rates[0].tick_volume = custom_rates[0].real_volume = 0;
|
|
custom_rates[0].spread = 0;
|
|
|
|
custom_rates[1].time = time - 60;
|
|
custom_rates[1].open = custom_rates[1].low = custom_rates[0].close;
|
|
custom_rates[1].high = custom_rates[1].close = custom_rates[0].close + brick_size;
|
|
custom_rates[1].tick_volume = custom_rates[1].real_volume = 0;
|
|
custom_rates[1].spread = 0;
|
|
|
|
//Current Buffer
|
|
up_wick = down_wick = price;
|
|
index = AddOne(time);
|
|
}
|
|
// New Daily Open
|
|
if(this.gaps)
|
|
if(TruncateTime(time, PERIOD_D1) != TruncateTime(real_time[index], PERIOD_D1))
|
|
{
|
|
if(this.level) price = NormalizeDouble(MathFloor(price/brick_size) * brick_size,_Digits);
|
|
else price = NormalizeDouble(MathFloor(price/tick_size) * tick_size,_Digits);
|
|
up_wick = down_wick = price;
|
|
index = AddOne(time, price);
|
|
}
|
|
// Points
|
|
if(custom_type == POINTS_TYPE_TICKS
|
|
|| custom_type == POINTS_TYPE_PIPS
|
|
|| custom_type == POINTS_TYPE_POINTS
|
|
|| custom_type == POINTS_TYPE_ATR)
|
|
{
|
|
//Fill
|
|
//Up
|
|
if(price >= custom_rates[index].open+brick_size)
|
|
for(; price >= custom_rates[index].open+brick_size;)
|
|
index = CloseUp(brick_size, time, spread);
|
|
//Down
|
|
if(price <= custom_rates[index].open-brick_size)
|
|
for(; price <= custom_rates[index].open-brick_size;)
|
|
index = CloseDown(brick_size, time, spread);
|
|
}
|
|
else if(custom_type == POINTS_TYPE_P)
|
|
{
|
|
//Up
|
|
if(price > custom_rates[index].open)
|
|
for(; price > custom_rates[index].open+brick_size;)
|
|
index = CloseUp(brick_size, time, spread);
|
|
//Down
|
|
if(price < custom_rates[index].open)
|
|
for(; price < custom_rates[index].open-brick_size;)
|
|
index = CloseDown(brick_size, time, spread);
|
|
}
|
|
//Renko
|
|
else if(custom_type == RENKO_TYPE_TICKS
|
|
|| custom_type == RENKO_TYPE_PIPS
|
|
|| custom_type == RENKO_TYPE_POINTS
|
|
|| custom_type == RENKO_TYPE_ATR)
|
|
{
|
|
//Up
|
|
if(custom_rates[index-1].close > custom_rates[index-2].close){
|
|
if(price >= custom_rates[index-1].close+brick_size)
|
|
{
|
|
for(; price >= custom_rates[index-1].close+brick_size;)
|
|
index = CloseUp(brick_size, time, spread);
|
|
}
|
|
//Reversal
|
|
else if(price <= custom_rates[index-1].close - brick_size * 2.0)
|
|
{
|
|
index = CloseDown(brick_size * 2.0, time, spread);
|
|
for(; price <= custom_rates[index-1].close-brick_size;)
|
|
index = CloseDown(brick_size, time, spread);
|
|
}
|
|
}
|
|
//Down
|
|
if(custom_rates[index-1].close < custom_rates[index-2].close){
|
|
if(price <= custom_rates[index-1].close-brick_size)
|
|
{
|
|
for(; price <= custom_rates[index-1].close-brick_size;)
|
|
index = CloseDown(brick_size, time, spread);
|
|
}
|
|
//Reversal
|
|
else if(price >= custom_rates[index-1].close + brick_size * 2.0)
|
|
{
|
|
index = CloseUp(brick_size * 2.0, time, spread);
|
|
for(; price >= custom_rates[index-1].close+brick_size;)
|
|
index = CloseUp(brick_size, time, spread);
|
|
}
|
|
}
|
|
}
|
|
else if(custom_type == RENKO_TYPE_R)
|
|
{
|
|
//Up
|
|
if(custom_rates[index-1].close > custom_rates[index-2].close)
|
|
{
|
|
if(price > custom_rates[index-1].close+brick_size)
|
|
{
|
|
for(; price > custom_rates[index-1].close+brick_size;)
|
|
index = CloseUp(brick_size, time, spread);
|
|
}
|
|
//Reversal
|
|
else if(price < custom_rates[index-1].close - brick_size * 2.0)
|
|
{
|
|
index = CloseDown(brick_size * 2.0, time, spread);
|
|
for(; price < custom_rates[index-1].close-brick_size;)
|
|
index = CloseDown(brick_size, time, spread);
|
|
}
|
|
}
|
|
//Down
|
|
if(custom_rates[index-1].close < custom_rates[index-2].close)
|
|
{
|
|
if(price < custom_rates[index-1].close-brick_size)
|
|
{
|
|
for(; price < custom_rates[index-1].close-brick_size;)
|
|
index = CloseDown(brick_size, time, spread);
|
|
}
|
|
//Reversal
|
|
else if(price > custom_rates[index-1].close + brick_size * 2.0)
|
|
{
|
|
index = CloseUp(brick_size * 2.0, time, spread);
|
|
for(; price > custom_rates[index-1].close+brick_size;)
|
|
index = CloseUp(brick_size, time, spread);
|
|
}
|
|
}
|
|
}
|
|
//Advance in time
|
|
if(!this.advance)
|
|
if(custom_rates[index].time > current_time)
|
|
{
|
|
datetime from = 0;
|
|
custom_rates[index].time = current_time;
|
|
for(int i = index; i > 1; i--)
|
|
if(custom_rates[i].time <= custom_rates[i-1].time)
|
|
custom_rates[i-1].time = from = custom_rates[i].time - 60;
|
|
else
|
|
break;
|
|
}
|
|
//Buffer
|
|
custom_rates[index].high = up_wick;
|
|
custom_rates[index].low = down_wick;
|
|
custom_rates[index].close = price;
|
|
custom_rates[index].tick_volume = tick_volumes + tick_volume;
|
|
custom_rates[index].real_volume = volumes + volume;
|
|
custom_rates[index].spread = spread;
|
|
//Size
|
|
last_rate = custom_rates[index].time;
|
|
size = rates_count = ArraySize(custom_rates);
|
|
return size;
|
|
}
|
|
|
|
//Load price rates information
|
|
int CustomCharts::OnCalculate(const MqlRates &price)
|
|
{
|
|
//Price
|
|
return this.OnCalculate(price.close, price.time, price.tick_volume, price.real_volume, price.spread);
|
|
}
|
|
|
|
//Load price tick information
|
|
int CustomCharts::OnCalculate(const MqlTick &price)
|
|
{
|
|
//Truncate current time
|
|
datetime time = TruncateTime(price.time, PERIOD_M1);
|
|
//Calculating volume
|
|
if(time > calc_tick_time)
|
|
calc_tick_volume_real = calc_tick_volume = 0;
|
|
calc_tick_volume += (long) price.volume;
|
|
calc_tick_volume_real += (long) price.volume_real;
|
|
//Calculate bars
|
|
int size = 0;
|
|
if(price.bid > price.ask) //Auction
|
|
size = 0;
|
|
else if(SymbolInfoInteger(custom_symbol, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_BID && price.bid > 0)
|
|
size = this.OnCalculate(price.bid, time, calc_tick_volume, calc_tick_volume_real);
|
|
else if(SymbolInfoInteger(custom_symbol, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_LAST && price.last > 0)
|
|
size = this.OnCalculate(price.last, time, calc_tick_volume, calc_tick_volume_real);
|
|
calc_tick_time = time;
|
|
return size;
|
|
}
|
|
|
|
//Load OHLC price rates information
|
|
int CustomCharts::OnCalculateOHLC(const MqlRates &price)
|
|
{
|
|
this.OnCalculate(price.open, price.time, 0, 0, price.spread);
|
|
if(price.close > price.open)
|
|
{
|
|
this.OnCalculate(price.low, price.time, 0, 0, price.spread);
|
|
this.OnCalculate(price.high, price.time, 0, 0, price.spread);
|
|
}
|
|
else
|
|
{
|
|
this.OnCalculate(price.high, price.time, 0, 0, price.spread);
|
|
this.OnCalculate(price.low, price.time, 0, 0, price.spread);
|
|
}
|
|
return this.OnCalculate(price.close, price.time, price.tick_volume, price.real_volume, price.spread);
|
|
}
|
|
|
|
//Update rates
|
|
int CustomCharts::UpdateRates(datetime from = 0)
|
|
{
|
|
if(!CheckCustomSymbol()) return 0;
|
|
int copied = 0;
|
|
//Copy rates history
|
|
if(from == 0) from = TimeTradeServer();
|
|
if(rates_count == 0)
|
|
{
|
|
//History
|
|
copied = CopyRates(chart_symbol, PERIOD_M1, from, TimeTradeServer(), rates);
|
|
if(copied > 0)
|
|
for(int i = 0; i < copied; i++)
|
|
this.OnCalculateOHLC(rates[i]);
|
|
}
|
|
else
|
|
{
|
|
//Last rate
|
|
copied = CopyRates(chart_symbol, PERIOD_M1, last_rate, TimeTradeServer(), rates);
|
|
if(copied > 0)
|
|
for(int i = 0; i < copied; i++)
|
|
this.OnCalculate(rates[i]);
|
|
}
|
|
//Return
|
|
copied = ArraySize(custom_rates);
|
|
return copied;
|
|
}
|
|
|
|
//Update ticks
|
|
int CustomCharts::UpdateTicks(datetime from = 0, bool last = false)
|
|
{
|
|
if(!CheckCustomSymbol()) return 0;
|
|
//Copy ticks history
|
|
if(from == 0) from = TimeTradeServer();
|
|
int copied = ArrayResize(custom_ticks, 0, 1000);
|
|
if(ticks_count == 0)
|
|
if(rates_count == 0)
|
|
copied = CopyTicksRange(chart_symbol, custom_ticks, COPY_TICKS_ALL, 1000 * (long) from);
|
|
else
|
|
copied = CopyTicks(chart_symbol, custom_ticks, COPY_TICKS_ALL, 0, 1);
|
|
else if(last == true)
|
|
copied = CopyTicks(chart_symbol, custom_ticks, COPY_TICKS_ALL, 0, 1);
|
|
else
|
|
copied = CopyTicks(chart_symbol, custom_ticks, COPY_TICKS_ALL, last_tick + 1, 1000);
|
|
//Discard repeated ticks
|
|
if(copied <= 0) return 0;
|
|
if(custom_ticks[0].time_msc <= last_tick) return 0;
|
|
ticks_count += copied;
|
|
//Update custom tick
|
|
for(int i = 0; i < copied; i++)
|
|
{
|
|
this.OnCalculate(custom_ticks[i]);
|
|
last_tick = custom_ticks[i].time_msc;
|
|
custom_ticks[i].time = (datetime) GetValue(0);
|
|
custom_ticks[i].time_msc = 0;
|
|
}
|
|
return copied;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| output methods |
|
|
//+------------------------------------------------------------------+
|
|
|
|
//Get rates
|
|
MqlRates CustomCharts::GetRate(int index = -1)
|
|
{
|
|
index = (index < 0) ? ArraySize(custom_rates)-1 : index;
|
|
if(index < 0) return custom_rates[0];
|
|
return custom_rates[index];
|
|
}
|
|
|
|
//Get rates as series
|
|
MqlRates CustomCharts::GetRateAsSeries(int index = -1)
|
|
{
|
|
index = ArraySize(custom_rates)-index;
|
|
index = (index<=0) ? ArraySize(custom_rates)-1 : index-1;
|
|
if(index < 0) return custom_rates[0];
|
|
return custom_rates[index];
|
|
}
|
|
|
|
//Get values
|
|
double CustomCharts::GetValue(int buffer = 0, int index = -1)
|
|
{
|
|
index = (index < 0) ? ArraySize(custom_rates)-1 : index;
|
|
if(index<0) return EMPTY_VALUE;
|
|
switch(buffer)
|
|
{
|
|
case 0: return (double) custom_rates[index].time; break; //Time
|
|
case 1: return custom_rates[index].open; break; //Open
|
|
case 2: return custom_rates[index].high; break; //High
|
|
case 3: return custom_rates[index].low; break; //Low
|
|
case 4: return custom_rates[index].close; break; //Close
|
|
case 5: return (double) custom_rates[index].tick_volume; break; //Tick volume
|
|
case 6: return (double) custom_rates[index].real_volume; break; //Volume
|
|
case 7: return (double) custom_rates[index].spread; break; //Spread
|
|
case 8: return (double) real_time[index]; break; //Real time
|
|
default: return EMPTY_VALUE; break;
|
|
}
|
|
}
|
|
|
|
//Get values as series
|
|
double CustomCharts::GetValueAsSeries(int buffer = 0, int index = -1)
|
|
{
|
|
index = ArraySize(custom_rates)-index;
|
|
index = (index<=0) ? ArraySize(custom_rates)-1 : index-1;
|
|
if(index<0) return EMPTY_VALUE;
|
|
switch(buffer)
|
|
{
|
|
case 0: return (double) custom_rates[index].time; break; //Time
|
|
case 1: return custom_rates[index].open; break; //Open
|
|
case 2: return custom_rates[index].high; break; //High
|
|
case 3: return custom_rates[index].low; break; //Low
|
|
case 4: return custom_rates[index].close; break; //Close
|
|
case 5: return (double) custom_rates[index].tick_volume; break; //Tick volume
|
|
case 6: return (double) custom_rates[index].real_volume; break; //Volume
|
|
case 7: return (double) custom_rates[index].spread; break; //Spread
|
|
case 8: return (double) real_time[index]; break; //Real time
|
|
default: return EMPTY_VALUE; break;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| custom symbol methods |
|
|
//+------------------------------------------------------------------+
|
|
//Return custom symbol name
|
|
string CustomCharts::GetSymbolName()
|
|
{
|
|
return custom_symbol;
|
|
}
|
|
|
|
//Create points custom symbol
|
|
bool CustomCharts::CreateCustomSymbol(string name = "")
|
|
{
|
|
custom_symbol = name;
|
|
//Symbol name
|
|
if(name == "" || name == NULL)
|
|
{
|
|
if(custom_type == RENKO_TYPE_TICKS) custom_symbol = StringFormat("%s.R%gtks", chart_symbol, chart_size);
|
|
else if(custom_type == RENKO_TYPE_PIPS) custom_symbol = StringFormat("%s.R%gpip", chart_symbol, chart_size);
|
|
else if(custom_type == RENKO_TYPE_POINTS) custom_symbol = StringFormat("%s.R%gpts", chart_symbol, chart_size);
|
|
else if(custom_type == RENKO_TYPE_R) custom_symbol = StringFormat("%s.R%g", chart_symbol, chart_size);
|
|
else if(custom_type == POINTS_TYPE_TICKS) custom_symbol = StringFormat("%s.P%gtks", chart_symbol, chart_size);
|
|
else if(custom_type == POINTS_TYPE_PIPS) custom_symbol = StringFormat("%s.P%gpip", chart_symbol, chart_size);
|
|
else if(custom_type == POINTS_TYPE_POINTS) custom_symbol = StringFormat("%s.P%gpts", chart_symbol, chart_size);
|
|
else if(custom_type == POINTS_TYPE_P) custom_symbol = StringFormat("%s.P%g", chart_symbol, chart_size);
|
|
|
|
}
|
|
//Delete symbol
|
|
if(this.clear_history)
|
|
DeleteCustomSymbol();
|
|
//Create symbol
|
|
bool is_custom = false;
|
|
if(SymbolExist(custom_symbol, is_custom))
|
|
return is_custom;
|
|
else
|
|
return CustomSymbolCreate(custom_symbol, CUSTOM_SYMBOL_PATH, chart_symbol);
|
|
|
|
}
|
|
|
|
//Check custom symbol
|
|
bool CustomCharts::CheckCustomSymbol()
|
|
{
|
|
if(custom_symbol == "" || custom_symbol == NULL)
|
|
return(false);
|
|
return((bool) SymbolInfoInteger(custom_symbol, SYMBOL_CUSTOM));
|
|
}
|
|
|
|
//Delete custom symbol
|
|
bool CustomCharts::DeleteCustomSymbol()
|
|
{
|
|
if(custom_symbol == _Symbol)
|
|
return false;
|
|
if(SymbolSelect(custom_symbol, false))
|
|
return CustomSymbolDelete(custom_symbol);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
//Clear custom rates
|
|
int CustomCharts::ClearCustomRates(datetime from = 0)
|
|
{
|
|
if(!CheckCustomSymbol()) return false;
|
|
//Delete custom ticks
|
|
int deleted = CustomRatesDelete(custom_symbol, from, TIME_MAX);
|
|
if(deleted > 0) PrintFormat("[%s] %d custom rates deleted", custom_symbol, deleted);
|
|
custom_rates_count = 0;
|
|
return deleted;
|
|
}
|
|
|
|
//Clear custom ticks
|
|
int CustomCharts::ClearCustomTicks(datetime from = 0)
|
|
{
|
|
if(!CheckCustomSymbol()) return false;
|
|
//Delete custom ticks
|
|
int deleted = CustomTicksDelete(custom_symbol, from * 1000, TIME_MAX * 1000);
|
|
if(deleted > 0) PrintFormat("[%s] %d custom ticks deleted", custom_symbol, deleted);
|
|
custom_ticks_count = 0;
|
|
return deleted;
|
|
}
|
|
|
|
//Update custom symbol rates
|
|
int CustomCharts::ReplaceCustomSymbol()
|
|
{
|
|
if(!CheckCustomSymbol()) return 0;
|
|
int copied = CustomRatesUpdate(custom_symbol, custom_rates);
|
|
return copied;
|
|
}
|
|
|
|
//Open custom symbol window
|
|
long CustomCharts::OpenCustomSymbol()
|
|
{
|
|
if(!CheckCustomSymbol()) return -1;
|
|
SymbolSelect(custom_symbol, true);
|
|
long chart_id = ChartOpen(custom_symbol, PERIOD_M1);
|
|
ChartSetInteger(chart_id, CHART_MODE, CHART_CANDLES);
|
|
ChartSetInteger(chart_id, CHART_SHOW_PERIOD_SEP, 0, true);
|
|
ChartSetInteger(chart_id, CHART_SHOW_GRID, 0, false);
|
|
ChartSetInteger(chart_id, CHART_AUTOSCROLL, true);
|
|
ChartSetInteger(chart_id, CHART_SHIFT, false);
|
|
ChartNavigate(chart_id, CHART_END, 0);
|
|
return chart_id;
|
|
}
|
|
|
|
//Set chart current symbol
|
|
void CustomCharts::SetCustomSymbol(long chart_id = 0)
|
|
{
|
|
if(!CheckCustomSymbol()) return;
|
|
SymbolSelect(custom_symbol, true);
|
|
ChartSetSymbolPeriod(chart_id, custom_symbol, PERIOD_M1);
|
|
ChartSetInteger(chart_id, CHART_MODE, CHART_CANDLES);
|
|
ChartSetInteger(chart_id, CHART_SHOW_PERIOD_SEP, 0, true);
|
|
ChartSetInteger(chart_id, CHART_SHOW_GRID, 0, false);
|
|
ChartSetInteger(chart_id, CHART_AUTOSCROLL, true);
|
|
ChartSetInteger(chart_id, CHART_SHIFT, false);
|
|
ChartNavigate(chart_id, CHART_END, 0);
|
|
}
|
|
|
|
//Validate original symbol
|
|
bool CustomCharts::ValidateSymbol(string &name)
|
|
{
|
|
if(name=="" || name==NULL)
|
|
name = _Symbol;
|
|
if(SymbolInfoInteger(name, SYMBOL_CUSTOM))
|
|
{
|
|
string new_name = StringAt(name, "_");
|
|
if(SymbolInfoInteger(new_name, SYMBOL_CUSTOM))
|
|
return false;
|
|
else
|
|
name = new_name;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//Update custom rates
|
|
int CustomCharts::UpdateCustomRates()
|
|
{
|
|
ResetLastError();
|
|
if(!CheckCustomSymbol()) return 0;
|
|
if(rates_count <= 0) return 0;
|
|
if(rates_count == custom_rates_count) return 0;
|
|
if(custom_rates_count == 0)
|
|
{
|
|
ClearCustomTicks(custom_rates[0].time);
|
|
ClearCustomRates(custom_rates[0].time);
|
|
}
|
|
int copied = CustomRatesUpdate(custom_symbol, custom_rates);
|
|
if(copied > 0) custom_rates_count = copied;
|
|
//Info
|
|
if(_LastError > 0)
|
|
{
|
|
printf("[%s] CustomRatesUpdate error: %d, %d of %d rates copied.", _Symbol, _LastError, copied, ArraySize(custom_rates));
|
|
ArrayPrint(custom_rates, _Digits, NULL, 0, 10);
|
|
}
|
|
//Return
|
|
return copied;
|
|
}
|
|
|
|
//Update custom tick
|
|
int CustomCharts::AddCustomTicks()
|
|
{
|
|
ResetLastError();
|
|
if(!CheckCustomSymbol()) return 0;
|
|
if(ArraySize(custom_ticks) <= 0) return 0;
|
|
if(custom_ticks_count == 0)
|
|
ClearCustomTicks(custom_rates[0].time);
|
|
int copied = CustomTicksAdd(custom_symbol, custom_ticks);
|
|
if(copied > 0) custom_ticks_count += copied;
|
|
//Info
|
|
if(_LastError > 0)
|
|
{
|
|
printf("[%s] CustomTicksAdd error: %d, %d of %d ticks added.", _Symbol, _LastError, copied, ArraySize(custom_ticks));
|
|
ArrayPrint(custom_ticks, _Digits, NULL, 0, 10);
|
|
}
|
|
return copied;
|
|
}
|
|
|
|
//Refresh custom data
|
|
void CustomCharts::Refresh()
|
|
{
|
|
//History
|
|
if(rates_count == 0)
|
|
{
|
|
if(real_ticks)
|
|
UpdateTicks(rates_history);
|
|
else
|
|
UpdateRates(rates_history);
|
|
UpdateCustomRates();
|
|
return;
|
|
}
|
|
//Update custom symbol
|
|
UpdateCustomRates();
|
|
if(UpdateTicks() > 0) AddCustomTicks();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| event methods |
|
|
//+------------------------------------------------------------------+
|
|
//Create Points Chart/Events
|
|
void CustomCharts::Start(ENUM_CUSTOM_WINDOW window = CUSTOM_NO_WINDOW, int event_timer = 0, bool event_book = false)
|
|
{
|
|
//Clear History
|
|
if(this.clear_history)
|
|
{
|
|
ClearCustomRates();
|
|
ClearCustomTicks();
|
|
}
|
|
//Open/Set Custom Symbol
|
|
if(window == CUSTOM_NEW_WINDOW)
|
|
{
|
|
OpenCustomSymbol();
|
|
Comment("Updating Custom Symbol: ", custom_symbol);
|
|
}
|
|
else if(window == CUSTOM_CURRENT_WINDOW)
|
|
{
|
|
SetCustomSymbol();
|
|
Print("Updating Custom Symbol: ", _Symbol);
|
|
}
|
|
//Events
|
|
if(event_timer > 0)
|
|
EventSetMillisecondTimer(event_timer);
|
|
if(event_book)
|
|
MarketBookAdd(chart_symbol);
|
|
if(_LastError > 0)
|
|
Print(TimeCurrent(), " - Erro: ", _LastError);
|
|
}
|
|
|
|
//Release OnBookEvent
|
|
void CustomCharts::Stop()
|
|
{
|
|
//---
|
|
Comment("");
|
|
MarketBookRelease(chart_symbol);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| custom functions |
|
|
//+------------------------------------------------------------------+
|
|
string StringAt(string text, string separator, int position = 0)
|
|
{
|
|
string result[];
|
|
ushort s = StringGetCharacter(separator, 0);
|
|
int n = StringSplit(text, s, result);
|
|
if(position < n)
|
|
return result[position];
|
|
else
|
|
return "";
|
|
}
|
|
|
|
string StringPeriod(ENUM_TIMEFRAMES value)
|
|
{
|
|
if(value == PERIOD_CURRENT) value = (ENUM_TIMEFRAMES) _Period;
|
|
string period = EnumToString(value);
|
|
return StringSubstr(period, 7);
|
|
}
|
|
|
|
string StringMethod(ENUM_MA_METHOD value)
|
|
{
|
|
string method = EnumToString(value);
|
|
return StringSubstr(method, 5);
|
|
}
|
|
|
|
datetime TruncateTime(datetime tm, ENUM_TIMEFRAMES tf) {
|
|
MqlDateTime ts;
|
|
switch (tf) {
|
|
case PERIOD_MN1:
|
|
TimeToStruct(tm, ts);
|
|
return tm - (tm % 86400) - ((ts.day - 1) * 86400);
|
|
case PERIOD_W1:
|
|
return (((tm - 259200) / 604800) * 604800) + 259200;
|
|
default:
|
|
return tm - (tm % (PeriodSeconds(tf)));
|
|
}
|
|
} |