目的
MetaTrader5でいい感じのトレードパネルを作る。
開発環境
- Microsoft Visual Studio Community 2019
- Version 16.11.9
- Microsoft .NET Framework Version 4.8.09032
データのやりとりの仕組み
ここからの 引用です。
MQL側と.net側のコントローラ(DLL)を介して,
WinFormsとヨロシクやってくれるいい感じのやつです。
一つ問題があるとすると,コントローラで定義されているものしか,
WinFormsのコントローラとして使えないところでしょうか。
なので,DataGridViewみたいのは,コントローラ側(真ん中)で定義されていないので,
自分でDLLを改良する必要があります。
C#側
ボタンを配置したり,TextBoxやらComboBoxを適当に配置するだけです。
イベントの割り当ては,コントローラ(DLL)で勝手にやってくれます。
余計なものもありますが,とりあえずコードを全部・・・。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TradePanel
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void txtProfit_TextChanged(object sender, EventArgs e)
{
if (double.Parse(txtProfit.Text) > 0.0)
{
allSettlement.BackColor = Color.FromArgb(192, 192, 255);
}
else if (double.Parse(txtProfit.Text) < 0.0)
{
allSettlement.BackColor = Color.FromArgb(255, 128, 128);
}
else
{
allSettlement.BackColor = btnSelClose.BackColor;
}
txtProfit.Text = String.Format("{0:#,0}", double.Parse(txtProfit.Text));
}
private void txtBox_TextChanged(object sender, EventArgs e)
{
TextBox text = (TextBox)sender;
text.Text = String.Format("{0:#,0}", double.Parse(text.Text));
}
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
CheckBox checkBox = (CheckBox)sender;
if (checkBox.Checked)
{
this.TopMost = true;
}
else
{
this.TopMost = false;
}
}
private void Form1_Load(object sender, EventArgs e)
{
CheckBox checkBox = checkBox1;
if (checkBox.Checked)
{
this.TopMost = true;
}
else
{
this.TopMost = false;
}
}
private void txtChangeBox_TextChanged(object sender, EventArgs e)
{
// DataGridView初期化(データクリア)
//dataGridView1.Columns.Clear();
//dataGridView1.Rows.Clear();
//string[] items1 = cbxTicket.Items.OfType<string>().ToArray();
//string[] items2 = cbxType.Items.OfType<string>().ToArray();
//string[] items3 = cbxSymbol.Items.OfType<string>().ToArray();
//string[] items4 = cbxOpenPrice.Items.OfType<string>().ToArray();
//string[] items5 = cbxTP.Items.OfType<string>().ToArray();
//string[] items6 = cbxSL.Items.OfType<string>().ToArray();
//string[] items7 = cbxPrice.Items.OfType<string>().ToArray();
//string[] items8 = cbxSwap.Items.OfType<string>().ToArray();
//string[] items9 = cbxCommission.Items.OfType<string>().ToArray();
//string[] items10 = cbxProfit.Items.OfType<string>().ToArray();
//DataGridViewRow[] rows = new DataGridViewRow[items1.Length * 10];
//for (int i = 0; i < items1.Length; i++)
//{
// string[] data = new string[10];
// data[0] = items1[i];
// data[1] = items2[i];
// data[2] = items3[i];
// data[3] = items4[i];
// data[4] = items5[i];
// data[5] = items6[i];
// data[6] = items7[i];
// data[7] = items8[i];
// data[8] = items9[i];
// data[9] = items10[i];
// DataGridViewRow row = new DataGridViewRow();
// row.CreateCells(this.dataGridView1);
// row.SetValues(data);
// rows[i] = row;
//}
//this.dataGridView1.Rows.AddRange(rows);
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
}
}
}
MQL側
続いてMQL側です。
MetaTrader5にしか対応してないです。
OnTickで,TickDataを受け取る度にイベントが発生し,
WinForms側のコントローラとつながるイメージです。
//+------------------------------------------------------------------+
//| GuiMtController.mq5 |
//| Copyright 2020, IINEbotXXX7963 |
//| https://twitter.com/IINEbotXXX7963 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, IINEbotXXX7963"
#property link "https://twitter.com/IINEbotXXX7963"
#property version "1.00"
//https://www.mql5.com/ja/articles/5563
//https://www.mql5.com/ja/articles/6549
//https://github.com/PublicMqlProjects/GuiControllerElementsDemo
//https://github.com/PublicMqlProjects/MtGuiController/blob/master/MtGuiController/IController.cs
#include <Trade\Trade.mqh>
string FormName = "Form1";
#import "MtGuiController.dll"
input string assembly = "C:\\MT5\\TradePanel\\TradePanel\\bin\\Debug\\TradePanel.exe"; //実行ファイルの保存場所
input string dummy1 =""; //-------------------------------
input int msl = 10;//初期のsl pips数
input int mtp = 20;//初期のtp pips数
int gsl;
int gtp;
input double mcurrent_volume = 0.01; //初期のlot数
input string dummy2 =""; //-------------------------------
//-- 注文を実行するための取引モジュール
CTrade Trade;
double current_volume;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- タイマーを作成する
EventSetMillisecondTimer(150);
GuiController::ShowForm(assembly, FormName);
current_volume = mcurrent_volume;
GuiController::SendEvent("currentVolume", MtGuiController::GuiEventType::TextChange, 0, 0.0, DoubleToString(current_volume, 2));
for(int i = 0; i < SymbolsTotal(true); i++)
{
GuiController::SendEvent("symbolCbx", MtGuiController::GuiEventType::AddItem, 0, 0, SymbolName(i, true));
}
//---値をゲット
double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
GuiController::SendEvent("askValue", MtGuiController::GuiEventType::TextChange, 0, 0.0, DoubleToString(ask, Digits()));
GuiController::SendEvent("bidValue", MtGuiController::GuiEventType::TextChange, 0, 0.0, DoubleToString(bid, Digits()));
GuiController::SendEvent("symbolCbx", MtGuiController::GuiEventType::TextChange, 0, 0.0, Symbol());
gsl = msl;
gtp = mtp;
GuiController::SendEvent("txtSL", MtGuiController::GuiEventType::TextChange, 0, 0.0,IntegerToString(msl));
GuiController::SendEvent("txtTP", MtGuiController::GuiEventType::TextChange, 0, 0.0,IntegerToString(mtp));
GuiController::SendEvent("txtBalance", MtGuiController::GuiEventType::TextChange, 0, 0.0,DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE),0));//残高
GuiController::SendEvent("txtCredi", MtGuiController::GuiEventType::TextChange, 0, 0.0,DoubleToString(AccountInfoDouble(ACCOUNT_CREDIT),0));//クレジット
GuiController::SendEvent("txtEquity", MtGuiController::GuiEventType::TextChange, 0, 0.0,DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),0));//有効証拠金
GuiController::SendEvent("txtMargin", MtGuiController::GuiEventType::TextChange, 0, 0.0,DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN),0)); //必要証拠金
GuiController::SendEvent("txtMarginFree", MtGuiController::GuiEventType::TextChange, 0, 0.0,DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_FREE),0)); //余剰証拠金
GuiController::SendEvent("txtMarginLevel", MtGuiController::GuiEventType::TextChange, 0, 0.0,DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL),2) +"%"); //証拠金維持率
GuiController::SendEvent("txtProfit", MtGuiController::GuiEventType::TextChange, 0, 0.0,DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT),0));//全損益
//---
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- destroy timer
GuiController::HideForm(assembly, FormName);
EventKillTimer();
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
//---
double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
GuiController::SendEvent("askValue", MtGuiController::GuiEventType::TextChange, 0, 0.0, DoubleToString(ask, Digits()));
GuiController::SendEvent("bidValue", MtGuiController::GuiEventType::TextChange, 0, 0.0, DoubleToString(bid, Digits()));
GuiController::SendEvent("txtBalance", MtGuiController::GuiEventType::TextChange, 0, 0.0,DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE),0));//残高
GuiController::SendEvent("txtCredi", MtGuiController::GuiEventType::TextChange, 0, 0.0,DoubleToString(AccountInfoDouble(ACCOUNT_CREDIT),0));//クレジット
GuiController::SendEvent("txtEquity", MtGuiController::GuiEventType::TextChange, 0, 0.0,DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),0));//有効証拠金
GuiController::SendEvent("txtMargin", MtGuiController::GuiEventType::TextChange, 0, 0.0,DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN),0)); //必要証拠金
GuiController::SendEvent("txtMarginFree", MtGuiController::GuiEventType::TextChange, 0, 0.0,DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_FREE),0)); //余剰証拠金
GuiController::SendEvent("txtMarginLevel", MtGuiController::GuiEventType::TextChange, 0, 0.0,DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL),2) +"%"); //証拠金維持率
GuiController::SendEvent("txtProfit", MtGuiController::GuiEventType::TextChange, 0, 0.0,DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT),0));//全損益
}
//+------------------------------------------------------------------+
//| Timer function |
//+------------------------------------------------------------------+
void OnTimer()
{
//---
//--- タイマーで新しいイベントを取得する
for(static int i = 0; i < GuiController::EventsTotal(); i++)
{
int id;
string el_name;
long lparam;
double dparam;
string sparam;
GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
if(id == MtGuiController::GuiEventType::TextChange)
{
if(el_name == "currentVolume")
{
TrySetNewVolume(sparam);
}
if(el_name == "txtSL")
{
Print("SL->" + sparam);
gsl = (int)StringToInteger(sparam);
}
if(el_name == "txtTP")
{
Print("TP->" + sparam);
gtp = (int)StringToInteger(sparam);
}
}
else
if(id == MtGuiController::GuiEventType::ScrollChange && el_name == "incrementVol")
OnIncrementVolume(lparam, dparam, sparam);
else
if(id == MtGuiController::GuiEventType::ClickOnElement)
TryTradeOnClick(el_name);
else
if(id == MtGuiController::GuiEventType::ComboBoxChange && el_name == "symbolCbx")
{
ChartSetSymbolPeriod(0,sparam,0);
}
}
double totalPips = 0.0;
int pt = PositionsTotal();
for(int j = 0; j < PositionsTotal(); j++)
{
string symbol=PositionGetSymbol(j); // Get the symbol name
if(PositionSelect(symbol))
{
double value = 0.0;
int digits ;
digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
double pips = 0.0;
// 3桁・5桁のFXブローカーの場合
if(digits == 3 || digits == 5)
{
pips = MathPow(10, digits) / 10;
}
// 2桁・4桁のFXブローカーの場合
if(digits == 2 || digits == 4)
{
pips = MathPow(10, digits);
}
int positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
string str="";
//---
if(positionType==0)
{
str="buy";
//POSITION_PRICE_CURRENT -> このポジションの現在価格
//POSITION_PRICE_OPEN -> このポジションの価格
value = PositionGetDouble(POSITION_PRICE_CURRENT) - PositionGetDouble(POSITION_PRICE_OPEN);
}
if(positionType==1)
{
str="sell";
value = PositionGetDouble(POSITION_PRICE_OPEN) - PositionGetDouble(POSITION_PRICE_CURRENT);
}
totalPips += value * pips;
//Print("TOTAL_PIPS-> " + DoubleToString(totalPips));
}
}
GuiController::SendEvent("txtTotalPips", MtGuiController::GuiEventType::TextChange, 0, 0.0,DoubleToString(totalPips,1));//全損益
//
// datetime tm=TimeCurrent();
// int position =PositionsTotal();
//
// for(int p = 0; p < position; p++)
// {
// string pos_symbol = PositionGetSymbol(p); // Get the symbol name
// int digits ;
// digits = (int)SymbolInfoInteger(pos_symbol, SYMBOL_DIGITS);
// if(PositionSelect(pos_symbol))
// {
//
//
// //GuiController::SendEvent("cbxTicket", MtGuiController::GuiEventType::AddItem, 0, 0, IntegerToString(PositionGetInteger(POSITION_IDENTIFIER)));
// //GuiController::SendEvent("cbxType", MtGuiController::GuiEventType::AddItem, 0, 0, PositionTypeToString((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)));
// //GuiController::SendEvent("cbxSymbol", MtGuiController::GuiEventType::AddItem, 0, 0, PositionGetString(POSITION_SYMBOL));
// //GuiController::SendEvent("cbxOpenPrice", MtGuiController::GuiEventType::AddItem, 0, 0, DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN),digits));
// //GuiController::SendEvent("cbxTP", MtGuiController::GuiEventType::AddItem, 0, 0, DoubleToString(PositionGetDouble(POSITION_TP),digits));
// //GuiController::SendEvent("cbxSL", MtGuiController::GuiEventType::AddItem, 0, 0, DoubleToString(PositionGetDouble(POSITION_SL),digits));
// //GuiController::SendEvent("cbxPrice", MtGuiController::GuiEventType::AddItem, 0, 0, DoubleToString(PositionGetDouble(POSITION_PRICE_CURRENT),digits));
// //GuiController::SendEvent("cbxSwap", MtGuiController::GuiEventType::AddItem, 0, 0, DoubleToString(PositionGetDouble(POSITION_SWAP),0));
// //GuiController::SendEvent("cbxCommission", MtGuiController::GuiEventType::AddItem, 0, 0, DoubleToString(PositionGetDouble(POSITION_COMMISSION),0));
// //GuiController::SendEvent("cbxProfit", MtGuiController::GuiEventType::AddItem, 0, 0, DoubleToString(PositionGetDouble(POSITION_PROFIT),0));
// }
// }
//GuiController::SendEvent("txtChangeBox", MtGuiController::GuiEventType::TextChange, 0, 0.0,TimeToString(tm)+TimeToString(tm,TIME_SECONDS));
}
//+------------------------------------------------------------------+
//| Trade function |
//+------------------------------------------------------------------+
void OnTrade()
{
//---
}
//+------------------------------------------------------------------+
//| TradeTransaction function |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
const MqlTradeRequest& request,
const MqlTradeResult& result)
{
//---
}
//+------------------------------------------------------------------+
//| ChartEvent function |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
//---
}
//+------------------------------------------------------------------+
//| BookEvent function |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
{
//---
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| 数量を検証する |
//+------------------------------------------------------------------+
double ValidateVolume(double n_vol)
{
double min_vol = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
double max_vol = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX);
//-- 下限を確認する
if(n_vol < min_vol)
return min_vol;
//-- 上限を確認する
if(n_vol > max_vol)
return max_vol;
//-- 数量を正規化する
double vol_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
double steps = MathRound(n_vol / vol_step);
double corr_vol = NormalizeDouble(vol_step * steps, 2);
return corr_vol;
}
//+------------------------------------------------------------------+
//| 与えられたテキストからの新しい現在の数量を設定する |
//+------------------------------------------------------------------+
bool TrySetNewVolume(string nstr_vol)
{
double n_vol = StringToDouble(nstr_vol);
current_volume = ValidateVolume(n_vol);
string corr_vol = DoubleToString(current_volume, 2);
GuiController::SendEvent("currentVolume", MtGuiController::GuiEventType::TextChange, 0, 0.0, corr_vol);
return true;
}
//+------------------------------------------------------------------+
//| 取引注文を実行する |
//+------------------------------------------------------------------+
bool TryTradeOnClick(string el_name)
{
//double tsl,ttp;
// 現在の通貨ペアの小数点以下の桁数を取得
int digits ;
digits = (int)SymbolInfoInteger(Symbol(), SYMBOL_DIGITS);
double pips = 0.0;
// 3桁・5桁のFXブローカーの場合
if(digits == 3 || digits == 5)
{
pips = MathPow(10, digits) / 10;
}
// 2桁・4桁のFXブローカーの場合
if(digits == 2 || digits == 4)
{
pips = MathPow(10, digits);
}
if(el_name == "btnBuy")
{
double dvalue = 0.0;
double avalue = SymbolInfoDouble(Symbol(), SYMBOL_BID);
double limitRate,stopRate;
dvalue = SymbolInfoDouble(Symbol(), SYMBOL_ASK); //buy時基準
limitRate = NormalizeDouble(dvalue + gtp * 1/pips, digits); // リミット価格 を正規化
stopRate = NormalizeDouble(avalue - gsl * 1/pips, digits); // ストップロス価格を正規化
////---
// if(avalue > stopRate)
// stopRate = avalue;
//Print("OpenPrice-> " + dvalue + " SL-> " + stopRate + " TP-> "+ limitRate);
if(gsl == 0.0 && gtp == 0.0)
{
return Trade.Buy(current_volume);
}
if(gsl == 0.0)
{
return Trade.Buy(current_volume,NULL,0,NULL,limitRate);
}
if(gtp == 0.0)
{
return Trade.Buy(current_volume,NULL,0,stopRate,NULL);
}
return Trade.Buy(current_volume,NULL,0,stopRate,limitRate);
}
if(el_name == "btnSell")
{
double dvalue = 0.0;
double avalue = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
double limitRate,stopRate;
dvalue = SymbolInfoDouble(Symbol(), SYMBOL_BID); //buy時基準
limitRate = NormalizeDouble(dvalue - gtp * 1/pips, digits); // リミット価格 を正規化
stopRate = NormalizeDouble(avalue + gsl * 1/pips, digits); // ストップロス価格を正規化
if(gsl == 0.0 && gtp == 0.0)
{
return Trade.Sell(current_volume);
}
if(gsl == 0.0)
{
return Trade.Sell(current_volume,NULL,0,NULL,limitRate);
}
if(gtp == 0.0)
{
return Trade.Sell(current_volume,NULL,0,stopRate,NULL);
}
return Trade.Sell(current_volume,NULL,0,stopRate,limitRate);
}
if(el_name == "allSettlement")
{
positionAllClose();
return true;
}
if(el_name == "btnSelClose")
{
positionSelectClose();
return true;
}//
return false;
}
//+------------------------------------------------------------------+
//| すべてのポジションを決済 |
//+------------------------------------------------------------------+
void positionAllClose()
{
CPositionInfo cpo;
int position =PositionsTotal();
for(int p = 0; p < position; p++)
{
string pos_symbol = PositionGetSymbol(p); // Get the symbol name
if(PositionSelect(pos_symbol))
{
int ti = (int)PositionGetInteger(POSITION_IDENTIFIER);
Print("pos_symbol:" + pos_symbol + " tiket:" + IntegerToString(ti));
if(!Trade.PositionClose(ti,0))
{
Sleep(20);
Trade.PositionClose(ti,0);
}
}
}
Sleep(20);
position = PositionsTotal();
if(position > 0)
{
positionAllClose();
}
}
//+------------------------------------------------------------------+
//| 現在表示されているシンボルのみ決済 |
//+------------------------------------------------------------------+
void positionSelectClose()
{
int position =PositionsTotal();
for(int p = 0; p < position; p++)
{
if(PositionSelect(_Symbol))
{
int ti = (int)PositionGetInteger(POSITION_IDENTIFIER);
Trade.PositionClose(ti,0);
}
}
}
//+------------------------------------------------------------------+
//| 現在の数量を増減する |
//+------------------------------------------------------------------+
void OnIncrementVolume(long lparam, double dparam, string sparam)
{
double vol_step = 0.0;
//-- インクリメントプレスを検出する
if(dparam > lparam)
vol_step = (-1.0) * SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
//-- デクリメントプレスを検出する
else
if(dparam < lparam)
vol_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
//-- インクリメントプレスを再検出する
else
if(lparam == 0)
vol_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
//-- デクリメントプレスを再検出する
else
vol_step = (-1.0) * SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
double n_vol = current_volume + vol_step;
current_volume = ValidateVolume(n_vol);
string nstr_vol = DoubleToString(current_volume, 2);
GuiController::SendEvent("currentVolume", MtGuiController::GuiEventType::TextChange, 0, 0.0, nstr_vol);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| CONVERTING POSITION TYPE TO A STRING |
//+------------------------------------------------------------------+
string PositionTypeToString(int position_type)
{
string str="";
//---
if(position_type==0) { str="buy"; }
if(position_type==1) { str="sell"; }
//---
return(str);
}
//+------------------------------------------------------------------+
このMQL5をExpertsフォルダに配置して,
Librariesフォルダには,ここから落としてくるか,
GitHubページからソースを落としてビルドしてください!
完成品
完成品はこちらにあります!
REFERENCEs
さいごに
カレンダー参加者募集してます!
Have a good MQL Life!!!