HOW SimpleScalpingOrchestrator 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 risk-sizing and automatic position size calculation.
π¦ What the orchestrator is made ofΒΆ
1. Class structure (lines 13-27)ΒΆ
public class SimpleScalpingOrchestrator
{
// β SINGLE DEPENDENCY
// ββββββββββββββββββββββββββββββββββββββββββ
private readonly MT5Service _service;
// β 6 CONFIGURABLE PARAMETERS
// ββββββββββββββββββββββββββββββββββββββββββ
public string Symbol { get; set; } = "EURUSD";
public double RiskAmount { get; set; } = 20.0; // $20 risk
public int StopLossPoints { get; set; } = 10;
public int TakeProfitPoints { get; set; } = 20;
public bool IsBuy { get; set; } = true;
public int MaxHoldSeconds { get; set; } = 60; // 60 seconds
// β DEPENDENCY INJECTION
// ββββββββββββββββββββββββββββββββββββββββββ
public SimpleScalpingOrchestrator(MT5Service service)
{
_service = service; // β Get MT5Service from outside
}
}
Dependency visualizationΒΆ
β SimpleScalpingOrchestrator
β
β β private readonly MT5Service _service
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββ
β
βΌ
β MT5Service
β
β β private MT5Account _account
β ββββββββββββββββ¬ββββββββββββββββ
βββββββββββββββββββΌββββββββββββββββββββ
β
βΌ
β MT5Account
β
β β gRPC Client
β ββββββββββββββββββββ
ββββββββββββββββββββββββββ
β
βΌ
[MT5 Terminal]
π How ExecuteAsync() works - step by stepΒΆ
Phase 1: Initialization (lines 30-41)ΒΆ
public async Task<double> ExecuteAsync(CancellationToken ct = default)
{
// β STEP 1: Output header
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Console.WriteLine("\n+============================================================+");
Console.WriteLine("| SIMPLE SCALPING ORCHESTRATOR |");
Console.WriteLine("+============================================================+\n");
// β STEP 2: Get initial balance
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var initialBalance = await _service.GetBalanceAsync();
// β STEP 3: Output strategy parameters
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Console.WriteLine($" Starting balance: ${initialBalance:F2}");
Console.WriteLine($" Symbol: {Symbol}");
Console.WriteLine($" Direction: {(IsBuy ? "BUY" : "SELL")}");
Console.WriteLine($" Risk: ${RiskAmount:F2}");
Console.WriteLine($" SL: {StopLossPoints} pts | TP: {TakeProfitPoints} pts");
Console.WriteLine($" Max hold: {MaxHoldSeconds}s\n");
}
Phase 2: Opening position with risk-sizing (lines 43-77)ΒΆ
try
{
// β Opening market position
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Console.WriteLine(" Opening position...");
OrderSendData result;
// β DIRECTION CHOICE: BUY or SELL
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if (IsBuy)
{
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
// BUY: Use BuyMarketByRisk
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
result = await _service.BuyMarketByRisk(
symbol: Symbol, // "EURUSD"
stopPoints: StopLossPoints, // 10 points
riskMoney: RiskAmount, // $20
tpPoints: TakeProfitPoints, // 20 points
comment: "Scalper"
);
}
else
{
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
// SELL: Use SellMarketByRisk
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
result = await _service.SellMarketByRisk(
symbol: Symbol,
stopPoints: StopLossPoints,
riskMoney: RiskAmount,
tpPoints: TakeProfitPoints,
comment: "Scalper"
);
}
// β RESULT CHECK
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if (result.ReturnedCode != 10009) // 10009 = TRADE_RETCODE_DONE
{
Console.WriteLine($" β Order failed: {result.Comment}");
return 0; // β EMERGENCY EXIT
}
Console.WriteLine($" β Position opened: #{result.Order}");
Console.WriteLine($" Volume: {result.Volume:F2} lots\n");
}
How BuyMarketByRisk() works internallyΒΆ
// MT5Sugar.cs (extension method)
public static async Task<OrderSendData> BuyMarketByRisk(
this MT5Service service,
string symbol,
int stopPoints, // β RECEIVES 10
double riskMoney, // β RECEIVES $20
int tpPoints = 0,
string comment = ""
)
{
// β STEP 1: Get symbol information
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var symbolInfo = await service.SymbolInfoAsync(symbol);
// EURUSD SymbolInfo:
// - Point = 0.00001 (point size)
// - Trade_Contract_Size = 100000 (contract size)
// - Digits = 5 (decimal places)
// β STEP 2: Calculate value of ONE point
// β for standard lot (1.0)
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
double pointValue = symbolInfo.Trade_Contract_Size * symbolInfo.Point;
// For EURUSD:
// pointValue = 100000 Γ 0.00001 = 1.0
// BUT! This is for base currency (EUR).
// Need to convert to USD (account currency).
// β STEP 3: Get current price for conversion
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var tick = await service.SymbolInfoTickAsync(symbol);
double currentPrice = tick.Ask; // For BUY use Ask
// EURUSD @ 1.10000:
// 1 point for 1.0 lot = 1 EUR = 1.10000 USD β $1.10
// (in reality MT5 uses special conversion tables)
// For simplicity, for major pairs:
// EURUSD, GBPUSD, AUDUSD, NZDUSD: ~$10 per point for 1.0 lot
double pointValueUSD = 10.0; // Simplified for example
// β STEP 4: KEY CALCULATION - Position size
// β
// β Risk-sizing formula:
// β Volume = RiskMoney / (StopPoints Γ PointValueUSD)
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
double volume = riskMoney / (stopPoints * pointValueUSD);
// For our parameters:
// riskMoney = $20
// stopPoints = 10
// pointValueUSD = $10
//
// volume = $20 / (10 Γ $10)
// = $20 / $100
// = 0.2 lots
// β STEP 5: Round to allowed volume step
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
double volumeStep = symbolInfo.Volume_Step; // Usually 0.01
volume = Math.Round(volume / volumeStep) * volumeStep;
// 0.2 β round to 0.01 step β 0.20
// β STEP 6: Check minimum/maximum volume
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
double minVolume = symbolInfo.Volume_Min; // Usually 0.01
double maxVolume = symbolInfo.Volume_Max; // Usually 100.0
if (volume < minVolume) volume = minVolume;
if (volume > maxVolume) volume = maxVolume;
// β STEP 7: Calculate SL and TP in absolute prices
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
double point = symbolInfo.Point;
double sl = stopPoints > 0
? currentPrice - (stopPoints * point) // For BUY: SL below
: 0;
double tp = tpPoints > 0
? currentPrice + (tpPoints * point) // For BUY: TP above
: 0;
// For BUY @ 1.10000:
// sl = 1.10000 - (10 Γ 0.00001) = 1.09990
// tp = 1.10000 + (20 Γ 0.00001) = 1.10020
// β STEP 8: Call low-level BuyMarketAsync
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
return await service.BuyMarketAsync(
symbol: symbol, // "EURUSD"
volume: volume, // 0.20 (CALCULATED!)
sl: sl, // 1.09990
tp: tp, // 1.10020
comment: comment // "Scalper"
);
}
Complete call chain for BuyMarketByRiskΒΆ
β USER CODE (SimpleScalpingOrchestrator.cs:51)
β await _service.BuyMarketByRisk(
β symbol: "EURUSD",
β stopPoints: 10, β SL POINTS
β riskMoney: 20.0, β RISK IN DOLLARS
β tpPoints: 20, β TP POINTS
β comment: "Scalper"
β )
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
MT5Sugar.BuyMarketByRisk() (extension method)
β
β β 1. SymbolInfoAsync() β get symbol info
β β - Point = 0.00001
β β - Trade_Contract_Size = 100000
β β - Volume_Step = 0.01
β β
β β 2. SymbolInfoTickAsync() β get Ask price
β β - Ask = 1.10000
β β
β β 3. CALCULATE PointValue:
β β pointValue = 100000 Γ 0.00001 = 1.0 (base currency)
β β pointValueUSD β $10 (for major pairs)
β β
β β 4. KEY CALCULATION Volume:
β β volume = riskMoney / (stopPoints Γ pointValueUSD)
β β = $20 / (10 Γ $10)
β β = $20 / $100
β β = 0.2 lots
β β
β β 5. Rounding: 0.2 β 0.20 (step 0.01)
β β
β β 6. Calculate SL/TP:
β β sl = 1.10000 - (10 Γ 0.00001) = 1.09990
β β tp = 1.10000 + (20 Γ 0.00001) = 1.10020
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β await service.BuyMarketAsync(
β symbol: "EURUSD",
β volume: 0.20, β AUTOMATICALLY CALCULATED!
β sl: 1.09990,
β tp: 1.10020,
β comment: "Scalper"
β )
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
β MT5Service.BuyMarketAsync()
β return await _account.BuyMarketAsync(...)
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
β MT5Account.BuyMarketAsync()
β
β β var request = new OrderSendRequest {
β β Symbol = "EURUSD",
β β Volume = 0.20, β CALCULATED volume
β β Type = ORDER_TYPE_BUY, // = 0
β β Price = 0, // Market order (no price specified)
β β Sl = 1.09990,
β β Tp = 1.10020,
β β Comment = "Scalper"
β β }
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β var response = await _client.OrderSendAsync(request);
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
β gRPC NETWORK
ββββββββ¬βββββββ
β
βΌ
β MT5 Terminal
β
β β Opens
β β BUY 0.20
β β @ 1.10000
β β SL: 1.09990
β β TP: 1.10020
β β Ticket:
β β 123456789
β βββββββββββββββ
ββββββββββ¬βββββββββββ
β
βΌ
β RESPONSE
β OrderSendData
β {
β ReturnedCode
β = 10009,
β Order =
β 123456789,
β Volume =
β 0.20 β CALCULATED!
β Price =
β 1.10000
β }
ββββββββ¬βββββββ
β
βΌ
β BACK to SimpleScalpingOrchestrator
β var result = OrderSendData {
β ReturnedCode = 10009
β Order = 123456789
β Volume = 0.20 β See calculated volume!
β }
ββββββββββββββββββββββββββββββββββββββ
Phase 3: Holding position (lines 79-81)ΒΆ
// β Wait MaxHoldSeconds (default 60 seconds)
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Console.WriteLine($" β³ Holding for {MaxHoldSeconds}s...\n");
await Task.Delay(MaxHoldSeconds * 1000, ct);
// MaxHoldSeconds = 60 β Task.Delay(60000 ms) = 60 seconds
What happens during these 60 seconds:
T=0 Position opened BUY 0.20 @ 1.10000
SL: 1.09990, TP: 1.10020
β
βββΊ MT5 Terminal AUTOMATICALLY monitors price
β
T=5s Price: 1.10005 (+5 pts in profit)
T=10s Price: 1.10012 (+12 pts in profit)
T=15s Price: 1.10020 (TP REACHED!)
β
βββΊ MT5 Terminal AUTOMATICALLY closes position
Profit: +20 pts Γ 0.20 = +$40
OR
T=5s Price: 1.09995 (-5 pts in loss)
T=8s Price: 1.09990 (SL REACHED!)
β
βββΊ MT5 Terminal AUTOMATICALLY closes position
Loss: -10 pts Γ 0.20 = -$20 (exactly riskMoney!)
OR
T=60s Price: 1.10008 (+8 pts in profit)
Neither SL nor TP triggered
β Proceed to next phase (status check)
Phase 4: Checking position status (lines 83-104)ΒΆ
// β Get list of OPEN POSITIONS
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var tickets = await _service.OpenedOrdersTicketsAsync();
bool stillOpen = false;
// β Check: is our position in the list?
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
foreach (var ticket in tickets.OpenedPositionTickets) // β POSITIONS, not ORDERS!
{
if (ticket == (long)result.Order) // Compare ticket
{
stillOpen = true;
break;
}
}
// β CONDITION: Position still open?
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if (stillOpen)
{
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// SCENARIO 1: Position NOT closed after 60 seconds
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Console.WriteLine($" Position still open after {MaxHoldSeconds}s - closing manually...");
// β Manual close at current market price
// ββββββββββββββββββββββββββββββββββββββββββββββββββ
await _service.CloseByTicket(result.Order);
Console.WriteLine(" β Position closed");
}
else
{
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// SCENARIO 2: Position ALREADY closed
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Console.WriteLine(" β Position closed automatically (SL/TP hit)");
}
Visualization of status checkΒΆ
AFTER Task.Delay(60 seconds):
MT5 Terminal state β SCENARIO 1 (position still open):
ββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββ
β PENDING ORDERS β OPEN POSITIONS β
ββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββ€
β (empty) β 123456789: BUY 0.20 EURUSD @ 1.10000 β
β β (in profit +8 pts) β
ββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββ
OpenedOrdersTicketsAsync() returns:
{
OpenedOrdersTickets: [],
OpenedPositionTickets: [123456789] β OUR POSITION FOUND!
}
foreach (var ticket in tickets.OpenedPositionTickets)
{
// ticket = 123456789
if (ticket == 123456789) stillOpen = true; β TRUE!
}
if (stillOpen) β TRUE β Manual close
{
CloseByTicket(123456789);
}
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
MT5 Terminal state β SCENARIO 2 (position already closed):
ββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββ
β PENDING ORDERS β OPEN POSITIONS β
ββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββ€
β (empty) β (empty) β
ββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββ
β
Position closed (TP triggered @ T=15s)
OpenedOrdersTicketsAsync() returns:
{
OpenedOrdersTickets: [],
OpenedPositionTickets: [] β POSITION NOT FOUND!
}
foreach (var ticket in tickets.OpenedPositionTickets)
{
// Empty list β loop doesn't execute
}
stillOpen = false; β Remains false
if (stillOpen) β FALSE β Skip manual close
else
{
Console.WriteLine("Position closed automatically (SL/TP hit)");
}
Phase 5: Finalization (lines 106-113)ΒΆ
// β Get final balance
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var finalBalance = await _service.GetBalanceAsync();
// β Calculate profit/loss
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
var profit = finalBalance - initialBalance;
Console.WriteLine($"\n Final balance: ${finalBalance:F2}");
Console.WriteLine($" Profit/Loss: ${profit:F2}");
// β Return profit as result of ExecuteAsync()
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
return profit;
π Complete Life Cycle (TP triggered scenario)ΒΆ
Execution timelineΒΆ
T=0 START ExecuteAsync()
β
βββΊ GetBalanceAsync() β $10000.00
β
T=1s βββΊ BuyMarketByRisk(risk=$20, SL=10pts, TP=20pts)
β β
β βββΊ SymbolInfoAsync("EURUSD")
β β βββΊ Point=0.00001, ContractSize=100000
β β
β βββΊ SymbolInfoTickAsync("EURUSD")
β β βββΊ Ask=1.10000
β β
β βββΊ CALCULATE Volume:
β β volume = $20 / (10 Γ $10) = 0.2 lots
β β
β βββΊ CALCULATE SL/TP:
β β sl = 1.10000 - (10 Γ 0.00001) = 1.09990
β β tp = 1.10000 + (20 Γ 0.00001) = 1.10020
β β
β βββΊ BuyMarketAsync(volume=0.2, sl=1.09990, tp=1.10020)
β βββΊ gRPC β MT5 Terminal
β
T=2s βββΊ MT5 Terminal: Position opened!
β
β β BUY 0.20 EURUSD @ 1.10000
β β SL: 1.09990
β β TP: 1.10020
β β Ticket: 123456789
β βββββββββββββββββββββββββββββββββββ
β
β result.ReturnedCode = 10009 β
β result.Order = 123456789
β result.Volume = 0.20
β
T=3s βββΊ Task.Delay(60000) START
β "Holding for 60s..."
β
β MARKET: Price moves...
β 1.10000 β 1.10005 β 1.10012 β 1.10020...
β
T=15s β MT5 Terminal: Price reached 1.10020!
β TP TRIGGERED!
β
β β Position closed automatically
β β Entry: 1.10000
β β Exit: 1.10020
β β Profit: +20 pts Γ 0.20 = +$40
β βββββββββββββββββββββββββββββββββββ
β
β MT5 Terminal state:
β ββββββββββββββββββββ¬βββββββββββββββ
β β PENDING ORDERS β POSITIONS β
β ββββββββββββββββββββΌβββββββββββββββ€
β β (empty) β (empty) β
β ββββββββββββββββββββ΄βββββββββββββββ
β
T=63s βββΊ Task.Delay(60000) END
β
βββΊ OpenedOrdersTicketsAsync()
β βββΊ OpenedPositionTickets = []
β
βββΊ foreach (var ticket in []) β Empty list
β βββΊ stillOpen = false
β
βββΊ if (stillOpen) β FALSE
β else β "Position closed automatically (SL/TP hit)"
β
T=64s βββΊ GetBalanceAsync() β $10040.00
βββΊ profit = 10040.00 - 10000.00 = +$40.00
β
βββΊ RETURN profit = 40.00
π What the result is made ofΒΆ
Profit calculation (TP triggered)ΒΆ
INITIAL BALANCE: $10000.00
POSITION:
- Direction: BUY
- Volume: 0.20 lots (AUTOMATICALLY CALCULATED!)
- Entry: 1.10000
- SL: 1.09990 (-10 pts)
- TP: 1.10020 (+20 pts)
TP TRIGGERED @ T=15s:
- Exit: 1.10020
- Pips: (1.10020 - 1.10000) / 0.00001 = 20 points
PROFIT CALCULATION:
- Profit = Pips Γ Point Value Γ Volume
- Point Value for EURUSD (0.20 lots) = 0.20 Γ $10 = $2 per point
- Profit = 20 Γ $2 = +$40
FINAL BALANCE: $10040.00
PROFIT = $40.00
return 40.00;
Loss calculation (SL triggered)ΒΆ
SL TRIGGERED @ T=8s:
- Exit: 1.09990
- Pips: (1.09990 - 1.10000) / 0.00001 = -10 points
LOSS CALCULATION:
- Loss = -10 Γ $2 = -$20
FINAL BALANCE: $9980.00
PROFIT = -$20.00
CRITICALLY IMPORTANT:
Loss EXACTLY = riskMoney ($20)!
This proves correctness of Volume calculation through risk-sizing!
π§© Components and their rolesΒΆ
1. SimpleScalpingOrchestratorΒΆ
Role: Scalping strategy coordinator
Tasks:
- Stores parameters (Symbol, RiskAmount, SL, TP, IsBuy, MaxHoldSeconds)
- Manages life cycle
- Chooses direction (BUY/SELL)
- Does NOT calculate Volume (delegates to BuyMarketByRisk)
- Monitors holding time
- Checks position status
- Closes manually if needed
- Returns result
2. MT5Sugar (extension methods)ΒΆ
Role: KEY component for risk-sizing
Tasks:
BuyMarketByRisk()- AUTOMATICALLY calculates VolumeSellMarketByRisk()- AUTOMATICALLY calculates VolumeCloseByTicket()- universal close
3. Risk-sizing formulaΒΆ
β VOLUME CALCULATION FORMULA:
β
β Volume = RiskMoney / (StopPoints Γ PointValueUSD)
β
β Where:
β - RiskMoney: Maximum risk in dollars ($20)
β - StopPoints: SL size in points (10)
β - PointValueUSD: Value of 1 point for 1.0 lot ($10)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
EXAMPLES:
Example 1: Tight SL
RiskMoney = $20
StopPoints = 5 β Very close SL
PointValueUSD = $10
Volume = $20 / (5 Γ $10) = $20 / $50 = 0.4 lots
β LARGER volume to compensate small SL!
Example 2: Wide SL
RiskMoney = $20
StopPoints = 20 β Wide SL
PointValueUSD = $10
Volume = $20 / (20 Γ $10) = $20 / $200 = 0.1 lots
β SMALLER volume to keep risk at $20!
Example 3: High risk
RiskMoney = $50 β Increased risk
StopPoints = 10
PointValueUSD = $10
Volume = $50 / (10 Γ $10) = $50 / $100 = 0.5 lots
β Proportionally larger volume!
π― SummaryΒΆ
SimpleScalpingOrchestrator is made of:
- 1 dependency:
MT5Service _service - 6 parameters: Symbol, RiskAmount, StopLossPoints, TakeProfitPoints, IsBuy, MaxHoldSeconds
- 3 key methods:
BuyMarketByRisk/SellMarketByRiskβ AUTOMATIC Volume calculation!OpenedOrdersTicketsAsyncβ Position status checkCloseByTicketβ Manual close
Works through:
- Market entry with automatic risk-sizing
- Holding position for MaxHoldSeconds seconds
- Check: did position close automatically (SL/TP)?
- Manual close if position still open
Returns:
double profit- difference between final and initial balance
Key insight:
All the magic of the orchestrator is in delegating Volume calculation to BuyMarketByRisk method. Orchestrator only specifies RISK ($20), and position volume is calculated automatically based on StopLoss size. This ensures constant risk regardless of SL parameter changes!
Success formula: