//+------------------------------------------------------------------+ //| 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))); } }