ダイアログを使用して取引支援ツールを作る(3)
前回のつづき。今回で一応の区切り。
少しトレード支援っぽい事を行う。
具体的には、保有しているポジションの合計損益(額, Pips)の表示と、全決済機能を実装する。
全決済はボタンで行わせようと思っていて、これは前回までのコードで既に記述できそうなため、損益の表示からコーディングする。
テキストの表示には、どのコンポーネントを使っても良いのだが、最もそれっぽいCLabelよりもCEditの使用を推奨する。
CLabelはテキストが常に左寄せで表示され、これを操作することができない。
サイズと位置を調整すれば良いのだが、文字数も影響するので非常に面倒くさい。というか損益など文字数が変化する場合においては現実的に無理なレベルかもしれない。
CEditは文字揃えの指定が可能なので、意図通りのレイアウトを組むのが容易だ。
CEditはテキスト入力用のコンポーネントだが、ReadOnlyにして入力を制限する。
また、入力欄に背景色と枠線色が付いているので、これをダイアログの背景色と同一にして、見た目上ただのテキストにする。
※デフォルトのダイアログの背景色(CWndClientの背景色)はRGB(247,247,247)。
では、実際にここまで書く。
損益額については、口座通貨がJPYなら小数点以下切り捨て、JPY以外ならよく分からないので小数点以下第2位まで表示する。
#property copyright "nisai"
#property link "https://nisaifx.com"
#property version "1.00"
#include <Trade/PositionInfo.mqh>
#include <Controls/Dialog.mqh>
#include <Controls/Edit.mqh>
class MyDialog:public CAppDialog{
private:
CEdit profitTitle; //”損益” テキスト表示欄
CEdit profitPipsTitle; //”損益(Pips)” テキスト表示欄
CEdit profitValue; //損益の数値表示欄
CEdit profitPipsValue; //損益(Pips)の数値表示欄
public:
virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
void Redraw(void); //損益更新用の関数
protected:
bool CreateProfitTitle(void);
bool CreateProfitPipsTitle(void);
bool CreateProfitValue(void);
bool CreateProfitPipsValue(void);
bool SetProfitValue(void); //損益をCEditにセットする関数
bool SetProfitPipsValue(void); //損益(Pips)をCEditにセットする関数
};
bool MyDialog::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2){
if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
return(false);
if(!CreateProfitTitle())
return(false);
if(!CreateProfitPipsTitle())
return(false);
if(!CreateProfitValue())
return(false);
if(!CreateProfitPipsValue())
return(false);
return(true);
}
bool MyDialog::CreateProfitTitle(void){
if(!profitTitle.Create(m_chart_id, m_name+"ProfitTitle", m_subwin, 10, 10, 80, 40))
return(false);
if(!profitTitle.ReadOnly(true))
return(false);
if(!profitTitle.Text("損益"))
return(false);
if(!profitTitle.ColorBackground(C'247,247,247'))
return(false);
if(!profitTitle.ColorBorder(C'247,247,247'))
return(false);
if(!Add(profitTitle))
return(false);
return(true);
}
bool MyDialog::CreateProfitValue(void){
if(!profitValue.Create(m_chart_id, m_name+"ProfitValue", m_subwin, 110, 10, Width()-20, 40))
return(false);
if(!profitValue.TextAlign(ALIGN_RIGHT))
return(false);
if(!profitValue.ReadOnly(true))
return(false);
if(!profitValue.ColorBackground(C'247,247,247'))
return(false);
if(!profitValue.ColorBorder(C'247,247,247'))
return(false);
if(!Add(profitValue))
return(false);
SetProfitValue();
return(true);
}
bool MyDialog::CreateProfitPipsTitle(void){
if(!profitPipsTitle.Create(m_chart_id, m_name+"ProfitPipsTitle", m_subwin, 10, 70, 80, 40))
return(false);
if(!profitPipsTitle.ReadOnly(true))
return(false);
if(!profitPipsTitle.ColorBackground(C'247,247,247'))
return(false);
if(!profitPipsTitle.ColorBorder(C'247,247,247'))
return(false);
if(!profitPipsTitle.Text("損益(Pips)"))
return(false);
if(!Add(profitPipsTitle))
return(false);
return(true);
}
bool MyDialog::CreateProfitPipsValue(void){
if(!profitPipsValue.Create(m_chart_id, m_name+"ProfitPipsValue", m_subwin, 110, 70, Width()-20, 40))
return(false);
if(!profitPipsValue.TextAlign(ALIGN_RIGHT))
return(false);
if(!profitPipsValue.ReadOnly(true))
return(false);
if(!profitPipsValue.ColorBackground(C'247,247,247'))
return(false);
if(!profitPipsValue.ColorBorder(C'247,247,247'))
return(false);
if(!Add(profitPipsValue))
return(false);
SetProfitPipsValue();
return(true);
}
bool MyDialog::SetProfitValue(void){
string profit=GetProfit();
if(profit=="0")
profitValue.Color(C'59,41,40'); //損益が0なら文字色は黒(デフォルト)
else if(StringToDouble(profit)>0)
profitValue.Color(clrBlue); //損益が正なら文字色は青
else
profitValue.Color(clrRed); //損益が負なら文字色は赤
if(!profitValue.Text(profit))
return(false);
return(true);
}
bool MyDialog::SetProfitPipsValue(void){
string profit=GetProfitPips();
if(profit=="0")
profitPipsValue.Color(C'59,41,40'); //損益が0なら文字色は黒(デフォルト)
else if(StringToDouble(profit)>0)
profitPipsValue.Color(clrBlue); //損益が正なら文字色は青
else
profitPipsValue.Color(clrRed); //損益が負なら文字色は赤
if(!profitPipsValue.Text(profit))
return(false);
return(true);
}
//再描画する関数
void MyDialog::Redraw(void){
SetProfitValue(); //損益を再計算してセット
SetProfitPipsValue(); //損益(Pips)を再計算してセット
}
MyDialog dialog;
CPositionInfo cPositionInfo;
int OnInit(){
if(!dialog.Create(0, "Dialog", 0, 20, 20, 300, 300))
return(INIT_FAILED);
if(!dialog.Run())
return(INIT_FAILED);
return(INIT_SUCCEEDED);
}
void OnTick(){
//Tickが発生する度に再描画する
dialog.Redraw();
}
void OnChartEvent(const int id,
const long& lparam,
const double& dparam,
const string& sparam){
dialog.ChartEvent(id,lparam,dparam,sparam);
}
void OnDeinit(const int reason){
dialog.Destroy(reason);
}
//損益の取得
string GetProfit(){
double profit=0;
for(int i=0;i<PositionsTotal();i++){
if(cPositionInfo.SelectByIndex(i)){
if(cPositionInfo.Symbol()==Symbol()){
profit+=cPositionInfo.Profit();
}
}
}
if(profit==0)
return "0";
if(AccountInfoString(ACCOUNT_CURRENCY)=="JPY"){
return IntegerToString((int)profit);
}else{
return StringSubstr(DoubleToString(profit),0,StringFind(DoubleToString(profit),".")+3);
}
}
//損益(Pips)の取得
string GetProfitPips(){
double total=0;
double lots=0;
for(int i=0;i<PositionsTotal();i++){
if(cPositionInfo.SelectByIndex(i)){
if(cPositionInfo.Symbol()==Symbol()){
total+=cPositionInfo.PriceOpen()*cPositionInfo.Volume();
lots+=cPositionInfo.Volume();
}
}
}
if(total==0)
return "0";
double pips=NormalizeDouble((SymbolInfoDouble(Symbol(),SYMBOL_BID)-total/lots)*MathPow(10,Digits())/10,1);
return StringSubstr(DoubleToString(pips),0,StringFind(DoubleToString(pips),".")+2);
}
冗長な記述がある。申し訳ない。
損益と損益(Pips)を計算し、文字列で返す関数はMyDialogの外に定義した。
MyDialogのpublicなメンバー関数としてRedrawを用意し、OnTickで呼ぶようにし、ティック発生事に損益の表示を更新させる。
損益(Pips)はポジションを合計した平均レートから算出している。
ただし、買いポジションと売りポジションを同時に保有した場合は、正しい値にはならない(SとLのポジションそれぞれでの平均レートを求めて合算させようと思って書いたがコレジャナイ感があり、SとLの保有数量が異なる場合もあるなあと思ったらなんか面倒くさそうなので辞めてしまった)。
実行する。

