HOW GridTradingOrchestrator WORKS - Detailed AnalysisΒΆ
π― Document PurposeΒΆ
Show WHAT the orchestrator consists of and HOW EXACTLY it works at the code, methods and data level.
π¦ What the orchestrator is made ofΒΆ
1. Class structure (lines 13-28)ΒΆ
public class GridTradingOrchestrator
{
// SINGLE DEPENDENCY
private readonly MT5Service _service;
// 7 CONFIGURABLE PARAMETERS
public string Symbol { get; set; } = "EURUSD";
public int GridLevels { get; set; } = 3;
public int GridSpacingPoints { get; set; } = 20;
public double VolumePerLevel { get; set; } = 0.01;
public int StopLossPoints { get; set; } = 50;
public int TakeProfitPoints { get; set; } = 30;
public int MaxRunMinutes { get; set; } = 15;
// DEPENDENCY INJECTION
public GridTradingOrchestrator(MT5Service service)
{
_service = service; // β Get MT5Service from outside
}
}
Dependency visualizationΒΆ
GridTradingOrchestrator
Contains: private readonly MT5Service _service
β
MT5Service
Contains: private MT5Account _account
β
MT5Account
Contains: gRPC Client
β
[MT5 Terminal]
π How ExecuteAsync() works - step by stepΒΆ
Phase 1: Initialization (lines 32-46)ΒΆ
public async Task<double> ExecuteAsync(CancellationToken ct = default)
{
// β STEP 1: Output header
// ββββββββββββββββββββββββββββββ
Console.WriteLine("\n+============================================================+");
Console.WriteLine("| GRID TRADING ORCHESTRATOR |");
Console.WriteLine("+============================================================+\n");
// β STEP 2: Get initial balance
// β Used: MT5Service.GetBalanceAsync()
// β β Calls: MT5Account.GetBalanceAsync()
// β β Sends gRPC: GetAccountInfoRequest
// β β Receives: AccountInfoData
// β β Returns: double balance
// βββββββββββββββββββββββββββββββββββββββββββββββββ
var initialBalance = await _service.GetBalanceAsync();
Console.WriteLine($" Starting balance: ${initialBalance:F2}");
// β STEP 3: Get current price
// β Used: MT5Service.SymbolInfoTickAsync()
// β β Calls: MT5Account.SymbolInfoTickAsync()
// β β Sends gRPC: SymbolInfoTickRequest
// β β Receives: SymbolInfoTickData
// β β Fields: Bid, Ask, Time, Volume
// βββββββββββββββββββββββββββββββββββββββββββββββββ
var tick = await _service.SymbolInfoTickAsync(Symbol);
Console.WriteLine($" Current: Bid={tick.Bid:F5}, Ask={tick.Ask:F5}\n");
// β STEP 4: Create list to track orders
// βββββββββββββββββββββββββββββββββββββββββββββββββ
var placedOrders = new System.Collections.Generic.List<ulong>();
}
Phase 2: Placing Buy Limit grid (lines 50-73)ΒΆ
// β LOOP: Placing BUY LIMIT orders BELOW current price
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Console.WriteLine($" Placing {GridLevels} BUY LIMIT levels...");
for (int i = 1; i <= GridLevels; i++) // i = 1, 2, 3
{
// β Calculate offset in points
// β IMPORTANT: NEGATIVE value!
// βββββββββββββββββββββββββββββββββββββββββββ
var pointsBelow = -(i * GridSpacingPoints);
// Examples with GridSpacingPoints = 20:
// i=1 β pointsBelow = -(1 Γ 20) = -20
// i=2 β pointsBelow = -(2 Γ 20) = -40
// i=3 β pointsBelow = -(3 Γ 20) = -60
// β Call MT5Sugar Extension Method
// βββββββββββββββββββββββββββββββββββββββββββ
var result = await _service.BuyLimitPoints(
symbol: Symbol, // "EURUSD"
volume: VolumePerLevel, // 0.01
priceOffsetPoints: pointsBelow, // -20, -40, -60
slPoints: StopLossPoints, // 50
tpPoints: TakeProfitPoints, // 30
comment: $"Grid-Buy-{i}" // "Grid-Buy-1"
);
// β Check result
// βββββββββββββββββββββββββββββββββββββββββββ
if (result.ReturnedCode == 10009) // 10009 = TRADE_RETCODE_DONE
{
placedOrders.Add(result.Order); // Save ticket
Console.WriteLine($" β Level {i}: #{result.Order} ({pointsBelow} pts below)");
}
else
{
Console.WriteLine($" β Level {i} failed: {result.Comment}");
}
}
How BuyLimitPoints() works - inside MT5SugarΒΆ
// MT5Sugar.cs (extension method)
public static async Task<OrderSendData> BuyLimitPoints(
this MT5Service service,
string symbol,
double volume,
int priceOffsetPoints, // β RECEIVES -20
int slPoints = 0,
int tpPoints = 0,
string comment = ""
)
{
// β STEP 1: Get current Ask price
// βββββββββββββββββββββββββββββββββββββββββββ
var tick = await service.SymbolInfoTickAsync(symbol);
double askPrice = tick.Ask; // For example: 1.10002
// β STEP 2: Get point size for symbol
// βββββββββββββββββββββββββββββββββββββββββββ
var symbolInfo = await service.SymbolInfoAsync(symbol);
double point = symbolInfo.Point; // For EURUSD: 0.00001
// β STEP 3: Calculate order placement price
// β priceOffsetPoints = -20
// β askPrice = 1.10002
// β point = 0.00001
// β price = 1.10002 + (-20 Γ 0.00001)
// β = 1.10002 - 0.00020
// β = 1.09982
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
double price = askPrice + (priceOffsetPoints * point);
// β STEP 4: Calculate SL and TP
// β slPoints = 50
// β sl = 1.09982 - (50 Γ 0.00001) = 1.09932
// β tpPoints = 30
// β tp = 1.09982 + (30 Γ 0.00001) = 1.10012
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
double sl = slPoints > 0 ? price - (slPoints * point) : 0;
double tp = tpPoints > 0 ? price + (tpPoints * point) : 0;
// β STEP 5: Call low-level method MT5Service
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
return await service.BuyLimitAsync(
symbol: symbol, // "EURUSD"
volume: volume, // 0.01
price: price, // 1.09982
sl: sl, // 1.09932
tp: tp, // 1.10012
comment: comment // "Grid-Buy-1"
);
}
Phase 3: Placing Sell Limit grid (lines 77-100)ΒΆ
// β LOOP: Placing SELL LIMIT orders ABOVE current price
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Console.WriteLine($" Placing {GridLevels} SELL LIMIT levels...");
for (int i = 1; i <= GridLevels; i++)
{
// β Calculate offset in points
// β IMPORTANT: POSITIVE value!
// ββββββββββββββββββββββββββββββββββββββββββββββ
var pointsAbove = i * GridSpacingPoints;
// Examples with GridSpacingPoints = 20:
// i=1 β pointsAbove = 1 Γ 20 = +20
// i=2 β pointsAbove = 2 Γ 20 = +40
// i=3 β pointsAbove = 3 Γ 20 = +60
var result = await _service.SellLimitPoints(
symbol: Symbol,
volume: VolumePerLevel,
priceOffsetPoints: pointsAbove, // +20, +40, +60
slPoints: StopLossPoints,
tpPoints: TakeProfitPoints,
comment: $"Grid-Sell-{i}"
);
if (result.ReturnedCode == 10009)
{
placedOrders.Add(result.Order);
Console.WriteLine($" β Level {i}: #{result.Order} ({pointsAbove} pts above)");
}
}
How SellLimitPoints() worksΒΆ
// MT5Sugar.cs (extension method)
public static async Task<OrderSendData> SellLimitPoints(
this MT5Service service,
string symbol,
double volume,
int priceOffsetPoints, // β RECEIVES +20
int slPoints = 0,
int tpPoints = 0,
string comment = ""
)
{
var tick = await service.SymbolInfoTickAsync(symbol);
double bidPrice = tick.Bid; // β FOR SELL use BID, not Ask!
var symbolInfo = await service.SymbolInfoAsync(symbol);
double point = symbolInfo.Point;
// β For SELL: placement price = Bid + offset
// β priceOffsetPoints = +20
// β bidPrice = 1.10000
// β price = 1.10000 + (20 Γ 0.00001) = 1.10020
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
double price = bidPrice + (priceOffsetPoints * point);
// β For SELL: SL ABOVE entry price (protection)
// β sl = 1.10020 + (50 Γ 0.00001) = 1.10070
// β For SELL: TP BELOW entry price (profit taking)
// β tp = 1.10020 - (30 Γ 0.00001) = 1.09990
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
double sl = slPoints > 0 ? price + (slPoints * point) : 0; // + for SELL
double tp = tpPoints > 0 ? price - (tpPoints * point) : 0; // - for SELL
return await service.SellLimitAsync(
symbol: symbol,
volume: volume,
price: price, // 1.10020
sl: sl, // 1.10070
tp: tp, // 1.09990
comment: comment
);
}
Result of placing gridΒΆ
After both loops in MT5 Terminal:
PENDING ORDERS (6 orders):
ββββββββββ¬ββββββββββββββ¬ββββββββββ¬ββββββββββ¬ββββββββββ¬ββββββββββ
β Ticket β Type β Price β SL β TP β Comment β
ββββββββββΌββββββββββββββΌββββββββββΌββββββββββΌββββββββββΌββββββββββ€
β ...789 β BUY LIMIT β 1.09982 β 1.09932 β 1.10012 β Grid-B-1β
β ...790 β BUY LIMIT β 1.09962 β 1.09912 β 1.09992 β Grid-B-2β
β ...791 β BUY LIMIT β 1.09942 β 1.09892 β 1.09972 β Grid-B-3β
β ...792 β SELL LIMIT β 1.10020 β 1.10070 β 1.09990 β Grid-S-1β
β ...793 β SELL LIMIT β 1.10040 β 1.10090 β 1.10010 β Grid-S-2β
β ...794 β SELL LIMIT β 1.10060 β 1.10110 β 1.10030 β Grid-S-3β
ββββββββββ΄ββββββββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββββ
placedOrders = [789, 790, 791, 792, 793, 794]
Phase 4: Monitoring (lines 105-114)ΒΆ
Console.WriteLine($"\n β Grid placed: {placedOrders.Count} pending orders");
Console.WriteLine($" β³ Running for {MaxRunMinutes} minutes...\n");
// β Calculate end time
// β MaxRunMinutes = 15
// β endTime = 12:00 + 15 min = 12:15
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var endTime = DateTime.UtcNow.AddMinutes(MaxRunMinutes);
// β MONITORING LOOP: runs until time expires
// β or CancellationToken triggers
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
while (DateTime.UtcNow < endTime && !ct.IsCancellationRequested)
{
// β Wait 5 seconds
// ββββββββββββββββββββββββββββββββββββββββββββββββββ
await Task.Delay(5000, ct);
// β Get current balance
// ββββββββββββββββββββββββββββββββββββββββββββββββββ
var currentBalance = await _service.GetBalanceAsync();
// β Calculate profit/loss
// β initialBalance = 10000.00
// β currentBalance = 10012.50
// β currentProfit = 10012.50 - 10000.00 = +12.50
// ββββββββββββββββββββββββββββββββββββββββββββββββββ
var currentProfit = currentBalance - initialBalance;
Console.WriteLine($" Current P/L: ${currentProfit:F2}");
}
What happens during monitoringΒΆ
TIME ACTION BALANCE P/L
ββββββββ βββββββββββββββββββββββββββββββ βββββββββ βββββ
12:00:00 Grid placed 10000.00 0.00
12:00:05 Waiting... 10000.00 0.00
12:00:10 Waiting... 10000.00 0.00
12:00:15 Price 1.09980 β BUY-1 triggered 10000.00 0.00
12:00:20 Position opened 10000.00 0.00
12:00:25 Price 1.09995 10001.50 +1.50
12:00:30 Price 1.10010 β TP triggered 10003.00 +3.00
12:00:35 Position closed 10003.00 +3.00
...
12:15:00 Time expired β exit loop 10012.50 +12.50
Phase 5: Closing (lines 117-128)ΒΆ
// β Time expired - close all remaining orders
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Console.WriteLine("\n β± Time expired - closing all remaining orders...");
// β Call MT5Sugar extension method: CloseAll()
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
await _service.CloseAll(Symbol);
Console.WriteLine(" β All closed");
// β Get final balance
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var finalBalance = await _service.GetBalanceAsync();
// β Calculate total profit
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var profit = finalBalance - initialBalance;
Console.WriteLine($"\n Final balance: ${finalBalance:F2}");
Console.WriteLine($" Total Profit/Loss: ${profit:F2}");
// β Return profit as result of ExecuteAsync()
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
return profit;
How CloseAll() worksΒΆ
// MT5Sugar.cs (extension method)
public static async Task CloseAll(
this MT5Service service,
string symbol
)
{
// β STEP 1: Get all open positions
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var positions = await service.PositionsAsync(symbol);
foreach (var position in positions)
{
// Close each position individually
await service.PositionCloseAsync(position.Ticket);
}
// β STEP 2: Get all pending orders
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var orders = await service.OrdersAsync(symbol);
foreach (var order in orders)
{
// Cancel each pending order
await service.OrderDeleteAsync(order.Ticket);
}
}
π Complete Life CycleΒΆ
Execution timelineΒΆ
T=0 START ExecuteAsync()
β
βββΊ GetBalanceAsync() β 10000.00
βββΊ SymbolInfoTickAsync() β Bid:1.10000, Ask:1.10002
β
T=1s βββΊ LOOP 1: BuyLimitPoints (i=1)
β βββΊ Order 789: BUY LIMIT @ 1.09982
β
T=2s βββΊ LOOP 1: BuyLimitPoints (i=2)
β βββΊ Order 790: BUY LIMIT @ 1.09962
β
T=3s βββΊ LOOP 1: BuyLimitPoints (i=3)
β βββΊ Order 791: BUY LIMIT @ 1.09942
β
T=4s βββΊ LOOP 2: SellLimitPoints (i=1)
β βββΊ Order 792: SELL LIMIT @ 1.10020
β
T=5s βββΊ LOOP 2: SellLimitPoints (i=2)
β βββΊ Order 793: SELL LIMIT @ 1.10040
β
T=6s βββΊ LOOP 2: SellLimitPoints (i=3)
β βββΊ Order 794: SELL LIMIT @ 1.10060
β
T=7s βββΊ MONITORING START
β endTime = DateTime.Now + 15 minutes
β
T=12s βββΊ Task.Delay(5000) β output P/L: $0.00
T=17s βββΊ Task.Delay(5000) β output P/L: $0.00
T=22s βββΊ Task.Delay(5000) β output P/L: $1.50
β (Market moved, position opened)
...
...
T=15min βββΊ MONITORING END (time expired)
β
βββΊ CloseAll("EURUSD")
β βββΊ Closed positions: 2
β βββΊ Canceled orders: 4
β
βββΊ GetBalanceAsync() β 10012.50
βββΊ profit = 10012.50 - 10000.00 = +12.50
β
βββΊ RETURN profit = 12.50
π What the result is made ofΒΆ
Profit calculationΒΆ
INITIAL BALANCE: $10000.00
TRIGGERED ORDERS:
1. Order 789: BUY LIMIT @ 1.09982
β Price fell to 1.09980 β opened position BUY
β Price rose to 1.10012 β TP triggered
β Profit: (1.10012 - 1.09982) Γ 100000 Γ 0.01 = +$3.00
2. Order 792: SELL LIMIT @ 1.10020
β Price rose to 1.10022 β opened position SELL
β Price fell to 1.09990 β TP triggered
β Profit: (1.10020 - 1.09990) Γ 100000 Γ 0.01 = +$3.00
NOT TRIGGERED (canceled at CloseAll):
- Order 790: BUY LIMIT @ 1.09962
- Order 791: BUY LIMIT @ 1.09942
- Order 793: SELL LIMIT @ 1.10040
- Order 794: SELL LIMIT @ 1.10060
FINAL RESULT:
- Profit from positions: $3.00 + $3.00 = $6.00
- Commissions/swap: -$0.50
- FINAL BALANCE: $10005.50
- PROFIT = $5.50
return 5.50;
π§© Components and their rolesΒΆ
1. GridTradingOrchestratorΒΆ
Role: Strategy coordinator
Tasks:
- Stores parameters (Symbol, GridLevels, etc.)
- Manages life cycle
- Calls MT5Service methods
- Handles errors
- Returns result
2. MT5ServiceΒΆ
Role: Service layer
Tasks:
- Provides high-level methods
- Delegates calls to MT5Account
- Contains no business logic
3. MT5Sugar (extension methods)ΒΆ
Role: Simplifying layer
Tasks:
- Converts points to prices
- Calculates SL/TP
- Makes API convenient
- Reduces code amount
4. MT5AccountΒΆ
Role: gRPC client
Tasks:
- Direct communication with MT5 Terminal
- Serialization/deserialization
- Connection management
5. MT5 TerminalΒΆ
Role: Executor
Tasks:
- Places orders on market
- Monitors execution
- Manages positions
π Final Dependency DiagramΒΆ
β USER CODE
β var orchestrator = new GridTradingOrchestrator(service);
β var profit = await orchestrator.ExecuteAsync();
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βΌ
β GridTradingOrchestrator
β
β β - Symbol, GridLevels, GridSpacingPoints, ...
β β - ExecuteAsync()
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β _service
βΌ
β MT5Service
β
β β Methods:
β β - GetBalanceAsync()
β β - SymbolInfoTickAsync()
β β - BuyLimitAsync()
β β - SellLimitAsync()
β β - PositionCloseAsync()
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β _account
βΌ
β MT5Account
β
β β gRPC Client:
β β - OrderSendAsync(OrderSendRequest)
β β - GetAccountInfoAsync(GetAccountInfoRequest)
β β - SymbolInfoTickAsync(SymbolInfoTickRequest)
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β gRPC
βΌ
β MT5 Terminal
βββββββββββββββ
β MT5Sugar (static extension methods)
β
β β Extension Methods on MT5Service:
β β - BuyLimitPoints(priceOffsetPoints, slPoints, ...)
β β - SellLimitPoints(priceOffsetPoints, tpPoints, ...)
β β - CloseAll(symbol)
β β - BuyMarketByRisk(riskMoney, stopPoints, ...)
β β
β β Role: Convert points β prices
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
ββββββββββββ¬ββββββββββββββββ
β Calls low-level methods
βΌ
[MT5Service methods]
π― SummaryΒΆ
GridTradingOrchestrator is made of:
- 1 dependency:
MT5Service _service(via DI) - 7 parameters: Symbol, GridLevels, GridSpacingPoints, Volume, SL, TP, MaxRunMinutes
- 3 MT5Sugar methods:
BuyLimitPoints,SellLimitPoints,CloseAll - 5 MT5Service methods:
GetBalanceAsync,SymbolInfoTickAsync,BuyLimitAsync,SellLimitAsync,PositionsAsync - gRPC protocol: Communication with MT5 Terminal
Works through:
- 2 loops for placing orders (Buy + Sell)
- 1 monitoring loop with Task.Delay(5000)
- 1 final close of all orders
- Error handling via try-catch
Returns:
double profit- difference between final and initial balance