イントロ
んーと、MQL5です。知ってる?
MetaTraderっていう、外国為替“とか”を売買するためのツールで略してMT。MQLは、このMTでシステムトレード、つまり自動売買システムを作るための言語です。
今回は、MQL5を使って外国為替の売買をプログラムで行ってみようと思うっす~の2回目で、建玉した玉を利益確定または損切りするっす。利益が出ていれば利確、損失が出ていれば損切りっす。英語だとポジションをクローズするって言うっす。
建て玉(たてぎょく)とは、外国為替や株式などの金融商品を買うあるいは空売りする事でポジションを持つことっす。ポジションをクローズした時にこの建て玉に対して市場価格がどのくらい上がったか、あるいは下がったかで損益が決まってくるっす。クローズしないと1円にもならないっす。
普通にMT5のインターフェイスを使えばクローズなんて簡単に出来るんですが、プログラムで売買するというのはその先に自動売買を見据えての事っすね。
何故自動売買するのか?って命題の答えは『【MT5_FX部0001】MQL5で建玉しようぜ!』のイントロを見て欲しいっす。
アジェンダ
今回は、『【MT5_FX部0001】MQL5で建玉しようぜ!』で作成した売買専用Class『COrder』に、close 関数を追加するっす。
売買専用Classのおさらい
売買専用Classはこんな感じっす。
//+------------------------------------------------------------------+
//| Order.mqh |
//| Copyright 2022, #ashworth |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, #ashworth"
#property version "1.00"
//+------------------------------------------------------------------+
#define DeviationPt 20
#define MagicNumber 12345
//+------------------------------------------------------------------+
class COrder
{
private:
public:
COrder();
~COrder();
static bool open (const string symbol, ENUM_ORDER_TYPE open_type, const double lots, const ulong deviation = DeviationPt, ulong magic = MagicNumber);
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
COrder::COrder(){}
COrder::~COrder(){}
//+------------------------------------------------------------------+
//| position open |
//+------------------------------------------------------------------+
bool COrder::open(const string symbol, ENUM_ORDER_TYPE order_type, const double lots, const ulong deviation = DeviationPt, ulong magic = MagicNumber)
{
bool status;
MqlTradeRequest request = {};
request.action = TRADE_ACTION_DEAL;
request.position = 0;
request.symbol = symbol;
request.volume = lots;
request.type = order_type;
request.price = (order_type == ORDER_TYPE_BUY)
? SymbolInfoDouble(symbol, SYMBOL_ASK)
: SymbolInfoDouble(symbol, SYMBOL_BID)
;
request.deviation = deviation;
request.magic = magic;
request.type_filling = ORDER_FILLING_IOC;
MqlTradeCheckResult check_result;
status = OrderCheck(request, check_result);
if(check_result.retcode != 0)
{
PrintFormat("Open OrderCheck Faild.(%d) %s. OrderType:%d , %.2flots", check_result.retcode, check_result.comment, order_type, lots);
return false;
}
MqlTradeResult result;
status = OrderSend(request, result);
if(result.retcode != TRADE_RETCODE_DONE)
{
PrintFormat("Open OrerSend Faild.(%d) %s", result.retcode, result.comment);
return false;
}
return true;
}
//+------------------------------------------------------------------+
見ての通り、建玉用の関数しかないっす。これに、クローズ用の関数を追加するっす。
クローズ用関数
まず、ヘッダに関数定義を追加するっす。
class COrder
{
private:
static bool getCloseType (const ENUM_POSITION_TYPE position_type, ENUM_ORDER_TYPE& close_type);
public:
COrder();
~COrder();
static bool open (const string symbol, const ENUM_ORDER_TYPE open_type, const double lots, const ulong deviation = DeviationPt, ulong magic = MagicNumber);
static bool close (const string symbol, const ENUM_POSITION_TYPE position_type, const ulong deviation = DeviationPt);
};
引数は、対象通貨ペア、クローズするポジションタイプ(買い or 売り)、deviation(スリッページ)っす。
スリッページについては『【MT5_FX部0001】MQL5で建玉しようぜ!』で説明したんすが、通常の取引の場合『建玉をしよう!』と思った時の値で伺いを立てるんすが、約定(やくじょう=売買が成立すること)するかどうか判断を行っているサーバにその注文情報が届くまでの間に多少タイムラグがあって、その間に値が動いてしまうことがあるっす。
deviationはそうやって値が動いてしまった場合に、どれだけの値動きを許容するかをポイントで指定するためのパラメタっす。
実装
さて、では実際のクローズ関数の実装を行うっす。
まず、保有している全てのポジション数を取得するっす。
int total = PositionsTotal();
次に、ループ処理で順繰りにポジションチケットを取得していくっす。
ポジションチケットの取得には PositionGetTicket() メソッドを使うっす。ポジションチケット取得に失敗した場合は 0 が返るのでひとまず次のポジションチケット取得を試みるっす。
for(i = total - 1; i >= 0; i--)
{
// ポジションを選択する
if(PositionGetTicket(i) == 0) { continue; }
}
ポジションチケットが取得出来たらクローズ処理をするっすが、この時、クローズが暴発しないように、まず、対象外の通貨ペア(symbol)は除外するっす。
そして、対象外のポジションタイプ(買い or 売り)も除外するっす。
for(int i = total - 1; i >= 0; i--)
{
// ポジションを選択する
if(PositionGetTicket(i) == 0) { continue; }
if(symbol != PositionGetString(POSITION_SYMBOL)) { continue; }
if(position_type != (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)) { continue; }
}
これで暴発はなくなったので、実際のクローズ処理を行うっす。クローズに必要な各種情報を取得してから、それを取引リクエスト構造体 (MqlTradeRequest)にセットするっす。
price については、買いオーダーの時は ASK、売りオーダーの時は BIDの値を設定する必要があるっす。
ulong ticket = PositionGetInteger(POSITION_TICKET);
double lots = PositionGetDouble(POSITION_VOLUME);
ulong magic = PositionGetInteger(POSITION_MAGIC);
ENUM_ORDER_TYPE close_type;
if(!getCloseType(position_type, close_type)) { continue; }
// クローズ注文
MqlTradeRequest request = {};
request.action = TRADE_ACTION_DEAL;
request.position = ticket;
request.symbol = symbol;
request.volume = lots;
request.type = close_type;
request.price = (close_type == ORDER_TYPE_BUY)
? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
: SymbolInfoDouble(_Symbol, SYMBOL_BID)
;
request.deviation = deviation;
request.magic = magic;
request.type_filling = ORDER_FILLING_IOC;
なお、注文type を取得する関数 getCloseType() を定義したっす。
MT5では買いポジションをクローズする時は売りオーダー、逆に売りポジションをクローズする時は買いオーダーを投げる必要があるので、その変換用の関数っす。
bool COrder::getCloseType(const ENUM_POSITION_TYPE position_type, const ENUM_ORDER_TYPE& close_type)
{
if(position_type == POSITION_TYPE_BUY)
{
close_type = ORDER_TYPE_SELL;
return true;
}
if(position_type == POSITION_TYPE_SELL)
{
close_type = ORDER_TYPE_BUY;
return true;
}
return false;
}
あとは『【MT5_FX部0001】MQL5で建玉しようぜ!』の時と同じように、まず OrderCheck() 関数でリクエストに問題ないかチェックを行ってから、OrderSend() で実際の注文を投げるっす。
MqlTradeCheckResult check_result;
status = OrderCheck(request, check_result);
if(check_result.retcode != 0)
{
PrintFormat("Close OrderCheck Faild.(%d) %s. OrderType: %d: ticket %d", check_result.retcode, check_result.comment, ticket, close_type);
status = false;
continue;
}
MqlTradeResult result;
status = OrderSend(request, result);
if(result.retcode != TRADE_RETCODE_DONE)
{
// retcode https://www.mql5.com/ja/docs/constants/errorswarnings/enum_trade_return_codes
PrintFormat("Close OrerSend Faild.(%d) %s : ticket %d", result.retcode, result.comment, ticket);
status = false;
continue;
}
これで、クローズ関数が出来上がりっす。
一応、完全な COrder クラスを貼っておくっす。
//+------------------------------------------------------------------+
//| Order.mqh |
//| Copyright 2022, #ashworth |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, #ashworth"
#property version "1.00"
//+------------------------------------------------------------------+
#define DeviationPt 20
#define MagicNumber 12345
//+------------------------------------------------------------------+
class COrder
{
private:
static bool getCloseType (const ENUM_POSITION_TYPE position_type, ENUM_ORDER_TYPE& close_type);
public:
COrder();
~COrder();
static bool open (const string symbol, const ENUM_ORDER_TYPE open_type, const double lots, const ulong deviation = DeviationPt, const ulong magic = MagicNumber);
static bool close (const string symbol, const ENUM_POSITION_TYPE position_type, const ulong deviation = DeviationPt);
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
COrder::COrder(){}
COrder::~COrder(){}
//+------------------------------------------------------------------+
//| position open |
//+------------------------------------------------------------------+
bool COrder::open
(
const string symbol,
const ENUM_ORDER_TYPE order_type,
const double lots,
const ulong deviation = DeviationPt,
const ulong magic = MagicNumber
){
bool status;
MqlTradeRequest request = {};
request.action = TRADE_ACTION_DEAL;
request.position = 0;
request.symbol = symbol;
request.volume = lots;
request.type = order_type;
request.price = (order_type == ORDER_TYPE_BUY)
? SymbolInfoDouble(symbol, SYMBOL_ASK)
: SymbolInfoDouble(symbol, SYMBOL_BID)
;
request.deviation = deviation;
request.magic = magic;
request.type_filling = ORDER_FILLING_IOC;
MqlTradeCheckResult check_result;
status = OrderCheck(request, check_result);
if(check_result.retcode != 0)
{
PrintFormat("Open OrderCheck Faild.(%d) %s. OrderType:%d , %.2flots", check_result.retcode, check_result.comment, order_type, lots);
return false;
}
MqlTradeResult result;
status = OrderSend(request, result);
if(result.retcode != TRADE_RETCODE_DONE)
{
PrintFormat("Open OrerSend Faild.(%d) %s", result.retcode, result.comment);
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| get type for close position |
//+------------------------------------------------------------------+
bool COrder::getCloseType(const ENUM_POSITION_TYPE position_type, ENUM_ORDER_TYPE& close_type)
{
if(position_type == POSITION_TYPE_BUY)
{
close_type = ORDER_TYPE_SELL;
return true;
}
if(position_type == POSITION_TYPE_SELL)
{
close_type = ORDER_TYPE_BUY;
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| position close |
//+------------------------------------------------------------------+
bool COrder::close
(
const string symbol,
const ENUM_POSITION_TYPE position_type,
const ulong deviation = DeviationPt
){
bool status = false;
datetime close_start_time = TimeCurrent();
// 保有しているポジション数
int total = PositionsTotal();
for(int i = total - 1; i >= 0; i--)
{
// ポジションを選択する
if(PositionGetTicket(i) == 0) { continue; }
if(symbol != PositionGetString(POSITION_SYMBOL)) { continue; }
if(position_type != (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)) { continue; }
ulong ticket = PositionGetInteger(POSITION_TICKET);
double lots = PositionGetDouble(POSITION_VOLUME);
ulong magic = PositionGetInteger(POSITION_MAGIC);
ENUM_ORDER_TYPE close_type;
if(!getCloseType(position_type, close_type)) { continue; }
// クローズ注文
MqlTradeRequest request = {};
request.action = TRADE_ACTION_DEAL;
request.position = ticket;
request.symbol = symbol;
request.volume = lots;
request.type = close_type;
request.price = (close_type == ORDER_TYPE_BUY)
? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
: SymbolInfoDouble(_Symbol, SYMBOL_BID)
;
request.deviation = deviation;
request.magic = magic;
request.type_filling = ORDER_FILLING_IOC;
MqlTradeCheckResult check_result;
status = OrderCheck(request, check_result);
if(check_result.retcode != 0)
{
PrintFormat("Close OrderCheck Faild.(%d) %s. OrderType: %d: ticket %d", check_result.retcode, check_result.comment, ticket, close_type);
status = false;
continue;
}
MqlTradeResult result;
status = OrderSend(request, result);
if(result.retcode != TRADE_RETCODE_DONE)
{
// retcode https://www.mql5.com/ja/docs/constants/errorswarnings/enum_trade_return_codes
PrintFormat("Close OrerSend Faild.(%d) %s : ticket %d", result.retcode, result.comment, ticket);
status = false;
continue;
}
}
if(!status) { return status; }
// 総損益の出力など
return status;
}
//+------------------------------------------------------------------+
クローズテスト
さて、ではクローズテストをしてみるっす。
まず、『【MT5_FX部0001】MQL5で建玉しようぜ!』で作成したスクリプト OrderBuy を改変してポジションを適当に建ててみるっす。たまたまこの記事を書いている時は下落相場だったので、売りポジションをいくつか建ててみるっす。
#include <Order.mqh>
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
COrder::open("EURJPY", ORDER_TYPE_SELL, 1);
}
そうしたら、今回作成した close() 関数を呼び出すスクリプトを作成するっす。ファイル > 新規作成 から スクリプト を選ぶっす。ファイル名は何でもいいっすが今回は Close にしましょうかね。
#include <Order.mqh>
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
COrder::close("EURJPY", POSITION_TYPE_SELL);
}
//+------------------------------------------------------------------+
スクリプトを作成したら、F7 でコンパイルするっす。MT5のナビゲータウィンドウにスクリプトの実行ファイルが作成されたら成功っす。
あとは、コンパイルされた Close 実行ファイルをダブルクリックまたはチャートウィンドウにドラッグ&ドロップするだけっす。
記事を書いていたら値動きが逆行してしまってしばらく帰ってきそうになかったので損切りになってしまったっす。
ティアダウン
はい、こんな感じでプログラムでのポジションのクローズは行われるわけっす。
MT5は一度に全ポジションクローズという事が出来ない仕様で、ポジションを1つずつクローズする必要があるっす。だから COrder クラスの実装ではループ処理で回して対象のポジションを1ポジションずつクローズしているっす。
これで建玉と利確・損切りがプログラムで制御出来るようになったので自動売買が行えるようになったっす。各々、自分の好きなトレードアルゴリズムで自動売買システムをつくってみればいいっす。
次回から実際に自動売買をしてみるっす。
コメント