Add files via upload

This commit is contained in:
amirghadiri1987
2025-02-07 19:15:50 +03:30
committed by GitHub
parent e829dab822
commit 781d26ab5c
46 changed files with 12926 additions and 0 deletions
+385
View File
@@ -0,0 +1,385 @@
//+------------------------------------------------------------------+
//| Box.mqh |
//| Enrico Lambino |
//| www.mql5.com/en/users/iceron|
//+------------------------------------------------------------------+
#property copyright "Enrico Lambino"
#property link "www.mql5.com/en/users/iceron"
#include <Controls\WndClient.mqh>
#define CLASS_LAYOUT 999
#ifdef LAYOUT_BOX_DEBUG
#define COLOR_BOX_BORDER clrRed
#else
#define COLOR_BOX_BORDER clrNONE
#endif
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
enum LAYOUT_STYLE
{
LAYOUT_STYLE_VERTICAL,
LAYOUT_STYLE_HORIZONTAL
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
enum VERTICAL_ALIGN
{
VERTICAL_ALIGN_CENTER,
VERTICAL_ALIGN_CENTER_NOSIDES,
VERTICAL_ALIGN_TOP,
VERTICAL_ALIGN_BOTTOM
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
enum HORIZONTAL_ALIGN
{
HORIZONTAL_ALIGN_CENTER,
HORIZONTAL_ALIGN_CENTER_NOSIDES,
HORIZONTAL_ALIGN_LEFT,
HORIZONTAL_ALIGN_RIGHT
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class CBox: public CWndClient
{
protected:
LAYOUT_STYLE m_layout_style;
VERTICAL_ALIGN m_vertical_align;
HORIZONTAL_ALIGN m_horizontal_align;
CSize m_min_size;
int m_controls_total;
int m_padding_top;
int m_padding_bottom;
int m_padding_left;
int m_padding_right;
int m_total_x;
int m_total_y;
public:
CBox();
~CBox();
virtual int Type() const
{
return CLASS_LAYOUT;
}
virtual bool Create(const long chart, const string name, const int subwin,
const int x1, const int y1, const int x2, const int y2);
virtual bool Pack();
void LayoutStyle(LAYOUT_STYLE style)
{
m_layout_style = style;
}
LAYOUT_STYLE LayoutStyle() const
{
return (m_layout_style);
}
void HorizontalAlign(const HORIZONTAL_ALIGN align)
{
m_horizontal_align = align;
}
HORIZONTAL_ALIGN HorizontalAlign() const
{
return (m_horizontal_align);
}
void VerticalAlign(const VERTICAL_ALIGN align)
{
m_vertical_align = align;
}
VERTICAL_ALIGN VerticalAlign() const
{
return (m_vertical_align);
}
void Padding(const int top, const int bottom, const int left, const int right);
void Padding(const int padding);
void PaddingTop(const int padding)
{
m_padding_top = padding;
}
int PaddingTop() const
{
return (m_padding_top);
}
void PaddingRight(const int padding)
{
m_padding_right = padding;
}
int PaddingRight() const
{
return (m_padding_right);
}
void PaddingBottom(const int padding)
{
m_padding_bottom = padding;
}
int PaddingBottom() const
{
return (m_padding_bottom);
}
void PaddingLeft(const int padding)
{
m_padding_left = padding;
}
int PaddingLeft() const
{
return (m_padding_left);
}
CSize GetMinSize() const
{
CSize sz;
sz.cx = m_min_size.cx + m_padding_left + m_padding_right;
sz.cy = m_min_size.cy + m_padding_top + m_padding_bottom;
return sz;
}
protected:
virtual void CheckControlSize(CWnd *control);
virtual void GetTotalControlsSize(void);
virtual bool GetSpace(int &x_space, int &y_space);
virtual bool Render(void);
virtual void Shift(CWnd *control, int &x, int &y, const int x_space, const int y_space);
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CBox::CBox():
m_layout_style(LAYOUT_STYLE_HORIZONTAL),
m_vertical_align(VERTICAL_ALIGN_CENTER),
m_horizontal_align(HORIZONTAL_ALIGN_CENTER),
m_controls_total(0),
m_padding_top(2),
m_padding_bottom(2),
m_padding_left(2),
m_padding_right(2),
m_total_x(0),
m_total_y(0)
{
m_min_size.cx = 0;
m_min_size.cy = 0;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CBox::~CBox()
{
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CBox::Create(const long chart, const string name, const int subwin,
const int x1, const int y1, const int x2, const int y2)
{
if(!CWndContainer::Create(chart, name, subwin, x1, y1, x2, y2))
return (false);
if(!CreateBack())
return (false);
if(!ColorBackground(CONTROLS_DIALOG_COLOR_CLIENT_BG))
return (false);
if(!ColorBorder(COLOR_BOX_BORDER))
return (false);
return (true);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CBox::Pack(void)
{
GetTotalControlsSize();
return (Render());
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CBox::CheckControlSize(CWnd *control)
{
bool adjust = false;
CSize size = Size();
CSize control_size = control.Size();
if(control_size.cx > size.cx - (m_padding_left + m_padding_right))
{
control_size.cx = size.cx - (m_padding_left + m_padding_right);
adjust = true;
}
if(control_size.cy > size.cy - (m_padding_top + m_padding_bottom))
{
control_size.cy = size.cy - (m_padding_top + m_padding_bottom);
adjust = true;
}
if(adjust)
control.Size(control_size.cx, control_size.cy);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CBox::GetTotalControlsSize(void)
{
m_total_x = 0;
m_total_y = 0;
m_controls_total = 0;
m_min_size.cx = 0;
m_min_size.cy = 0;
int total = ControlsTotal();
for(int i = 0; i < total; i++)
{
CWnd *control = Control(i);
if(control == NULL) continue;
if(control == &m_background) continue;
CheckControlSize(control);
if(control.Type() == CLASS_LAYOUT)
{
((CBox *)control).GetTotalControlsSize();
}
CSize control_size = control.Size();
if(m_min_size.cx < control_size.cx)
m_min_size.cx = control_size.cx;
if(m_min_size.cy < control_size.cy)
m_min_size.cy = control_size.cy;
if(m_layout_style == LAYOUT_STYLE_HORIZONTAL) m_total_x += control_size.cx;
else m_total_x = MathMax(m_min_size.cx, m_total_x);
if(m_layout_style == LAYOUT_STYLE_VERTICAL) m_total_y += control_size.cy;
else m_total_y = MathMax(m_min_size.cy, m_total_y);
m_controls_total++;
}
CSize size = Size();
if(m_total_x > size.cx && m_layout_style == LAYOUT_STYLE_HORIZONTAL)
{
size.cx = m_total_x;
}
if(m_total_y > size.cy && m_layout_style == LAYOUT_STYLE_VERTICAL) // shrink
{
size.cy = m_total_y;
}
Size(size);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CBox::GetSpace(int &x_space, int &y_space)
{
if(m_controls_total == 0)
return (true);
if(m_controls_total == 1)
{
if(m_horizontal_align == HORIZONTAL_ALIGN_CENTER_NOSIDES)
m_horizontal_align = HORIZONTAL_ALIGN_CENTER;
if(m_vertical_align == VERTICAL_ALIGN_CENTER_NOSIDES)
m_vertical_align = VERTICAL_ALIGN_CENTER;
}
CSize size = Size();
int x_space_total = 0;
int y_space_total = 0;
if(m_layout_style == LAYOUT_STYLE_HORIZONTAL)
{
x_space_total = size.cx - (m_total_x + m_padding_left + m_padding_right);
y_space_total = size.cy - (m_min_size.cy + m_padding_top + m_padding_bottom);
if(m_horizontal_align == HORIZONTAL_ALIGN_CENTER_NOSIDES)
x_space = x_space_total / (m_controls_total - 1);
else if(m_horizontal_align == HORIZONTAL_ALIGN_CENTER)
x_space = x_space_total / (m_controls_total + 1);
else
x_space = x_space_total / m_controls_total;
if(m_vertical_align == VERTICAL_ALIGN_CENTER || m_vertical_align == VERTICAL_ALIGN_CENTER_NOSIDES)
y_space = y_space_total / 2;
else
y_space = y_space_total;
}
else if(m_layout_style == LAYOUT_STYLE_VERTICAL)
{
x_space_total = size.cx - (m_min_size.cx + m_padding_left + m_padding_right);
y_space_total = size.cy - (m_total_y + m_padding_top + m_padding_bottom);
if(m_horizontal_align == HORIZONTAL_ALIGN_CENTER || m_horizontal_align == HORIZONTAL_ALIGN_CENTER_NOSIDES)
x_space = x_space_total / 2;
else
x_space = x_space_total;
if(m_vertical_align == VERTICAL_ALIGN_CENTER_NOSIDES)
y_space = y_space_total / (m_controls_total - 1);
else if(m_vertical_align == VERTICAL_ALIGN_CENTER)
y_space = y_space_total / (m_controls_total + 1);
else
y_space = y_space_total / m_controls_total;
}
else
return (false);
if(x_space < 0) x_space = 0;
if(y_space < 0) y_space = 0;
return (true);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CBox::Shift(CWnd *control, int &x, int &y, const int x_space, const int y_space)
{
if(m_layout_style == LAYOUT_STYLE_HORIZONTAL)
x += x_space + control.Width();
else if(m_layout_style == LAYOUT_STYLE_VERTICAL)
y += y_space + control.Height();
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CBox::Render(void)
{
int x_space = 0, y_space = 0;
if(!GetSpace(x_space, y_space))
return (false);
int x = Left() + m_padding_left +
((m_horizontal_align == HORIZONTAL_ALIGN_LEFT || m_horizontal_align == HORIZONTAL_ALIGN_CENTER_NOSIDES) ? 0 : x_space);
int y = Top() + m_padding_top +
((m_vertical_align == VERTICAL_ALIGN_TOP || m_vertical_align == VERTICAL_ALIGN_CENTER_NOSIDES) ? 0 : y_space);
for(int j = 0; j < ControlsTotal(); j++)
{
CWnd *control = Control(j);
if(control == NULL)
continue;
if(control == GetPointer(m_background))
continue;
control.Move(x, y);
if(control.Type() == CLASS_LAYOUT)
{
CBox *container = control;
container.Pack();
}
if(j < ControlsTotal() - 1)
Shift(GetPointer(control), x, y, x_space, y_space);
}
return (true);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CBox::Padding(const int top, const int right, const int bottom, const int left)
{
m_padding_top = top;
m_padding_right = right;
m_padding_bottom = bottom;
m_padding_left = left;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CBox::Padding(const int padding)
{
m_padding_top = padding;
m_padding_right = padding;
m_padding_bottom = padding;
m_padding_left = padding;
}
//+------------------------------------------------------------------+
+66
View File
@@ -0,0 +1,66 @@
//+------------------------------------------------------------------+
//| ComboBoxResizable.mqh |
//| Copyright (c) 2019, Marketeer |
//| https://www.mql5.com/en/users/marketeer |
//+------------------------------------------------------------------+
#include <Controls/ComboBox.mqh>
class ComboBoxResizable: public CComboBox
{
public:
virtual bool OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) override;
virtual bool OnResize(void) override
{
m_edit.Width(Width());
int x1 = Width() - (CONTROLS_BUTTON_SIZE + CONTROLS_COMBO_BUTTON_X_OFF);
int y1 = (Height() - CONTROLS_BUTTON_SIZE) / 2;
m_drop.Move(Left() + x1, Top() + y1);
m_list.Width(Width());
return CWndContainer::OnResize();
}
virtual bool OnClickButton(void) override
{
// this is a hack to trigger resizing of elements in the list
// we need it because standard ListView is incorrectly coded in such a way
// that elements are resized only if vscroll is present
bool vs = m_list.VScrolled();
if(m_drop.Pressed())
{
m_list.VScrolled(true);
}
bool b = CComboBox::OnClickButton();
m_list.VScrolled(vs);
return b;
}
virtual bool Enable(void) override
{
m_edit.Show();
m_drop.Show();
return CComboBox::Enable();
}
virtual bool Disable(void) override
{
m_edit.Hide();
m_drop.Hide();
return CComboBox::Disable();
}
};
#define EXIT_ON_DISABLED \
if(!IsEnabled()) \
{ \
return false; \
}
EVENT_MAP_BEGIN(ComboBoxResizable)
EXIT_ON_DISABLED
ON_EVENT(ON_CLICK, m_drop, OnClickButton)
EVENT_MAP_END(CComboBox)
+153
View File
@@ -0,0 +1,153 @@
//+------------------------------------------------------------------+
//| Grid.mqh |
//| Enrico Lambino |
//| www.mql5.com/en/users/iceron|
//+------------------------------------------------------------------+
#property copyright "Enrico Lambino"
#property link "http://www.mql5.com"
#property strict
#include "Box.mqh"
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class CGrid: public CBox
{
protected:
int m_cols;
int m_rows;
int m_hgap;
int m_vgap;
CSize m_cell_size;
public:
CGrid();
CGrid(int rows, int cols, int hgap = 0, int vgap = 0);
~CGrid();
virtual int Type() const
{
return CLASS_LAYOUT;
}
virtual bool Init(int rows, int cols, int hgap = 0, int vgap = 0);
virtual bool Create(const long chart, const string name, const int subwin,
const int x1, const int y1, const int x2, const int y2);
virtual int Columns()
{
return (m_cols);
}
virtual void Columns(int cols)
{
m_cols = cols;
}
virtual int Rows()
{
return (m_rows);
}
virtual void Rows(int rows)
{
m_rows = rows;
}
virtual int HGap()
{
return (m_hgap);
}
virtual void HGap(int gap)
{
m_hgap = gap;
}
virtual int VGap()
{
return (m_vgap);
}
virtual void VGap(int gap)
{
m_vgap = gap;
}
virtual bool Pack();
protected:
virtual void CheckControlSize(CWnd *control);
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CGrid::CGrid()
{
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CGrid::CGrid(int rows, int cols, int hgap = 0, int vgap = 0)
{
Init(rows, cols, hgap, vgap);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CGrid::~CGrid()
{
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CGrid::Init(int rows, int cols, int hgap = 0, int vgap = 0)
{
Columns(cols);
Rows(rows);
HGap(hgap);
VGap(vgap);
return (true);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CGrid::Create(const long chart, const string name, const int subwin,
const int x1, const int y1, const int x2, const int y2)
{
return (CBox::Create(chart, name, subwin, x1, y1, x2, y2));
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CGrid::Pack()
{
CSize size = Size();
m_cell_size.cx = (size.cx - ((m_cols + 1) * m_hgap)) / m_cols;
m_cell_size.cy = (size.cy - ((m_rows + 1) * m_vgap)) / m_rows;
int x = Left(), y = Top();
int cnt = 0;
for(int i = 0; i < ControlsTotal(); i++)
{
CWnd *control = Control(i);
if(control == NULL)
continue;
if(control == GetPointer(m_background))
continue;
if(cnt == 0 || Right() - (x + m_cell_size.cx) < m_cell_size.cx + m_hgap)
{
if(cnt == 0)
y += m_vgap;
else
y += m_vgap + m_cell_size.cy;
x = Left() + m_hgap;
}
else
x += m_cell_size.cx + m_hgap;
CheckControlSize(control);
control.Move(x, y);
if(control.Type() == CLASS_LAYOUT)
{
CBox *container = control;
container.Pack();
}
cnt++;
}
return (true);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CGrid::CheckControlSize(CWnd *control)
{
control.Size(m_cell_size.cx, m_cell_size.cy);
}
//+------------------------------------------------------------------+
+154
View File
@@ -0,0 +1,154 @@
//+------------------------------------------------------------------+
//| GridTk.mqh |
//| Enrico Lambino |
//| www.mql5.com/en/users/iceron|
//+------------------------------------------------------------------+
#property copyright "Enrico Lambino"
#property link "http://www.mql5.com"
#property strict
#include "Grid.mqh"
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class CGridConstraints: public CObject
{
protected:
CWnd *m_control;
int m_row;
int m_col;
int m_rowspan;
int m_colspan;
public:
CGridConstraints(CWnd *control, int row, int column, int rowspan = 1, int colspan = 1);
~CGridConstraints();
CWnd *Control()
{
return (m_control);
}
int Row()
{
return (m_row);
}
int Column()
{
return (m_col);
}
int RowSpan()
{
return (m_rowspan);
}
int ColSpan()
{
return (m_colspan);
}
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CGridConstraints::CGridConstraints(CWnd *control, int row, int column, int rowspan = 1, int colspan = 1)
{
m_control = control;
m_row = MathMax(0, row);
m_col = MathMax(0, column);
m_rowspan = MathMax(1, rowspan);
m_colspan = MathMax(1, colspan);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CGridConstraints::~CGridConstraints()
{
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class CGridTk: public CGrid
{
protected:
CArrayObj m_constraints;
public:
CGridTk();
~CGridTk();
bool Grid(CWnd *control, int row, int column, int rowspan, int colspan);
bool Pack();
CGridConstraints *GetGridConstraints(CWnd *control);
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CGridTk::CGridTk(void)
{
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CGridTk::~CGridTk(void)
{
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CGridTk::Grid(CWnd *control, int row, int column, int rowspan = 1, int colspan = 1)
{
CGridConstraints *constraints = new CGridConstraints(control, row, column, rowspan, colspan);
if(!CheckPointer(constraints))
return (false);
if(!m_constraints.Add(constraints))
return (false);
return (Add(control));
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CGridTk::Pack()
{
CGrid::Pack();
CSize size = Size();
m_cell_size.cx = (size.cx - (m_cols + 1) * m_hgap) / m_cols;
m_cell_size.cy = (size.cy - (m_rows + 1) * m_vgap) / m_rows;
for(int i = 0; i < ControlsTotal(); i++)
{
int x = Left(), y = Top();
CWnd *control = Control(i);
if(control == NULL)
continue;
if(control == GetPointer(m_background))
continue;
CGridConstraints *constraints = GetGridConstraints(control);
if(constraints == NULL)
continue;
int column = constraints.Column();
int row = constraints.Row();
x += (column * m_cell_size.cx) + ((column + 1) * m_hgap);
y += (row * m_cell_size.cy) + ((row + 1) * m_vgap);
int colspan = constraints.ColSpan();
int rowspan = constraints.RowSpan();
control.Size(colspan * m_cell_size.cx + ((colspan - 1) * m_hgap), rowspan * m_cell_size.cy + ((rowspan - 1) * m_vgap));
control.Move(x, y);
if(control.Type() == CLASS_LAYOUT)
{
CBox *container = control;
container.Pack();
}
}
return (true);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CGridConstraints *CGridTk::GetGridConstraints(CWnd *control)
{
for(int i = 0; i < m_constraints.Total(); i++)
{
CGridConstraints *constraints = m_constraints.At(i);
CWnd *ctrl = constraints.Control();
if(ctrl == NULL)
continue;
if(ctrl == control)
return (constraints);
}
return (NULL);
}
//+------------------------------------------------------------------+
+337
View File
@@ -0,0 +1,337 @@
//+------------------------------------------------------------------+
//| MaximizableAppDialog.mqh |
//| Copyright (c) 2019, Marketeer |
//| https://www.mql5.com/en/users/marketeer |
//+------------------------------------------------------------------+
#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>
#resource "res\\Expand2.bmp"
#resource "res\\size6.bmp"
#resource "res\\size10.bmp"
class MaximizableAppDialog: public CAppDialog
{
protected:
CBmpButton m_button_truemax;
CBmpButton m_button_size;
bool m_maximized;
CRect m_max_rect;
CSize m_size_limit;
bool m_sizing;
// window maximization
virtual bool CreateButtonMinMax(void) override;
virtual void OnClickButtonMinMax(void) override;
virtual void OnClickButtonTrueMax(void);
virtual void OnClickButtonSizeFixMe(void);
virtual void Expand(void);
virtual void Restore(void);
virtual void Minimize(void) override;
// window resizing
bool CreateButtonSize(void);
bool OnDialogSizeStart(void);
virtual bool OnDialogDragStart(void) override;
virtual bool OnDialogDragProcess(void) override;
virtual bool OnDialogDragEnd(void) override;
virtual void SelfAdjustment(const bool minimized = false) = 0;
public:
MaximizableAppDialog(): m_maximized(false), m_sizing(false) {}
virtual bool Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2) override;
virtual bool OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam) override;
virtual bool OnChartChange(const long &lparam, const double &dparam, const string &sparam);
void ChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
void SetSizeLimit(const CSize &limit) { m_size_limit = limit; }
CSize GetSizeLimit() { return m_size_limit; }
};
void MaximizableAppDialog::ChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_CHART_CHANGE)
{
if(OnChartChange(lparam, dparam, sparam)) return;
}
CAppDialog::ChartEvent(id, lparam, dparam, sparam);
}
EVENT_MAP_BEGIN(MaximizableAppDialog)
ON_EVENT(ON_CLICK, m_button_truemax, OnClickButtonTrueMax)
ON_EVENT(ON_CLICK, m_button_size, OnClickButtonSizeFixMe)
ON_EVENT(ON_DRAG_START, m_button_size, OnDialogSizeStart)
ON_EVENT_PTR(ON_DRAG_PROCESS, m_drag_object, OnDialogDragProcess)
ON_EVENT_PTR(ON_DRAG_END, m_drag_object, OnDialogDragEnd)
EVENT_MAP_END(CAppDialog)
bool MaximizableAppDialog::Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2)
{
// 1 * CONTROLS_BORDER_WIDTH - stays here, because the standard control library minimizes window
// when it's height is 1 pixel smaller than the entire chart height
m_max_rect.SetBound(0,
0,
(int)ChartGetInteger(ChartID(), CHART_WIDTH_IN_PIXELS) - 0 * CONTROLS_BORDER_WIDTH,
(int)ChartGetInteger(ChartID(), CHART_HEIGHT_IN_PIXELS) - 1 * CONTROLS_BORDER_WIDTH);
if(!CAppDialog::Create(chart, name, subwin, x1, y1, x2, y2)) return false;
if(!CreateButtonSize()) return false;
m_size_limit.cx = x2 - x1;
m_size_limit.cy = y2 - y1;
if(m_size_limit.cx >= m_max_rect.Width() || m_size_limit.cy >= m_max_rect.Height())
{
m_size_limit.cx = m_min_rect.Width() * 3;
m_size_limit.cy = m_min_rect.Height() * 7;
}
return true;
}
bool MaximizableAppDialog::CreateButtonMinMax(void) override
{
if(!CAppDialog::CreateButtonMinMax()) return false;
// add maximization button
int off = (m_panel_flag) ? 0 : 2 * CONTROLS_BORDER_WIDTH;
int x1 = Width() - off - 3 * (CONTROLS_BUTTON_SIZE + CONTROLS_DIALOG_BUTTON_OFF);
int y1 = off + CONTROLS_DIALOG_BUTTON_OFF;
int x2 = x1 + CONTROLS_BUTTON_SIZE;
int y2 = y1 + CONTROLS_BUTTON_SIZE;
if(!m_button_truemax.Create(m_chart_id, m_name + "TrueMax", m_subwin, x1, y1, x2, y2)) return false;
if(!m_button_truemax.BmpNames("::res\\Expand2.bmp", "::res\\Restore.bmp")) return false;
if(!CWndContainer::Add(m_button_truemax)) return false;
m_button_truemax.Locking(true);
m_button_truemax.Alignment(WND_ALIGN_RIGHT, 0, 0, off + 2 * CONTROLS_BUTTON_SIZE + 2 * CONTROLS_DIALOG_BUTTON_OFF, 0);
CaptionAlignment(WND_ALIGN_WIDTH, off, 0, off + 3 * (CONTROLS_BUTTON_SIZE + CONTROLS_DIALOG_BUTTON_OFF), 0);
return true;
}
bool MaximizableAppDialog::CreateButtonSize(void)
{
int off = (m_panel_flag) ? 0 : 2 * CONTROLS_BORDER_WIDTH;
int x1 = Width() - CONTROLS_BUTTON_SIZE + 1;
int y1 = Height() - CONTROLS_BUTTON_SIZE + 1;
int x2 = x1 + CONTROLS_BUTTON_SIZE - 1;
int y2 = y1 + CONTROLS_BUTTON_SIZE - 1;
if(!m_button_size.Create(m_chart_id, m_name + "Size", m_subwin, x1, y1, x2, y2)) return false;
if(!m_button_size.BmpNames("::res\\size6.bmp", "::res\\size10.bmp")) return false;
if(!CWndContainer::Add(m_button_size)) return false;
m_button_size.Alignment(WND_ALIGN_RIGHT|WND_ALIGN_BOTTOM, 0, 0, 0, 0);
m_button_size.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG);
return true;
}
void MaximizableAppDialog::OnClickButtonTrueMax(void)
{
if(m_button_truemax.Pressed())
Expand();
else
Restore();
SubwinOff();
}
// This is a hack. It's required because in minimized state sizing button somehow "overlaps"
// the close button and intercepts clicks on it (which prevents exit from minimized app).
// This happens despite the fact that the sizing button is hidden, disabled and assigned
// with minimal Z-order (checked out, then removed).
// Looks like a bug in the standard control library, specifically:
// In CWnd::OnMouseEvent there must be a line:
//
// if(!IS_ENABLED || !IS_VISIBLE) return false;
//
// but it's not there, so invisible, disabled and even background objects are processed
// in the same manner as all other objects. Specifically in CWndContainer::OnMouseEvent
// there is a reverse loop through all objects (it does _not_ respect Z-order anyhow).
void MaximizableAppDialog::OnClickButtonSizeFixMe(void)
{
if(m_minimized)
{
Destroy();
}
}
void MaximizableAppDialog::Expand(void)
{
m_maximized = true;
m_minimized = false;
m_button_minmax.Pressed(false);
Rebound(m_max_rect);
m_button_size.Hide();
m_button_size.StateFlagsReset(WND_STATE_FLAG_ENABLE);
m_button_size.PropFlagsReset(WND_PROP_FLAG_CAN_DRAG);
if(!m_panel_flag)
{
m_caption.PropFlagsReset(WND_PROP_FLAG_CAN_DRAG);
}
ClientAreaVisible(true);
SelfAdjustment();
}
//+------------------------------------------------------------------+
//| Restore dialog window |
//+------------------------------------------------------------------+
void MaximizableAppDialog::Restore(void)
{
m_maximized = false;
m_minimized = false;
m_button_minmax.Pressed(false);
m_button_size.Show();
m_button_size.StateFlagsSet(WND_STATE_FLAG_ENABLE);
m_button_size.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG);
CAppDialog::Maximize();
if(!m_panel_flag)
{
m_caption.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG);
}
SelfAdjustment();
}
void MaximizableAppDialog::Minimize()
{
CAppDialog::Minimize();
m_button_size.Hide();
m_button_size.StateFlagsReset(WND_STATE_FLAG_ENABLE);
m_button_size.PropFlagsReset(WND_PROP_FLAG_CAN_DRAG);
}
bool MaximizableAppDialog::OnChartChange(const long &lparam, const double &dparam, const string &sparam)
{
m_max_rect.SetBound(0, 0,
(int)ChartGetInteger(ChartID(), CHART_WIDTH_IN_PIXELS) - 0 * CONTROLS_BORDER_WIDTH,
(int)ChartGetInteger(ChartID(), CHART_HEIGHT_IN_PIXELS) - 1 * CONTROLS_BORDER_WIDTH);
if(m_maximized)
{
if(m_rect.Width() != m_max_rect.Width() || m_rect.Height() != m_max_rect.Height())
{
Rebound(m_max_rect);
SelfAdjustment();
m_chart.Redraw();
}
return true;
}
return false;
}
void MaximizableAppDialog::OnClickButtonMinMax(void)
{
CAppDialog::OnClickButtonMinMax();
m_button_truemax.Pressed(false);
m_maximized = false;
if(m_minimized)
{
m_button_size.Hide();
m_button_size.StateFlagsReset(WND_STATE_FLAG_ENABLE);
m_button_size.PropFlagsReset(WND_PROP_FLAG_CAN_DRAG);
}
else
{
m_button_size.Show();
m_button_size.StateFlagsSet(WND_STATE_FLAG_ENABLE);
m_button_size.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG);
}
if(!m_panel_flag)
{
m_caption.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG);
}
SelfAdjustment(m_minimized);
}
bool MaximizableAppDialog::OnDialogSizeStart(void)
{
if(m_drag_object == NULL)
{
m_drag_object = new CDragWnd;
if(m_drag_object == NULL) return false;
}
int x1 = m_button_size.Left() - CONTROLS_DRAG_SPACING;
int y1 = m_button_size.Top() - CONTROLS_DRAG_SPACING;
int x2 = m_button_size.Right() + CONTROLS_DRAG_SPACING;
int y2 = m_button_size.Bottom() + CONTROLS_DRAG_SPACING;
m_drag_object.Create(m_chart_id, "", m_subwin, x1, y1, x2, y2);
m_drag_object.PropFlagsSet(WND_PROP_FLAG_CAN_DRAG);
CChart chart;
chart.Attach(m_chart_id);
m_drag_object.Limits(-CONTROLS_DRAG_SPACING, -CONTROLS_DRAG_SPACING,
chart.WidthInPixels() + CONTROLS_DRAG_SPACING,
chart.HeightInPixels(m_subwin) + CONTROLS_DRAG_SPACING);
chart.Detach();
m_drag_object.MouseX(m_button_size.MouseX());
m_drag_object.MouseY(m_button_size.MouseY());
m_drag_object.MouseFlags(m_button_size.MouseFlags());
m_sizing = true;
return true;
}
bool MaximizableAppDialog::OnDialogDragStart(void)
{
if(m_maximized) return false;
return CAppDialog::OnDialogDragStart();
}
//+------------------------------------------------------------------+
//| Continue dragging the dialog box |
//+------------------------------------------------------------------+
bool MaximizableAppDialog::OnDialogDragProcess(void)
{
if(!m_sizing) return CDialog::OnDialogDragProcess();
if(m_drag_object == NULL) return false;
int x = m_drag_object.Right() - Right() - CONTROLS_DRAG_SPACING;
int y = m_drag_object.Bottom() - Bottom() - CONTROLS_DRAG_SPACING;
// resize dialog
CRect r = Rect();
r.right += x;
r.bottom += y;
if(r.Width() < m_size_limit.cx) r.right = r.left + m_size_limit.cx;
if(r.Height() < m_size_limit.cy) r.bottom = r.top + m_size_limit.cy;
Rebound(r);
SelfAdjustment();
return true;
}
//+------------------------------------------------------------------+
//| End dragging the dialog box |
//+------------------------------------------------------------------+
bool MaximizableAppDialog::OnDialogDragEnd(void)
{
if(!m_sizing) return CDialog::OnDialogDragEnd();
if(m_drag_object != NULL)
{
m_button_size.MouseFlags(m_drag_object.MouseFlags());
delete m_drag_object;
m_drag_object = NULL;
}
m_norm_rect.SetBound(m_rect);
m_sizing = false;
SelfAdjustment();
return true;
}
+27
View File
@@ -0,0 +1,27 @@
//+------------------------------------------------------------------+
//| SpinEditResizable.mqh |
//| Copyright (c) 2019, Marketeer |
//| https://www.mql5.com/en/users/marketeer |
//+------------------------------------------------------------------+
#include <Controls/SpinEdit.mqh>
class SpinEditResizable: public CSpinEdit
{
public:
virtual bool OnResize(void) override
{
m_edit.Width(Width());
m_edit.Height(Height());
int x1 = Width() - (CONTROLS_BUTTON_SIZE + CONTROLS_SPIN_BUTTON_X_OFF);
int y1 = (Height() - 2 * CONTROLS_SPIN_BUTTON_SIZE) / 2;
m_inc.Move(Left() + x1, Top() + y1);
x1 = Width() - (CONTROLS_BUTTON_SIZE + CONTROLS_SPIN_BUTTON_X_OFF);
y1 = (Height() - 2 * CONTROLS_SPIN_BUTTON_SIZE) / 2 + CONTROLS_SPIN_BUTTON_SIZE;
m_dec.Move(Left() + x1, Top() + y1);
return CWndContainer::OnResize();
}
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long
+112
View File
@@ -0,0 +1,112 @@
//+------------------------------------------------------------------+
//| CSVReader.mqh |
//| Copyright (c) 2019, Marketeer |
//| https://www.mql5.com/ru/articles/5913 |
//+------------------------------------------------------------------+
#include <Marketeer/IndexMap.mqh>
#include <Marketeer/GroupSettings.mqh>
input GroupSettings SCV_Settings; // C S V S E T T I N G S
input string CSVDelimiter = ";" /*mql5 signals use ';' instead of ','*/; // · Delimiter
class CSVConverter
{
private:
class File
{
int file;
public:
File(const string name, const int flags, const short delimiter)
{
file = FileOpen(name, flags, delimiter, CP_UTF8);
}
File(const string name, const int flags)
{
file = FileOpen(name, flags);
}
bool isOpened()
{
return (file != INVALID_HANDLE);
}
int handle()
{
return file;
}
~File()
{
if(file != INVALID_HANDLE) FileClose(file);
}
};
public:
static IndexMap *ReadCSV(const string inputFileName)
{
// history.csv - 13 columns, positions.csv - 10 columns
int columns = StringFind(inputFileName, ".history.csv") > 0 ? 13 : (StringFind(inputFileName, ".positions.csv") > 0 ? 10 : 0);
if(columns == 0)
{
Print("Supported files: *.history.csv and .positions.csv");
return NULL;
}
Print("Reading csv-file ", inputFileName);
uchar delimiter = (uchar)CSVDelimiter[0];
File f(inputFileName, FILE_READ|FILE_TXT|FILE_ANSI|FILE_SHARE_READ|FILE_SHARE_WRITE, delimiter);
if(!f.isOpened())
{
Alert("Can't read file " + inputFileName);
return NULL;
}
int file = f.handle();
bool headerLine = true;
IndexMap *data = new IndexMap();
string headers[];
uint count = 0;
while(!FileIsEnding(file))
{
string stLine = "";
string stParts[];
stLine = FileReadString(file);
int nParts = StringSplit(stLine, delimiter, stParts);
if(nParts != columns)
{
Print("File " + inputFileName + " contains " + (string)nParts + " columns (" + (string)(columns) + "+ required)");
Print("Line: ", stLine);
return NULL;
}
if(headerLine)
{
headerLine = false;
ArrayCopy(headers, stParts);
continue;
}
IndexMap *row = new IndexMap();
for(int i = 0; i < nParts; i++)
{
row.setValue((string)i + "." + headers[i], stParts[i]);
}
data.add((string)count, row);
++count;
}
return data;
}
};
+11
View File
@@ -0,0 +1,11 @@
#define CSV_COLUMN_TIME1 0
#define CSV_COLUMN_TYPE 1
#define CSV_COLUMN_VOLUME 2
#define CSV_COLUMN_SYMBOL 3
#define CSV_COLUMN_PRICE1 4
#define CSV_COLUMN_TIME2 5 // 7
#define CSV_COLUMN_PRICE2 6 // 8
#define CSV_COLUMN_COMMISSION 7 // 9
#define CSV_COLUMN_SWAP 8 // 10
#define CSV_COLUMN_PROFIT 9 // 11
#define CSV_COLUMN_COMMENT 12
+24
View File
@@ -0,0 +1,24 @@
template<typename T1,typename T2>
class Converter
{
private:
union _L2D
{
T1 L;
T2 D;
}
L2D;
public:
T2 operator[](const T1 L)
{
L2D.L = L;
return L2D.D;
}
T1 operator[](const T2 D)
{
L2D.D = D;
return L2D.L;
}
};
+1
View File
@@ -0,0 +1 @@
enum GroupSettings {};
+15
View File
@@ -0,0 +1,15 @@
#define COLUMNS_COUNT 13
#define COLUMN_TIME 0
#define COLUMN_DEAL 1
#define COLUMN_SYMBOL 2
#define COLUMN_TYPE 3
#define COLUMN_DIRECTION 4
#define COLUMN_VOLUME 5
#define COLUMN_PRICE 6
#define COLUMN_ORDER 7
#define COLUMN_COMISSION 8
#define COLUMN_SWAP 9
#define COLUMN_PROFIT 10
#define COLUMN_BALANCE 11
#define COLUMN_COMMENT 12
+401
View File
@@ -0,0 +1,401 @@
//+------------------------------------------------------------------+
//| IndexMapT.mqh |
//| https://www.mql5.com/ru/articles/5706/ |
//+------------------------------------------------------------------+
#define EMPTY ((int)EMPTY_VALUE)
#ifdef HASHMAP_WARNING
#define NULL_PLACEHOLDER "n/a"
#else
#define NULL_PLACEHOLDER ""
#endif
class Object
{
public:
virtual string asString() const
{
return(__FUNCSIG__);
}
virtual string asCSVString() const
{
return(__FUNCSIG__);
}
virtual string getTypeName() const = 0;
};
/**
* Base container for indexed map. Can contain plain types and pointers.
*/
class Container: public Object
{
protected:
enum datatype
{
null,
s,
d,
t,
i,
o,
u
};
datatype type;
public:
datatype getType() const
{
return type;
}
// helper method to access plain type values from the base class
template<typename R>
R get() const;
// helper method to access object pointers from the base class
template<typename R>
Object *getObject() const;
};
/**
* Variable type data for indexed map; plain types only.
*/
template<typename T>
class TypeContainer: public Container
{
private:
int digits;
int flags;
protected:
T v;
TypeContainer()
{
digits = _Digits;
flags = TIME_DATE | TIME_MINUTES;
}
public:
TypeContainer(T _v, int precision = INT_MIN, int timeflags = TIME_DATE | TIME_MINUTES)
{
v = _v;
digits = precision == INT_MIN ? _Digits : precision;
flags = timeflags;
if(typename(T) == "string")
{
type = datatype::s;
}
else
if(typename(T) == "double" || typename(T) == "float")
{
type = datatype::d;
}
else
if(typename(T) == "datetime")
{
type = datatype::t;
}
else
if(typename(T) == "char" || typename(T) == "short" || typename(T) == "int" || typename(T) == "long")
{
type = datatype::i;
}
else
{
type = datatype::u;
}
}
virtual T getValue() const
{
return v;
}
// represent data as a string, convert if necessary
virtual string asString() const
{
switch(type)
{
case datatype::s: return (string)v;
case datatype::d: return DoubleToString((double)v, digits);
case datatype::t: return TimeToString((datetime)v, flags);
case datatype::i: return IntegerToString((long)v);
default: return (string)v;
}
}
virtual string asCSVString() const
{
return asString();
}
virtual string getTypeName() const override
{
return typename(this);
}
};
/**
* Object pointer types for indexed map.
* Pointer will be deleted automatically.
*/
template<typename T>
class ObjectContainer: public Container
{
protected:
Object *o;
public:
ObjectContainer(T _v)
{
o = _v;
type = datatype::o;
}
~ObjectContainer()
{
if(CheckPointer(o) == POINTER_DYNAMIC)
{
delete(o);
}
}
T getObject() const
{
return o;
}
virtual string asString() const override
{
return o.asString();
}
virtual string asCSVString() const
{
return o.asCSVString();
}
virtual string getTypeName() const override
{
return typename(this);
}
};
template<typename R>
R Container::get() const
{
const TypeContainer<R> *ptr = dynamic_cast<const TypeContainer<R> *>(&this);
if(ptr != NULL) return (R)ptr.getValue();
return (R)NULL;
}
template<typename R> // R is supposed to be an object pointer
Object *Container::getObject() const
{
const ObjectContainer<R> *obj = dynamic_cast<const ObjectContainer<R> *>(&this);
if(obj != NULL) return obj.getObject();
return NULL;
}
/**
* Indexed map with random access by key and index.
*/
template<typename T>
class IndexMapT: public Container // Object
{
private:
T keys[];
Container *values[];
int count;
string id;
uchar delimiter;
public:
IndexMapT(): count(0), delimiter(',') {}
IndexMapT(string obj): id (obj), count(0), delimiter(',') {}
IndexMapT(uchar d): count(0), delimiter(d) {}
~IndexMapT()
{
reset();
}
virtual string getTypeName() const override
{
return typename(this);
}
void add(const T key, Container *value)
{
ArrayResize(keys, count + 1);
ArrayResize(values, count + 1);
keys[count] = key;
values[count] = value;
count++;
}
void reset()
{
for(int i = 0; i < count; i++)
{
if(CheckPointer(values[i]) == POINTER_DYNAMIC)
{
delete(values[i]);
}
}
ArrayResize(keys, 0);
ArrayResize(values, 0);
count = 0;
}
bool isKeyExisting(const T key) const
{
return (getIndex(key) != EMPTY);
}
int getIndex(const T key) const
{
for(int i = 0; i < count; i++)
{
if(keys[i] == key) return(i);
}
#ifdef HASHMAP_VERBOSE
Print(__FUNCSIG__, ": no key=", key);
#endif
return EMPTY;
}
Container *operator[](const int index) const
{
if(index < 0 || index >= count)
{
#ifdef HASHMAP_VERBOSE
Print(__FUNCSIG__, ": index=", index);
#endif
return(NULL);
}
return(GetPointer(values[index]));
}
Container *operator[](const T key) const
{
for(int i = 0; i < count; i++)
{
if(keys[i] == key) return(GetPointer(values[i]));
}
#ifdef HASHMAP_VERBOSE
Print(__FUNCSIG__, ": no key=", key);
#endif
return(NULL);
}
T getKey(const int index) const
{
if(index < 0 || index >= count)
{
Print(__FUNCSIG__, ": index=", index);
}
return(keys[index]);
}
template<typename R>
void setValue(const T key, R value)
{
// NB: implementation specific
// in HTML every attribute can occur only once in a tag,
// all successive assignments are ignored
if(!isKeyExisting(key))
{
set(key, new TypeContainer<R>(value));
}
}
void set(const T key, Container *value)
{
int index = getIndex(key);
if(index != EMPTY)
{
#ifdef HASHMAP_WARNING
Print(__FUNCSIG__, ": overwritten key=", key, ", old value=", (values[index] != NULL ? values[index].asString() : "null"), ", new value=", (value != NULL ? value.asString() : "null"));
#endif
values[index] = value;
}
else
{
add(key, value);
}
}
void set(const T key)
{
int index = getIndex(key);
if(index == EMPTY)
{
add(key, NULL);
}
}
int getSize() const
{
return(count);
}
virtual string asString() const
{
string result = "";
for(int i = 0; i < count; i++)
{
result += (string)keys[i] + "=" + (CheckPointer(values[i]) == POINTER_INVALID ? NULL_PLACEHOLDER : values[i].asString()) + ";";
}
return result;
}
virtual string asCSVString() const
{
string result = "";
string d = CharToString(delimiter);
for(int i = 0; i < count; i++)
{
string v = NULL_PLACEHOLDER;
if(CheckPointer(values[i]) != POINTER_INVALID)
{
v = values[i].asCSVString();
StringReplace(v, d, "");
}
if(i < count - 1)
{
result += v + d;
}
else
{
result += v;
}
}
return result;
}
};
class IndexMap: public IndexMapT<string>
{
public:
IndexMap(): IndexMapT() {}
IndexMap(string obj): IndexMapT(obj) {}
IndexMap(uchar d): IndexMapT(d) {}
string get(const string key)
{
Container *c = this[key];
if(c == NULL) return NULL;
return c.get<string>();
}
};
+195
View File
@@ -0,0 +1,195 @@
//+------------------------------------------------------------------+
//| RubbArray.mqh |
//| Copyright (c) 2019, Marketeer |
//| https://www.mql5.com/en/users/marketeer |
//| https://www.mql5.com/ru/articles/5638 |
//+------------------------------------------------------------------+
template <typename T>
interface Clonable
{
T clone();
};
template <typename T>
class BaseArray
{
protected:
T data[];
public:
virtual ~BaseArray()
{
clear();
}
virtual void clear()
{
ArrayResize(data, 0);
}
T operator[](int i) const
{
return get(i);
}
T get(int i) const
{
if(i < 0 || i >= ArraySize(data))
{
Print("Array size=", ArraySize(data), ", index=", i);
return NULL;
}
return data[i];
}
T top() const
{
if(ArraySize(data) == 0)
{
Print("Array size=0");
return NULL;
}
return data[ArraySize(data) - 1];
}
T peek() const
{
return top();
}
BaseArray *add(T d)
{
int n = ArraySize(data);
ArrayResize(data, n + 1);
data[n] = d;
return &this;
}
BaseArray *operator<<(T d)
{
return add(d);
}
BaseArray *operator<<(const BaseArray<T> *x)
{
for(int i = 0; i < x.size(); i++)
{
Clonable<T> *clone = dynamic_cast<Clonable<T> *>(x[i]);
if(clone != NULL)
{
add(clone.clone());
}
else
{
add(x[i]);
}
}
return &this;
}
BaseArray *push(T d)
{
return add(d);
}
void operator=(const BaseArray &d)
{
int i, n = d.size();
ArrayResize(data, n);
for(i = 0; i < n; i++)
{
data[i] = d[i];
}
}
T operator>>(int i)
{
T d = this[i];
if(d == NULL) return NULL;
int n = ArraySize(data) - 1;
if(i < n)
{
ArrayCopy(data, data, i, i + 1);
}
ArrayResize(data, n);
return d;
}
T pop()
{
int _size = ArraySize(data) - 1;
T d = this[_size];
ArrayResize(data, _size);
return d;
}
int size() const
{
return ArraySize(data);
}
string toString() const
{
static string formats[4][2] = {{"double", "%f"}, {"long", "%i"}, {"string", "%s"}, {"int", "%i"}};
string fmt = "%x";
for(int k = 0; k < ArrayRange(formats, 0); k++)
{
if(typename(T) == formats[k][0])
{
fmt = formats[k][1];
break;
}
}
int i, n = ArraySize(data);
string s;
for(i = 0; i < n; i++)
{
s += StringFormat(fmt, data[i]) + ",";
}
return (s);
}
};
template <typename T>
class RubbArray: public BaseArray<T>
{
public:
RubbArray()
{
}
~RubbArray()
{
clear();
}
virtual void clear() override
{
int i, n = ArraySize(data);
for(i = 0; i < n; i++)
{
if(CheckPointer(data[i]) == POINTER_DYNAMIC) delete data[i];
}
ArrayResize(data, 0);
}
T replace(const int i, T v)
{
int n = ArraySize(data);
if(i < n)
{
if(CheckPointer(data[i]) == POINTER_DYNAMIC) delete data[i];
data[i] = v;
}
return v;
}
};
#define List RubbArray
#define Stack RubbArray
+43
View File
@@ -0,0 +1,43 @@
//#define DATETIME_PLACEHOLDER ((datetime)0xFFFFFFFFFFFFFFFF)
class DateTime
{
private:
MqlDateTime mdtstruct;
public:
DateTime(){TimeToStruct(0, mdtstruct);}
DateTime *assign(datetime dt) {TimeToStruct(dt, mdtstruct); return &this;}
int __TimeDayOfWeek() {return mdtstruct.day_of_week;}
int __TimeDayOfYear() {return mdtstruct.day_of_year;}
int __TimeYear() {return mdtstruct.year;}
int __TimeMonth() {return mdtstruct.mon;}
int __TimeDay() {return mdtstruct.day;}
int __TimeHour() {return mdtstruct.hour;}
int __TimeMinute() {return mdtstruct.min;}
int __TimeSeconds() {return mdtstruct.sec;}
};
DateTime _DateTime;
#define TimeDayOfWeek(T) _DateTime.assign(T).__TimeDayOfWeek()
#define TimeYear(T) _DateTime.assign(T).__TimeYear()
#define TimeMonth(T) _DateTime.assign(T).__TimeMonth()
#define TimeDay(T) _DateTime.assign(T).__TimeDay()
#define TimeHour(T) _DateTime.assign(T).__TimeHour()
#define TimeMinute(T) _DateTime.assign(T).__TimeMinute()
#define TimeSeconds(T) _DateTime.assign(T).__TimeSeconds()
#define _TimeYear _DateTime.__TimeYear
#define _TimeMonth _DateTime.__TimeMonth
#define _TimeDay _DateTime.__TimeDay
#define _TimeHour _DateTime.__TimeHour
#define _TimeMinute _DateTime.__TimeMinute
#define _TimeSeconds _DateTime.__TimeSeconds
#define Year _DateTime.assign(TimeCurrent()).__TimeYear
#define Month _DateTime.assign(TimeCurrent()).__TimeMonth
#define Day _DateTime.assign(TimeCurrent()).__TimeDay
#define Hour _DateTime.assign(TimeCurrent()).__TimeHour
#define Minute _DateTime.assign(TimeCurrent()).__TimeMinute
#define Seconds _DateTime.assign(TimeCurrent()).__TimeSeconds
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
// header
"isindex",
"base",
"meta",
"link",
"nextid",
"range",
// elsewhere
"img",
"br",
"hr",
"frame",
"wbr",
"basefont",
"spacer",
"area",
"param",
"keygen",
"col",
"limittext"
+151
View File
@@ -0,0 +1,151 @@
//+------------------------------------------------------------------+
//| CSVcube.mqh |
//| Copyright (c) 2019, Marketeer |
//| https://www.mql5.com/en/users/marketeer |
//| Online Analytical Processing of trading hypercubes |
//| https://www.mql5.com/ru/articles/6602 |
//| https://www.mql5.com/ru/articles/6603 |
//+------------------------------------------------------------------+
#include <Marketeer/CSVReader.mqh>
#include <Marketeer/CSVcolumns.mqh>
template<typename T>
class CSVTradeRecord: public T // TradeRecord
{
public:
CSVTradeRecord(const double balance, const string symbol, const IndexMap *row)
{
const int add = row.getSize() == 13 ? 2 : 0;
set(FIELD_NUMBER, counter);
set(FIELD_TICKET, counter++);
set(FIELD_SYMBOL, symbols.add(symbol));
string t = row[CSV_COLUMN_TYPE].get<string>();
StringToLower(t);
const int _type = t == "buy" ? +1 : (t == "sell" ? -1 : 0);
set(FIELD_TYPE, _type == +1 ? OP_BUY : (_type == -1 ? OP_SELL : OP_BALANCE));
datetime time1 = StringToTime(row[CSV_COLUMN_TIME1].get<string>()) + TimeShift;
datetime time2 = StringToTime(row[CSV_COLUMN_TIME2 + add].get<string>()) + TimeShift;
set(FIELD_DATETIME1, time1);
set(FIELD_DATETIME2, time2);
set(FIELD_DURATION, time2 - time1);
double price1 = StringToDouble(row[CSV_COLUMN_PRICE1].get<string>());
double price2 = StringToDouble(row[CSV_COLUMN_PRICE2 + add].get<string>());
set(FIELD_PRICE1, price1);
set(FIELD_PRICE2, price2);
set(FIELD_MAGIC, 0);
magics.add(0);
set(FIELD_LOT, StringToDouble(row[CSV_COLUMN_VOLUME].get<string>()));
t = row[CSV_COLUMN_PROFIT + add].get<string>();
StringReplace(t, " ", "");
const double profit = StringToDouble(t);
set(FIELD_PROFIT_AMOUNT, profit);
set(FIELD_PROFIT_PERCENT, (profit / balance));
set(FIELD_PROFIT_POINT, (_type * (price2 - price1) / SymbolInfoDouble(symbol, SYMBOL_POINT)));
set(FIELD_COMMISSION, StringToDouble(row[CSV_COLUMN_COMMISSION + add].get<string>()));
set(FIELD_SWAP, StringToDouble(row[CSV_COLUMN_SWAP + add].get<string>()));
fillCustomFields();
}
};
template<typename T>
class CSVReportAdapter: public DataAdapter
{
private:
RubbArray<CSVTradeRecord<T> *> trades;
int cursor;
int size;
double balance;
IndexMap *data;
void reset()
{
size = 0;
cursor = 0;
balance = 0;
if(CheckPointer(data) == POINTER_DYNAMIC) delete data;
}
public:
CSVReportAdapter()
{
reset();
TradeRecord::reset();
}
~CSVReportAdapter()
{
if(CheckPointer(data) == POINTER_DYNAMIC) delete data;
}
bool load(const string file)
{
reset();
data = CSVConverter::ReadCSV(file);
if(data != NULL)
{
size = generate();
Print(data.getSize(), " records transferred to ", size, " trades");
}
return data != NULL;
}
virtual int reservedSize() override
{
return size;
}
virtual Record *getNext() override
{
if(cursor < size)
{
return trades[cursor++];
}
return NULL;
}
protected:
int generate()
{
int count = 0;
balance = 0;
for(int i = data.getSize() - 1; i >= 0; --i) // csv-files have reverse chronological order
{
IndexMap *row = data[i];
const int add = row.getSize() == 13 ? 2 : 0;
string s = row[CSV_COLUMN_SYMBOL].get<string>();
StringTrimLeft(s);
if(StringLen(s) > 0)
{
if(balance == 0)
{
Print("Zero balance, 10000 emulated");
balance = 10000;
}
string real = TradeRecord::realsymbol(s);
if(real == NULL) continue;
trades << new CSVTradeRecord<T>(balance, real, row);
++count;
}
else
{
string type = row[CSV_COLUMN_TYPE].get<string>();
StringToLower(type);
if(type == "balance")
{
string t = row[CSV_COLUMN_PROFIT + add].get<string>();
StringReplace(t, " ", "");
balance += StringToDouble(t);
}
}
}
return count;
}
};
+350
View File
@@ -0,0 +1,350 @@
//+------------------------------------------------------------------+
//| HTMLcube.mqh |
//| Copyright (c) 2019, Marketeer |
//| https://www.mql5.com/en/users/marketeer |
//| Online Analytical Processing of trading hypercubes |
//| https://www.mql5.com/ru/articles/6602 |
//| https://www.mql5.com/ru/articles/6603 |
//+------------------------------------------------------------------+
#include <Marketeer/GroupSettings.mqh>
input GroupSettings Common_Settings; // G E N E R A L S E T T I N G S
input string ReportFile = ""; // · ReportFile
input string Prefix = ""; // · Prefix
input string Suffix = ""; // · Suffix
input int TimeShift = 0; // · TimeShift
#include <Marketeer/WebDataExtractor.mqh>
#include <Marketeer/RubbArray.mqh>
#include <Marketeer/HTMLcolumns.mqh>
template<typename T>
class HTMLTradeRecord: public T // TradeRecord
{
public:
HTMLTradeRecord(
const double balance,
const long ticket,
const string symbol,
const int type,
const datetime time1,
const datetime time2,
const double price1,
const double price2,
const double lot,
const double profit,
const double commission,
const double swap)
{
set(FIELD_NUMBER, counter++);
set(FIELD_TICKET, ticket);
set(FIELD_SYMBOL, symbols.add(symbol));
set(FIELD_TYPE, type);
set(FIELD_DATETIME1, time1);
set(FIELD_DATETIME2, time2);
set(FIELD_DURATION, time2 - time1);
set(FIELD_PRICE1, (float)price1);
set(FIELD_PRICE2, (float)price2);
set(FIELD_MAGIC, 0);
magics.add(0);
set(FIELD_LOT, (float)lot);
set(FIELD_PROFIT_AMOUNT, (float)profit);
set(FIELD_PROFIT_PERCENT, (float)(profit / balance));
set(FIELD_PROFIT_POINT, (float)((type == OP_BUY ? +1 : -1) * (price2 - price1) / SymbolInfoDouble(symbol, SYMBOL_POINT)));
set(FIELD_COMMISSION, (float)commission);
set(FIELD_SWAP, (float)swap);
fillCustomFields(); // calls implementation from T
}
};
template<typename T>
class HTMLReportAdapter: public DataAdapter
{
private:
class Deal // if MQL5 could respect private access specifier for classes,
{ // Trades will be unreachable from outer world, so it would be fine to have
public: // fields made public for direct access from Processor only
datetime time;
double price;
int type; // +1 - buy, -1 - sell
int direction; // +1 - in, -1 - out, 0 - in/out
double volume;
double profit;
long deal;
long order;
string comment;
string symbol;
double commission;
double swap;
public:
Deal(const IndexMap *row) // this is MT5 deal
{
time = StringToTime(row[COLUMN_TIME].get<string>()) + TimeShift;
price = StringToDouble(row[COLUMN_PRICE].get<string>());
string t = row[COLUMN_TYPE].get<string>();
type = t == "buy" ? +1 : (t == "sell" ? -1 : 0);
t = row[COLUMN_DIRECTION].get<string>();
direction = 0;
if(StringFind(t, "in") > -1) ++direction;
if(StringFind(t, "out") > -1) --direction;
volume = StringToDouble(row[COLUMN_VOLUME].get<string>());
t = row[COLUMN_PROFIT].get<string>();
StringReplace(t, " ", "");
profit = StringToDouble(t);
deal = StringToInteger(row[COLUMN_DEAL].get<string>());
order = StringToInteger(row[COLUMN_ORDER].get<string>());
comment = row[COLUMN_COMMENT].get<string>();
symbol = row[COLUMN_SYMBOL].get<string>();
commission = StringToDouble(row[COLUMN_COMISSION].get<string>());
swap = StringToDouble(row[COLUMN_SWAP].get<string>());
}
bool isIn() const
{
return direction >= 0;
}
bool isOut() const
{
return direction <= 0;
}
bool isOpposite(const Deal *t) const
{
return type * t.type < 0;
}
bool isActive() const
{
return volume > 0;
}
int op_type() const
{
if(type == +1) return OP_BUY;
else if(type == -1) return OP_SELL;
return OP_BALANCE;
}
};
RubbArray<Deal *> array;
RubbArray<Deal *> queue;
int size;
int cursor;
double balance;
IndexMap *data;
RubbArray<HTMLTradeRecord<T> *> trades;
protected:
int generate()
{
array.clear();
balance = 0;
for(int i = 0; i < data.getSize(); ++i)
{
IndexMap *row = data[i];
if(CheckPointer(row) == POINTER_INVALID || row.getSize() != COLUMNS_COUNT) return 0; // something is broken
string s = row[COLUMN_SYMBOL].get<string>();
StringTrimLeft(s);
if(StringLen(s) > 0)
{
array << new Deal(row);
}
else if(row[COLUMN_TYPE].get<string>() == "balance")
{
string t = row[COLUMN_PROFIT].get<string>();
StringReplace(t, " ", "");
balance += StringToDouble(t);
}
}
if(balance == 0) balance = 10000; // default, if missing
int count = 0;
// abstract:
// if direction <= 0
// collect all Trades from the queue which have direction >= 0 and opposite type
// if this volume is greater than collected volumes
// reduce volume in this Deal by the total volume of collected Trades
// else if collected volumes are greater than this volume
// reduce volume in matched Trades in a loop until all volume of this Deal is exhausted
// create object-lines from all affected Trades to this Deal
// 'delete' all affected Trades with zero volume from queue
// if volume == 0, 'delete' this Deal (disactivate)
// if direction >= 0 push the new Deal object to the queue
for(int i = 0; i < array.size(); ++i)
{
Deal *current = array[i];
if(!current.isActive()) continue;
string real = TradeRecord::realsymbol(current.symbol);
if(real == NULL) continue;
if(current.isOut())
{
// first try to find exact match
for(int j = 0; j < queue.size(); ++j)
{
if(queue[j].isIn() && queue[j].isOpposite(current) && queue[j].volume == current.volume && queue[j].symbol == current.symbol)
{
trades << new HTMLTradeRecord<T>(
balance,
queue[j].deal,
real, // current.symbol,
queue[j].op_type(),
queue[j].time,
current.time,
queue[j].price,
current.price,
current.volume,
current.profit,
queue[j].commission + current.commission,
current.swap);
balance += current.profit;
current.volume = 0;
queue >> j; // remove from queue
++count;
break;
}
}
if(!current.isActive()) continue;
// second try to perform partial close
for(int j = 0; j < queue.size(); ++j)
{
if(queue[j].isIn() && queue[j].isOpposite(current) && queue[j].symbol == current.symbol)
{
if(current.volume >= queue[j].volume)
{
double fraction = queue[j].volume / current.volume;
trades << new HTMLTradeRecord<T>(
balance,
queue[j].deal,
real, // current.symbol,
queue[j].op_type(),
queue[j].time,
current.time,
queue[j].price,
current.price,
queue[j].volume,
current.profit * fraction,
queue[j].commission + current.commission * fraction,
current.swap * fraction);
balance += current.profit * fraction;
current.volume -= queue[j].volume;
queue[j].volume = 0;
++count;
}
else
{
double fraction = current.volume / queue[j].volume;
trades << new HTMLTradeRecord<T>(
balance,
queue[j].deal,
real, // current.symbol,
queue[j].op_type(),
queue[j].time,
current.time,
queue[j].price,
current.price,
current.volume,
queue[j].profit * fraction, // should be 0
queue[j].commission * fraction + current.commission,
current.swap);
balance += queue[j].profit * fraction;
queue[j].volume -= current.volume;
current.volume = 0;
++count;
break;
}
}
}
// purge all inactive from queue
for(int j = queue.size() - 1; j >= 0; --j)
{
if(!queue[j].isActive())
{
queue >> j;
}
}
}
if(current.isActive()) // is _still_ active
{
if(current.isIn())
{
queue << current;
}
}
}
return count;
}
void reset()
{
cursor = 0;
balance = 0;
if(CheckPointer(data) == POINTER_DYNAMIC) delete data;
}
public:
HTMLReportAdapter()
{
reset();
TradeRecord::reset();
}
~HTMLReportAdapter()
{
if(CheckPointer(data) == POINTER_DYNAMIC) delete data;
((BaseArray<Deal *> *)&queue).clear();
}
bool load(const string file)
{
reset();
data = HTMLConverter::convertReport2Map(file, true);
if(data != NULL)
{
size = generate();
Print(data.getSize(), " deals transferred to ", size, " trades");
}
return data != NULL;
}
virtual int reservedSize() override
{
return size;
}
virtual Record *getNext() override
{
if(cursor < size)
{
return trades[cursor++];
}
return NULL;
}
};
+241
View File
@@ -0,0 +1,241 @@
//+------------------------------------------------------------------+
//| OLAPcore.mqh |
//| Copyright © 2019, Marketeer |
//| https://www.mql5.com/en/users/marketeer |
//| Online Analytical Processing of trading hypercubes |
//| https://www.mql5.com/ru/articles/6602 |
//| https://www.mql5.com/ru/articles/6603 |
//+------------------------------------------------------------------+
#include <OLAP/OLAPcube.mqh>
#include <OLAP/HTMLcube.mqh>
#include <OLAP/CSVcube.mqh>
class DaysRangeSelector: public DateTimeSelector<TRADE_RECORD_FIELDS>
{
protected:
int granulatity;
public:
DaysRangeSelector(const int n): DateTimeSelector<TRADE_RECORD_FIELDS>(FIELD_DURATION, 7), granulatity(n)
{
_typename = typename(this);
}
virtual int getRange() const
{
return granulatity;
}
virtual bool select(const Record *r, int &index) const
{
double d = r.get(selector);
int days = (int)(d / (60 * 60 * 24));
index = MathMin(days, granulatity - 1);
return true;
}
virtual string getLabel(const int index) const
{
return index < granulatity - 1 ? ((index < 10 ? " ": "") + (string)index + "D") : ((string)index + "D+");
}
};
class OLAPWrapper
{
protected:
Selector<TRADE_RECORD_FIELDS> *createSelector(const SELECTORS selector, const TRADE_RECORD_FIELDS field)
{
switch(selector)
{
case SELECTOR_TYPE:
return new TypeSelector();
case SELECTOR_SYMBOL:
return new SymbolSelector();
case SELECTOR_SERIAL:
return new SerialNumberSelector();
case SELECTOR_MAGIC:
return new MagicSelector();
case SELECTOR_PROFITABLE:
return new ProfitableSelector();
case SELECTOR_DURATION:
return new DaysRangeSelector(15); // up to 14 days
case SELECTOR_WEEKDAY:
return field != FIELD_NONE ? new WeekDaySelector(field) : NULL;
case SELECTOR_DAYHOUR:
return field != FIELD_NONE ? new DayHourSelector(field) : NULL;
case SELECTOR_HOURMINUTE:
return field != FIELD_NONE ? new DayHourSelector(field) : NULL;
case SELECTOR_SCALAR:
return field != FIELD_NONE ? new TradeSelector(field) : NULL;
case SELECTOR_QUANTS:
return field != FIELD_NONE ? new QuantizationSelector(field) : NULL;
}
return NULL;
}
public:
void process(
const SELECTORS &selectorArray[], const TRADE_RECORD_FIELDS &selectorField[],
const AGGREGATORS AggregatorType, const TRADE_RECORD_FIELDS AggregatorField, Display &display,
const SORT_BY SortBy = SORT_BY_NONE,
const double Filter1value1 = 0, const double Filter1value2 = 0)
{
int selectorCount = 0;
for(int i = 0; i < MathMin(ArraySize(selectorArray), 3); i++)
{
selectorCount += selectorArray[i] != SELECTOR_NONE;
}
if(selectorCount == 0)
{
Alert("No selectors. Setup at least one of them.");
return;
}
// filter section not used yet >>>
SELECTORS Filter1 = SELECTOR_NONE;
TRADE_RECORD_FIELDS Filter1Field = FIELD_NONE;
if(ArraySize(selectorArray) > 3)
{
Filter1 = selectorArray[3];
}
if(ArraySize(selectorField) > 3)
{
Filter1Field = selectorField[3];
}
// <<< filter section not used
HistoryDataAdapter<CustomTradeRecord> history;
HTMLReportAdapter<CustomTradeRecord> report;
CSVReportAdapter<CustomTradeRecord> external;
DataAdapter *adapter = &history;
if(ReportFile != "")
{
if(StringFind(ReportFile, ".htm") > 0 && report.load(ReportFile))
{
adapter = &report;
}
else
if(StringFind(ReportFile, ".csv") > 0 && external.load(ReportFile))
{
adapter = &external;
}
else
{
Alert("Unknown file format: ", ReportFile);
return;
}
}
else
{
Print("Analyzing account history");
}
Analyst<TRADE_RECORD_FIELDS> *analyst;
Selector<TRADE_RECORD_FIELDS> *selectors[];
ArrayResize(selectors, selectorCount);
for(int i = 0; i < selectorCount; i++)
{
selectors[i] = createSelector(selectorArray[i], selectorField[i]);
if(selectors[i] == NULL)
{
Print("Selector ", i, " is empty. Setup selectors successively (don't leave a hole in-between), specify a field when required");
return;
}
}
// filter section not used yet >>>
Filter<TRADE_RECORD_FIELDS> *filters[];
if(Filter1 != SELECTOR_NONE)
{
ArrayResize(filters, 1);
Selector<TRADE_RECORD_FIELDS> *filterSelector = createSelector(Filter1, Filter1Field);
if(Filter1value1 != Filter1value2)
{
filters[0] = new FilterRange<TRADE_RECORD_FIELDS>(filterSelector, Filter1value1, Filter1value2);
}
else
{
filters[0] = new Filter<TRADE_RECORD_FIELDS>(filterSelector, Filter1value1);
}
}
// <<< filter section not used
Aggregator<TRADE_RECORD_FIELDS> *aggregator;
// MQL does not support a 'class info' metaclass.
// Otherwise we could use an array of classes instead of the switch
switch(AggregatorType)
{
case AGGREGATOR_SUM:
aggregator = new SumAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
break;
case AGGREGATOR_AVERAGE:
aggregator = new AverageAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
break;
case AGGREGATOR_MAX:
aggregator = new MaxAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
break;
case AGGREGATOR_MIN:
aggregator = new MinAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
break;
case AGGREGATOR_COUNT:
aggregator = new CountAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
break;
case AGGREGATOR_PROFITFACTOR:
aggregator = new ProfitFactorAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
break;
case AGGREGATOR_PROGRESSIVE:
aggregator = new ProgressiveTotalAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
break;
case AGGREGATOR_IDENTITY:
aggregator = new IdentityAggregator<TRADE_RECORD_FIELDS>(AggregatorField, selectors, filters);
break;
}
analyst = new Analyst<TRADE_RECORD_FIELDS>(adapter, aggregator, display);
analyst.acquireData();
Print("Symbol number: ", TradeRecord::getSymbolCount());
for(int i = 0; i < TradeRecord::getSymbolCount(); i++)
{
Print(i, "] ", TradeRecord::getSymbol(i));
}
Print("Magic number: ", TradeRecord::getMagicCount());
for(int i = 0; i < TradeRecord::getMagicCount(); i++)
{
Print(i, "] ", TradeRecord::getMagic(i));
}
Print("Filters: ", aggregator.getFilterTitles());
Print("Selectors: ", selectorCount);
analyst.build();
analyst.display(SortBy, AggregatorType == AGGREGATOR_IDENTITY);
delete analyst;
delete aggregator;
for(int i = 0; i < selectorCount; i++)
{
delete selectors[i];
}
for(int i = 0; i < ArraySize(filters); i++)
{
delete filters[i].getSelector();
delete filters[i];
}
}
};
File diff suppressed because it is too large Load Diff
+148
View File
@@ -0,0 +1,148 @@
//+------------------------------------------------------------------+
//| PairArray.mqh |
//| Copyright © 2019, Marketeer |
//| https://www.mql5.com/en/users/marketeer |
//+------------------------------------------------------------------+
class PairArray
{
public:
// aux struct to populate temp array when sorting is enabled
struct Pair
{
double value;
string title;
Pair(): value(DBL_MAX), title(NULL) {}
Pair(const double v, const string s): value(v), title(s) {}
Pair(const string s, const double v): value(v), title(s) {}
bool operator>(const double v) const
{
return value > v;
}
bool operator>(const string s) const
{
return title > s;
}
};
// this is a common parent, so it can not be templatized
class Comparator
{
public:
// templatized method can not be virtual,
// so we do artificial dynamic dispatching manually
// (see below after declaration of descendant classes)
template<typename T>
bool compare(const Pair &v1, const T v2);
};
class Greater: public Comparator
{
public:
template<typename T>
bool compare(const Pair &v1, const T v2)
{
return v1 > v2;
}
};
class Lesser: public Comparator
{
public:
template<typename T>
bool compare(const Pair &v1, const T v2)
{
return !(v1 > v2);
}
};
private:
Comparator *comparator;
public:
// temp array for sorting (if enabled)
Pair array[];
PairArray(): comparator(NULL)
{
}
PairArray(const int reserved, Comparator *c = NULL)
{
comparator = c;
ArrayResize(array, reserved);
}
~PairArray()
{
ArrayResize(array, 0);
if(CheckPointer(comparator) == POINTER_DYNAMIC) delete comparator;
}
void allocate(const int reserved)
{
ArrayResize(array, reserved);
}
void compareBy(Comparator *c)
{
if(CheckPointer(comparator) == POINTER_DYNAMIC) delete comparator;
comparator = c;
}
void move(const int index, const int count)
{
for(int i = count - 1; i >= index; --i)
{
array[i + 1] = array[i];
}
}
template<typename T1, typename T2>
void insert(const int count, const T1 v, const T2 s)
{
Pair p(v, s);
for(int i = 0; i < count; i++)
{
if(comparator != NULL && comparator.compare(array[i], v))
{
move(i, count);
array[i] = p;
return;
}
}
array[count] = p;
}
void convert(double &x[], string &s[]) const
{
int n = ArraySize(array);
ArrayResize(x, n);
ArrayResize(s, n);
for(int i = 0; i < n; i++)
{
x[i] = array[i].value;
s[i] = array[i].title;
}
}
void convert(double &x[]) const
{
int n = ArraySize(array);
ArrayResize(x, n);
for(int i = 0; i < n; i++)
{
x[i] = array[i].value;
}
}
void convert(string &s[]) const
{
int n = ArraySize(array);
ArrayResize(s, n);
for(int i = 0; i < n; i++)
{
s[i] = array[i].title;
}
}
};
+525
View File
@@ -0,0 +1,525 @@
//+------------------------------------------------------------------+
//| Plot.mqh |
//| Copyright (c) 2019, Marketeer |
//| https://www.mql5.com/en/users/marketeer |
//+------------------------------------------------------------------+
#property copyright "Copyright (c) 2019, Marketeer"
#property link "https://www.mql5.com/en/users/marketeer"
#property version "1.0"
#include <Controls\WndClient.mqh>
#include <Graphics\Graphic.mqh>
#include <OLAP/PairArray.mqh>
class CurveSubtitles
{
public:
CCurve *curve;
PairArray *data;
void assign(const CCurve *c, const PairArray *d)
{
curve = (CCurve *)c;
data = (PairArray *)d;
}
};
class CGraphicInPlot: public CGraphic
{
protected:
long m_chart_id; // chart ID
CurveSubtitles curvecache[];
void CGraphicInPlot::Customize(CCurve *c, const int points);
CCurve *CGraphicInPlot::CacheIt(const CCurve *c, const PairArray *data = NULL);
public:
CGraphicInPlot();
~CGraphicInPlot();
virtual bool Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2);
CCurve *CurveAdd(const PairArray *data, ENUM_CURVE_TYPE type, const string name = NULL); // overload
CCurve *CurveAdd(const double &x[], const double &y[], ENUM_CURVE_TYPE type, const string name = NULL); // overload
void CurvesRemoveAll(void);
virtual bool Shift(const int dx, const int dy);
virtual bool Show(void);
virtual bool Hide(void);
void Destroy(void);
void ResetColors(void);
CCurve *CurveDetach(const int index);
bool CurveAttach(CCurve *curve);
int getIndexInCache(CCurve *c);
void replaceInCache(const int index, CCurve *c);
int cacheSize() const
{
return ArraySize(curvecache);
}
const CurveSubtitles *cacheItem(const int index) const
{
return &curvecache[index];
}
void InitXAxis(const bool custom);
void InitYAxis(const bool custom);
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
string CustomDoubleToStringFunction(double value, void *ptr)
{
CGraphicInPlot *self = dynamic_cast<CGraphicInPlot *>(ptr);
if(self != NULL)
{
if(self.cacheSize() > 0)
{
const int index = (int)value;
if(MathAbs(((double)index) - value) <= DBL_EPSILON)
{
const CurveSubtitles *s = self.cacheItem(0);
if(index < 0 || index >= ArraySize(s.data.array)) return NULL; // (string)(float)value; // debug
return s.data.array[index].title;
}
}
}
return NULL;
}
CGraphicInPlot::CGraphicInPlot()
{
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CGraphicInPlot::~CGraphicInPlot()
{
CurvesRemoveAll();
}
void CGraphicInPlot::Destroy(void)
{
m_generator.Reset();
m_canvas.Destroy();
}
void CGraphicInPlot::ResetColors(void)
{
m_generator.Reset();
}
/* TODO: enable this to support Y marks customization
class AxisCustomizer
{
public:
const bool Y; // true for Y, false for X (default)
const CGraphicInPlot *parent;
AxisCustomizer(const bool axisY, CGraphicInPlot *p): Y(axisY), parent(p) {}
};
*/
void CGraphicInPlot::InitXAxis(const bool custom)
{
if(custom)
{
m_x.Type(AXIS_TYPE_CUSTOM);
m_x.ValuesFunctionFormat(CustomDoubleToStringFunction);
m_x.ValuesFunctionFormatCBData(&this);
}
else
{
m_x.Type(AXIS_TYPE_DOUBLE);
}
}
void CGraphicInPlot::InitYAxis(const bool custom)
{
if(custom)
{
m_y.Type(AXIS_TYPE_CUSTOM);
m_y.ValuesFunctionFormat(CustomDoubleToStringFunction);
m_y.ValuesFunctionFormatCBData(&this);
}
else
{
m_y.Type(AXIS_TYPE_DOUBLE);
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CGraphicInPlot::Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2)
{
if(!CGraphic::Create(chart, name, subwin, x1, y1, x2, y2)) return false;
m_chart_id = chart;
return true;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CGraphicInPlot::Show(void)
{
string obj_name = ChartObjectName();
if(obj_name == NULL || ObjectFind(m_chart_id, obj_name) < 0) return false;
if(!ObjectSetInteger(m_chart_id, obj_name, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS)) return false;
Update(false);
return true;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CGraphicInPlot::Hide(void)
{
string obj_name = ChartObjectName();
if(obj_name == NULL || ObjectFind(m_chart_id, obj_name) < 0) return false;
return ObjectSetInteger(m_chart_id, obj_name, OBJPROP_TIMEFRAMES, OBJ_NO_PERIODS);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CGraphicInPlot::Shift(const int dx, const int dy)
{
string obj_name = ChartObjectName();
if(obj_name == NULL || ObjectFind(m_chart_id, obj_name) < 0) return false;
int x = (int)ObjectGetInteger(m_chart_id, obj_name, OBJPROP_XDISTANCE) + dx;
int y = (int)ObjectGetInteger(m_chart_id, obj_name, OBJPROP_YDISTANCE) + dy;
if(!ObjectSetInteger(m_chart_id, obj_name, OBJPROP_XDISTANCE, x)) return false;
if(!ObjectSetInteger(m_chart_id, obj_name, OBJPROP_YDISTANCE, y)) return false;
return true;
}
CCurve *CGraphicInPlot::CurveDetach(const int index)
{
return m_arr_curves.Detach(index);
}
bool CGraphicInPlot::CurveAttach(CCurve *curve)
{
return m_arr_curves.Add(curve);
}
void CGraphicInPlot::Customize(CCurve *c, const int points)
{
int w = MathMax(Width() / points / 4, 1);
c.HistogramWidth(w);
c.LinesWidth(3);
c.PointsFill(true);
}
CCurve *CGraphicInPlot::CacheIt(const CCurve *c, const PairArray *data = NULL)
{
int n = ArraySize(curvecache);
ArrayResize(curvecache, n + 1);
curvecache[n].assign(c, data);
return (CCurve *)c;
}
CCurve *CGraphicInPlot::CurveAdd(const PairArray *data, ENUM_CURVE_TYPE type, const string name = NULL)
{
double y[];
string s[];
data.convert(y, s);
CCurve *c = CGraphic::CurveAdd(y, type, name);
Customize(c, ArraySize(y));
return CacheIt(c, data);
}
CCurve *CGraphicInPlot::CurveAdd(const double &x[], const double &y[], ENUM_CURVE_TYPE type, const string name = NULL)
{
CCurve *c = CGraphic::CurveAdd(x, y, type, name);
Customize(c, ArraySize(x));
return CacheIt(c);
}
int CGraphicInPlot::getIndexInCache(CCurve *c)
{
int n = ArraySize(curvecache);
for(int i = 0; i < n; i++)
{
if(curvecache[i].curve == c) return i;
}
return -1;
}
void CGraphicInPlot::replaceInCache(const int index, CCurve *c)
{
curvecache[index].curve = c;
}
void CGraphicInPlot::CurvesRemoveAll(void)
{
int n = m_arr_curves.Total();
for(int i = n - 1; i >= 0; i--)
{
CurveRemoveByIndex(i);
}
n = ArraySize(curvecache);
for(int i = n - 1; i >= 0; i--)
{
if(CheckPointer(curvecache[i].data) == POINTER_DYNAMIC) delete curvecache[i].data;
}
ArrayResize(curvecache, 0);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class CPlot: public CWndClient
{
private:
CGraphicInPlot *m_graphic;
ENUM_CURVE_TYPE type;
uint i_text_color;
CCurve *temp[];
public:
CPlot();
~CPlot();
bool Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2, const ENUM_CURVE_TYPE t = CURVE_HISTOGRAM);
virtual void Destroy(const int reason = 0) override;
bool Refresh(const bool enforce = false);
bool SetTextColor(color value);
virtual bool Shift(const int dx, const int dy) override;
virtual bool Show(void);
virtual bool Hide(void);
bool Resize(const int x1, const int y1, const int x2, const int y2);
CCurve *CurveAdd(const PairArray *data, const string name = NULL);
CCurve *CurveAdd(const double &x[], const double &y[], const string name = NULL);
void CurvesRemoveAll(void);
void SetDefaultCurveType(ENUM_CURVE_TYPE t)
{
type = t;
}
void InitXAxis(const bool custom)
{
if(CheckPointer(m_graphic) != POINTER_INVALID)
{
m_graphic.InitXAxis(custom);
}
}
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CPlot::CPlot():
type(CURVE_HISTOGRAM), i_text_color(ColorToARGB(clrBlack, 255))
{
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
CPlot::~CPlot()
{
if(CheckPointer(m_graphic) != POINTER_INVALID)
{
m_graphic.Destroy();
delete m_graphic;
}
}
void CPlot::Destroy(const int reason = 0)
{
if(CheckPointer(m_graphic) != POINTER_INVALID)
{
m_graphic.Destroy();
delete m_graphic;
m_graphic = NULL;
}
CWndClient::Destroy(reason);
}
CCurve *CPlot::CurveAdd(const PairArray *data, const string name = NULL)
{
if(CheckPointer(m_graphic) == POINTER_INVALID) return NULL;
return m_graphic.CurveAdd(data, type, name);
}
CCurve *CPlot::CurveAdd(const double &x[], const double &y[], const string name = NULL)
{
if(CheckPointer(m_graphic) == POINTER_INVALID) return NULL;
return m_graphic.CurveAdd(x, y, type, name);
}
void CPlot::CurvesRemoveAll(void)
{
m_graphic.CurvesRemoveAll();
m_graphic.ResetColors();
}
bool CPlot::Resize(const int x1, const int y1, const int x2, const int y2)
{
if(CheckPointer(m_graphic) == POINTER_INVALID) return false;
int width = Width();
int height = Height();
Size(x2 - x1, y2 - y1);
string obj_name = m_name + "_0_0";
int obj_x1 = m_rect.left;
int obj_x2 = obj_x1 + width;
int obj_y1 = m_rect.top;
int obj_y2 = obj_y1 + height;
m_graphic.Destroy();
if(!m_graphic.Create(m_chart_id, obj_name, m_subwin, obj_x1, obj_y1, obj_x2, obj_y2))
{
Print(GetLastError());
return false;
}
for(int i = 0; i < ArraySize(temp); ++i)
{
if(CheckPointer(temp[i]) == POINTER_DYNAMIC) delete temp[i];
}
ArrayResize(temp, m_graphic.CurvesTotal());
// Graphic library does not provide a method to update curve without array copying
for(int i = m_graphic.CurvesTotal() - 1; i >= 0; --i)
{
temp[i] = m_graphic.CurveDetach(i);
}
return true;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CPlot::Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2, const ENUM_CURVE_TYPE t = CURVE_HISTOGRAM)
{
if(!CWndClient::Create(chart, name, subwin, x1, y1, x2, y2)) return false;
type = t;
int width = Width();
int height = Height();
string obj_name = m_name + "_0_0";
int obj_x1 = m_rect.left;
int obj_x2 = obj_x1 + width;
int obj_y1 = m_rect.top;
int obj_y2 = obj_y1 + height;
m_graphic = new CGraphicInPlot();
if(CheckPointer(m_graphic) == POINTER_INVALID) return false;
if(!m_graphic.Create(m_chart_id, obj_name, m_subwin, obj_x1, obj_y1, obj_x2, obj_y2)) return false;
return true;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CPlot::Hide(void)
{
if(CheckPointer(m_graphic) == POINTER_INVALID) return false;
if(!m_graphic.Hide()) return false;
return CWndClient::Hide();
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CPlot::Show(void)
{
if(!CWndClient::Show()) return false;
if(CheckPointer(m_graphic) == POINTER_INVALID) return false;
if(m_graphic.Show()) return false;
return true;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CPlot::Shift(const int dx, const int dy)
{
if(CheckPointer(m_graphic) == POINTER_INVALID) return false;
if(!m_graphic.Shift(dx, dy)) return false;
return CWndClient::Shift(dx, dy);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CPlot::Refresh(const bool enforce = false)
{
if(CheckPointer(m_graphic) == POINTER_INVALID) return false;
if(ArraySize(temp) == 0 && enforce)
{
m_graphic.ResetColors();
ArrayResize(temp, m_graphic.CurvesTotal());
for(int i = m_graphic.CurvesTotal() - 1; i >= 0; --i)
{
temp[i] = m_graphic.CurveDetach(i);
}
}
for(int i = 0; i < ArraySize(temp); ++i)
{
if(CheckPointer(temp[i]) == POINTER_DYNAMIC)
{
double x[], y[];
temp[i].GetX(x);
temp[i].GetY(y);
string name = temp[i].Name();
int index = m_graphic.getIndexInCache(temp[i]);
delete temp[i];
CCurve *curve = NULL;
if(ArraySize(x) > 0)
{
if(ArraySize(y) > 0)
{
curve = m_graphic.CurveAdd(x, y, type, name);
}
else
{
curve = m_graphic.CurveAdd(x, type, name);
}
}
m_graphic.replaceInCache(index, curve);
// axis does not yet calculated, it's done only during CurvePlotAll
// so we can't automatically adjust histogram width
// double range = (m_graphic.XAxis().Max() - m_graphic.XAxis().Min());
// double data = (x[ArrayMaximum(x)] - x[ArrayMinimum(x)]);
// int downsize = (int)(range / data);
curve.HistogramWidth(Width() / ArraySize(x) / 4);
curve.LinesWidth(3);
}
}
ArrayResize(temp, 0);
if(!m_graphic.CurvePlotAll()) return false;
m_graphic.Update(false);
ChartRedraw(m_chart_id);
return true;
}