ポジションを取る。

複数のポジションを取った場合は、合計値が表示される。
含み益になると、文字が青くなる(はず)。
次に、全決済ボタンを用意する。
下記ハイライト箇所を追記。
#property copyright "nisai"
#property link "https://nisaifx.com"
#property version "1.00"
#include <Trade/Trade.mqh>
#include <Trade/PositionInfo.mqh>
#include <Controls/Dialog.mqh>
#include <Controls/Edit.mqh>
#include <Controls/Button.mqh>
class MyDialog:public CAppDialog{
private:
CEdit profitTitle;
CEdit profitPipsTitle;
CEdit profitValue;
CEdit profitPipsValue;
CButton closeAllButton;
public:
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 OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
void Redraw(void);
protected:
bool CreateProfitTitle(void);
bool CreateProfitPipsTitle(void);
bool CreateProfitValue(void);
bool CreateProfitPipsValue(void);
bool CreateCloseAllButton(void);
bool SetProfitValue(void);
bool SetProfitPipsValue(void);
void OnClickCloseAllButton(void);
};
EVENT_MAP_BEGIN(MyDialog)
ON_EVENT(ON_CLICK,closeAllButton,OnClickCloseAllButton)
EVENT_MAP_END(CAppDialog)
bool MyDialog::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2){
if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
return(false);
if(!CreateProfitTitle())
return(false);
if(!CreateProfitPipsTitle())
return(false);
if(!CreateProfitValue())
return(false);
if(!CreateProfitPipsValue())
return(false);
if(!CreateCloseAllButton())
return(false);
return(true);
}
bool MyDialog::CreateProfitTitle(void){
if(!profitTitle.Create(m_chart_id, m_name+"ProfitTitle", m_subwin, 10, 10, 80, 40))
return(false);
if(!profitTitle.ReadOnly(true))
return(false);
if(!profitTitle.Text("損益"))
return(false);
if(!profitTitle.ColorBackground(C'247,247,247'))
return(false);
if(!profitTitle.ColorBorder(C'247,247,247'))
return(false);
if(!Add(profitTitle))
return(false);
return(true);
}
bool MyDialog::CreateProfitValue(void){
if(!profitValue.Create(m_chart_id, m_name+"ProfitValue", m_subwin, 110, 10, Width()-20, 40))
return(false);
if(!profitValue.TextAlign(ALIGN_RIGHT))
return(false);
if(!profitValue.ReadOnly(true))
return(false);
if(!profitValue.ColorBackground(C'247,247,247'))
return(false);
if(!profitValue.ColorBorder(C'247,247,247'))
return(false);
if(!Add(profitValue))
return(false);
SetProfitValue();
return(true);
}
bool MyDialog::CreateProfitPipsTitle(void){
if(!profitPipsTitle.Create(m_chart_id, m_name+"ProfitPipsTitle", m_subwin, 10, 70, 80, 40))
return(false);
if(!profitPipsTitle.ReadOnly(true))
return(false);
if(!profitPipsTitle.ColorBackground(C'247,247,247'))
return(false);
if(!profitPipsTitle.ColorBorder(C'247,247,247'))
return(false);
if(!profitPipsTitle.Text("損益(Pips)"))
return(false);
if(!Add(profitPipsTitle))
return(false);
return(true);
}
bool MyDialog::CreateProfitPipsValue(void){
if(!profitPipsValue.Create(m_chart_id, m_name+"ProfitPipsValue", m_subwin, 110, 70, Width()-20, 40))
return(false);
if(!profitPipsValue.TextAlign(ALIGN_RIGHT))
return(false);
if(!profitPipsValue.ReadOnly(true))
return(false);
if(!profitPipsValue.ColorBackground(C'247,247,247'))
return(false);
if(!profitPipsValue.ColorBorder(C'247,247,247'))
return(false);
if(!Add(profitPipsValue))
return(false);
SetProfitPipsValue();
return(true);
}
bool MyDialog::CreateCloseAllButton(void){
if(!closeAllButton.Create(m_chart_id, m_name+"CloseAllButton", m_subwin, 50, 80, Width()-60, 120))
return(false);
Print(closeAllButton.Left());
Print(closeAllButton.Right());
if(!closeAllButton.ColorBackground(clrRed))
return(false);
if(!closeAllButton.ColorBorder(clrRed))
return(false);
if(!closeAllButton.Color(clrWhite))
return(false);
if(!closeAllButton.Text("全決済"))
return(false);
if(!Add(closeAllButton))
return(false);
return(true);
}
bool MyDialog::SetProfitValue(void){
string profit=GetProfit();
if(profit=="0")
profitValue.Color(C'59,41,40'); //損益が0なら文字色は黒(デフォルト)
else if(StringToDouble(profit)>0)
profitValue.Color(clrBlue); //損益が正なら文字色は青
else
profitValue.Color(clrRed); //損益が負なら文字色は赤
if(!profitValue.Text(profit))
return(false);
return(true);
}
bool MyDialog::SetProfitPipsValue(void){
string profit=GetProfitPips();
if(profit=="0")
profitPipsValue.Color(C'59,41,40'); //損益が0なら文字色は黒(デフォルト)
else if(StringToDouble(profit)>0)
profitPipsValue.Color(clrBlue); //損益が正なら文字色は青
else
profitPipsValue.Color(clrRed); //損益が負なら文字色は赤
if(!profitPipsValue.Text(profit))
return(false);
return(true);
}
void MyDialog::Redraw(void){
SetProfitValue();
SetProfitPipsValue();
}
void MyDialog::OnClickCloseAllButton(void){
closeAll();
Redraw();
}
MyDialog dialog;
CTrade cTrade;
CPositionInfo cPositionInfo;
int OnInit(){
if(!dialog.Create(0, "Dialog", 0, 20, 20, 300, 300))
return(INIT_FAILED);
if(!dialog.Run())
return(INIT_FAILED);
return(INIT_SUCCEEDED);
}
void OnTick(){
dialog.Redraw();
}
void OnChartEvent(const int id,
const long& lparam,
const double& dparam,
const string& sparam){
dialog.ChartEvent(id,lparam,dparam,sparam);
}
void OnDeinit(const int reason){
dialog.Destroy(reason);
}
string GetProfit(){
double profit=0;
for(int i=0;i<PositionsTotal();i++){
if(cPositionInfo.SelectByIndex(i)){
if(cPositionInfo.Symbol()==Symbol()){
profit+=cPositionInfo.Profit();
}
}
}
if(profit==0)
return "0";
if(AccountInfoString(ACCOUNT_CURRENCY)=="JPY"){
return IntegerToString((int)profit);
}else{
return StringSubstr(DoubleToString(profit),0,StringFind(DoubleToString(profit),".")+3);
}
}
string GetProfitPips(){
double total=0;
double lots=0;
for(int i=0;i<PositionsTotal();i++){
if(cPositionInfo.SelectByIndex(i)){
if(cPositionInfo.Symbol()==Symbol()){
total+=cPositionInfo.PriceOpen()*cPositionInfo.Volume();
lots+=cPositionInfo.Volume();
}
}
}
if(total==0)
return "0";
double pips=NormalizeDouble((SymbolInfoDouble(Symbol(),SYMBOL_BID)-total/lots)*MathPow(10,Digits())/10,1);
return StringSubstr(DoubleToString(pips),0,StringFind(DoubleToString(pips),".")+2);
}
void closeAll(){
int total=PositionsTotal();
for(int i=total-1;i>=0;i--){
if(cPositionInfo.SelectByIndex(i)){
if(cPositionInfo.Symbol()==Symbol() && (cPositionInfo.PositionType()==POSITION_TYPE_BUY || cPositionInfo.PositionType()==POSITION_TYPE_SELL)){
cTrade.PositionClose(cPositionInfo.Ticket());
}
}
}
}
実行する。

全決済ボタンをクリックすると、すべてのポジションが決済された。
簡単だが以上で終わる。
前々回からの内容を踏まえれば、複雑なポジション操作を行う支援ツールも作成できると思う。