Skip to content

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 Volume
  • SellMarketByRisk() - AUTOMATICALLY calculates Volume
  • CloseByTicket() - 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. 1 dependency: MT5Service _service
  2. 6 parameters: Symbol, RiskAmount, StopLossPoints, TakeProfitPoints, IsBuy, MaxHoldSeconds
  3. 3 key methods:
  4. BuyMarketByRisk / SellMarketByRisk ← AUTOMATIC Volume calculation!
  5. OpenedOrdersTicketsAsync ← Position status check
  6. CloseByTicket ← 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:

CONSTANT RISK = RiskMoney
VARIABLE VOLUME = f(RiskMoney, StopPoints)

If SL increases β†’ Volume decreases
If SL decreases β†’ Volume increases
Risk ALWAYS remains = $20!