HOW PendingBreakoutOrchestrator WORKS - Detailed AnalysisΒΆ
π― Document PurposeΒΆ
Show WHAT the orchestrator consists of and HOW EXACTLY it works at the code, methods and data level. Special attention is paid to the breakout detection logic and OCO mechanism.
π¦ What the orchestrator is made ofΒΆ
1. Class structure (lines 13-27)ΒΆ
public class PendingBreakoutOrchestrator
{
// β SINGLE DEPENDENCY
// ββββββββββββββββββββββββββββββββββββββββββ
private readonly MT5Service _service;
// β 6 CONFIGURABLE PARAMETERS
// ββββββββββββββββββββββββββββββββββββββββββ
public string Symbol { get; set; } = "EURUSD";
public int BreakoutDistancePoints { get; set; } = 25;
public int StopLossPoints { get; set; } = 15;
public int TakeProfitPoints { get; set; } = 30;
public double Volume { get; set; } = 0.01;
public int MaxWaitMinutes { get; set; } = 30;
// β DEPENDENCY INJECTION
// ββββββββββββββββββββββββββββββββββββββββββ
public PendingBreakoutOrchestrator(MT5Service service)
{
_service = service;
}
}
Dependency visualizationΒΆ
β PendingBreakoutOrchestrator
β
β β private readonly MT5Service _service
β ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββ
βββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββ
β
βΌ
β MT5Service
β
β β private MT5Account _account
β ββββββββββββββββ¬ββββββββββββββββ
βββββββββββββββββββΌββββββββββββββββββββ
β
βΌ
β MT5Account
β
β β gRPC Client
β ββββββββββββββββββββ
ββββββββββββββββββββββββββ
β
βΌ
[MT5 Terminal]
π How ExecuteAsync() works - step by stepΒΆ
Phase 1: Initialization (lines 31-46)ΒΆ
public async Task<double> ExecuteAsync(CancellationToken ct = default)
{
// β STEP 1: Output header
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Console.WriteLine("\n+============================================================+");
Console.WriteLine("| PENDING BREAKOUT ORCHESTRATOR |");
Console.WriteLine("+============================================================+\n");
// β STEP 2: Get initial balance
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var initialBalance = await _service.GetBalanceAsync();
Console.WriteLine($" Starting balance: ${initialBalance:F2}");
// β STEP 3: Get current price
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var tick = await _service.SymbolInfoTickAsync(Symbol);
Console.WriteLine($" Current: Bid={tick.Bid:F5}, Ask={tick.Ask:F5}\n");
}
Phase 2: Placing BuyStop (lines 48-65)ΒΆ
// β PLACING BUY STOP (upward breakout)
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Console.WriteLine(" Placing BUY STOP order...");
var buyStopResult = await _service.BuyStopPoints(
symbol: Symbol, // "EURUSD"
volume: Volume, // 0.01
priceOffsetPoints: BreakoutDistancePoints, // +25 (POSITIVE!)
slPoints: StopLossPoints, // 15
tpPoints: TakeProfitPoints, // 30
comment: "Breakout-Buy"
);
// β RESULT CHECK
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if (buyStopResult.ReturnedCode != 10009)
{
Console.WriteLine($" β BUY STOP failed: {buyStopResult.Comment}\n");
return 0; // β EMERGENCY EXIT
}
Console.WriteLine($" β BUY STOP placed: #{buyStopResult.Order}\n");
How BuyStopPoints() works internallyΒΆ
// MT5Sugar.cs (extension method)
public static async Task<OrderSendData> BuyStopPoints(
this MT5Service service,
string symbol,
double volume,
int priceOffsetPoints, // β RECEIVES +25
int slPoints = 0,
int tpPoints = 0,
string comment = ""
)
{
// β STEP 1: Get current Ask price
// β For BUY STOP use ASK (buy price)
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var tick = await service.SymbolInfoTickAsync(symbol);
double askPrice = tick.Ask; // For example: 1.10002
// β STEP 2: Get point size
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var symbolInfo = await service.SymbolInfoAsync(symbol);
double point = symbolInfo.Point; // For EURUSD: 0.00001
// β STEP 3: Calculate BUY STOP price
// β IMPORTANT: priceOffsetPoints POSITIVE (+25)
// β BUY STOP is placed ABOVE current price
// β
// β price = askPrice + (priceOffsetPoints Γ point)
// β = 1.10002 + (25 Γ 0.00001)
// β = 1.10002 + 0.00025
// β = 1.10027
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
double price = askPrice + (priceOffsetPoints * point);
// β STEP 4: Calculate SL and TP for BUY
// β SL below entry price (protection from fall)
// β TP above entry price (profit taking)
// β
// β sl = price - (slPoints Γ point)
// β = 1.10027 - (15 Γ 0.00001)
// β = 1.10027 - 0.00015
// β = 1.10012
// β
// β tp = price + (tpPoints Γ point)
// β = 1.10027 + (30 Γ 0.00001)
// β = 1.10027 + 0.00030
// β = 1.10057
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
double sl = slPoints > 0 ? price - (slPoints * point) : 0;
double tp = tpPoints > 0 ? price + (tpPoints * point) : 0;
// β STEP 5: Call low-level method
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
return await service.BuyStopAsync(
symbol: symbol, // "EURUSD"
volume: volume, // 0.01
price: price, // 1.10027
sl: sl, // 1.10012
tp: tp, // 1.10057
comment: comment // "Breakout-Buy"
);
}
Complete call chain for BuyStopΒΆ
β USER CODE (PendingBreakoutOrchestrator.cs:50)
β await _service.BuyStopPoints(
β symbol: "EURUSD",
β volume: 0.01,
β priceOffsetPoints: +25, β POINTS (POSITIVE)
β slPoints: 15,
β tpPoints: 30,
β comment: "Breakout-Buy"
β )
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
β MT5Sugar.BuyStopPoints() (extension method)
β
β β 1. Get Ask: 1.10002
β β 2. Get point: 0.00001
β β 3. Calculate price: 1.10002 + (25 Γ 0.00001) = 1.10027
β β 4. Calculate SL: 1.10027 - (15 Γ 0.00001) = 1.10012
β β 5. Calculate TP: 1.10027 + (30 Γ 0.00001) = 1.10057
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β await service.BuyStopAsync(
β price: 1.10027, β ABSOLUTE PRICE
β sl: 1.10012,
β tp: 1.10057
β )
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
β MT5Service.BuyStopAsync()
β return await _account.BuyStopAsync(...)
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
β MT5Account.BuyStopAsync()
β
β β var request = new OrderSendRequest {
β β Symbol = "EURUSD",
β β Volume = 0.01,
β β Type = ORDER_TYPE_BUY_STOP, // = 4
β β Price = 1.10027,
β β Sl = 1.10012,
β β Tp = 1.10057,
β β Comment = "Breakout-Buy"
β β }
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β var response = await _client.OrderSendAsync(request);
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
β gRPC NETWORK
ββββββββ¬βββββββ
β
βΌ
β MT5 Terminal
β
β β Places
β β BUY STOP
β β @ 1.10027
β β SL: 1.10012
β β TP: 1.10057
β β Ticket:
β β 123456789
β βββββββββββββββ
ββββββββββ¬βββββββββββ
β
βΌ
β RESPONSE
β OrderSendData
β {
β ReturnedCode
β = 10009,
β Order =
β 123456789
β }
βββββββββββββββ
Phase 3: Placing SellStop (lines 67-86)ΒΆ
// β PLACING SELL STOP (downward breakout)
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Console.WriteLine(" Placing SELL STOP order...");
var sellStopResult = await _service.SellStopPoints(
symbol: Symbol,
volume: Volume,
priceOffsetPoints: -BreakoutDistancePoints, // -25 (NEGATIVE!)
slPoints: StopLossPoints,
tpPoints: TakeProfitPoints,
comment: "Breakout-Sell"
);
// β CRITICALLY IMPORTANT CHECK
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if (sellStopResult.ReturnedCode != 10009)
{
Console.WriteLine($" β SELL STOP failed: {sellStopResult.Comment}");
Console.WriteLine(" Canceling BUY STOP...");
// β IMPORTANT: Cancel first order!
// β Otherwise hanging BuyStop remains without pair
// ββββββββββββββββββββββββββββββββββββββββββββββββββ
await _service.CloseByTicket(buyStopResult.Order);
return 0;
}
Console.WriteLine($" β SELL STOP placed: #{sellStopResult.Order}\n");
How SellStopPoints() works internallyΒΆ
// MT5Sugar.cs (extension method)
public static async Task<OrderSendData> SellStopPoints(
this MT5Service service,
string symbol,
double volume,
int priceOffsetPoints, // β RECEIVES -25
int slPoints = 0,
int tpPoints = 0,
string comment = ""
)
{
// β STEP 1: Get current Bid price
// β For SELL STOP use BID (sell price)
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var tick = await service.SymbolInfoTickAsync(symbol);
double bidPrice = tick.Bid; // For example: 1.10000
var symbolInfo = await service.SymbolInfoAsync(symbol);
double point = symbolInfo.Point; // 0.00001
// β STEP 2: Calculate SELL STOP price
// β IMPORTANT: priceOffsetPoints NEGATIVE (-25)
// β SELL STOP is placed BELOW current price
// β
// β price = bidPrice + (priceOffsetPoints Γ point)
// β = 1.10000 + (-25 Γ 0.00001)
// β = 1.10000 - 0.00025
// β = 1.09975
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
double price = bidPrice + (priceOffsetPoints * point);
// β STEP 3: Calculate SL and TP for SELL
// β SL ABOVE entry price (protection from rise)
// β TP BELOW entry price (profit taking)
// β
// β sl = price + (slPoints Γ point) β PLUS for SELL!
// β = 1.09975 + (15 Γ 0.00001)
// β = 1.09975 + 0.00015
// β = 1.09990
// β
// β tp = price - (tpPoints Γ point) β MINUS for SELL!
// β = 1.09975 - (30 Γ 0.00001)
// β = 1.09975 - 0.00030
// β = 1.09945
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
double sl = slPoints > 0 ? price + (slPoints * point) : 0; // + for SELL
double tp = tpPoints > 0 ? price - (tpPoints * point) : 0; // - for SELL
return await service.SellStopAsync(
symbol: symbol,
volume: volume,
price: price, // 1.09975
sl: sl, // 1.09990
tp: tp, // 1.09945
comment: comment
);
}
Result of placing both ordersΒΆ
After successfully placing both orders in MT5 Terminal:
PENDING ORDERS (2 orders):
ββββββββββ¬ββββββββββββββ¬ββββββββββ¬ββββββββββ¬ββββββββββ¬βββββββββββββββ
β Ticket β Type β Price β SL β TP β Comment β
ββββββββββΌββββββββββββββΌββββββββββΌββββββββββΌββββββββββΌβββββββββββββββ€
β ...789 β BUY STOP β 1.10027 β 1.10012 β 1.10057 β Breakout-Buy β
β ...790 β SELL STOP β 1.09975 β 1.09990 β 1.09945 β Breakout-Sellβ
ββββββββββ΄ββββββββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββββ΄βββββββββββββββ
VISUALIZATION:
β Price rises
β
1.10057 ββββ TP for BuyStop
1.10027 ββββ BUY STOP (upward breakout)
1.10012 ββββ SL for BuyStop
β
1.10002 ββββ Current Ask
1.10000 ββββ Current Bid
β
1.09990 ββββ SL for SellStop
1.09975 ββββ SELL STOP (downward breakout)
1.09945 ββββ TP for SellStop
β
β Price falls
Phase 4: BREAKOUT MONITORING (lines 89-129)ΒΆ
This is the most important part of the orchestrator - the breakout detection logic.
Console.WriteLine($" β³ Waiting up to {MaxWaitMinutes} minutes for breakout...\n");
// β MONITORING INITIALIZATION
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var startTime = DateTime.UtcNow;
var timeout = TimeSpan.FromMinutes(MaxWaitMinutes);
// Variables to track which order executed
ulong? executedOrder = null; // Ticket of executed order
ulong? cancelOrder = null; // Ticket of order to cancel
// β MAIN MONITORING LOOP
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
while (DateTime.UtcNow - startTime < timeout && !ct.IsCancellationRequested)
{
// β Wait 3 seconds before next check
// ββββββββββββββββββββββββββββββββββββββββββββββββββ
await Task.Delay(3000, ct);
// β STEP 1: Get list of PENDING orders
// β IMPORTANT: This is only PENDING orders!
// β Executed orders become positions
// ββββββββββββββββββββββββββββββββββββββββββββββββββ
var tickets = await _service.OpenedOrdersTicketsAsync();
bool buyStillPending = false;
bool sellStillPending = false;
// β STEP 2: Check if our orders are in the list
// ββββββββββββββββββββββββββββββββββββββββββββββββββ
foreach (var ticket in tickets.OpenedOrdersTickets)
{
if (ticket == (long)buyStopResult.Order) buyStillPending = true;
if (ticket == (long)sellStopResult.Order) sellStillPending = true;
}
// β STEP 3: BREAKOUT DETECTION (4 scenarios)
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// SCENARIO 1: UPWARD BREAKOUT
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if (!buyStillPending && sellStillPending)
{
// BuyStop DISAPPEARED from list β executed!
// SellStop STILL in list β not executed
Console.WriteLine(" π BUY STOP EXECUTED! Upward breakout!");
executedOrder = buyStopResult.Order;
cancelOrder = sellStopResult.Order; // β Need to cancel
break; // Exit loop
}
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// SCENARIO 2: DOWNWARD BREAKOUT
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
else if (buyStillPending && !sellStillPending)
{
// SellStop DISAPPEARED β executed!
// BuyStop STILL in list β not executed
Console.WriteLine(" π SELL STOP EXECUTED! Downward breakout!");
executedOrder = sellStopResult.Order;
cancelOrder = buyStopResult.Order; // β Need to cancel
break;
}
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// SCENARIO 3: BOTH EXECUTED (rare case)
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
else if (!buyStillPending && !sellStillPending)
{
// Both orders disappeared
// Possible causes:
// 1. Very strong volatility (both triggered)
// 2. Connection error (didn't get actual data)
// 3. Manual cancellation of both orders
Console.WriteLine(" β Both orders executed or canceled");
break;
}
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// SCENARIO 4: BOTH STILL PENDING (continue waiting)
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// else: both orders still in list β no breakout β continue loop
}
Detailed breakout detection logic visualizationΒΆ
β HOW OpenedOrdersTicketsAsync() WORKS
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
MT5 Terminal state:
ββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββ
β PENDING ORDERS β OPEN POSITIONS β
ββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββ€
β 789: BUY STOP β (empty) β
β 790: SELL STOP β β
ββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββ
OpenedOrdersTicketsAsync() returns:
{
OpenedOrdersTickets: [789, 790], β PENDING ORDERS
OpenedPositionTickets: [] β OPEN POSITIONS
}
foreach (var ticket in tickets.OpenedOrdersTickets)
{
if (ticket == 789) buyStillPending = true; // β found
if (ticket == 790) sellStillPending = true; // β found
}
Result: buyStillPending = true, sellStillPending = true
β Both orders still pending β no breakout β continue waiting
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
5 MINUTES LATER: Price rose to 1.10027
MT5 Terminal state:
ββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββ
β PENDING ORDERS β OPEN POSITIONS β
ββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββ€
β 790: SELL STOP β 789: BUY 0.01 EURUSD @ 1.10027 β
ββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββ
β
BuyStop EXECUTED β became POSITION!
OpenedOrdersTicketsAsync() returns:
{
OpenedOrdersTickets: [790], β only SellStop
OpenedPositionTickets: [789] β BuyStop became position
}
foreach (var ticket in tickets.OpenedOrdersTickets)
{
// ticket = 790
if (ticket == 789) buyStillPending = true; // β NOT found!
if (ticket == 790) sellStillPending = true; // β found
}
Result: buyStillPending = false, sellStillPending = true
if (!buyStillPending && sellStillPending) β TRUE!
{
Console.WriteLine("π BUY STOP EXECUTED! Upward breakout!");
cancelOrder = 790; β Need to cancel SellStop
break;
}
Phase 5: Canceling opposite order (lines 131-145)ΒΆ
// β CHECK: Did breakout happen?
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if (cancelOrder.HasValue)
{
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// BREAKOUT HAPPENED β Cancel opposite order
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Console.WriteLine($" Canceling opposite order #{cancelOrder.Value}...");
// β CloseByTicket - universal method:
// β - If ticket = pending order β CANCEL
// β - If ticket = position β CLOSE
// ββββββββββββββββββββββββββββββββββββββββββββββββββ
await _service.CloseByTicket(cancelOrder.Value);
Console.WriteLine(" β Canceled\n");
}
else
{
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// TIMEOUT β Breakout didn't happen β Cancel BOTH orders
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Console.WriteLine($" β± Timeout after {MaxWaitMinutes} minutes - canceling both orders...");
// β Cancel both orders separately
// ββββββββββββββββββββββββββββββββββββββββββββββββββ
await _service.CloseByTicket(buyStopResult.Order);
await _service.CloseByTicket(sellStopResult.Order);
Console.WriteLine(" β Both canceled\n");
}
How CloseByTicket() worksΒΆ
// MT5Sugar.cs (extension method)
public static async Task CloseByTicket(
this MT5Service service,
ulong ticket
)
{
// β STEP 1: Check ticket type (order or position?)
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// Attempt 1: Get as pending order
var orders = await service.OrdersAsync();
var order = orders.FirstOrDefault(o => o.Ticket == ticket);
if (order != null)
{
// β This is PENDING ORDER β CANCEL
// ββββββββββββββββββββββββββββββββββββββββββββββ
await service.OrderDeleteAsync(ticket);
return;
}
// Attempt 2: Get as open position
var positions = await service.PositionsAsync();
var position = positions.FirstOrDefault(p => p.Ticket == ticket);
if (position != null)
{
// β This is OPEN POSITION β CLOSE
// ββββββββββββββββββββββββββββββββββββββββββββββ
await service.PositionCloseAsync(ticket);
return;
}
// Ticket not found (already closed/canceled or doesn't exist)
}
Detailed order cancellation chainΒΆ
SITUATION: Upward breakout, need to cancel SellStop #790
USER CODE:
await _service.CloseByTicket(790);
β
βΌ
MT5Sugar.CloseByTicket():
β 1. OrdersAsync() β check pending orders
β 2. Found order #790 β this is PENDING ORDER
β 3. Call OrderDeleteAsync(790)
ββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
MT5Service.OrderDeleteAsync(790):
return await _account.OrderDeleteAsync(790);
β
βΌ
MT5Account.OrderDeleteAsync(790):
var request = new OrderDeleteRequest {
Ticket = 790
};
var response = await _client.OrderDeleteAsync(request);
β
βΌ
gRPC β MT5 Terminal
β
βΌ
MT5 Terminal:
β PENDING ORDERS
β β 790: SELL STOP @ 1.09975 (DELETED)
ββββββββββββββββββββββββββββββββββββββββββ
RESULT:
MT5 Terminal state:
ββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββ
β PENDING ORDERS β OPEN POSITIONS β
ββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββ€
β (empty) β 789: BUY 0.01 EURUSD @ 1.10027 β
ββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββ
Now only ONE position (BUY) is open.
SellStop successfully canceled β OCO strategy completed.
Phase 6: Finalization (lines 147-154)ΒΆ
// β Get final balance
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var finalBalance = await _service.GetBalanceAsync();
// β Calculate profit/loss
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var profit = finalBalance - initialBalance;
Console.WriteLine($" Final balance: ${finalBalance:F2}");
Console.WriteLine($" Profit/Loss: ${profit:F2}");
Console.WriteLine("\n+============================================================+\n");
// β Return profit as result of ExecuteAsync()
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
return profit;
π Complete Life Cycle (Upward breakout scenario)ΒΆ
Execution timelineΒΆ
T=0 START ExecuteAsync()
β
βββΊ GetBalanceAsync() β $10000.00
βββΊ SymbolInfoTickAsync() β Bid:1.10000, Ask:1.10002
β
T=1s βββΊ BuyStopPoints(+25)
β βββΊ Created BuyStop #789 @ 1.10027
β
T=2s βββΊ SellStopPoints(-25)
β βββΊ Created SellStop #790 @ 1.09975
β
β MT5 Terminal state:
β ββββββββββββββββββ¬βββββββββββββββ
β β PENDING ORDERS β POSITIONS β
β ββββββββββββββββββΌβββββββββββββββ€
β β 789: BUY STOP β (empty) β
β β 790: SELL STOP β β
β ββββββββββββββββββ΄βββββββββββββββ
β
T=3s βββΊ MONITORING START
β
T=6s βββΊ Task.Delay(3000)
β βββΊ OpenedOrdersTicketsAsync() β [789, 790]
β βββΊ buyStillPending = true
β βββΊ sellStillPending = true
β βββΊ Both pending β continue
β
T=9s βββΊ Task.Delay(3000)
β βββΊ [789, 790] β both pending β continue
β
β MARKET: Price rises...
β 1.10005 β 1.10015 β 1.10020 β 1.10027...
β
T=12s βββΊ Task.Delay(3000)
β β
β β MT5 Terminal: Price reached 1.10027!
β β BuyStop #789 EXECUTED β became position
β β
β β MT5 Terminal state:
β β ββββββββββββββββββ¬βββββββββββββββββββββββ
β β β PENDING ORDERS β POSITIONS β
β β ββββββββββββββββββΌβββββββββββββββββββββββ€
β β β 790: SELL STOP β 789: BUY 0.01 β
β β β β @ 1.10027 β
β β ββββββββββββββββββ΄βββββββββββββββββββββββ
β β
β βββΊ OpenedOrdersTicketsAsync() β [790]
β βββΊ buyStillPending = false β NOT found!
β βββΊ sellStillPending = true
β β
β βββΊ if (!buy && sell) β TRUE!
β βββΊ "π BUY STOP EXECUTED! Upward breakout!"
β βββΊ executedOrder = 789
β βββΊ cancelOrder = 790
β βββΊ break (exit loop)
β
T=13s βββΊ CANCEL opposite order
β βββΊ CloseByTicket(790)
β β βββΊ OrderDeleteAsync(790)
β β βββΊ SellStop #790 CANCELED
β β
β β MT5 Terminal state:
β β ββββββββββββββββββ¬βββββββββββββββββββββββ
β β β PENDING ORDERS β POSITIONS β
β β ββββββββββββββββββΌβββββββββββββββββββββββ€
β β β (empty) β 789: BUY 0.01 β
β β β β @ 1.10027 β
β β β β SL: 1.10012 β
β β β β TP: 1.10057 β
β β ββββββββββββββββββ΄βββββββββββββββββββββββ
β
β OCO STRATEGY COMPLETED!
β Position opened, opposite order canceled.
β
β MARKET: Price continues rising...
β 1.10030 β 1.10040 β 1.10050 β 1.10057...
β
T=180s β MT5 Terminal: Price reached 1.10057!
(3 min) β TP triggered β position closed
β
β MT5 Terminal state:
β ββββββββββββββββββ¬βββββββββββββββββββββββ
β β PENDING ORDERS β POSITIONS β
β ββββββββββββββββββΌβββββββββββββββββββββββ€
β β (empty) β (empty) β
β ββββββββββββββββββ΄βββββββββββββββββββββββ
β
β CLOSED POSITION:
β Entry: 1.10027
β Exit: 1.10057
β Profit: (1.10057 - 1.10027) Γ 100000 Γ 0.01
β = 0.00030 Γ 100000 Γ 0.01
β = 30 Γ 0.01
β = $3.00
β
T=183s βββΊ GetBalanceAsync() β $10003.00
βββΊ profit = 10003.00 - 10000.00 = +$3.00
β
βββΊ RETURN profit = 3.00
π What the result is made ofΒΆ
Profit calculation (upward breakout)ΒΆ
INITIAL BALANCE: $10000.00
UPWARD BREAKOUT:
1. BuyStop #789 @ 1.10027 EXECUTED
β Opened position BUY 0.01 lots
β SL: 1.10012 (protection -15 points)
β TP: 1.10057 (target +30 points)
2. SellStop #790 CANCELED
β Cancellation fee: $0.00
POSITION OUTCOME (TP triggered):
Entry: 1.10027
Exit: 1.10057
Pips: (1.10057 - 1.10027) / 0.00001 = 30 points
PROFIT CALCULATION:
Profit = Pips Γ Point Value Γ Volume
Point Value for EURUSD (1.0 lot) = $10
Point Value for 0.01 lot = $0.10
Profit = 30 Γ $0.10 = $3.00
FINAL BALANCE: $10003.00
PROFIT = $3.00
return 3.00;
Loss calculation (SL triggered)ΒΆ
IF SL HAD TRIGGERED:
Entry: 1.10027
Exit: 1.10012 (SL)
Pips: (1.10012 - 1.10027) / 0.00001 = -15 points
Loss = -15 Γ $0.10 = -$1.50
FINAL BALANCE: $9998.50
PROFIT = -$1.50
R:R ratio = 1.50 / 3.00 = 1:2 β
π§© Components and their rolesΒΆ
1. PendingBreakoutOrchestratorΒΆ
Role: OCO strategy coordinator
Tasks:
- Stores parameters (Symbol, BreakoutDistancePoints, etc.)
- Manages life cycle
- Places both orders
- Monitors breakout through OpenedOrdersTicketsAsync()
- Cancels opposite order
- Handles placement errors
- Returns result
2. MT5ServiceΒΆ
Role: Service layer
Tasks:
- Provides methods BuyStopAsync, SellStopAsync
- Delegates calls to MT5Account
3. MT5Sugar (extension methods)ΒΆ
Role: Simplifying layer
Tasks:
BuyStopPoints()- converts points to prices for BuyStopSellStopPoints()- converts points to prices for SellStopCloseByTicket()- universal cancel/close
4. OpenedOrdersTicketsAsync()ΒΆ
Role: CRITICAL component for breakout detection
How it works:
// Returns TWO lists:
OpenedOrdersTicketsData {
OpenedOrdersTickets: [list of pending order tickets],
OpenedPositionTickets: [list of open position tickets]
}
// KEY MOMENT:
// When pending order executes β it disappears from OpenedOrdersTickets
// and appears in OpenedPositionTickets
// Check ticket presence in pending list:
foreach (var ticket in tickets.OpenedOrdersTickets)
{
if (ticket == ourBuyStopTicket) buyStillPending = true;
}
// If ticket NOT found β order executed!
if (!buyStillPending) {
// BuyStop no longer pending β breakout happened!
}
π Final Dependency DiagramΒΆ
β USER CODE
β var orch = new PendingBreakoutOrchestrator(service);
β var profit = await orch.ExecuteAsync();
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βΌ
β PendingBreakoutOrchestrator
β
β β ExecuteAsync() {
β β 1. BuyStopPoints(+25)
β β 2. SellStopPoints(-25)
β β 3. LOOP: OpenedOrdersTicketsAsync()
β β βββΊ Check ticket presence
β β 4. CloseByTicket(opposite)
β β }
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β _service
βΌ
β MT5Service
β - OpenedOrdersTicketsAsync() β KEY method!
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βΌ
β MT5Sugar (extension methods)
β - BuyStopPoints(+offsetPoints) β price ABOVE Ask
β - SellStopPoints(-offsetPoints) β price BELOW Bid
β - CloseByTicket(ticket) β cancel/close
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βΌ
[MT5 Terminal]
π― SummaryΒΆ
PendingBreakoutOrchestrator is made of:
- 1 dependency:
MT5Service _service - 6 parameters: Symbol, BreakoutDistancePoints, StopLoss, TakeProfit, Volume, MaxWaitMinutes
- 4 MT5Sugar methods:
BuyStopPoints,SellStopPoints,CloseByTicket,OpenedOrdersTicketsAsync - OCO logic: Breakout detection through checking ticket presence in pending orders list
Works through:
- Placing 2 opposite Stop orders
- Monitoring pending orders list every 3 seconds
- Detecting breakout: if ticket disappeared from list β order executed
- Canceling opposite order
- Timeout handling
Returns:
double profit- difference between final and initial balance
Key insight:
All the magic of breakout detection is based on a simple fact: when a pending order executes, it disappears from the OpenedOrdersTickets list. By checking for our tickets' presence in this list, we know exactly which order triggered and which is still waiting for execution.