今回は、MT4のEAサンプルソース(FX用)を公開します。
許容できる損失額からエントリーする際のロット数を自動計算する関数となっておりますので、是非使ってみて下さい。
はじめに
今回は、許容できる損失額からロット数を計算する関数について公開します。
エントリー時にストップロスを設定する際、
-30pipsをストップロスにしたけど口座残高の5%分の損失を許容できる場合のロット数はいくつになるんだろう
-30pipsをストップロスにしたけど1万円の損失を許容できる場合のロット数はいくつになるんだろう
と、こういった場合に役に立ちます。
以前、証拠金維持率からロット数を計算する関数を作ったのですが少し切り口を変えた形になります。
証拠金維持率からロット数を計算する関数はこちら↓
ソースコード
以下、関数のサンプルソースです。
許容できる損失額からロット数を計算する関数(許容損失計算関数)です。
//+------------------------------------------------------------------+
//|【関数】許容損失額に対してのロット数を取得する
//|
//|【引数】Type:0か1(0:% 1:額)Pips:ストップロスのPips(50=50pips) PerSec:許容損失額(5% 50000円等)
//|
//|【戻値】ロット数(1000通貨=0.01)
//|
double funcLotGet(int Type,double Pips,double PerSec)
{
double RiyouGaku=0,SaidaiLot=0,KouzaMoney=0,Kekka=0;
if(SL == 0){
Print("ストップロス値が設定されていません");
return(0);
}
//最大ロット数確認
double server_Lot = MarketInfo(Symbol(),MODE_MAXLOT);
if(SaidaiLot == 0 || SaidaiLot > server_Lot){
SaidaiLot = server_Lot;
}
//口座残高(余剰証拠金)
KouzaMoney = AccountInfoDouble(ACCOUNT_FREEMARGIN);
if(Type == 0){
//利用可能金額算出
RiyouGaku = NormalizeDouble(KouzaMoney * (PerSec / 100),0);
}else{
//利用可能金額算出
RiyouGaku = PerSec;
}
Print("余剰証拠金:" + DoubleToString(KouzaMoney,0) + " 利用可能金額:" + DoubleToString(RiyouGaku,0));
//通貨ペア取得
string Tuuka = Symbol();
//通貨ペア名称保持
int MojiCnt = StringLen(Tuuka);
string subMoji = "";
if(MojiCnt > 6){
subMoji = StringSubstr(Tuuka, 6, MojiCnt - 6);
}
//クロス円
if(StringFind(Tuuka, "JPY",0) != -1){
//ロット数計算
Kekka = RiyouGaku / Pips /1000;
//ドルストレート
}else if(StringFind(Tuuka, "USD",3) != -1){
//double Nedan = MarketInfo("USDJPY"+subMoji, MODE_ASK);
double Nedan = iOpen("USDJPY"+subMoji,PERIOD_CURRENT,0);
//ロット数計算
Kekka = RiyouGaku / Pips / Nedan / 10;
}else{
Print("計算対象外通貨です:"+Tuuka);
return(0);
}
//少数点を0.01単位までにする
Kekka = NormalizeDouble(Kekka, 2);
//最大ロット数を超えている場合は、最大ロット数にする
if(Kekka > SaidaiLot){
Kekka = SaidaiLot;
}
return Kekka;
}
解説
引数
引数1はタイプです。タイプ1は需要があるかわかりませんがお好みで設定してください。
- 0の場合 ➡ 余剰証拠金から許容できる損失額をパーセンテージで計算するタイプ(引数3は%で渡す)
- 1の場合 ➡ 引数3に直接損失額を直打ちするタイプ(引数3は金額で渡す)
引数2はストップロス(Pips)です。エントリー時にストップロスを何Pipsに設定するかの際の値を渡すようにしてください
(例)20Pipsの場合は、20を渡す
引数3は許容損失額です。引数1でも説明しましたが、余剰証拠金から許容できる損失額をパーセンテージで渡してください。
- 引数1が0だった場合 ➡ 余剰証拠金から許容できる損失額をパーセンテージで渡す
- 引数1が1だった場合 ➡ 引数3は直接損失許容金額で渡す
例
以下、状況別の引数の例です。
余剰証拠金が50万円あり、1トレード10%(5万円)失ってもいい場合でストップロスは30Pips
↓
funcLotGet(0,30,10)
1トレード7万円失ってもいい場合でストップロスは50Pips
↓
funcLotGet(1,50,70000)
タイプによって引数3の数値の意味合いが変わるのは少し気持ち悪いですが、汎用性を意識しています。
戻り値
許容損失額に応じた、ロット数を返します。 ※1=10万通貨
(ブローカーによる最大ロットをオーバーしている場合は、ブローカーの最大ロット数になります)
また、ストップロス値が設定されていない場合や対円・ドルストレート以外(AUDCAD、GBPNZD等)の場合は0を返しています。
処理内容
まず自分の口座の口座残高(余剰証拠金)を取得し、引数3の許容損失額(%)を使って利用可能金額を算出しています。
Kekka = NormalizeDouble(KouzaMoney * (PerSec / 100),0);
↓
(利用可能金額:50,000)= (余剰証拠金:500,000) * (許容損失額:0.1 )
次に、対円なのかドルストレートかで計算方法が変わってくるので通貨ペアの確認をしています。
対円の場合は計算は簡単ですが、ドルストレートの場合はドル/円の値段が必要になってきます。
※補足情報・・・MarketInfo(Tuuka, MODE_ASK)でドル値段を取得してもよかったのですが、バックテストで値が取れないためiOpen()で開始値を取得しています。
最後に、ロット数の確認をし最大ロット数を超えている場合は最大ロット数にしています。
許容損失計算関数の使用例
// 新規エントリー注文
int CNo = OrderSend(
NULL, // 通貨ペア
OP_BUY, // オーダータイプ[OP_BUY / OP_SELL]
funcLotGet(0,20,10), // ロット[0.01単位](10%損失許容)
Ask, // オーダープライスレート
・・・
OrderSend()関数のロット数のところに書き込む感じです。あとは、funcLotGet()関数をソース内にそのまま貼り付けてください。
使用した結果はこんな感じで、履歴は下から上へ流れているので余剰証拠金が50万円あり、1トレード10%(50,000円)失っても良い場合(ストップロスは30Pips)のロット数は1.67という事がわかりますね。
で、このトレードはストップロスになり50,100円失い余剰証拠金が449,900円になったため、次回のトレードの10%(44,990円)失っても良い場合(ストップロスは30Pips)のロット数は1.50という事がわかります。
その他注意点
今回の関数は、価格差益のみの計算となっておりスワップポイントのマイナスやプラス分までは考慮されていません。
主な使用関数
MarketInfo()
マーケット情報を返す。
MarketInfo(Symbol(),MODE_MAXLOT));でそのブローカーの取引通貨の最大ロット数を取得します。
AccountInfoDouble()
アカウント情報を返す。
AccountInfoDouble(ACCOUNT_FREEMARGIN)を使って自分の口座の余剰証拠金を取得します。
NormalizeDouble()
少数点以下の桁数を四捨五入した値を返す。
NormalizeDouble(x, 0)で小数点を四捨五入し整数にした値を取得します。
StringFind()
文字列検索。
見つかったワードが開始される文字列位置を返します。
見つからなかった場合は-1を返します。
さいごに
以上、許容できる損失額からロット数を計算する処理となります。
今回は読者さんからの要望でしたが、自分の普段考えていないロット数計算だったため新鮮でした。
また、対円・ドルストレート以外の計算(AUDCAD、GBPNZD等)も対応したいと思います。
※ EAのサンプルソースを一覧表にまとめました
※オンラインレッスンやってます
コメント
さっそく記事にしてくださって、ありがとうございます!
今日もMQLの勉強をしていますが、本当にいつも参考にさせていただいております。
これからもよろしくお願いします!
MQLの勉強頑張ってくださいね!
こちらこそよろしくお願いします!
お世話になっております。
以前に、教えていただいた許容損失額によってロットサイズが変動する関数を使用して決まった時間に取引するコードを書いてみました。
しかし、爆テストするとロット数量が1.00のままでどこが原因か自分では理解に及んでいません。
もし可能でしたら、ご教授お願いいたしますm(__)m
//+——————————————————————+
//| TokyoTime.mq4 |
//| Copyright 2020, mef Software. |
//| https://fx-prog.com/ |
//+——————————————————————+
#property copyright “Copyright 2020, mef Software.”
#property link “https://fx-prog.com/”
#property version “1.00”
input int A_SPREAD = 10; //エントリー及び決済時に許容するスプレッドの値です(10 = 10pips)
datetime prevtime;
//+——————————————————————+
//| Expert initialization function |
//+——————————————————————+
int init()
{
//—
//—
return(INIT_SUCCEEDED);
}
//+——————————————————————+
//| Expert tick function |
//+——————————————————————+
int start()
{
int orderPtn = 0; // 0: 何もしない, 1: 買い, 2: 売り
int total = 0; // 未決済ポジションのカウント
double ea_order_stop_price = 0; // ストップロス価格
double lotSize = 0; // ロットサイズ
bool OrderKekka;
//新しい足ができた時だけやる
if(Time[0] != prevtime){
prevtime = Time[0];
}else{
return(0);
}
// *** 売買判断箇所 ***
// 日本時間の11時にロング
if (DayOfWeek() == 1) {
if (IsSummerTime() == true) {
if (Hour() == 4) { // サマータイム中はサーバー時間の8時
orderPtn = 1;
}
} else {
if (Hour() == 5) { // 通常時はサーバー時間の9時
orderPtn = 1;
}
}
}
// *** ストップロス幅とロットサイズの計算 ***
if (orderPtn == 1) {
double stopLossPips = 20; // ストップロス幅を20ピップスに設定
double riskPercent = 2; // 許容リスクを2%に設定
// ロットサイズを計算
lotSize = funcLotGet(0, stopLossPips, 2);
// ストップロス価格の設定
ea_order_stop_price = Ask – stopLossPips*10*Point;
}
//***決済判断箇所***//
total=OrdersTotal();
//午後18時に決済
if (total == 1) {
if ((IsSummerTime() && Hour() == 11) || (!IsSummerTime() && Hour() == 12)) {
OrderKekka = funcOrder_Close();
}
}
// *** 新規注文 ***
if (total == 0 && orderPtn > 0 && lotSize > 0) {
// 新規注文をロットサイズに基づき送信
OrderKekka = funcOrder_Send(orderPtn – 1, ea_order_stop_price, 0, lotSize, 0);
}
return(0);
}
//+——————————————————————+
//|【関数】新規注文関数 |
//| |
//|【引数】 ea_order_entry_Type:売買(0:買 1:売) |
//|【引数】 ea_order_stop_price:損切値 ea_order_good_price:利確値 |
//|【引数】 orderComment:オーダーコメント ea_order_MagicNo:マジックNo |
//| |
//|【戻値】True:成功 |
//| |
//|
bool funcOrder_Send(int ea_order_entry_Type, double ea_order_stop_price, double ea_order_good_price,int orderComment,int ea_order_MagicNo)
{
int order_resend_num; // エントリー試行回数
int ea_ticket_res; // チケットNo
int errorcode; // エラーコード
double ea_order_entry_price; // エントリーレート
color order_Color;
bool kekka;
for( order_resend_num = 0; order_resend_num < 10; order_resend_num++ ) { // エントリー試行回数上限:10回
if(MarketInfo(NULL,MODE_SPREAD) < A_SPREAD){
if(ea_order_entry_Type == OP_BUY){
ea_order_entry_price = Ask; // 現在の買値でエントリー
order_Color = clrBlue;
}else if(ea_order_entry_Type == OP_SELL){
ea_order_entry_price = Bid; // 現在の売値でエントリー
order_Color = clrRed;
}
// FXCMでは新規エントリー時にストップ/リミットを設定出来ない。
ea_ticket_res = OrderSend( // 新規エントリー注文
NULL, // 通貨ペア
ea_order_entry_Type, // オーダータイプ[OP_BUY / OP_SELL]
1, // ロット[0.01単位](FXTFは1=10Lot)
ea_order_entry_price, // オーダープライスレート
20, // スリップ上限 (int)[分解能 0.1pips]
ea_order_stop_price, // ストップレート
ea_order_good_price, // リミットレート
orderComment, // オーダーコメント
ea_order_MagicNo, // マジックナンバー(識別用)
0, // オーダーリミット時間
order_Color // オーダーアイコンカラー
);
}
if ( ea_ticket_res == -1) { // オーダーエラー
errorcode = GetLastError(); // エラーコード取得
if( errorcode != ERR_NO_ERROR) { // エラー発生
printf("エラー");
}
Sleep(2000); // 1000msec待ち
RefreshRates(); // レート更新
printf("再エントリー要求回数:%d, 更新エントリーレート:%g",order_resend_num+1 ,ea_order_entry_price);
} else { // 注文約定
Print("新規注文約定。 チケットNo=",ea_ticket_res," レート:",ea_order_entry_price);
Sleep(300); // 300msec待ち(オーダー要求頻度が多過ぎるとエラーになる為)
break;
}
}
return kekka;
}
//+——————————————————————+
//|【関数】決済関数
//|
//|【引数】 なし
//|
//|【戻値】True:成功 False:失敗
//|
//|
bool funcOrder_Close()
{
bool kekka,OrderKekka;
int total,order_resend_num;
double ea_order_entry_price; // エントリーレート
color order_Color;
kekka=false;
for(order_resend_num = 0; order_resend_num < 100; order_resend_num++ ) {
if(MarketInfo(NULL,MODE_SPREAD) 0 && OrderSelect(0,SELECT_BY_POS,MODE_TRADES)==true){
if(OrderType() == OP_BUY){
ea_order_entry_price = Bid; // 現在の売値で決済
order_Color = clrLightBlue;
}else if(OrderType() == OP_SELL){
ea_order_entry_price = Ask; // 現在の買値で決済
order_Color = clrLightPink;
}
OrderKekka = OrderClose(OrderTicket(),OrderLots(),ea_order_entry_price,10,order_Color);
if(OrderKekka==false){
Print(“決済失敗 エラーNo=”,GetLastError(),” リトライ回数=”,order_resend_num+1);
RefreshRates();
Sleep(1000);
}else{
Print(“決済注文約定。 レート=”,ea_order_entry_price);
kekka=true;
break;
}
}
}else{
Print(“スプレッド拡大中(銭)=”,MarketInfo(NULL,MODE_SPREAD) / 10,” リトライ回数=”,order_resend_num+1);
Sleep(5000); // msec待ち
RefreshRates(); // レート更新
}
}
return kekka;
}
//+——————————————————————+
//|【関数】サマータイム確認 |
//| |
//|【引数】 なし |
//| |
//|【戻値】True:サマータイム |
//|【備考】3月第2日曜日~11月第1日曜日までをサマータイムとする |
//| |
bool IsSummerTime(){
switch(Year()){
case 2005: if(StringToTime(“2005.3.13”)<=TimeCurrent()&&TimeCurrent()<=StringToTime("2005.11.6"))return true; break;
case 2006: if(StringToTime("2006.3.12")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2006.11.5"))return true; break;
case 2007: if(StringToTime("2007.3.11")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2007.11.4"))return true; break;
case 2008: if(StringToTime("2008.3.9") <=TimeCurrent()&&TimeCurrent()<=StringToTime("2008.11.2"))return true; break;
case 2009: if(StringToTime("2009.3.8") <=TimeCurrent()&&TimeCurrent()<=StringToTime("2009.11.1"))return true; break;
case 2010: if(StringToTime("2010.3.14")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2010.11.7"))return true; break;
case 2011: if(StringToTime("2011.3.13")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2011.11.6"))return true; break;
case 2012: if(StringToTime("2012.3.11")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2012.11.4"))return true; break;
case 2013: if(StringToTime("2013.3.10")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2013.11.3"))return true; break;
case 2014: if(StringToTime("2014.3.9") <=TimeCurrent()&&TimeCurrent()<=StringToTime("2014.11.2"))return true; break;
case 2015: if(StringToTime("2015.3.8") <=TimeCurrent()&&TimeCurrent()<=StringToTime("2015.11.1"))return true; break;
case 2016: if(StringToTime("2016.3.13")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2016.11.6"))return true; break;
case 2017: if(StringToTime("2017.3.12")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2017.11.5"))return true; break;
case 2018: if(StringToTime("2018.3.11")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2018.11.4"))return true; break;
case 2019: if(StringToTime("2019.3.10")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2019.11.3"))return true; break;
case 2020: if(StringToTime("2020.3.8") <=TimeCurrent()&&TimeCurrent()<=StringToTime("2020.11.1"))return true; break;
case 2021: if(StringToTime("2021.3.14")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2021.11.7"))return true; break;
case 2022: if(StringToTime("2022.3.13")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2022.11.6"))return true; break;
case 2023: if(StringToTime("2023.3.12")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2023.11.5"))return true; break;
case 2024: if(StringToTime("2024.3.10")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2024.11.3"))return true; break;
case 2025: if(StringToTime("2025.3.9") <=TimeCurrent()&&TimeCurrent()<=StringToTime("2025.11.2"))return true; break;
case 2026: if(StringToTime("2026.3.8") <=TimeCurrent()&&TimeCurrent()<=StringToTime("2026.11.1"))return true; break;
case 2027: if(StringToTime("2027.3.14")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2027.11.7"))return true; break;
case 2028: if(StringToTime("2028.3.12")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2028.11.5"))return true; break;
case 2029: if(StringToTime("2029.3.11")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2029.11.4"))return true; break;
case 2030: if(StringToTime("2030.3.10")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2030.11.3"))return true; break;
case 2031: if(StringToTime("2031.3.9") <=TimeCurrent()&&TimeCurrent()<=StringToTime("2031.11.2"))return true; break;
case 2032: if(StringToTime("2032.3.14")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2032.11.7"))return true; break;
case 2033: if(StringToTime("2033.3.13")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2033.11.6"))return true; break;
case 2034: if(StringToTime("2034.3.12")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2034.11.5"))return true; break;
case 2035: if(StringToTime("2035.3.11")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2035.11.4"))return true; break;
case 2036: if(StringToTime("2036.3.9") <=TimeCurrent()&&TimeCurrent()<=StringToTime("2036.11.2"))return true; break;
case 2037: if(StringToTime("2037.3.8") <=TimeCurrent()&&TimeCurrent()<=StringToTime("2037.11.1"))return true; break;
case 2038: if(StringToTime("2038.3.14")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2038.11.7"))return true; break;
case 2039: if(StringToTime("2039.3.13")<=TimeCurrent()&&TimeCurrent()<=StringToTime("2039.11.6"))return true; break;
case 2040: if(StringToTime("2040.3.11")<=TimeCurrent()&&TimeCurrent() server_Lot){
SaidaiLot = server_Lot;
}
//口座残高(余剰証拠金)
KouzaMoney = AccountInfoDouble(ACCOUNT_FREEMARGIN);
if(Type == 0){
//利用可能金額算出
RiyouGaku = NormalizeDouble(KouzaMoney * (PerSec / 100),0);
}else{
//利用可能金額算出
RiyouGaku = PerSec;
}
Print(“余剰証拠金:” + DoubleToString(KouzaMoney,0) + ” 利用可能金額:” + DoubleToString(RiyouGaku,0));
//通貨ペア取得
string Tuuka = Symbol();
//通貨ペア名称保持
int MojiCnt = StringLen(Tuuka);
string subMoji = “”;
if(MojiCnt > 6){
subMoji = StringSubstr(Tuuka, 6, MojiCnt – 6);
}
//クロス円
if(StringFind(Tuuka, “JPY”,0) != -1){
//ロット数計算
Kekka = RiyouGaku / Pips /1000;
//ドルストレート
}else if(StringFind(Tuuka, “USD”,3) != -1){
//double Nedan = MarketInfo(“USDJPY”+subMoji, MODE_ASK);
double Nedan = iOpen(“USDJPY”+subMoji,PERIOD_CURRENT,0);
//ロット数計算
Kekka = RiyouGaku / Pips / Nedan / 10;
}else{
Print(“計算対象外通貨です:”+Tuuka);
return(0);
}
//少数点を0.01単位までにする
Kekka = NormalizeDouble(Kekka, 2);
//最大ロット数を超えている場合は、最大ロット数にする
if(Kekka > SaidaiLot){
Kekka = SaidaiLot;
}
return Kekka;
}
お世話になっております。
ソースコードをコピペしてみたところ、文字列がトリミングされているかなにかでうまい事コンパイルできませんでした。
ですのでソースコードをざっと見た感じでお伝えしますと、
まず、『lotSize = funcLotGet(0, stopLossPips, 2);』でlotSize変数にロット数をセットしています。
その後、『OrderKekka = funcOrder_Send(orderPtn – 1, ea_order_stop_price, 0, lotSize, 0);』
で引数にもlotSize変数があり、ロット数を新規注文関数側に引き渡している所まではOKです。
※ただここも引数が1つ少ないですが取り合えず保留
ただし、新規注文関数(funcOrder_Send)に問題点が2点あります。
●1つ目
『bool funcOrder_Send(int ea_order_entry_Type, double ea_order_stop_price, double ea_order_good_price,int orderComment,int ea_order_MagicNo)
{』
ここでロット数を引き取れていないのでロット数が新規注文関数で分からない状態になっています。
●2つ目
新規注文関数(funcOrder_Send)内の、OrderSend()関数のロット数の部分(3つ目の引数)が1固定になっています。
ここは、lotSize変数の値にしてあげる必要があります。
簡単に修正方法です。
【修正方法①】
[OrderKekka = funcOrder_Send(orderPtn – 1, ea_order_stop_price,]でソースコード検索する。
ヒットした箇所を以下のように不足している引数を追加する。
OrderKekka = funcOrder_Send(orderPtn – 1, ea_order_stop_price, 0, 0,lotSize, 0);
【修正方法②】
[bool funcOrder_Send(]でソースコード検索する。
ヒットした箇所を以下のようにロットサイズ引数を追加する。
bool funcOrder_Send(int ea_order_entry_Type, double ea_order_stop_price, double ea_order_good_price,int orderComment,double ea_lotSize,int ea_order_MagicNo)
【修正方法③】
[// ロット[0.01単位](FXTFは1=10Lot)]でソースコード検索する。
ヒットした箇所を以下のようにea_lotSize値を反映する。
ea_lotSize, // ロット[0.01単位](FXTFは1=10Lot)
こんな感じでいけると思います。
ご丁寧な返信ありがとうございます!
できました!嬉しいです(‘◇’)ゞ