Add files via upload
This commit is contained in:
@@ -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;
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -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);
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
enum GroupSettings {};
|
||||
@@ -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
|
||||
@@ -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>();
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
@@ -0,0 +1,20 @@
|
||||
// header
|
||||
"isindex",
|
||||
"base",
|
||||
"meta",
|
||||
"link",
|
||||
"nextid",
|
||||
"range",
|
||||
// elsewhere
|
||||
"img",
|
||||
"br",
|
||||
"hr",
|
||||
"frame",
|
||||
"wbr",
|
||||
"basefont",
|
||||
"spacer",
|
||||
"area",
|
||||
"param",
|
||||
"keygen",
|
||||
"col",
|
||||
"limittext"
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user