Consolidate Python ignore rules into root gitignore
This commit is contained in:
@@ -0,0 +1,561 @@
|
||||
#ifndef HIT_TRADE_MANAGER_MQH
|
||||
#define HIT_TRADE_MANAGER_MQH
|
||||
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| エントリー注文を送信する関数 (1:buy-stop, 2:buy-limit, 3:sell-stop, 4:sell-limit)
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 指定された注文タイプでペンディング注文を送信します。
|
||||
*
|
||||
* @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。
|
||||
* @param price エントリー価格。
|
||||
* @param tp 利確価格。
|
||||
* @param sl 損切価格。
|
||||
* @param volume 注文ロット。
|
||||
* @param comment_suffix 分割注文識別用のコメント接尾辞。空なら従来コメント。
|
||||
* @return 注文が正常に受理された場合はtrue、それ以外はfalse。
|
||||
*/
|
||||
bool SendOrder(int orderType, double price, double tp, double sl, double volume, string comment_suffix)
|
||||
{
|
||||
MqlTradeRequest request = {};
|
||||
MqlTradeResult result = {};
|
||||
|
||||
request.magic = magic_number;
|
||||
request.symbol = _Symbol;
|
||||
request.volume = volume;
|
||||
request.deviation = slippage;
|
||||
request.type_filling = GetOrderFillingPolicy(_Symbol);
|
||||
request.price = NormalizeDouble(price, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
|
||||
request.tp = NormalizeDouble(tp, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
|
||||
request.sl = NormalizeDouble(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
|
||||
request.comment = RequestComment(orderType, comment_suffix);
|
||||
|
||||
string orderTypeStr = "";
|
||||
|
||||
// 指値または逆指値注文
|
||||
request.action = TRADE_ACTION_PENDING;
|
||||
ApplyPendingOrderExpiration(request);
|
||||
|
||||
switch(orderType)
|
||||
{
|
||||
case 1: // 順張り買い (buy-stop)
|
||||
request.type = ORDER_TYPE_BUY_STOP;
|
||||
orderTypeStr = "buy-stop";
|
||||
break;
|
||||
|
||||
case 2: // 逆張り買い (buy-limit)
|
||||
request.type = ORDER_TYPE_BUY_LIMIT;
|
||||
orderTypeStr = "buy-limit";
|
||||
break;
|
||||
|
||||
case 3: // 順張り売り (sell-stop)
|
||||
request.type = ORDER_TYPE_SELL_STOP;
|
||||
orderTypeStr = "sell-stop";
|
||||
break;
|
||||
|
||||
case 4: // 逆張り売り (sell-limit)
|
||||
request.type = ORDER_TYPE_SELL_LIMIT;
|
||||
orderTypeStr = "sell-limit";
|
||||
break;
|
||||
|
||||
default: // 不正なorderType
|
||||
Print(__FUNCTION__, ": invalid orderType=", orderType);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 注文送信
|
||||
if(!OrderSend(request, result))
|
||||
{
|
||||
Print(__FUNCTION__, ": OrderSend failed. err=", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 注文成功確認
|
||||
if(result.retcode == TRADE_RETCODE_PLACED ||
|
||||
result.retcode == TRADE_RETCODE_DONE ||
|
||||
result.retcode == TRADE_RETCODE_DONE_PARTIAL)
|
||||
{
|
||||
Print(request.comment + " - " + orderTypeStr + " order success (ticket=", result.order, ")");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Print(__FUNCTION__, ": OrderSend retcode=", result.retcode);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文の執行ポリシーを取得する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief シンボルに対応する注文執行ポリシーを取得します。
|
||||
*
|
||||
* @param symbol 対象シンボル。
|
||||
* @return 利用可能な執行ポリシー。IOC、FOK、RETURNの順で選択します。
|
||||
*/
|
||||
ENUM_ORDER_TYPE_FILLING GetOrderFillingPolicy(string symbol)
|
||||
{
|
||||
long fill_mode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE);
|
||||
if((fill_mode & SYMBOL_FILLING_IOC) != 0)
|
||||
return ORDER_FILLING_IOC;
|
||||
if((fill_mode & SYMBOL_FILLING_FOK) != 0)
|
||||
return ORDER_FILLING_FOK;
|
||||
return ORDER_FILLING_RETURN;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| ペンディング注文の有効期限を設定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief brokerが対応している範囲でpending orderにサーバー側の期限を設定します。
|
||||
*
|
||||
* @param request 更新対象の取引リクエスト。
|
||||
*/
|
||||
void ApplyPendingOrderExpiration(MqlTradeRequest &request)
|
||||
{
|
||||
int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1);
|
||||
long expiration_mode = SymbolInfoInteger(request.symbol, SYMBOL_EXPIRATION_MODE);
|
||||
|
||||
if(expiration_seconds > 0 &&
|
||||
(expiration_mode & SYMBOL_EXPIRATION_SPECIFIED) != 0)
|
||||
{
|
||||
request.type_time = ORDER_TIME_SPECIFIED;
|
||||
request.expiration = TimeCurrent() + expiration_seconds;
|
||||
return;
|
||||
}
|
||||
|
||||
if(expiration_seconds > 0 &&
|
||||
(expiration_mode & SYMBOL_EXPIRATION_SPECIFIED_DAY) != 0)
|
||||
{
|
||||
request.type_time = ORDER_TIME_SPECIFIED_DAY;
|
||||
request.expiration = TimeCurrent() + expiration_seconds;
|
||||
return;
|
||||
}
|
||||
|
||||
if((expiration_mode & SYMBOL_EXPIRATION_DAY) != 0)
|
||||
{
|
||||
request.type_time = ORDER_TIME_DAY;
|
||||
return;
|
||||
}
|
||||
|
||||
request.type_time = ORDER_TIME_GTC;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文コメント(ローカル時刻)を生成する関数(先頭に orderType を付与)
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 注文コメント文字列を生成します。
|
||||
*
|
||||
* @param orderType 注文タイプ番号。
|
||||
* @param comment_suffix 分割注文識別用のコメント接尾辞。
|
||||
* @return 注文タイプとPCローカル時刻、または分割注文識別子を含むコメント文字列。
|
||||
*/
|
||||
string RequestComment(int orderType, string comment_suffix)
|
||||
{
|
||||
if(comment_suffix != "")
|
||||
return "T" + IntegerToString(orderType) + " " + comment_suffix;
|
||||
|
||||
datetime localTime = TimeLocal();
|
||||
string timeStr = TimeToString(localTime, TIME_DATE|TIME_MINUTES);
|
||||
|
||||
// 先頭に 1,2,3,4 を付ける(例: "1 | PC Time: 2026.02.14 19:05")
|
||||
return IntegerToString(orderType) + " | PC Time: " + timeStr;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| H4 market_stateに対して許可された注文タイプか判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H4 market_stateに対して指定注文タイプが許可されるか判定します。
|
||||
*
|
||||
* @param orderType 注文タイプ番号。
|
||||
* @param trend_state H4 market_state(0..5、6は技術エラー停止)。
|
||||
* @return 許可される注文タイプならtrue。技術エラー停止または異常値ではfalse。
|
||||
*/
|
||||
bool IsOrderTypeAllowedByTrend(const int orderType, const int trend_state)
|
||||
{
|
||||
switch(trend_state)
|
||||
{
|
||||
case MARKET_LOW_VOL_RANGE:
|
||||
case MARKET_HIGH_VOL_RANGE:
|
||||
return (orderType == 2 || orderType == 4); // Range: Buy Limit / Sell Limitのみ
|
||||
|
||||
case MARKET_LOW_VOL_UP:
|
||||
case MARKET_HIGH_VOL_UP:
|
||||
return (orderType == 1 || orderType == 2); // Up: Buy Stop / Buy Limitのみ
|
||||
|
||||
case MARKET_LOW_VOL_DOWN:
|
||||
case MARKET_HIGH_VOL_DOWN:
|
||||
return (orderType == 3 || orderType == 4); // Down: Sell Stop / Sell Limitのみ
|
||||
|
||||
case MARKET_TECHNICAL_ERROR_STOP:
|
||||
default:
|
||||
return false; // 技術エラー停止または異常値は新規注文しない
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| target_prices.txt の価格が有効か判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief エントリー価格、利確価格、損切価格が有効値か判定します。
|
||||
*
|
||||
* @param en エントリー価格。
|
||||
* @param tp 利確価格。
|
||||
* @param sl 損切価格。
|
||||
* @return すべて0より大きい場合はtrue。
|
||||
*/
|
||||
bool HasValidTargetPrices(const double en, const double tp, const double sl)
|
||||
{
|
||||
return (en > 0.0 && tp > 0.0 && sl > 0.0);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 自EAの未約定注文数を数える関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief このEAが管理対象とする未約定注文数を数えます。
|
||||
*
|
||||
* @return `_Symbol` と `magic_number` が一致する未約定注文数。
|
||||
*/
|
||||
int CountMyPendingOrders()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = OrderGetTicket(i);
|
||||
if(ticket == 0)
|
||||
continue;
|
||||
|
||||
if(!OrderSelect(ticket))
|
||||
continue;
|
||||
|
||||
if(OrderGetString(ORDER_SYMBOL) != _Symbol)
|
||||
continue;
|
||||
|
||||
if((int)OrderGetInteger(ORDER_MAGIC) != magic_number)
|
||||
continue;
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 自EAのポジション数を数える関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief このEAが管理対象とする保有ポジション数を数えます。
|
||||
*
|
||||
* @return `_Symbol` と `magic_number` が一致するポジション数。
|
||||
*/
|
||||
int CountMyPositions()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
string symbol = PositionGetSymbol(i);
|
||||
if(symbol == "")
|
||||
continue;
|
||||
|
||||
if(symbol != _Symbol)
|
||||
continue;
|
||||
|
||||
if((int)PositionGetInteger(POSITION_MAGIC) != magic_number)
|
||||
continue;
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 自EAの注文+ポジション数を数える関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief このEAが使用中の注文数とポジション数の合計を返します。
|
||||
*
|
||||
* @return 未約定注文数 + 保有ポジション数。
|
||||
*/
|
||||
int CountMyUsed()
|
||||
{
|
||||
return CountMyPendingOrders() + CountMyPositions();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 実効ポジション上限を返す関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief input指定の上限を安全な範囲に丸めて返します。
|
||||
*/
|
||||
int EffectivePositionLimit()
|
||||
{
|
||||
int limit = input_position_limit;
|
||||
if(limit < 1)
|
||||
limit = 1;
|
||||
if(limit > POSITION_LIMIT)
|
||||
limit = POSITION_LIMIT;
|
||||
|
||||
return limit;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 分割注文コメントか判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsSplitOrderComment(const string comment)
|
||||
{
|
||||
return (StringFind(comment, " Z") >= 0 && StringFind(comment, "#") >= 0);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 指定候補IDの分割注文コメントか判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsCurrentSplitCandidateComment(const string comment, const string candidate_id)
|
||||
{
|
||||
if(candidate_id == "" || candidate_id == "0")
|
||||
return false;
|
||||
|
||||
return (StringFind(comment, "Z" + candidate_id + "#") >= 0);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 分割slotが既存注文/ポジションに存在するか判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
bool HasExistingSplitSlot(const string slot_key)
|
||||
{
|
||||
if(slot_key == "")
|
||||
return false;
|
||||
|
||||
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = OrderGetTicket(i);
|
||||
if(ticket == 0)
|
||||
continue;
|
||||
if(!OrderSelect(ticket))
|
||||
continue;
|
||||
if(OrderGetString(ORDER_SYMBOL) != _Symbol)
|
||||
continue;
|
||||
if((int)OrderGetInteger(ORDER_MAGIC) != magic_number)
|
||||
continue;
|
||||
if(StringFind(OrderGetString(ORDER_COMMENT), slot_key) >= 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
string symbol = PositionGetSymbol(i);
|
||||
if(symbol == "")
|
||||
continue;
|
||||
if(symbol != _Symbol)
|
||||
continue;
|
||||
if((int)PositionGetInteger(POSITION_MAGIC) != magic_number)
|
||||
continue;
|
||||
if(StringFind(PositionGetString(POSITION_COMMENT), slot_key) >= 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 古い分割pending注文を取消する関数
|
||||
//+------------------------------------------------------------------+
|
||||
bool CancelStaleSplitPendingOrders(const string current_candidate_id)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if(current_candidate_id == "" || current_candidate_id == "0")
|
||||
return false;
|
||||
|
||||
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = OrderGetTicket(i);
|
||||
if(ticket == 0)
|
||||
continue;
|
||||
if(!OrderSelect(ticket))
|
||||
continue;
|
||||
if(OrderGetString(ORDER_SYMBOL) != _Symbol)
|
||||
continue;
|
||||
if((int)OrderGetInteger(ORDER_MAGIC) != magic_number)
|
||||
continue;
|
||||
|
||||
string comment = OrderGetString(ORDER_COMMENT);
|
||||
if(!IsSplitOrderComment(comment))
|
||||
continue;
|
||||
if(IsCurrentSplitCandidateComment(comment, current_candidate_id))
|
||||
continue;
|
||||
|
||||
MqlTradeRequest request = {};
|
||||
MqlTradeResult trade_result = {};
|
||||
request.action = TRADE_ACTION_REMOVE;
|
||||
request.order = ticket;
|
||||
|
||||
if(!OrderSend(request, trade_result))
|
||||
{
|
||||
Print("Failed to delete stale split order. Ticket: ", ticket, " Error: ", GetLastError());
|
||||
continue;
|
||||
}
|
||||
|
||||
if(trade_result.retcode == TRADE_RETCODE_DONE)
|
||||
{
|
||||
Print("Stale split order canceled. Ticket: ", ticket, " comment=", comment);
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Stale split order cancellation failed. Ticket: ", ticket, " Retcode: ", trade_result.retcode);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 時間が経過したエントリー注文をキャンセルする関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 実時間でENTRY_H1_LIMIT時間を超えた未約定注文をキャンセルします。
|
||||
*
|
||||
* @return 1件以上キャンセルに成功した場合はtrue。
|
||||
*
|
||||
* 対象は`_Symbol` と `magic_number` が一致する未約定注文のみです。
|
||||
*/
|
||||
bool CancelExpiredOrders()
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
// 未決済注文の数を取得
|
||||
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = OrderGetTicket(i);
|
||||
if(OrderSelect(ticket)) // 注文を選択
|
||||
{
|
||||
if(OrderGetString(ORDER_SYMBOL) != _Symbol)
|
||||
continue;
|
||||
if(OrderGetInteger(ORDER_MAGIC) != magic_number)
|
||||
continue;
|
||||
|
||||
datetime open_time = (datetime)OrderGetInteger(ORDER_TIME_SETUP);
|
||||
|
||||
int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1);
|
||||
if(open_time <= 0 || expiration_seconds <= 0)
|
||||
continue;
|
||||
|
||||
if(TimeCurrent() - open_time >= expiration_seconds)
|
||||
{
|
||||
MqlTradeRequest request = {};
|
||||
MqlTradeResult trade_result = {};
|
||||
request.action=TRADE_ACTION_REMOVE; // 取引操作タイプ
|
||||
request.order = ticket; // 注文チケット
|
||||
if(!OrderSend(request, trade_result)) // 削除リクエスト送信
|
||||
{
|
||||
Print("Failed to delete order. Ticket: ", ticket, " Error: ", GetLastError());
|
||||
}
|
||||
else
|
||||
{
|
||||
if(trade_result.retcode == TRADE_RETCODE_DONE)
|
||||
{
|
||||
Print("Order canceled successfully due to a time limit. Ticket: ", ticket);
|
||||
result = true; // 削除成功フラグ
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Order cancellation failed. Ticket: ", ticket, " Retcode: ", trade_result.retcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 時間が経過したポジションをクローズする関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 実時間でCLOSE_H1_LIMIT時間を超えた保有ポジションをクローズします。
|
||||
*
|
||||
* @return 1件以上クローズに成功した場合はtrue。
|
||||
*
|
||||
* 対象は`_Symbol` と `magic_number` が一致するポジションのみです。
|
||||
*/
|
||||
bool CloseExpiredPositions()
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
// ポジションを逆順でループ
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
string position_symbol = PositionGetSymbol(i);
|
||||
if(position_symbol!="")
|
||||
{
|
||||
// Magic Number とシンボルでフィルタリング
|
||||
if(position_symbol != _Symbol)
|
||||
continue;
|
||||
if(PositionGetInteger(POSITION_MAGIC) == magic_number)
|
||||
{
|
||||
// ポジションのエントリー時刻を取得
|
||||
datetime entry_time = (datetime)PositionGetInteger(POSITION_TIME);
|
||||
|
||||
int expiration_seconds = CLOSE_H1_LIMIT * PeriodSeconds(PERIOD_H1);
|
||||
if(entry_time <= 0 || expiration_seconds <= 0)
|
||||
continue;
|
||||
|
||||
if(TimeCurrent() - entry_time >= expiration_seconds)
|
||||
{
|
||||
MqlTradeRequest request = {};
|
||||
MqlTradeResult trade_result = {};
|
||||
|
||||
// ポジションタイプに応じてリクエストを設定
|
||||
int position_type = (int)PositionGetInteger(POSITION_TYPE);
|
||||
request.action = TRADE_ACTION_DEAL;
|
||||
request.position = PositionGetInteger(POSITION_TICKET); // ポジションのチケット番号
|
||||
request.symbol = position_symbol; // シンボル
|
||||
request.volume = PositionGetDouble(POSITION_VOLUME); // ポジションサイズ
|
||||
request.price = (position_type == POSITION_TYPE_BUY)
|
||||
? SymbolInfoDouble(position_symbol, SYMBOL_BID) // BUYの場合はBIDでクローズ
|
||||
: SymbolInfoDouble(position_symbol, SYMBOL_ASK); // SELLの場合はASKでクローズ
|
||||
request.deviation = slippage;
|
||||
request.type = (position_type == POSITION_TYPE_BUY)
|
||||
? ORDER_TYPE_SELL // BUYポジションをSELLでクローズ
|
||||
: ORDER_TYPE_BUY; // SELLポジションをBUYでクローズ
|
||||
request.type_filling = GetOrderFillingPolicy(position_symbol); // 注文執行ポリシー
|
||||
request.magic = magic_number;
|
||||
|
||||
// 注文送信
|
||||
if(!OrderSend(request, trade_result))
|
||||
{
|
||||
Print("Failed to close position. Ticket: ", request.position, " Error: ", GetLastError());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 結果の確認
|
||||
if(trade_result.retcode == TRADE_RETCODE_DONE || trade_result.retcode == TRADE_RETCODE_DONE_PARTIAL)
|
||||
{
|
||||
Print("Position closed successfully due to a time limit. Ticket: ", request.position);
|
||||
result = true; // 少なくとも1つ成功した場合
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Failed to close position. Ticket: ", request.position, " Retcode: ", trade_result.retcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Failed to select position at index ", i, ". Error: ", GetLastError());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,427 @@
|
||||
#ifndef HIT_TRADE_MANAGER_MQH
|
||||
#define HIT_TRADE_MANAGER_MQH
|
||||
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| エントリー注文を送信する関数 (1:buy-stop, 2:buy-limit, 3:sell-stop, 4:sell-limit)
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 指定された注文タイプでペンディング注文を送信します。
|
||||
*
|
||||
* @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。
|
||||
* @param price エントリー価格。
|
||||
* @param tp 利確価格。
|
||||
* @param sl 損切価格。
|
||||
* @return 注文が正常に受理された場合はtrue、それ以外はfalse。
|
||||
*/
|
||||
bool SendOrder(int orderType, double price, double tp, double sl)
|
||||
{
|
||||
MqlTradeRequest request = {};
|
||||
MqlTradeResult result = {};
|
||||
|
||||
request.magic = magic_number;
|
||||
request.symbol = _Symbol;
|
||||
request.volume = lot_size;
|
||||
request.deviation = slippage;
|
||||
request.type_filling = GetOrderFillingPolicy(_Symbol);
|
||||
request.price = NormalizeDouble(price, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
|
||||
request.tp = NormalizeDouble(tp, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
|
||||
request.sl = NormalizeDouble(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
|
||||
request.comment = RequestComment(orderType);
|
||||
|
||||
string orderTypeStr = "";
|
||||
|
||||
// 指値または逆指値注文
|
||||
request.action = TRADE_ACTION_PENDING;
|
||||
ApplyPendingOrderExpiration(request);
|
||||
|
||||
switch(orderType)
|
||||
{
|
||||
case 1: // 順張り買い (buy-stop)
|
||||
request.type = ORDER_TYPE_BUY_STOP;
|
||||
orderTypeStr = "buy-stop";
|
||||
break;
|
||||
|
||||
case 2: // 逆張り買い (buy-limit)
|
||||
request.type = ORDER_TYPE_BUY_LIMIT;
|
||||
orderTypeStr = "buy-limit";
|
||||
break;
|
||||
|
||||
case 3: // 順張り売り (sell-stop)
|
||||
request.type = ORDER_TYPE_SELL_STOP;
|
||||
orderTypeStr = "sell-stop";
|
||||
break;
|
||||
|
||||
case 4: // 逆張り売り (sell-limit)
|
||||
request.type = ORDER_TYPE_SELL_LIMIT;
|
||||
orderTypeStr = "sell-limit";
|
||||
break;
|
||||
|
||||
default: // 不正なorderType
|
||||
Print(__FUNCTION__, ": invalid orderType=", orderType);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 注文送信
|
||||
if(!OrderSend(request, result))
|
||||
{
|
||||
Print(__FUNCTION__, ": OrderSend failed. err=", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 注文成功確認
|
||||
if(result.retcode == TRADE_RETCODE_PLACED ||
|
||||
result.retcode == TRADE_RETCODE_DONE ||
|
||||
result.retcode == TRADE_RETCODE_DONE_PARTIAL)
|
||||
{
|
||||
Print(request.comment + " - " + orderTypeStr + " order success (ticket=", result.order, ")");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Print(__FUNCTION__, ": OrderSend retcode=", result.retcode);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文の執行ポリシーを取得する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief シンボルに対応する注文執行ポリシーを取得します。
|
||||
*
|
||||
* @param symbol 対象シンボル。
|
||||
* @return 利用可能な執行ポリシー。IOC、FOK、RETURNの順で選択します。
|
||||
*/
|
||||
ENUM_ORDER_TYPE_FILLING GetOrderFillingPolicy(string symbol)
|
||||
{
|
||||
long fill_mode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE);
|
||||
if((fill_mode & SYMBOL_FILLING_IOC) != 0)
|
||||
return ORDER_FILLING_IOC;
|
||||
if((fill_mode & SYMBOL_FILLING_FOK) != 0)
|
||||
return ORDER_FILLING_FOK;
|
||||
return ORDER_FILLING_RETURN;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| ペンディング注文の有効期限を設定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief brokerが対応している範囲でpending orderにサーバー側の期限を設定します。
|
||||
*
|
||||
* @param request 更新対象の取引リクエスト。
|
||||
*/
|
||||
void ApplyPendingOrderExpiration(MqlTradeRequest &request)
|
||||
{
|
||||
int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1);
|
||||
long expiration_mode = SymbolInfoInteger(request.symbol, SYMBOL_EXPIRATION_MODE);
|
||||
|
||||
if(expiration_seconds > 0 &&
|
||||
(expiration_mode & SYMBOL_EXPIRATION_SPECIFIED) != 0)
|
||||
{
|
||||
request.type_time = ORDER_TIME_SPECIFIED;
|
||||
request.expiration = TimeCurrent() + expiration_seconds;
|
||||
return;
|
||||
}
|
||||
|
||||
if(expiration_seconds > 0 &&
|
||||
(expiration_mode & SYMBOL_EXPIRATION_SPECIFIED_DAY) != 0)
|
||||
{
|
||||
request.type_time = ORDER_TIME_SPECIFIED_DAY;
|
||||
request.expiration = TimeCurrent() + expiration_seconds;
|
||||
return;
|
||||
}
|
||||
|
||||
if((expiration_mode & SYMBOL_EXPIRATION_DAY) != 0)
|
||||
{
|
||||
request.type_time = ORDER_TIME_DAY;
|
||||
return;
|
||||
}
|
||||
|
||||
request.type_time = ORDER_TIME_GTC;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 注文コメント(ローカル時刻)を生成する関数(先頭に orderType を付与)
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 注文コメント文字列を生成します。
|
||||
*
|
||||
* @param orderType 注文タイプ番号。
|
||||
* @return 注文タイプとPCローカル時刻を含むコメント文字列。
|
||||
*/
|
||||
string RequestComment(int orderType)
|
||||
{
|
||||
datetime localTime = TimeLocal();
|
||||
string timeStr = TimeToString(localTime, TIME_DATE|TIME_MINUTES);
|
||||
|
||||
// 先頭に 1,2,3,4 を付ける(例: "1 | PC Time: 2026.02.14 19:05")
|
||||
return IntegerToString(orderType) + " | PC Time: " + timeStr;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| H4 market_stateに対して許可された注文タイプか判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief H4 market_stateに対して指定注文タイプが許可されるか判定します。
|
||||
*
|
||||
* @param orderType 注文タイプ番号。
|
||||
* @param trend_state H4 market_state(0..5、6は技術エラー停止)。
|
||||
* @return 許可される注文タイプならtrue。技術エラー停止または異常値ではfalse。
|
||||
*/
|
||||
bool IsOrderTypeAllowedByTrend(const int orderType, const int trend_state)
|
||||
{
|
||||
switch(trend_state)
|
||||
{
|
||||
case MARKET_LOW_VOL_RANGE:
|
||||
case MARKET_HIGH_VOL_RANGE:
|
||||
return (orderType == 2 || orderType == 4); // Range: Buy Limit / Sell Limitのみ
|
||||
|
||||
case MARKET_LOW_VOL_UP:
|
||||
case MARKET_HIGH_VOL_UP:
|
||||
return (orderType == 1 || orderType == 2); // Up: Buy Stop / Buy Limitのみ
|
||||
|
||||
case MARKET_LOW_VOL_DOWN:
|
||||
case MARKET_HIGH_VOL_DOWN:
|
||||
return (orderType == 3 || orderType == 4); // Down: Sell Stop / Sell Limitのみ
|
||||
|
||||
case MARKET_TECHNICAL_ERROR_STOP:
|
||||
default:
|
||||
return false; // 技術エラー停止または異常値は新規注文しない
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| target_prices.txt の価格が有効か判定する関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief エントリー価格、利確価格、損切価格が有効値か判定します。
|
||||
*
|
||||
* @param en エントリー価格。
|
||||
* @param tp 利確価格。
|
||||
* @param sl 損切価格。
|
||||
* @return すべて0より大きい場合はtrue。
|
||||
*/
|
||||
bool HasValidTargetPrices(const double en, const double tp, const double sl)
|
||||
{
|
||||
return (en > 0.0 && tp > 0.0 && sl > 0.0);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 自EAの未約定注文数を数える関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief このEAが管理対象とする未約定注文数を数えます。
|
||||
*
|
||||
* @return `_Symbol` と `magic_number` が一致する未約定注文数。
|
||||
*/
|
||||
int CountMyPendingOrders()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = OrderGetTicket(i);
|
||||
if(ticket == 0)
|
||||
continue;
|
||||
|
||||
if(!OrderSelect(ticket))
|
||||
continue;
|
||||
|
||||
if(OrderGetString(ORDER_SYMBOL) != _Symbol)
|
||||
continue;
|
||||
|
||||
if((int)OrderGetInteger(ORDER_MAGIC) != magic_number)
|
||||
continue;
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 自EAのポジション数を数える関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief このEAが管理対象とする保有ポジション数を数えます。
|
||||
*
|
||||
* @return `_Symbol` と `magic_number` が一致するポジション数。
|
||||
*/
|
||||
int CountMyPositions()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
string symbol = PositionGetSymbol(i);
|
||||
if(symbol == "")
|
||||
continue;
|
||||
|
||||
if(symbol != _Symbol)
|
||||
continue;
|
||||
|
||||
if((int)PositionGetInteger(POSITION_MAGIC) != magic_number)
|
||||
continue;
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 自EAの注文+ポジション数を数える関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief このEAが使用中の注文数とポジション数の合計を返します。
|
||||
*
|
||||
* @return 未約定注文数 + 保有ポジション数。
|
||||
*/
|
||||
int CountMyUsed()
|
||||
{
|
||||
return CountMyPendingOrders() + CountMyPositions();
|
||||
}
|
||||
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 時間が経過したエントリー注文をキャンセルする関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 実時間でENTRY_H1_LIMIT時間を超えた未約定注文をキャンセルします。
|
||||
*
|
||||
* @return 1件以上キャンセルに成功した場合はtrue。
|
||||
*
|
||||
* 対象は`_Symbol` と `magic_number` が一致する未約定注文のみです。
|
||||
*/
|
||||
bool CancelExpiredOrders()
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
// 未決済注文の数を取得
|
||||
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = OrderGetTicket(i);
|
||||
if(OrderSelect(ticket)) // 注文を選択
|
||||
{
|
||||
if(OrderGetString(ORDER_SYMBOL) != _Symbol)
|
||||
continue;
|
||||
if(OrderGetInteger(ORDER_MAGIC) != magic_number)
|
||||
continue;
|
||||
|
||||
datetime open_time = (datetime)OrderGetInteger(ORDER_TIME_SETUP);
|
||||
|
||||
int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1);
|
||||
if(open_time <= 0 || expiration_seconds <= 0)
|
||||
continue;
|
||||
|
||||
if(TimeCurrent() - open_time >= expiration_seconds)
|
||||
{
|
||||
MqlTradeRequest request = {};
|
||||
MqlTradeResult trade_result = {};
|
||||
request.action=TRADE_ACTION_REMOVE; // 取引操作タイプ
|
||||
request.order = ticket; // 注文チケット
|
||||
if(!OrderSend(request, trade_result)) // 削除リクエスト送信
|
||||
{
|
||||
Print("Failed to delete order. Ticket: ", ticket, " Error: ", GetLastError());
|
||||
}
|
||||
else
|
||||
{
|
||||
if(trade_result.retcode == TRADE_RETCODE_DONE)
|
||||
{
|
||||
Print("Order canceled successfully due to a time limit. Ticket: ", ticket);
|
||||
result = true; // 削除成功フラグ
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Order cancellation failed. Ticket: ", ticket, " Retcode: ", trade_result.retcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 時間が経過したポジションをクローズする関数
|
||||
//+------------------------------------------------------------------+
|
||||
/**
|
||||
* @brief 実時間でCLOSE_H1_LIMIT時間を超えた保有ポジションをクローズします。
|
||||
*
|
||||
* @return 1件以上クローズに成功した場合はtrue。
|
||||
*
|
||||
* 対象は`_Symbol` と `magic_number` が一致するポジションのみです。
|
||||
*/
|
||||
bool CloseExpiredPositions()
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
// ポジションを逆順でループ
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
string position_symbol = PositionGetSymbol(i);
|
||||
if(position_symbol!="")
|
||||
{
|
||||
// Magic Number とシンボルでフィルタリング
|
||||
if(position_symbol != _Symbol)
|
||||
continue;
|
||||
if(PositionGetInteger(POSITION_MAGIC) == magic_number)
|
||||
{
|
||||
// ポジションのエントリー時刻を取得
|
||||
datetime entry_time = (datetime)PositionGetInteger(POSITION_TIME);
|
||||
|
||||
int expiration_seconds = CLOSE_H1_LIMIT * PeriodSeconds(PERIOD_H1);
|
||||
if(entry_time <= 0 || expiration_seconds <= 0)
|
||||
continue;
|
||||
|
||||
if(TimeCurrent() - entry_time >= expiration_seconds)
|
||||
{
|
||||
MqlTradeRequest request = {};
|
||||
MqlTradeResult trade_result = {};
|
||||
|
||||
// ポジションタイプに応じてリクエストを設定
|
||||
int position_type = (int)PositionGetInteger(POSITION_TYPE);
|
||||
request.action = TRADE_ACTION_DEAL;
|
||||
request.position = PositionGetInteger(POSITION_TICKET); // ポジションのチケット番号
|
||||
request.symbol = position_symbol; // シンボル
|
||||
request.volume = PositionGetDouble(POSITION_VOLUME); // ポジションサイズ
|
||||
request.price = (position_type == POSITION_TYPE_BUY)
|
||||
? SymbolInfoDouble(position_symbol, SYMBOL_BID) // BUYの場合はBIDでクローズ
|
||||
: SymbolInfoDouble(position_symbol, SYMBOL_ASK); // SELLの場合はASKでクローズ
|
||||
request.deviation = slippage;
|
||||
request.type = (position_type == POSITION_TYPE_BUY)
|
||||
? ORDER_TYPE_SELL // BUYポジションをSELLでクローズ
|
||||
: ORDER_TYPE_BUY; // SELLポジションをBUYでクローズ
|
||||
request.type_filling = GetOrderFillingPolicy(position_symbol); // 注文執行ポリシー
|
||||
request.magic = magic_number;
|
||||
|
||||
// 注文送信
|
||||
if(!OrderSend(request, trade_result))
|
||||
{
|
||||
Print("Failed to close position. Ticket: ", request.position, " Error: ", GetLastError());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 結果の確認
|
||||
if(trade_result.retcode == TRADE_RETCODE_DONE || trade_result.retcode == TRADE_RETCODE_DONE_PARTIAL)
|
||||
{
|
||||
Print("Position closed successfully due to a time limit. Ticket: ", request.position);
|
||||
result = true; // 少なくとも1つ成功した場合
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Failed to close position. Ticket: ", request.position, " Retcode: ", trade_result.retcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Failed to select position at index ", i, ". Error: ", GetLastError());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,760 @@
|
||||
#ifndef SLTP_MANAGER_MQH
|
||||
#define SLTP_MANAGER_MQH
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
/// @brief 同一シンボル・同一マジックナンバーの建玉に対して、SLを段階的に引き上げる管理クラス。
|
||||
/// @details 建値移動、アクティブトレーリング、TP進捗率ベースのSL固定をまとめて評価し、
|
||||
/// 現在のSLより有利な候補だけを `CTrade::PositionModify` で反映する。
|
||||
class CSLTPManager
|
||||
{
|
||||
private:
|
||||
ulong m_magic;
|
||||
string m_symbol;
|
||||
ulong m_deviation_points;
|
||||
CTrade m_trade;
|
||||
|
||||
bool m_use_breakeven;
|
||||
double m_breakeven_trigger_pips;
|
||||
double m_breakeven_buffer_pips;
|
||||
|
||||
bool m_use_elapsed_breakeven;
|
||||
double m_elapsed_breakeven_hours;
|
||||
double m_elapsed_breakeven_buffer_pips;
|
||||
|
||||
bool m_use_active_trailing;
|
||||
double m_active_breakeven_pips;
|
||||
double m_active_stop_loss_offset_pips;
|
||||
double m_active_step_trigger_pips;
|
||||
double m_active_step_move_pips;
|
||||
|
||||
bool m_use_tp_progress_stop;
|
||||
double m_tp_progress_trigger_percent;
|
||||
double m_tp_progress_sl_lock_percent;
|
||||
|
||||
bool m_use_high_volatility_limit;
|
||||
|
||||
public:
|
||||
/// @brief SL/TP管理クラスの初期状態を構築する。
|
||||
/// @details デフォルトでは現在チャートのシンボルを対象にし、すべてのSL移動ロジックを無効化する。
|
||||
CSLTPManager()
|
||||
{
|
||||
m_magic = 0;
|
||||
m_symbol = _Symbol;
|
||||
m_deviation_points = 10;
|
||||
|
||||
m_use_breakeven = false;
|
||||
m_breakeven_trigger_pips = 0.0;
|
||||
m_breakeven_buffer_pips = 0.0;
|
||||
|
||||
m_use_elapsed_breakeven = false;
|
||||
m_elapsed_breakeven_hours = 0.0;
|
||||
m_elapsed_breakeven_buffer_pips = 0.0;
|
||||
|
||||
m_use_active_trailing = false;
|
||||
m_active_breakeven_pips = 0.0;
|
||||
m_active_stop_loss_offset_pips = 0.0;
|
||||
m_active_step_trigger_pips = 0.0;
|
||||
m_active_step_move_pips = 0.0;
|
||||
|
||||
m_use_tp_progress_stop = false;
|
||||
m_tp_progress_trigger_percent = 0.0;
|
||||
m_tp_progress_sl_lock_percent = 0.0;
|
||||
|
||||
m_use_high_volatility_limit = false;
|
||||
}
|
||||
|
||||
/// @brief 管理対象ポジションのマジックナンバーを設定する。
|
||||
/// @param magic 対象EAに割り当てられたマジックナンバー。
|
||||
/// @details `CTrade` にも同じマジックナンバーを渡し、注文変更ログと実行元を揃える。
|
||||
void SetMagicNumber(const ulong magic)
|
||||
{
|
||||
m_magic = magic;
|
||||
m_trade.SetExpertMagicNumber(m_magic);
|
||||
}
|
||||
|
||||
/// @brief 管理対象のシンボルを設定する。
|
||||
/// @param symbol `_Symbol` または任意の取引シンボル名。
|
||||
void SetSymbol(const string symbol)
|
||||
{
|
||||
m_symbol = symbol;
|
||||
}
|
||||
|
||||
/// @brief SL変更注文に使用する許容スリッページをポイント単位で設定する。
|
||||
/// @param deviation_points `CTrade::SetDeviationInPoints` に渡すポイント数。
|
||||
void SetDeviationInPoints(const ulong deviation_points)
|
||||
{
|
||||
m_deviation_points = deviation_points;
|
||||
m_trade.SetDeviationInPoints((int)m_deviation_points);
|
||||
}
|
||||
|
||||
/// @brief 建値移動ロジックの有効化とバッファー幅を設定する。
|
||||
/// @param use_breakeven true の場合、利益が閾値を超えた時にSLを建値付近へ移動する。
|
||||
/// @param breakeven_trigger_pips 建値移動を開始する含み益のpips。
|
||||
/// @param breakeven_buffer_pips 建値から利益方向へずらして固定するバッファーpips。
|
||||
void SetBreakevenSettings(const bool use_breakeven,
|
||||
const double breakeven_trigger_pips,
|
||||
const double breakeven_buffer_pips)
|
||||
{
|
||||
m_use_breakeven = use_breakeven;
|
||||
m_breakeven_trigger_pips = breakeven_trigger_pips;
|
||||
m_breakeven_buffer_pips = breakeven_buffer_pips;
|
||||
}
|
||||
|
||||
/// @brief 保有時間ベースの建値移動ロジックを設定する。
|
||||
/// @param use_elapsed_breakeven true の場合、保有時間と建値超過を条件にSLを建値付近へ移動する。
|
||||
/// @param elapsed_breakeven_hours 建値移動を許可するまでの保有時間。小数指定も可能。
|
||||
/// @param elapsed_breakeven_buffer_pips 建値から利益方向へずらして固定するバッファーpips。
|
||||
void SetElapsedBreakevenSettings(const bool use_elapsed_breakeven,
|
||||
const double elapsed_breakeven_hours,
|
||||
const double elapsed_breakeven_buffer_pips)
|
||||
{
|
||||
m_use_elapsed_breakeven = use_elapsed_breakeven;
|
||||
m_elapsed_breakeven_hours = elapsed_breakeven_hours;
|
||||
m_elapsed_breakeven_buffer_pips = elapsed_breakeven_buffer_pips;
|
||||
}
|
||||
|
||||
/// @brief アクティブトレーリングロジックの有効化と段階移動幅を設定する。
|
||||
/// @param use_active_trailing true の場合、利益進捗に応じてSLを段階的に更新する。
|
||||
/// @param active_breakeven_pips トレーリングを開始する含み益のpips。
|
||||
/// @param active_stop_loss_offset_pips 開始時点で建値から固定する初期pips。
|
||||
/// @param active_step_trigger_pips SLを次段階へ進めるために必要な追加利益pips。
|
||||
/// @param active_step_move_pips 1段階ごとにSLを利益方向へ動かすpips。
|
||||
void SetActiveTrailingSettings(const bool use_active_trailing,
|
||||
const double active_breakeven_pips,
|
||||
const double active_stop_loss_offset_pips,
|
||||
const double active_step_trigger_pips,
|
||||
const double active_step_move_pips)
|
||||
{
|
||||
m_use_active_trailing = use_active_trailing;
|
||||
m_active_breakeven_pips = active_breakeven_pips;
|
||||
m_active_stop_loss_offset_pips = active_stop_loss_offset_pips;
|
||||
m_active_step_trigger_pips = active_step_trigger_pips;
|
||||
m_active_step_move_pips = active_step_move_pips;
|
||||
}
|
||||
|
||||
/// @brief TP到達進捗率を基準にSLを固定するロジックを設定する。
|
||||
/// @param use_tp_progress_stop true の場合、現在価格がTP距離の一定割合へ到達した時にSLを固定する。
|
||||
/// @param tp_progress_trigger_percent SL固定を開始するTP進捗率。範囲は `(0, 100]`。
|
||||
/// @param tp_progress_sl_lock_percent TP距離のうちSLで固定する割合。範囲は `[0, 100]`。
|
||||
void SetTpProgressStopSettings(const bool use_tp_progress_stop,
|
||||
const double tp_progress_trigger_percent,
|
||||
const double tp_progress_sl_lock_percent)
|
||||
{
|
||||
m_use_tp_progress_stop = use_tp_progress_stop;
|
||||
m_tp_progress_trigger_percent = tp_progress_trigger_percent;
|
||||
m_tp_progress_sl_lock_percent = tp_progress_sl_lock_percent;
|
||||
}
|
||||
|
||||
/// @brief 高ボラティリティ時のSL引き締めロジックを有効化/無効化する。
|
||||
/// @param use_high_volatility_limit true の場合、HighVolatilityLimit() 呼び出し時に急変幅ベースでSLを更新する。
|
||||
void SetHighVolatilityLimitSettings(const bool use_high_volatility_limit)
|
||||
{
|
||||
m_use_high_volatility_limit = use_high_volatility_limit;
|
||||
}
|
||||
|
||||
/// @brief 設定値とシンボル状態が実行可能か検証する。
|
||||
/// @return すべての設定が有効で、シンボル選択とpips換算が可能な場合は true。
|
||||
/// @details 建値移動とアクティブトレーリングの同時有効化など、矛盾する設定を事前に弾く。
|
||||
bool ValidateSettings()
|
||||
{
|
||||
if (m_symbol == "")
|
||||
{
|
||||
Print("SLTPManager settings error: symbol is empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SymbolSelect(m_symbol, true))
|
||||
{
|
||||
Print("SLTPManager settings error: failed to select symbol=", m_symbol,
|
||||
" err=", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_use_breakeven && m_use_active_trailing)
|
||||
{
|
||||
Print("SLTPManager settings error: use_breakeven and use_active_trailing cannot both be true.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_use_breakeven)
|
||||
{
|
||||
if (m_breakeven_trigger_pips < 0.0 || m_breakeven_buffer_pips < 0.0)
|
||||
{
|
||||
Print("SLTPManager settings error: breakeven trigger and buffer pips must be non-negative.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_use_elapsed_breakeven)
|
||||
{
|
||||
if (m_elapsed_breakeven_hours < 0.0 || m_elapsed_breakeven_buffer_pips < 0.0)
|
||||
{
|
||||
Print("SLTPManager settings error: elapsed breakeven hours and buffer pips must be non-negative.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_use_active_trailing)
|
||||
{
|
||||
if (m_active_breakeven_pips < 0.0 ||
|
||||
m_active_stop_loss_offset_pips < 0.0 ||
|
||||
m_active_step_trigger_pips <= 0.0 ||
|
||||
m_active_step_move_pips <= 0.0)
|
||||
{
|
||||
Print("SLTPManager settings error: active trailing requires non-negative breakeven/offset and positive step values.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_use_tp_progress_stop)
|
||||
{
|
||||
if (m_tp_progress_trigger_percent <= 0.0 ||
|
||||
m_tp_progress_trigger_percent > 100.0 ||
|
||||
m_tp_progress_sl_lock_percent < 0.0 ||
|
||||
m_tp_progress_sl_lock_percent > 100.0)
|
||||
{
|
||||
Print("SLTPManager settings error: TP progress percentages must be trigger=(0,100], lock=[0,100].");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (PipSize() <= 0.0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief 現在保有中の対象ポジションを走査し、必要に応じてSLを更新する。
|
||||
/// @details 対象は `m_magic` と `m_symbol` が一致するポジションのみ。複数のSL候補がある場合、
|
||||
/// BUYでは最も高いSL、SELLでは最も低いSLを採用し、既存SLより不利な変更は行わない。
|
||||
void ManagePositions()
|
||||
{
|
||||
const double pip_size = PipSize();
|
||||
if (pip_size <= 0.0)
|
||||
return;
|
||||
|
||||
MqlTick tick;
|
||||
if (!SymbolInfoTick(m_symbol, tick))
|
||||
{
|
||||
Print("SLTPManager tick read failed: symbol=", m_symbol, " err=", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
const int digits = (int)SymbolInfoInteger(m_symbol, SYMBOL_DIGITS);
|
||||
const datetime current_time = tick.time;
|
||||
const int pos_total = PositionsTotal();
|
||||
|
||||
for (int i = pos_total - 1; i >= 0; --i)
|
||||
{
|
||||
const ulong ticket = PositionGetTicket(i);
|
||||
if (!PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if (!IsTargetPosition())
|
||||
continue;
|
||||
|
||||
const ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
if (type != POSITION_TYPE_BUY && type != POSITION_TYPE_SELL)
|
||||
continue;
|
||||
|
||||
const double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
const double current_sl = PositionGetDouble(POSITION_SL);
|
||||
const double current_tp = PositionGetDouble(POSITION_TP);
|
||||
const double market_price = (type == POSITION_TYPE_BUY) ? tick.bid : tick.ask;
|
||||
const double profit_pips = GetProfitPips(type, open_price, market_price, pip_size);
|
||||
const datetime open_time = (datetime)PositionGetInteger(POSITION_TIME);
|
||||
|
||||
bool has_candidate = false;
|
||||
double candidate_sl = 0.0;
|
||||
double sl_value = 0.0;
|
||||
|
||||
if (CalcBreakevenSL(type, open_price, profit_pips, pip_size, digits, sl_value))
|
||||
UpdateBestCandidate(type, sl_value, has_candidate, candidate_sl);
|
||||
|
||||
if (CalcElapsedBreakevenSL(type, open_price, profit_pips, open_time, current_time, pip_size, digits, sl_value))
|
||||
UpdateBestCandidate(type, sl_value, has_candidate, candidate_sl);
|
||||
|
||||
if (CalcActiveTrailingSL(type, open_price, profit_pips, pip_size, digits, sl_value))
|
||||
UpdateBestCandidate(type, sl_value, has_candidate, candidate_sl);
|
||||
|
||||
if (CalcTpProgressSL(type, open_price, current_tp, market_price, digits, sl_value))
|
||||
UpdateBestCandidate(type, sl_value, has_candidate, candidate_sl);
|
||||
|
||||
if (!has_candidate)
|
||||
continue;
|
||||
|
||||
candidate_sl = NormalizeDouble(candidate_sl, digits);
|
||||
|
||||
if (!IsBetterStopLoss(type, candidate_sl, current_sl))
|
||||
continue;
|
||||
|
||||
if (!IsStopDistanceAllowed(type, market_price, candidate_sl))
|
||||
continue;
|
||||
|
||||
ModifyStopLoss(ticket, candidate_sl, current_tp);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief 短時間で大きな価格変動が発生した場合にSLを引き締める。
|
||||
/// @details M1/M3/M5/M10/M15 の始値から現在Bidまでの変動幅を見て、
|
||||
/// しきい値を超えた場合だけ利益方向へSL候補を作る。ManagePositions() とは独立して呼び出す。
|
||||
void HighVolatilityLimit()
|
||||
{
|
||||
if (!m_use_high_volatility_limit)
|
||||
return;
|
||||
|
||||
const double pip_size = PipSize();
|
||||
if (pip_size <= 0.0)
|
||||
return;
|
||||
|
||||
MqlTick tick;
|
||||
if (!SymbolInfoTick(m_symbol, tick))
|
||||
{
|
||||
Print("SLTPManager tick read failed: symbol=", m_symbol, " err=", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
const int digits = (int)SymbolInfoInteger(m_symbol, SYMBOL_DIGITS);
|
||||
const double open_m1 = iOpen(m_symbol, PERIOD_M1, 0);
|
||||
const double open_m3 = iOpen(m_symbol, PERIOD_M3, 0);
|
||||
const double open_m5 = iOpen(m_symbol, PERIOD_M5, 0);
|
||||
const double open_m10 = iOpen(m_symbol, PERIOD_M10, 0);
|
||||
const double open_m15 = iOpen(m_symbol, PERIOD_M15, 0);
|
||||
const int pos_total = PositionsTotal();
|
||||
|
||||
for (int i = pos_total - 1; i >= 0; --i)
|
||||
{
|
||||
const ulong ticket = PositionGetTicket(i);
|
||||
if (!PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if (!IsTargetPosition())
|
||||
continue;
|
||||
|
||||
const ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
if (type != POSITION_TYPE_BUY && type != POSITION_TYPE_SELL)
|
||||
continue;
|
||||
|
||||
double candidate_sl = 0.0;
|
||||
if (!CalcHighVolatilitySL(type,
|
||||
tick.bid,
|
||||
open_m1,
|
||||
open_m3,
|
||||
open_m5,
|
||||
open_m10,
|
||||
open_m15,
|
||||
pip_size,
|
||||
digits,
|
||||
candidate_sl))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const double current_sl = PositionGetDouble(POSITION_SL);
|
||||
const double current_tp = PositionGetDouble(POSITION_TP);
|
||||
const double market_price = (type == POSITION_TYPE_BUY) ? tick.bid : tick.ask;
|
||||
|
||||
if (!IsBetterStopLoss(type, candidate_sl, current_sl))
|
||||
continue;
|
||||
|
||||
if (!IsStopDistanceAllowed(type, market_price, candidate_sl))
|
||||
continue;
|
||||
|
||||
ModifyStopLoss(ticket, candidate_sl, current_tp);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/// @brief 現在選択中のポジションが管理対象か判定する。
|
||||
/// @return マジックナンバーとシンボルが一致する場合は true。
|
||||
bool IsTargetPosition()
|
||||
{
|
||||
if ((ulong)PositionGetInteger(POSITION_MAGIC) != m_magic)
|
||||
return false;
|
||||
if (PositionGetString(POSITION_SYMBOL) != m_symbol)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief 対象シンボルの1 pip相当の価格幅を取得する。
|
||||
/// @return 1 pip の価格幅。取得できない場合は 0.0。
|
||||
/// @details GOLD/XAUUSD は 0.1、2/3桁は0.01、4/5桁は0.0001を1 pipとして扱う。
|
||||
double PipSize()
|
||||
{
|
||||
if (m_symbol == "XAUUSD" || m_symbol == "GOLD")
|
||||
return 0.1;
|
||||
|
||||
const int digits = (int)SymbolInfoInteger(m_symbol, SYMBOL_DIGITS);
|
||||
|
||||
switch (digits)
|
||||
{
|
||||
case 2:
|
||||
case 3:
|
||||
return 0.01;
|
||||
case 4:
|
||||
case 5:
|
||||
return 0.0001;
|
||||
}
|
||||
|
||||
Print("SLTPManager pip conversion error: unsupported symbol digits. symbol=", m_symbol,
|
||||
" digits=", digits);
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/// @brief 現在価格から建値に対する含み益をpipsで計算する。
|
||||
/// @param type ポジション種別。BUY/SELLで計算方向を切り替える。
|
||||
/// @param open_price 建値。
|
||||
/// @param market_price BUYはBid、SELLはAskを想定した現在価格。
|
||||
/// @param pip_size `PipSize()` で取得した1 pipの価格幅。
|
||||
/// @return 利益方向なら正、損失方向なら負のpips。
|
||||
double GetProfitPips(const ENUM_POSITION_TYPE type,
|
||||
const double open_price,
|
||||
const double market_price,
|
||||
const double pip_size)
|
||||
{
|
||||
if (pip_size <= 0.0)
|
||||
return 0.0;
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
return (market_price - open_price) / pip_size;
|
||||
|
||||
return (open_price - market_price) / pip_size;
|
||||
}
|
||||
|
||||
/// @brief 建値移動ロジックに基づくSL候補を計算する。
|
||||
/// @param type ポジション種別。
|
||||
/// @param open_price 建値。
|
||||
/// @param profit_pips 現在の含み益pips。
|
||||
/// @param pip_size 1 pipの価格幅。
|
||||
/// @param digits 対象シンボルの小数桁数。
|
||||
/// @param sl_value 計算に成功したSL候補の出力先。
|
||||
/// @return 建値移動条件を満たし、SL候補を出力した場合は true。
|
||||
/// @details BUYでは建値 + バッファー、SELLでは建値 - バッファーへSLを置き、
|
||||
/// スプレッドや手数料分を考慮した利益保護ラインとして扱う。
|
||||
bool CalcBreakevenSL(const ENUM_POSITION_TYPE type,
|
||||
const double open_price,
|
||||
const double profit_pips,
|
||||
const double pip_size,
|
||||
const int digits,
|
||||
double &sl_value)
|
||||
{
|
||||
if (!m_use_breakeven)
|
||||
return false;
|
||||
if (profit_pips < m_breakeven_trigger_pips)
|
||||
return false;
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
sl_value = open_price + m_breakeven_buffer_pips * pip_size;
|
||||
else
|
||||
sl_value = open_price - m_breakeven_buffer_pips * pip_size;
|
||||
|
||||
sl_value = NormalizeDouble(sl_value, digits);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief 保有時間ベースの建値移動ロジックに基づくSL候補を計算する。
|
||||
/// @param type ポジション種別。
|
||||
/// @param open_price 建値。
|
||||
/// @param profit_pips 現在の含み益pips。現在レートが建値を超えているかの判定に使う。
|
||||
/// @param open_time ポジションの約定時刻。
|
||||
/// @param current_time 評価時点のサーバー時刻。
|
||||
/// @param pip_size 1 pipの価格幅。
|
||||
/// @param digits 対象シンボルの小数桁数。
|
||||
/// @param sl_value 計算に成功したSL候補の出力先。
|
||||
/// @return 保有時間と建値超過条件を満たし、SL候補を出力した場合は true。
|
||||
/// @details 通常ブレークイーブンとは独立した候補として評価し、長時間保有ポジションの損失化を防ぐ。
|
||||
bool CalcElapsedBreakevenSL(const ENUM_POSITION_TYPE type,
|
||||
const double open_price,
|
||||
const double profit_pips,
|
||||
const datetime open_time,
|
||||
const datetime current_time,
|
||||
const double pip_size,
|
||||
const int digits,
|
||||
double &sl_value)
|
||||
{
|
||||
if (!m_use_elapsed_breakeven)
|
||||
return false;
|
||||
if (open_time <= 0 || current_time <= open_time)
|
||||
return false;
|
||||
if (profit_pips <= 0.0)
|
||||
return false;
|
||||
|
||||
const double required_seconds = m_elapsed_breakeven_hours * 3600.0;
|
||||
const double held_seconds = (double)(current_time - open_time);
|
||||
if (held_seconds < required_seconds)
|
||||
return false;
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
sl_value = open_price + m_elapsed_breakeven_buffer_pips * pip_size;
|
||||
else
|
||||
sl_value = open_price - m_elapsed_breakeven_buffer_pips * pip_size;
|
||||
|
||||
sl_value = NormalizeDouble(sl_value, digits);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief アクティブトレーリングロジックに基づくSL候補を計算する。
|
||||
/// @param type ポジション種別。
|
||||
/// @param open_price 建値。
|
||||
/// @param profit_pips 現在の含み益pips。
|
||||
/// @param pip_size 1 pipの価格幅。
|
||||
/// @param digits 対象シンボルの小数桁数。
|
||||
/// @param sl_value 計算に成功したSL候補の出力先。
|
||||
/// @return トレーリング開始条件を満たし、SL候補を出力した場合は true。
|
||||
/// @details 開始利益からの超過分をステップ数へ変換し、固定済み利益幅を段階的に増やす。
|
||||
bool CalcActiveTrailingSL(const ENUM_POSITION_TYPE type,
|
||||
const double open_price,
|
||||
const double profit_pips,
|
||||
const double pip_size,
|
||||
const int digits,
|
||||
double &sl_value)
|
||||
{
|
||||
if (!m_use_active_trailing)
|
||||
return false;
|
||||
if (profit_pips < m_active_breakeven_pips)
|
||||
return false;
|
||||
|
||||
int trail_steps = (int)MathFloor((profit_pips - m_active_breakeven_pips) / m_active_step_trigger_pips);
|
||||
if (trail_steps < 0)
|
||||
trail_steps = 0;
|
||||
|
||||
const double locked_pips = m_active_stop_loss_offset_pips + trail_steps * m_active_step_move_pips;
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
sl_value = open_price + locked_pips * pip_size;
|
||||
else
|
||||
sl_value = open_price - locked_pips * pip_size;
|
||||
|
||||
sl_value = NormalizeDouble(sl_value, digits);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief TP進捗率ベースのSL候補を計算する。
|
||||
/// @param type ポジション種別。
|
||||
/// @param open_price 建値。
|
||||
/// @param current_tp 現在設定されているTP価格。未設定の場合は 0.0。
|
||||
/// @param market_price BUYはBid、SELLはAskを想定した現在価格。
|
||||
/// @param digits 対象シンボルの小数桁数。
|
||||
/// @param sl_value 計算に成功したSL候補の出力先。
|
||||
/// @return TP方向と進捗率が有効で、SL候補を出力した場合は true。
|
||||
bool CalcTpProgressSL(const ENUM_POSITION_TYPE type,
|
||||
const double open_price,
|
||||
const double current_tp,
|
||||
const double market_price,
|
||||
const int digits,
|
||||
double &sl_value)
|
||||
{
|
||||
if (!m_use_tp_progress_stop)
|
||||
return false;
|
||||
if (current_tp == 0.0)
|
||||
return false;
|
||||
|
||||
double tp_distance = 0.0;
|
||||
double progressed_distance = 0.0;
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
{
|
||||
if (current_tp <= open_price)
|
||||
return false;
|
||||
|
||||
tp_distance = current_tp - open_price;
|
||||
progressed_distance = market_price - open_price;
|
||||
sl_value = open_price + tp_distance * (m_tp_progress_sl_lock_percent / 100.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (current_tp >= open_price)
|
||||
return false;
|
||||
|
||||
tp_distance = open_price - current_tp;
|
||||
progressed_distance = open_price - market_price;
|
||||
sl_value = open_price - tp_distance * (m_tp_progress_sl_lock_percent / 100.0);
|
||||
}
|
||||
|
||||
if (tp_distance <= 0.0)
|
||||
return false;
|
||||
|
||||
const double progress_percent = (progressed_distance / tp_distance) * 100.0;
|
||||
if (progress_percent < m_tp_progress_trigger_percent)
|
||||
return false;
|
||||
|
||||
sl_value = NormalizeDouble(sl_value, digits);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief 高ボラティリティ判定に基づくSL候補を計算する。
|
||||
/// @return いずれかの時間足でしきい値を超え、SL候補を出力した場合は true。
|
||||
bool CalcHighVolatilitySL(const ENUM_POSITION_TYPE type,
|
||||
const double current_bid,
|
||||
const double open_m1,
|
||||
const double open_m3,
|
||||
const double open_m5,
|
||||
const double open_m10,
|
||||
const double open_m15,
|
||||
const double pip_size,
|
||||
const int digits,
|
||||
double &sl_value)
|
||||
{
|
||||
bool has_candidate = false;
|
||||
double candidate_sl = 0.0;
|
||||
const double rate_ratio = 0.9;
|
||||
|
||||
EvaluateHighVolatilityOpen(type, current_bid, open_m1, 100.0, rate_ratio, pip_size, digits, has_candidate, candidate_sl);
|
||||
EvaluateHighVolatilityOpen(type, current_bid, open_m3, 150.0, rate_ratio, pip_size, digits, has_candidate, candidate_sl);
|
||||
EvaluateHighVolatilityOpen(type, current_bid, open_m5, 200.0, rate_ratio, pip_size, digits, has_candidate, candidate_sl);
|
||||
EvaluateHighVolatilityOpen(type, current_bid, open_m10, 300.0, rate_ratio, pip_size, digits, has_candidate, candidate_sl);
|
||||
EvaluateHighVolatilityOpen(type, current_bid, open_m15, 400.0, rate_ratio, pip_size, digits, has_candidate, candidate_sl);
|
||||
|
||||
if (!has_candidate)
|
||||
return false;
|
||||
|
||||
sl_value = NormalizeDouble(candidate_sl, digits);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief 1本の時間足始値から急変幅を評価し、有効なSL候補なら最良候補へ反映する。
|
||||
void EvaluateHighVolatilityOpen(const ENUM_POSITION_TYPE type,
|
||||
const double current_bid,
|
||||
const double open_price,
|
||||
const double limit_pips,
|
||||
const double rate_ratio,
|
||||
const double pip_size,
|
||||
const int digits,
|
||||
bool &has_candidate,
|
||||
double &candidate_sl)
|
||||
{
|
||||
if (open_price <= 0.0 || pip_size <= 0.0)
|
||||
return;
|
||||
|
||||
double movement_pips = 0.0;
|
||||
double sl_value = 0.0;
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
{
|
||||
movement_pips = (current_bid - open_price) / pip_size;
|
||||
sl_value = open_price + limit_pips * rate_ratio * pip_size;
|
||||
}
|
||||
else if (type == POSITION_TYPE_SELL)
|
||||
{
|
||||
movement_pips = (open_price - current_bid) / pip_size;
|
||||
sl_value = open_price - limit_pips * rate_ratio * pip_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (movement_pips < limit_pips)
|
||||
return;
|
||||
|
||||
sl_value = NormalizeDouble(sl_value, digits);
|
||||
UpdateBestCandidate(type, sl_value, has_candidate, candidate_sl);
|
||||
}
|
||||
|
||||
/// @brief 複数ロジックから得たSL候補のうち、最も有利な値を保持する。
|
||||
/// @param type ポジション種別。BUYは高いSL、SELLは低いSLを優先する。
|
||||
/// @param sl_value 新しく評価するSL候補。
|
||||
/// @param has_candidate 既に候補が存在するかを示す入出力フラグ。
|
||||
/// @param candidate_sl 現在の最良SL候補の入出力値。
|
||||
void UpdateBestCandidate(const ENUM_POSITION_TYPE type,
|
||||
const double sl_value,
|
||||
bool &has_candidate,
|
||||
double &candidate_sl)
|
||||
{
|
||||
if (!has_candidate)
|
||||
{
|
||||
candidate_sl = sl_value;
|
||||
has_candidate = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
candidate_sl = MathMax(candidate_sl, sl_value);
|
||||
else
|
||||
candidate_sl = MathMin(candidate_sl, sl_value);
|
||||
}
|
||||
|
||||
/// @brief 新しいSLが既存SLより利益保護方向へ改善しているか判定する。
|
||||
/// @param type ポジション種別。
|
||||
/// @param new_sl 新しく設定したいSL価格。
|
||||
/// @param current_sl 現在のSL価格。未設定の場合は 0.0。
|
||||
/// @return 未設定または利益保護方向へ十分に改善している場合は true。
|
||||
/// @details 小数誤差による微小な再変更を避けるため、pointの10%を許容差として使う。
|
||||
bool IsBetterStopLoss(const ENUM_POSITION_TYPE type,
|
||||
const double new_sl,
|
||||
const double current_sl)
|
||||
{
|
||||
const double point = SymbolInfoDouble(m_symbol, SYMBOL_POINT);
|
||||
const double tolerance = point * 0.1;
|
||||
|
||||
if (current_sl == 0.0)
|
||||
return true;
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
return (new_sl > current_sl + tolerance);
|
||||
|
||||
return (new_sl < current_sl - tolerance);
|
||||
}
|
||||
|
||||
/// @brief ブローカーのストップレベルとフリーズレベルに対してSL変更が可能か判定する。
|
||||
/// @param type ポジション種別。
|
||||
/// @param market_price BUYはBid、SELLはAskを想定した現在価格。
|
||||
/// @param new_sl 新しく設定したいSL価格。
|
||||
/// @return 現在価格から必要距離を確保できている場合は true。
|
||||
bool IsStopDistanceAllowed(const ENUM_POSITION_TYPE type,
|
||||
const double market_price,
|
||||
const double new_sl)
|
||||
{
|
||||
const double point = SymbolInfoDouble(m_symbol, SYMBOL_POINT);
|
||||
const int stops_level = (int)SymbolInfoInteger(m_symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
const int freeze_level = (int)SymbolInfoInteger(m_symbol, SYMBOL_TRADE_FREEZE_LEVEL);
|
||||
const double stop_gap = stops_level * point;
|
||||
const double freeze_gap = freeze_level * point;
|
||||
|
||||
if (point <= 0.0)
|
||||
return false;
|
||||
|
||||
if (type == POSITION_TYPE_BUY)
|
||||
{
|
||||
if (new_sl > market_price - stop_gap)
|
||||
return false;
|
||||
if (freeze_level > 0 && new_sl > market_price - freeze_gap)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (new_sl < market_price + stop_gap)
|
||||
return false;
|
||||
if (freeze_level > 0 && new_sl < market_price + freeze_gap)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief 対象チケットのSLを更新し、TPは現在値を維持する。
|
||||
/// @param ticket 変更対象ポジションのチケット番号。
|
||||
/// @param new_sl 新しいSL価格。
|
||||
/// @param current_tp 変更前のTP価格。
|
||||
/// @return `CTrade::PositionModify` が成功した場合は true。
|
||||
/// @details 失敗時はretcode、GetLastError、設定しようとしたSLをログ出力する。
|
||||
bool ModifyStopLoss(const ulong ticket,
|
||||
const double new_sl,
|
||||
const double current_tp)
|
||||
{
|
||||
ResetLastError();
|
||||
if (!m_trade.PositionModify(ticket, new_sl, current_tp))
|
||||
{
|
||||
Print("SLTPManager SL modify failed: ticket=", ticket,
|
||||
" symbol=", m_symbol,
|
||||
" magic=", m_magic,
|
||||
" retcode=", m_trade.ResultRetcode(),
|
||||
" err=", GetLastError(),
|
||||
" new_sl=", DoubleToString(new_sl, (int)SymbolInfoInteger(m_symbol, SYMBOL_DIGITS)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user