「残高の2%だけリスクにする」というロット計算は、
既刊の記事でEAに自動発注させる形で解説しました。
ただ、発注そのものはEAに任せず、
手動で発注したい人や、
発注前に数字だけ確認したい人もいるはずです。
この記事では、チャート上のSLラインをドラッグするだけで、
「このリスク%ならこのロット数」を表示する、
発注は一切行わない、計算専用のインジケーターを作ります。
・OBJ_HLINEでドラッグ可能なSLラインを作る方法
・ラインの価格からリスク%ベースのロット数を計算する方法
・OBJ_LABELでチャート上に常時表示する方法
・EA版との役割分担の考え方
なぜEAではなくインジケーターで計算するのか
まず、この記事が解決したい課題を確認します。
既刊EA版との違い
・既刊lot-size-by-risk-percent-mt4.mdは発注まで行うEA
・本記事は表示だけで、発注は一切行わないインジケーター
・数字を見て、実際の注文は自分の判断で手動で出す設計
インジケーター版が向いている場面
裁量トレードで、
エントリーのタイミングは自分で判断したいが、
ロット数の計算だけは自動化したい、
という人に向いています。
固定入力値ではなく、ラインの位置から計算する
既刊EA版は損切り幅を`input`の固定値で指定していましたが、
本記事はチャート上のラインをドラッグした位置から、
その都度、損切り幅を自動算出します。
OBJ_HLINEでドラッグ可能なSLラインを作る
まず、ロット計算の基準になるラインを用意します。
OBJ_HLINEの作成
if(ObjectFind(0, LineName) < 0) ObjectCreate(0, LineName, OBJ_HLINE, 0, 0, Bid - 200 * Point); ObjectSetInteger(0, LineName, OBJPROP_COLOR, clrRed); ObjectSetInteger(0, LineName, OBJPROP_STYLE, STYLE_DASH); ObjectSetInteger(0, LineName, OBJPROP_SELECTABLE, true);
水平線オブジェクトを1本だけ作り、
マウスでドラッグできるように`OBJPROP_SELECTABLE`を有効にします。
ラインの現在価格を取得する
double linePrice = ObjectGetDouble(0, LineName, OBJPROP_PRICE, 0);
`OBJPROP_PRICE`で、いまラインが引かれている価格を取得できます。
CHARTEVENT_OBJECT_DRAGで即座に再計算する
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
if(id == CHARTEVENT_OBJECT_DRAG && sparam == LineName)
UpdatePanel();
}
ラインをドラッグした瞬間に、表示を更新します。
リスク%ベースのロット計算
既刊EA版と同じ計算式を、ラインの価格から求めた損切り幅に適用します。
損切り幅(ポイント数)をラインから求める
double stopLossPoints = MathAbs(Bid - linePrice) / Point;
現在値とラインの価格差を、ポイント数に変換します。
買いか売りかは区別せず、距離だけを使う設計です。
リスク許容額とロット数の計算
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE); double tickSize = MarketInfo(Symbol(), MODE_TICKSIZE); double pointValue = tickValue * (Point / tickSize); double riskAmount = AccountBalance() * (InpRiskPercent / 100.0); double rawLot = riskAmount / (stopLossPoints * pointValue);
口座残高にリスク%を掛けた金額を、
損切り幅×1ポイントの価値で割ると、
ロット数が求まります。
LOTSTEPで丸め、MIN/MAXの範囲に収める
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP); double minLot = MarketInfo(Symbol(), MODE_MINLOT); double maxLot = MarketInfo(Symbol(), MODE_MAXLOT); double lot = MathFloor(rawLot / lotStep) * lotStep; if(lot < minLot) lot = minLot; if(lot > maxLot) lot = maxLot; lot = NormalizeDouble(lot, 2);
切り捨てで丸めるのは、
繰り上げるとリスク%をわずかに超えてしまうためです。
OBJ_LABELでチャート上に常時表示する
計算結果を、チャートの左上に表示する仕組みを作ります。
OBJ_LABELの作成
ObjectCreate(0, LabelName, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, LabelName, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, LabelName, OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, LabelName, OBJPROP_YDISTANCE, 20);
ラベルオブジェクトを1つ作り、左上の固定位置に配置します。
EventSetTimerで1秒ごとに更新する
・口座残高は決済が無くても変動する(スワップ等)ため、ティック以外でも更新したい
・EventSetTimer(1)で1秒ごとにも更新し、最新の数字を保つ
表示内容
ラインの価格・損切り幅(ポイント)・推奨ロット数の3つを、
1つのラベルにまとめて表示します。
実際に動くコード全体
ここまでの要素をまとめた、実際にMetaEditorでコンパイルできるコードです。
//+------------------------------------------------------------------+
//| RiskPercentLotCalculatorIndicator.mq4 |
//| Copyright 2026, FX-EA System Project Creator |
//| https://creator.fx-ea-system-project.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, FX-EA System Project Creator"
#property link "https://creator.fx-ea-system-project.com/"
#property version "1.00"
#property strict
#property indicator_chart_window
#property indicator_buffers 0
input double InpRiskPercent = 2.0;//口座残高に対するリスク%
string LineName = "RiskCalcSLLine";
string LabelName = "RiskCalcLabel";
//+------------------------------------------------------------------+
//| 表示内容を更新する |
//+------------------------------------------------------------------+
void UpdatePanel()
{
double linePrice = ObjectGetDouble(0, LineName, OBJPROP_PRICE, 0);
double stopLossPoints = MathAbs(Bid - linePrice) / Point;
double lot = 0;
if(stopLossPoints >= 1)
{
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double tickSize = MarketInfo(Symbol(), MODE_TICKSIZE);
double pointValue = tickValue * (Point / tickSize);
double riskAmount = AccountBalance() * (InpRiskPercent / 100.0);
double rawLot = riskAmount / (stopLossPoints * pointValue);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
lot = MathFloor(rawLot / lotStep) * lotStep;
if(lot < minLot) lot = minLot;
if(lot > maxLot) lot = maxLot;
lot = NormalizeDouble(lot, 2);
}
string text = "SLライン価格: " + DoubleToString(linePrice, _Digits) + "\n" +
"損切り幅: " + DoubleToString(stopLossPoints, 0) + "ポイント\n" +
"リスク" + DoubleToString(InpRiskPercent, 1) + "%の推奨ロット: " + DoubleToString(lot, 2);
ObjectSetString(0, LabelName, OBJPROP_TEXT, text);
}
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
if(ObjectFind(0, LineName) < 0)
ObjectCreate(0, LineName, OBJ_HLINE, 0, 0, Bid - 200 * Point);
ObjectSetInteger(0, LineName, OBJPROP_COLOR, clrRed);
ObjectSetInteger(0, LineName, OBJPROP_STYLE, STYLE_DASH);
ObjectSetInteger(0, LineName, OBJPROP_SELECTABLE, true);
ObjectCreate(0, LabelName, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, LabelName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, LabelName, OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, LabelName, OBJPROP_YDISTANCE, 20);
EventSetTimer(1);
UpdatePanel();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
EventKillTimer();
ObjectDelete(0, LineName);
ObjectDelete(0, LabelName);
}
void OnTimer()
{
UpdatePanel();
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
UpdatePanel();
return(rates_total);
}
//+------------------------------------------------------------------+
//| ChartEvent function |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
if(id == CHARTEVENT_OBJECT_DRAG && sparam == LineName)
UpdatePanel();
}
//+------------------------------------------------------------------+
ラインをドラッグするたびに、`OnChartEvent`がその場で再計算します。
MetaEditorでのコンパイル・動作確認手順
実際に動かすまでの手順です。
- MetaEditorで新規インジケーター「RiskPercentLotCalculatorIndicator」を作成し、上のコードを貼り付けてコンパイルする
- 「0 error(s), 0 warning(s)」になることを確認する
- チャートへ適用し、左上に赤い点線のSLラインとラベルが表示されることを確認する
- ラインをマウスでドラッグし、ラベルの推奨ロット数が変わることを確認する
よくあるエラーと注意点
実装・運用でつまずきやすいポイントをまとめます。
ラベルの数字が注文のロット数と自動で一致するわけではない
・このインジケーターは表示だけで、注文ロット欄には反映されない
・発注時は、表示された数字を自分でロット欄に入力する必要がある
・入力を忘れると、意図しないリスク量で発注してしまう
損切り幅が小さすぎるとロットが異常に大きくなる
ラインを現在値の近くまでドラッグすると、
`stopLossPoints`が極端に小さくなり、
計算上のロットが異常に大きくなることがあります。
`MODE_MAXLOT`での上限はありますが、
現実的な損切り幅を使っているか、目視でも確認してください。
ラインを消してしまうと計算できない
チャート上で誤ってラインを削除すると、
`ObjectGetDouble`が0を返し、
計算結果が不正確になります。
インジケーターを外して再度適用すれば、ラインは再作成されます。
よくある質問
リスク%ロット計算インジケーターについて、よく検索される疑問に回答します。
既刊のEA版とどちらを使うべきですか?
目的が異なるため、使い分けが前提です。
エントリーまで自動化したい場合は、
既刊lot-size-by-risk-percent-mt4.mdのEA版、
エントリー判断は自分で行い、
ロット計算だけ自動化したい場合は本記事のインジケーター版が向いています。
複数の通貨ペアで同時に使えますか?
各チャートに個別に適用すれば使えますが、
通貨ペアごとに独立してリスク%を計算するため、
複数ペアを同時に持つ場合の合計リスクは別途自分で管理する必要があります。
ロットが小数点以下まで細かく出るのはなぜですか?
`MODE_LOTSTEP`で丸めているため、
ブローカーが許可する最小単位(0.01等)の精度で表示されます。
ブローカーによって単位が異なる点に注意してください。
【MQL4】SLラインをドラッグするとロット数が自動計算されるインジケーターの作り方(リスク%ベース):まとめ
既刊EA版のリスク%ロット計算式を、
発注を伴わないインジケーターの形に作り直すことで、
裁量トレードでも安全にロット計算だけを自動化できます。
OBJ_HLINEのドラッグ検知と、
OBJ_LABELでの常時表示という、
既刊の2つの技術を組み合わせれば、
このような実用ツールに発展させられます。
コメント