//+------------------------------------------------------------------+ //| ProjectName | //| Copyright 2020, CompanyName | //| http://www.companyname.net | //+------------------------------------------------------------------+ #property strict #property indicator_separate_window #property indicator_plots 1 #property indicator_buffers 5 #property indicator_type1 DRAW_NONE #property indicator_color1 clrLimeGreen, clrTomato #property indicator_width1 1 #property indicator_label1 "Renko bricks" #property version "1.00" #property description "Configurable Renko brick size in points; cycle starts at 00:03 Moscow" input int InpBrickPoints = 10; // Renko brick size, points input int InpHistoryDays = 150; // Initial history window, days input int InpMinimumHistoryBricks = 500; // Extend history until at least this many bricks are built input int InpMaximumHistoryDays = 3650; // Maximum history search window, days input color InpUpColor = clrLimeGreen; input color InpDownColor = clrTomato; struct RenkoBrick { datetime time; double open; double high; double low; double close; int direction; }; double g_brick_size = 0.0; double g_last_close = 0.0; double g_last_bid = 0.0; datetime g_cycle_start = 0; int g_direction = 0; // 1 = up, -1 = down, 0 = not established long g_last_tick_msc = 0; bool g_have_anchor = false; bool g_history_initialized = false; bool g_alert_enabled = true; string g_alert_button_name = ""; double g_open_buffer[]; double g_high_buffer[]; double g_low_buffer[]; double g_close_buffer[]; double g_color_buffer[]; RenkoBrick g_bricks[]; int g_brick_count = 0; string g_brick_object_prefix = ""; int g_rendered_object_count = 0; int g_last_rendered_brick_count = -1; int g_last_chart_width = -1; int g_last_subwindow_height = -1; // Independent Renko viewport: one-past-last visible brick and pixels per brick. int g_view_end = -1; int g_cell_width = 12; int g_drag_last_x = -1; int g_drag_last_y = -1; double g_drag_remainder_x = 0.0; double g_view_price_min = 0.0; double g_view_price_max = 0.0; bool g_manual_vertical_view = false; bool g_mouse_move_previous = false; bool g_mouse_wheel_previous = false; // Returns the 00:03 boundary of the daily cycle containing t. // Tick timestamps are treated as Moscow time; no server-to-Moscow conversion is applied. datetime CycleStart(datetime t) { MqlDateTime dt; if(!TimeToStruct(t, dt)) return 0; if(dt.hour == 0 && dt.min < 3) t -= 86400; if(!TimeToStruct(t, dt)) return 0; dt.hour = 0; dt.min = 3; dt.sec = 0; return StructToTime(dt); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ResetCycle(datetime cycle_start) { g_cycle_start = cycle_start; g_last_close = 0.0; g_direction = 0; g_have_anchor = false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void DrawBrick(datetime tick_time, double open_price, double close_price, int direction) { const int new_count = g_brick_count + 1; if(ArrayResize(g_bricks, new_count, 256) != new_count) { Print("Renko5PointsMoscow: failed to allocate brick storage, error=", GetLastError()); return; } g_bricks[g_brick_count].time = tick_time; g_bricks[g_brick_count].open = open_price; g_bricks[g_brick_count].high = MathMax(open_price, close_price); g_bricks[g_brick_count].low = MathMin(open_price, close_price); g_bricks[g_brick_count].close = close_price; g_bricks[g_brick_count].direction = direction; g_brick_count = new_count; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void RenderBricks(const int rates_total) { for(int i = 0; i < rates_total; ++i) { g_open_buffer[i] = 0.0; g_high_buffer[i] = 0.0; g_low_buffer[i] = 0.0; g_close_buffer[i] = 0.0; g_color_buffer[i] = 0.0; } int count = g_brick_count; if(count > rates_total) count = rates_total; // Renko has its own brick sequence, not a time-based candle sequence. // Put the newest brick at buffer index 0 and older bricks to its left. for(int i = 0; i < count; ++i) { const int source = g_brick_count - 1 - i; g_open_buffer[i] = g_bricks[source].open; g_high_buffer[i] = g_bricks[source].high; g_low_buffer[i] = g_bricks[source].low; g_close_buffer[i] = g_bricks[source].close; g_color_buffer[i] = (g_bricks[source].direction > 0 ? 0.0 : 1.0); } } // Keep the Renko price axis linear and fit the complete rendered brick range. // Equal price-sized bricks therefore retain equal on-screen heights. void RenderPixelBricks() { const string short_name = StringFormat("Renko %d points / 00:03 Moscow", InpBrickPoints); const int subwindow = ChartWindowFind(0, short_name); if(subwindow < 1 || g_brick_count <= 0) return; long width_value = 0; long height_value = 0; long y_offset_value = 0; if(!ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0, width_value) || !ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, subwindow, height_value) || !ChartGetInteger(0, CHART_WINDOW_YDISTANCE, subwindow, y_offset_value)) return; const int chart_width = (int)width_value; const int window_height = (int)height_value; if(chart_width <= 24 || window_height <= 24) return; if(g_brick_object_prefix == "") g_brick_object_prefix = StringFormat("RM%d_%I64d_", InpBrickPoints, ChartID()); const int margin_x = 8; const int margin_y = 8; const int available_width = chart_width - 2 * margin_x; int visible_count = available_width / g_cell_width; if(visible_count < 1) visible_count = 1; if(visible_count > g_brick_count) visible_count = g_brick_count; if(g_view_end < 0 || g_view_end > g_brick_count) g_view_end = g_brick_count; if(g_view_end < visible_count) g_view_end = visible_count; const int first_brick = g_view_end - visible_count; const int x_start = chart_width - margin_x - visible_count * g_cell_width; double auto_min = DBL_MAX; double auto_max = -DBL_MAX; for(int i = first_brick; i < g_view_end; ++i) { auto_min = MathMin(auto_min, g_bricks[i].low); auto_max = MathMax(auto_max, g_bricks[i].high); } if(auto_min == DBL_MAX || auto_max == -DBL_MAX) return; double scale_min = auto_min; double scale_max = auto_max; if(g_manual_vertical_view && g_view_price_max > g_view_price_min) { scale_min = g_view_price_min; scale_max = g_view_price_max; } else { const double price_range = auto_max - auto_min; const double padding = MathMax(g_brick_size, price_range * 0.03); scale_min = auto_min - padding; scale_max = auto_max + padding; } const double scale_range = scale_max - scale_min; if(scale_range <= 0.0) return; const int plot_height = window_height - 2 * margin_y; if(plot_height <= 0) return; IndicatorSetInteger(INDICATOR_FIXED_MINIMUM, true); IndicatorSetInteger(INDICATOR_FIXED_MAXIMUM, true); IndicatorSetDouble(INDICATOR_MINIMUM, scale_min); IndicatorSetDouble(INDICATOR_MAXIMUM, scale_max); for(int i = 0; i < visible_count; ++i) { const int source = first_brick + i; const string object_name = g_brick_object_prefix + IntegerToString(i); const int x = x_start + i * g_cell_width; const int y_top = margin_y + (int)MathRound((scale_max - g_bricks[source].high) / scale_range * plot_height); int y_bottom = margin_y + (int)MathRound((scale_max - g_bricks[source].low) / scale_range * plot_height); if(y_bottom <= y_top) y_bottom = y_top + 1; const color brick_color = (g_bricks[source].direction > 0 ? InpUpColor : InpDownColor); if(ObjectFind(0, object_name) < 0) { if(!ObjectCreate(0, object_name, OBJ_RECTANGLE_LABEL, subwindow, 0, 0)) { Print("Renko5PointsMoscow: failed to create brick object, error=", GetLastError()); continue; } } ObjectSetInteger(0, object_name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, object_name, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, object_name, OBJPROP_YDISTANCE, y_top); ObjectSetInteger(0, object_name, OBJPROP_XSIZE, g_cell_width); ObjectSetInteger(0, object_name, OBJPROP_YSIZE, y_bottom - y_top); ObjectSetInteger(0, object_name, OBJPROP_BGCOLOR, brick_color); ObjectSetInteger(0, object_name, OBJPROP_COLOR, brick_color); ObjectSetInteger(0, object_name, OBJPROP_BORDER_TYPE, BORDER_FLAT); ObjectSetInteger(0, object_name, OBJPROP_WIDTH, 1); ObjectSetInteger(0, object_name, OBJPROP_BACK, false); ObjectSetInteger(0, object_name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, object_name, OBJPROP_SELECTED, false); ObjectSetInteger(0, object_name, OBJPROP_HIDDEN, true); ObjectSetInteger(0, object_name, OBJPROP_ZORDER, 0); } for(int i = visible_count; i < g_rendered_object_count; ++i) ObjectDelete(0, g_brick_object_prefix + IntegerToString(i)); g_rendered_object_count = visible_count; g_last_rendered_brick_count = g_brick_count; g_last_chart_width = chart_width; g_last_subwindow_height = window_height; ChartRedraw(0); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ProcessBid(datetime tick_time, double bid, const bool enable_alert) { if(bid <= 0.0 || g_brick_size <= 0.0) return; const datetime tick_cycle = CycleStart(tick_time); if(tick_cycle == 0) return; if(tick_cycle != g_cycle_start) ResetCycle(tick_cycle); // Before 00:03, CycleStart() assigns ticks to the previous day's cycle. if(!g_have_anchor) { g_last_close = NormalizeDouble(bid, _Digits); g_last_bid = bid; g_have_anchor = true; return; } int safety = 0; bool changed = true; while(changed && safety++ < 10000) { changed = false; if(g_direction == 0) { if(bid >= g_last_close + g_brick_size) { const double next_close = g_last_close + g_brick_size; DrawBrick(tick_time, g_last_close, next_close, 1); g_last_close = next_close; g_direction = 1; changed = true; } else if(bid <= g_last_close - g_brick_size) { const double next_close = g_last_close - g_brick_size; DrawBrick(tick_time, g_last_close, next_close, -1); g_last_close = next_close; g_direction = -1; changed = true; } } else if(g_direction > 0) { if(bid >= g_last_close + g_brick_size) { const double next_close = g_last_close + g_brick_size; DrawBrick(tick_time, g_last_close, next_close, 1); g_last_close = next_close; changed = true; } else if(bid <= g_last_close - 2.0 * g_brick_size) { const double brick_open = g_last_close - g_brick_size; const double next_close = g_last_close - 2.0 * g_brick_size; DrawBrick(tick_time, brick_open, next_close, -1); g_last_close = next_close; g_direction = -1; if(enable_alert && g_alert_enabled) Alert(InpBrickPoints, " bricks DOWN ", _Symbol); changed = true; } } else { if(bid <= g_last_close - g_brick_size) { const double next_close = g_last_close - g_brick_size; DrawBrick(tick_time, g_last_close, next_close, -1); g_last_close = next_close; changed = true; } else if(bid >= g_last_close + 2.0 * g_brick_size) { const double brick_open = g_last_close + g_brick_size; const double next_close = g_last_close + 2.0 * g_brick_size; DrawBrick(tick_time, brick_open, next_close, 1); g_last_close = next_close; g_direction = 1; if(enable_alert && g_alert_enabled) Alert(InpBrickPoints, " bricks UP ", _Symbol); changed = true; } } } g_last_bid = bid; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool RebuildHistoryForDays(const int requested_days) { ArrayResize(g_bricks, 0); g_brick_count = 0; const datetime now = TimeCurrent(); const datetime current_cycle = CycleStart(now); if(current_cycle == 0) return false; ResetCycle(0); g_last_tick_msc = 0; g_last_bid = 0.0; int days = requested_days; if(days < 1) days = 1; if(days > 3650) days = 3650; int total_ticks_copied = 0; for(int day = days - 1; day >= 0; --day) { const datetime from_time = current_cycle - (datetime)(day * 86400); const datetime to_time = from_time + 86400; MqlTick ticks[]; ResetLastError(); const int copied = CopyTicksRange(_Symbol, ticks, COPY_TICKS_INFO, (ulong)from_time * 1000, (ulong)to_time * 1000 - 1); if(copied < 0) { const int error = GetLastError(); Print("Renko5PointsMoscow: CopyTicksRange failed, error=", error, ", cycle=", TimeToString(from_time, TIME_DATE|TIME_MINUTES), (error == 4401 ? ". No tick history for this cycle; skipping this range." : ". Skipping this range; live tick processing remains active.")); continue; } total_ticks_copied += copied; for(int i = 0; i < copied; ++i) { if(ticks[i].bid <= 0.0) continue; ProcessBid((datetime)ticks[i].time, ticks[i].bid, false); if(ticks[i].time_msc > g_last_tick_msc) g_last_tick_msc = ticks[i].time_msc; g_last_bid = ticks[i].bid; } } if(total_ticks_copied == 0) Print("Renko5PointsMoscow: no ticks returned for the requested history window; starting with live ticks."); Print("Renko5PointsMoscow: history window=", days, " days; ticks=", total_ticks_copied, "; built bricks=", g_brick_count); return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool RebuildHistory() { int days = InpHistoryDays; if(days < 1) days = 1; int maximum_days = InpMaximumHistoryDays; if(maximum_days < 1) maximum_days = 1; if(maximum_days > 3650) maximum_days = 3650; if(days > maximum_days) days = maximum_days; int minimum_bricks = InpMinimumHistoryBricks; if(minimum_bricks < 1) minimum_bricks = 1; while(true) { if(!RebuildHistoryForDays(days)) return false; if(g_brick_count >= minimum_bricks || days >= maximum_days) break; const int previous_days = days; if(days > maximum_days / 2) days = maximum_days; else days *= 2; Print("Renko5PointsMoscow: only ", g_brick_count, " bricks in ", previous_days, " days; expanding requested history to ", days, " days."); } if(g_brick_count < minimum_bricks) Print("Renko5PointsMoscow: requested minimum ", minimum_bricks, " bricks not reached. Available history produced ", g_brick_count, " bricks after checking ", days, " days. The terminal may not have sufficient tick history."); return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CreateAlertButton() { if(g_alert_button_name != "") return true; const string short_name = StringFormat("Renko %d points / 00:03 Moscow", InpBrickPoints); const int subwindow = ChartWindowFind(0, short_name); if(subwindow < 0) return false; g_alert_button_name = StringFormat("Renko%d.AlertToggle.%d", InpBrickPoints, subwindow); if(ObjectFind(0, g_alert_button_name) >= 0) return true; if(!ObjectCreate(0, g_alert_button_name, OBJ_BUTTON, subwindow, 0, 0)) { g_alert_button_name = ""; return false; } ObjectSetInteger(0, g_alert_button_name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, g_alert_button_name, OBJPROP_XDISTANCE, 8); ObjectSetInteger(0, g_alert_button_name, OBJPROP_YDISTANCE, 35); ObjectSetInteger(0, g_alert_button_name, OBJPROP_XSIZE, 90); ObjectSetInteger(0, g_alert_button_name, OBJPROP_YSIZE, 22); ObjectSetInteger(0, g_alert_button_name, OBJPROP_FONTSIZE, 9); ObjectSetString(0, g_alert_button_name, OBJPROP_TEXT, (g_alert_enabled ? "Alert: ON" : "Alert: OFF")); ObjectSetInteger(0, g_alert_button_name, OBJPROP_STATE, false); return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int OnInit() { if(InpBrickPoints < 1 || InpMinimumHistoryBricks < 1 || InpMaximumHistoryDays < 1) { Print("Renko5PointsMoscow: brick size, minimum history bricks, and maximum history days must be >= 1."); return INIT_PARAMETERS_INCORRECT; } if(!SetIndexBuffer(0, g_open_buffer, INDICATOR_DATA) || !SetIndexBuffer(1, g_high_buffer, INDICATOR_DATA) || !SetIndexBuffer(2, g_low_buffer, INDICATOR_DATA) || !SetIndexBuffer(3, g_close_buffer, INDICATOR_DATA) || !SetIndexBuffer(4, g_color_buffer, INDICATOR_COLOR_INDEX)) return INIT_FAILED; ArraySetAsSeries(g_open_buffer, true); ArraySetAsSeries(g_high_buffer, true); ArraySetAsSeries(g_low_buffer, true); ArraySetAsSeries(g_close_buffer, true); ArraySetAsSeries(g_color_buffer, true); PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0.0); // Keep the Renko bricks visible, but hide OHLC values from the Data Window. PlotIndexSetInteger(0, PLOT_SHOW_DATA, false); PlotIndexSetInteger(0, PLOT_COLOR_INDEXES, 2); PlotIndexSetInteger(0, PLOT_LINE_COLOR, 0, InpUpColor); PlotIndexSetInteger(0, PLOT_LINE_COLOR, 1, InpDownColor); IndicatorSetInteger(INDICATOR_DIGITS, _Digits); g_brick_size = (double)InpBrickPoints * _Point; const string short_name = StringFormat("Renko %d points / 00:03 Moscow", InpBrickPoints); IndicatorSetString(INDICATOR_SHORTNAME, short_name); long event_flag = 0; if(ChartGetInteger(0, CHART_EVENT_MOUSE_MOVE, 0, event_flag)) g_mouse_move_previous = (event_flag != 0); if(ChartGetInteger(0, CHART_EVENT_MOUSE_WHEEL, 0, event_flag)) g_mouse_wheel_previous = (event_flag != 0); ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, true); g_view_end = -1; CreateAlertButton(); ChartRedraw(0); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(g_alert_button_name != "") ObjectDelete(0, g_alert_button_name); if(g_brick_object_prefix != "") ObjectsDeleteAll(0, g_brick_object_prefix); ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, g_mouse_move_previous); ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, g_mouse_wheel_previous); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_CHART_CHANGE) { RenderPixelBricks(); return; } const string short_name = StringFormat("Renko %d points / 00:03 Moscow", InpBrickPoints); const int subwindow = ChartWindowFind(0, short_name); if(subwindow < 1) return; long height_value = 0; long y_offset_value = 0; if(!ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, subwindow, height_value) || !ChartGetInteger(0, CHART_WINDOW_YDISTANCE, subwindow, y_offset_value)) return; const int window_top = (int)y_offset_value; const int window_bottom = window_top + (int)height_value; if(id == CHARTEVENT_MOUSE_MOVE) { const int x = (int)lparam; const int y = (int)dparam; const int state = (int)StringToInteger(sparam); const bool left_down = ((state & 1) != 0); const bool ctrl_down = ((state & 8) != 0); const bool inside = (y >= window_top && y <= window_bottom); if(!left_down || !inside) { g_drag_last_x = -1; g_drag_last_y = -1; g_drag_remainder_x = 0.0; return; } if(g_drag_last_x >= 0 && g_drag_last_y >= 0) { const int dx = x - g_drag_last_x; const int dy = y - g_drag_last_y; if(ctrl_down) { double price_min = g_view_price_min; double price_max = g_view_price_max; if(!g_manual_vertical_view || price_max <= price_min) { const long chart_width = ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0); const int visible = (int)MathMax(1, (chart_width - 16) / g_cell_width); const int first_index = MathMax(0, end_index - visible); price_min = DBL_MAX; price_max = -DBL_MAX; for(int i = first_index; i < end_index; ++i) { price_min = MathMin(price_min, g_bricks[i].low); price_max = MathMax(price_max, g_bricks[i].high); } const double pad = MathMax(g_brick_size, (price_max-price_min)*0.03); price_min -= pad; price_max += pad; } const double shift = (double)dy / MathMax(1, (int)height_value - 16) * (price_max - price_min); g_view_price_min += -shift; g_view_price_max += -shift; if(!g_manual_vertical_view) { g_view_price_min = price_min - shift; g_view_price_max = price_max - shift; } g_manual_vertical_view = true; } else { g_drag_remainder_x += (double)dx; const int steps = (int)(g_drag_remainder_x / g_cell_width); if(steps != 0) { if(g_view_end < 0) g_view_end = g_brick_count; g_view_end -= steps; const int max_visible = MathMax(1, ((int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0) - 16) / g_cell_width); if(g_view_end < max_visible) g_view_end = max_visible; if(g_view_end > g_brick_count) g_view_end = g_brick_count; g_drag_remainder_x -= (double)(steps * g_cell_width); } } RenderPixelBricks(); } g_drag_last_x = x; g_drag_last_y = y; return; } if(id == CHARTEVENT_MOUSE_WHEEL) { const int keymask = (int)(lparam >> 32); const int mouse_x = (short)lparam; const int mouse_y = (short)(lparam >> 16); const int delta = (int)dparam; if(mouse_y < window_top || mouse_y > window_bottom || delta == 0) return; if((keymask & 8) != 0) { double scale_min = g_view_price_min; double scale_max = g_view_price_max; if(!g_manual_vertical_view || scale_max <= scale_min) { const int visible = MathMax(1, ((int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0) - 16) / g_cell_width); const int end_index = (g_view_end < 0 ? g_brick_count : MathMin(g_view_end, g_brick_count)); const int first_index = MathMax(0, end_index - visible); scale_min = DBL_MAX; scale_max = -DBL_MAX; for(int i = first_index; i < end_index; ++i) { scale_min = MathMin(scale_min, g_bricks[i].low); scale_max = MathMax(scale_max, g_bricks[i].high); } const double pad = MathMax(g_brick_size, (scale_max-scale_min)*0.03); scale_min -= pad; scale_max += pad; } const double old_range = scale_max - scale_min; const double factor = (delta > 0 ? 0.85 : 1.176470588); const double new_range = MathMax(g_brick_size * 2.0, old_range * factor); const double relative_y = (double)(mouse_y - window_top) / MathMax(1, (int)height_value); const double anchor_price = scale_max - relative_y * old_range; g_view_price_min = anchor_price - relative_y * new_range; g_view_price_max = g_view_price_min + new_range; g_manual_vertical_view = true; } else { const int old_width = g_cell_width; const int new_width = (int)MathMax(5, MathMin(48, old_width + (delta > 0 ? 2 : -2))); if(new_width != old_width) { const int old_visible = MathMax(1, ((int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0) - 16) / old_width); const int new_visible = MathMax(1, ((int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0) - 16) / new_width); if(g_view_end < 0) g_view_end = g_brick_count; const double relative_x = (double)mouse_x / MathMax(1, (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0)); const double anchor_from_right = (1.0 - relative_x) * old_visible; g_cell_width = new_width; const int new_anchor_from_right = (int)MathRound(anchor_from_right / old_visible * new_visible); g_view_end += new_anchor_from_right - (int)MathRound(anchor_from_right); g_view_end = (int)MathMax(1, MathMin(g_brick_count, g_view_end)); } } RenderPixelBricks(); return; } if(id == CHARTEVENT_OBJECT_CLICK && sparam == g_alert_button_name) { g_alert_enabled = !g_alert_enabled; ObjectSetString(0, g_alert_button_name, OBJPROP_TEXT, (g_alert_enabled ? "Alert: ON" : "Alert: OFF")); ObjectSetInteger(0, g_alert_button_name, OBJPROP_STATE, false); ChartRedraw(0); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { if(rates_total <= 0) return 0; const bool follow_latest = (g_view_end < 0 || g_view_end >= g_brick_count); if(g_alert_button_name == "") CreateAlertButton(); if(!g_history_initialized) { if(RebuildHistory()) g_history_initialized = true; } MqlTick latest_tick; if(SymbolInfoTick(_Symbol, latest_tick) && latest_tick.bid > 0.0) { if(g_last_tick_msc == 0) { ProcessBid((datetime)latest_tick.time, latest_tick.bid, true); g_last_tick_msc = latest_tick.time_msc; g_last_bid = latest_tick.bid; } else if(latest_tick.time_msc > g_last_tick_msc) { MqlTick new_ticks[]; ResetLastError(); const int copied = CopyTicksRange(_Symbol, new_ticks, COPY_TICKS_INFO, (ulong)(g_last_tick_msc + 1), (ulong)latest_tick.time_msc); if(copied > 0) { for(int i = 0; i < copied; ++i) { if(new_ticks[i].time_msc <= g_last_tick_msc || new_ticks[i].bid <= 0.0) continue; ProcessBid((datetime)new_ticks[i].time, new_ticks[i].bid, true); g_last_tick_msc = new_ticks[i].time_msc; g_last_bid = new_ticks[i].bid; } } else { ProcessBid((datetime)latest_tick.time, latest_tick.bid, true); g_last_tick_msc = latest_tick.time_msc; g_last_bid = latest_tick.bid; } } } RenderBricks(rates_total); if(follow_latest) g_view_end = g_brick_count; long current_width = ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0); long current_height = 0; const int current_subwindow = ChartWindowFind(0, StringFormat("Renko %d points / 00:03 Moscow", InpBrickPoints)); if(current_subwindow >= 1) ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, current_subwindow, current_height); if(g_last_rendered_brick_count != g_brick_count || g_last_chart_width != (int)current_width || g_last_subwindow_height != (int)current_height) RenderPixelBricks(); return rates_total; } //+------------------------------------------------------------------+