Skip to content

HOW NewsStraddleOrchestrator 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 timing of order placement before news and handling three breakout scenarios.


πŸ“¦ What the orchestrator is made ofΒΆ

1. Class structure (lines 13-28)ΒΆ

public class NewsStraddleOrchestrator
{

    // β”‚  SINGLE DEPENDENCY                      
    // └─────────────────────────────────────────
    private readonly MT5Service _service;


    // β”‚  7 CONFIGURABLE PARAMETERS              
    // └─────────────────────────────────────────
    public string Symbol { get; set; } = "EURUSD";
    public int StraddleDistancePoints { get; set; } = 15;
    public double Volume { get; set; } = 0.02;
    public int StopLossPoints { get; set; } = 20;
    public int TakeProfitPoints { get; set; } = 40;
    public int SecondsBeforeNews { get; set; } = 60;          // ← Countdown timer
    public int MaxWaitAfterNewsSeconds { get; set; } = 180;   // ← Breakout timeout

    public NewsStraddleOrchestrator(MT5Service service)
    {
        _service = service;
    }
}

Dependency visualizationΒΆ

β”‚         NewsStraddleOrchestrator                           
β”‚    
β”‚  β”‚  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)
{
    Console.WriteLine("\n+============================================================+");
    Console.WriteLine("|  NEWS STRADDLE ORCHESTRATOR                               |");
    Console.WriteLine("+============================================================+\n");

    var initialBalance = await _service.GetBalanceAsync();
    Console.WriteLine($"  Starting balance: ${initialBalance:F2}");
    Console.WriteLine($"  Symbol: {Symbol}");
    Console.WriteLine($"  Straddle distance: {StraddleDistancePoints} pts");
    Console.WriteLine($"  Volume: {Volume:F2} lots");
    Console.WriteLine($"  SL: {StopLossPoints} pts | TP: {TakeProfitPoints} pts\n");
}

Phase 2: Countdown until news (lines 44-51)ΒΆ

try
{

    // β”‚  CRITICAL TIMING:                                   
    // β”‚  Wait SecondsBeforeNews seconds until the event     
    // └─────────────────────────────────────────────────────
    Console.WriteLine($"  ⏲  Waiting {SecondsBeforeNews}s before news event...\n");
    await Task.Delay(SecondsBeforeNews * 1000, ct);


    // β”‚  Get current price IMMEDIATELY before               
    // β”‚  placing orders (maximum accuracy)                  
    // └─────────────────────────────────────────────────────
    var tick = await _service.SymbolInfoTickAsync(Symbol);
    Console.WriteLine($"  πŸ“° NEWS EVENT IMMINENT!");
    Console.WriteLine($"  Current: Bid={tick.Bid:F5}, Ask={tick.Ask:F5}\n");
}

Key timing moment:

EXAMPLE: NFP releases at 13:30:00 UTC

Orchestrator launch:
  User: await orchestrator.ExecuteAsync() @ 13:29:00

Countdown:
  SecondsBeforeNews = 60
  Task.Delay(60000) β†’ wait 60 seconds

Order placement:
  @ 13:30:00 (exactly when news releases!)

IMPORTANT:
- Too early β†’ risk triggering from noise
- Too late β†’ miss the beginning of movement
- 60 seconds = optimal balance

Phase 3: Placing the straddle (lines 53-91)ΒΆ

3.1. Placing BuyStop (upper order)ΒΆ

// β”‚  BUY STOP: Catches upward breakout                      
// └─────────────────────────────────────────────────────────
Console.WriteLine("  Placing BUY STOP (upper straddle)...");

var buyStopResult = await _service.BuyStopPoints(
    symbol: Symbol,                         // "EURUSD"
    volume: Volume,                         // 0.02
    priceOffsetPoints: StraddleDistancePoints,  // +15 (POSITIVE!)
    slPoints: StopLossPoints,               // 20
    tpPoints: TakeProfitPoints,             // 40
    comment: "News-Buy"
);

if (buyStopResult.ReturnedCode != 10009)
{
    Console.WriteLine($"  βœ— BUY STOP failed: {buyStopResult.Comment}\n");
    return 0;  // ← EMERGENCY EXIT
}

Console.WriteLine($"  βœ“ BUY STOP: #{buyStopResult.Order}\n");

How BuyStopPoints() works for straddleΒΆ

// MT5Sugar.cs (extension method)
public static async Task<OrderSendData> BuyStopPoints(
    this MT5Service service,
    string symbol,
    double volume,
    int priceOffsetPoints,  // ← RECEIVES +15
    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                             
    // └─────────────────────────────────────────────────────
    var symbolInfo = await service.SymbolInfoAsync(symbol);
    double point = symbolInfo.Point;  // 0.00001


    // β”‚  STEP 3: Calculate BUY STOP price                   
    // β”‚  BUY STOP is placed ABOVE current price             
    // β”‚                                                     
    // β”‚  priceOffsetPoints = +15 (POSITIVE!)                
    // β”‚  price = askPrice + (priceOffsetPoints Γ— point)     
    // β”‚       = 1.10002 + (15 Γ— 0.00001)                    
    // β”‚       = 1.10002 + 0.00015                           
    // β”‚       = 1.10017                                     
    // └─────────────────────────────────────────────────────
    double price = askPrice + (priceOffsetPoints * point);


    // β”‚  STEP 4: Calculate SL and TP for BUY STOP           
    // β”‚                                                     
    // β”‚  sl = price - (slPoints Γ— point)                    
    // β”‚     = 1.10017 - (20 Γ— 0.00001)                      
    // β”‚     = 1.09997                                       
    // β”‚                                                     
    // β”‚  tp = price + (tpPoints Γ— point)                    
    // β”‚     = 1.10017 + (40 Γ— 0.00001)                      
    // β”‚     = 1.10057                                       
    // └─────────────────────────────────────────────────────
    double sl = slPoints > 0 ? price - (slPoints * point) : 0;
    double tp = tpPoints > 0 ? price + (tpPoints * point) : 0;


    // β”‚  STEP 5: Call low-level BuyStopAsync                
    // └─────────────────────────────────────────────────────
    return await service.BuyStopAsync(
        symbol: symbol,
        volume: volume,
        price: price,    // 1.10017
        sl: sl,          // 1.09997
        tp: tp,          // 1.10057
        comment: comment
    );
}

3.2. Placing SellStop (lower order)ΒΆ

// β”‚  SELL STOP: Catches downward breakout                   
// └─────────────────────────────────────────────────────────
Console.WriteLine("  Placing SELL STOP (lower straddle)...");

var sellStopResult = await _service.SellStopPoints(
    symbol: Symbol,
    volume: Volume,
    priceOffsetPoints: -StraddleDistancePoints,  // -15 (NEGATIVE!)
    slPoints: StopLossPoints,
    tpPoints: TakeProfitPoints,
    comment: "News-Sell"
);

if (sellStopResult.ReturnedCode != 10009)
{
    Console.WriteLine($"  βœ— SELL STOP failed: {sellStopResult.Comment}");
    Console.WriteLine("  Canceling BUY STOP...");


    // β”‚  CRITICALLY IMPORTANT:                          
    // β”‚  If second order failed β†’ cancel first one      
    // └─────────────────────────────────────────────────
    await _service.CloseByTicket(buyStopResult.Order);
    return 0;
}

Console.WriteLine($"  βœ“ SELL STOP: #{sellStopResult.Order}\n");
Console.WriteLine("  βœ… STRADDLE ACTIVE - Waiting for news spike!\n");

SellStop price calculationΒΆ

Current Bid price: 1.10000
StraddleDistancePoints: 15 (but we use -15)
point: 0.00001

price = bidPrice + (priceOffsetPoints Γ— point)
      = 1.10000 + (-15 Γ— 0.00001)
      = 1.10000 - 0.00015
      = 1.09985

sl = price + (slPoints Γ— point)  ← PLUS for SELL!
   = 1.09985 + (20 Γ— 0.00001)
   = 1.10005

tp = price - (tpPoints Γ— point)  ← MINUS for SELL!
   = 1.09985 - (40 Γ— 0.00001)
   = 1.09945

Result of straddle placementΒΆ

MT5 Terminal state AFTER straddle placement:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  PENDING ORDERS  β”‚  OPEN POSITIONS                        
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  #123456789:     β”‚  (empty)                               
β”‚  BUY STOP 0.02   β”‚                                        
β”‚  @ 1.10017       β”‚                                        
β”‚  SL: 1.09997     β”‚                                        
β”‚  TP: 1.10057     β”‚                                        
β”‚                  β”‚                                        
β”‚  #123456790:     β”‚                                        
β”‚  SELL STOP 0.02  β”‚                                        
β”‚  @ 1.09985       β”‚                                        
β”‚  SL: 1.10005     β”‚                                        
β”‚  TP: 1.09945     β”‚                                        
└──────────────────┴─────────────────────────────

VISUALIZATION:
          ↑ Price rises
          β”‚
  1.10057 β”œβ”€β”€β”€ TP for BuyStop
  1.10017 β”œβ”€β”€β”€ BUY STOP (upward breakout)
  1.09997 β”œβ”€β”€β”€ SL for BuyStop
          β”‚
  1.10002 β”œβ”€β”€β”€ Current Ask
  1.10000 β”œβ”€β”€β”€ Current Bid
          β”‚
  1.10005 β”œβ”€β”€β”€ SL for SellStop
  1.09985 β”œβ”€β”€β”€ SELL STOP (downward breakout)
  1.09945 β”œβ”€β”€β”€ TP for SellStop
          β”‚
          ↓ Price falls

Phase 4: Monitoring for breakout (lines 94-136)ΒΆ

This is the key phase - determining which order triggered after news release.

var monitorStart = DateTime.UtcNow;
var timeout = TimeSpan.FromSeconds(MaxWaitAfterNewsSeconds);
ulong? executedOrder = null;
ulong? pendingOrder = null;
string direction = "";


// β”‚  MONITORING LOOP (every 1 sec, max 3 minutes)          
// β”‚  FASTER than PendingBreakout (news requires speed!)    
// └─────────────────────────────────────────────────────────
while (DateTime.UtcNow - monitorStart < timeout && !ct.IsCancellationRequested)
{
    await Task.Delay(1000, ct);  // Every second!


    // β”‚  Get list of PENDING ORDERS                     
    // └─────────────────────────────────────────────────
    var tickets = await _service.OpenedOrdersTicketsAsync();

    bool buyStillPending = false;
    bool sellStillPending = false;


    // β”‚  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;
    }


    // β”‚  BREAKOUT SCENARIO DETECTION (4 options)            
    // └─────────────────────────────────────────────────────

    // ═══════════════════════════════════════════════════════
    // SCENARIO 1: UPWARD BREAKOUT
    // ═══════════════════════════════════════════════════════
    if (!buyStillPending && sellStillPending)
    {
        // BuyStop DISAPPEARED (executed) β†’ became position
        // SellStop STILL PENDING β†’ not executed
        executedOrder = buyStopResult.Order;
        pendingOrder = sellStopResult.Order;
        direction = "UPWARD";
        break;
    }

    // ═══════════════════════════════════════════════════════
    // SCENARIO 2: DOWNWARD BREAKOUT
    // ═══════════════════════════════════════════════════════
    else if (buyStillPending && !sellStillPending)
    {
        // SellStop DISAPPEARED (executed)
        // BuyStop STILL PENDING
        executedOrder = sellStopResult.Order;
        pendingOrder = buyStopResult.Order;
        direction = "DOWNWARD";
        break;
    }

    // ═══════════════════════════════════════════════════════
    // SCENARIO 3: BOTH TRIGGERED (extreme volatility!)
    // ═══════════════════════════════════════════════════════
    else if (!buyStillPending && !sellStillPending)
    {
        // Both orders DISAPPEARED β†’ both became positions!
        // Price whipsawed up AND down very quickly
        Console.WriteLine("  ⚑ BOTH ORDERS TRIGGERED - Extreme volatility!");
        direction = "BOTH";
        break;
    }

    // ═══════════════════════════════════════════════════════
    // SCENARIO 4: BOTH STILL PENDING (continue waiting)
    // ═══════════════════════════════════════════════════════
    // else: both orders still in list β†’ news didn't cause breakout
}

Detailed breakout detection logicΒΆ

STATE BEFORE NEWS (T=0):

MT5 Terminal:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  PENDING ORDERS  β”‚  OPEN POSITIONS                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  789: BUY STOP   β”‚  (empty)                               β”‚
β”‚  790: SELL STOP  β”‚                                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

OpenedOrdersTicketsAsync() β†’ OpenedOrdersTickets: [789, 790]

buyStillPending = true (789 found)
sellStillPending = true (790 found)

β†’ Both pending β†’ continue waiting

─────────────────────────────────────────────────────────────

NEWS RELEASED (T=1s): NFP better than expected β†’ price up!

Price: 1.10000 β†’ 1.10010 β†’ 1.10017 β†’ 1.10020...

MT5 Terminal (T=2s):
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  PENDING ORDERS  β”‚  OPEN POSITIONS                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  790: SELL STOP  β”‚  789: BUY 0.02 @ 1.10017               β”‚
β”‚                  β”‚      (BuyStop triggered!)              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

OpenedOrdersTicketsAsync() β†’ OpenedOrdersTickets: [790]
                              (BuyStop 789 DISAPPEARED!)

buyStillPending = false  ← 789 NOT found!
sellStillPending = true  ← 790 still in list

if (!buyStillPending && sellStillPending) β†’ TRUE!
{
    direction = "UPWARD";
    executedOrder = 789;
    pendingOrder = 790;
    break;  ← Exit monitoring
}

─────────────────────────────────────────────────────────────

SCENARIO 3: Extreme volatility

News released UNEXPECTEDLY β†’ price jerked DOWN:
1.10000 β†’ 1.09985 (SellStop triggered!)

Then SHARPLY UP (correction):
1.09985 β†’ 1.10017 (BuyStop also triggered!)

MT5 Terminal:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  PENDING ORDERS  β”‚  OPEN POSITIONS                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  (empty)         β”‚  789: BUY 0.02 @ 1.10017               β”‚
β”‚                  β”‚  790: SELL 0.02 @ 1.09985              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

OpenedOrdersTicketsAsync() β†’ OpenedOrdersTickets: []
                              (BOTH DISAPPEARED!)

buyStillPending = false
sellStillPending = false

if (!buyStillPending && !sellStillPending) β†’ TRUE!
{
    Console.WriteLine("⚑ BOTH ORDERS TRIGGERED - Extreme volatility!");
    direction = "BOTH";
    break;
}

Phase 5: Handling breakout result (lines 138-161)ΒΆ

// ═══════════════════════════════════════════════════════
// SCENARIO A: ONE ORDER TRIGGERED (normal breakout)
// ═══════════════════════════════════════════════════════
if (executedOrder.HasValue && pendingOrder.HasValue)
{
    Console.WriteLine($"  πŸš€ {direction} BREAKOUT DETECTED!");
    Console.WriteLine($"  Position opened: #{executedOrder.Value}");
    Console.WriteLine($"  Canceling opposite order #{pendingOrder.Value}...");


    // β”‚  OCO mechanism: cancel opposite order           
    // └─────────────────────────────────────────────────
    await _service.CloseByTicket(pendingOrder.Value);
    Console.WriteLine("  βœ“ Opposite order canceled\n");


    // β”‚  Hold position for 60 seconds                   
    // β”‚  SL or TP may trigger during this time          
    // └─────────────────────────────────────────────────
    Console.WriteLine("  ⏳ Holding position for 60 seconds...");
    await Task.Delay(60000, ct);
}

// ═══════════════════════════════════════════════════════
// SCENARIO B: BOTH ORDERS TRIGGERED
// ═══════════════════════════════════════════════════════
else if (direction == "BOTH")
{
    // TWO positions opened (BUY and SELL) β†’ this is a HEDGE!
    // Hold for shorter time (30 sec instead of 60)
    Console.WriteLine("  ⏳ Holding both positions for 30 seconds...");
    await Task.Delay(30000, ct);
}

// ═══════════════════════════════════════════════════════
// SCENARIO C: TIMEOUT (breakout didn't happen)
// ═══════════════════════════════════════════════════════
else
{
    Console.WriteLine($"  ⏱ No breakout after {MaxWaitAfterNewsSeconds}s");
    Console.WriteLine("  Canceling both pending orders...");


    // β”‚  Cancel BOTH orders                             
    // └─────────────────────────────────────────────────
    await _service.CloseByTicket(buyStopResult.Order);
    await _service.CloseByTicket(sellStopResult.Order);
}

Phase 6: Final closing (lines 163-176)ΒΆ

// β”‚  Close all remaining positions                          
// └─────────────────────────────────────────────────────────
Console.WriteLine("\n  Closing all remaining positions...");
await _service.CloseAll(Symbol);
Console.WriteLine("  βœ“ All closed");

var finalBalance = await _service.GetBalanceAsync();
var profit = finalBalance - initialBalance;

Console.WriteLine($"\n  Final balance: ${finalBalance:F2}");
Console.WriteLine($"  Profit/Loss: ${profit:F2}");
Console.WriteLine($"  Direction: {(string.IsNullOrEmpty(direction) ? "None" : direction)}");

return profit;

🎭 Complete Life Cycle (Upward breakout scenario)¢

Execution timelineΒΆ

T=-60s   START ExecuteAsync()
         β”‚
         β”œβ”€β–Ί GetBalanceAsync()           β†’ $10000.00
         β”‚
         β”œβ”€β–Ί "Waiting 60s before news event..."
         β”œβ”€β–Ί Task.Delay(60000)  ← COUNTDOWN
         β”‚
         β”‚   USER sees countdown until news
         β”‚
T=0      β”œβ”€β–Ί Task.Delay COMPLETED
         β”‚
         β”œβ”€β–Ί "NEWS EVENT IMMINENT!"
         β”œβ”€β–Ί SymbolInfoTickAsync()
         β”‚   └─► Bid=1.10000, Ask=1.10002
         β”‚
T=1s     β”œβ”€β–Ί BuyStopPoints(+15)
         β”‚   └─► Created BuyStop @ 1.10017
         β”‚       SL: 1.09997, TP: 1.10057
         β”‚
T=2s     β”œβ”€β–Ί SellStopPoints(-15)
         β”‚   └─► Created SellStop @ 1.09985
         β”‚       SL: 1.10005, TP: 1.09945
         β”‚
         β”‚   "βœ… STRADDLE ACTIVE"
         β”‚
         β”‚   MT5 Terminal state:
         β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚   β”‚ PENDING ORDERS β”‚ POSITIONS    β”‚
         β”‚   β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
         β”‚   β”‚ 789: BUY STOP  β”‚ (empty)      β”‚
         β”‚   β”‚ 790: SELL STOP β”‚              β”‚
         β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
T=3s     β”œβ”€β–Ί MONITORING START (max 180 sec)
         β”‚
T=4s     β”œβ”€β–Ί Task.Delay(1000)
         β”‚   OpenedOrdersTicketsAsync() β†’ [789, 790]
         β”‚   buyStillPending = true
         β”‚   sellStillPending = true
         β”‚   β†’ Both pending β†’ continue
         β”‚
         β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚   β”‚  NEWS RELEASED!                β”‚
         β”‚   β”‚  NFP: +350K jobs (forecast +200K)β”‚
         β”‚   β”‚  Much better than expected!    β”‚
         β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β”‚   MARKET: Price sharply UP!
         β”‚   1.10000 β†’ 1.10010 β†’ 1.10017 β†’ 1.10020...
         β”‚
T=5s     β”œβ”€β–Ί MT5 Terminal: Price reached 1.10017!
         β”‚   BuyStop TRIGGERED!
         β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚   β”‚ Opened position BUY 0.02         β”‚
         β”‚   β”‚ Entry: 1.10017                   β”‚
         β”‚   β”‚ SL: 1.09997                      β”‚
         β”‚   β”‚ TP: 1.10057                      β”‚
         β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
T=6s     β”œβ”€β–Ί Task.Delay(1000)
         β”‚   OpenedOrdersTicketsAsync() β†’ [790]
         β”‚   buyStillPending = false  ← 789 DISAPPEARED!
         β”‚   sellStillPending = true
         β”‚
         β”‚   if (!buy && sell) β†’ TRUE!
         β”‚   {
         β”‚       direction = "UPWARD";
         β”‚       executedOrder = 789;
         β”‚       pendingOrder = 790;
         β”‚       break;  ← Exit monitoring
         β”‚   }
         β”‚
         β”œβ”€β–Ί "πŸš€ UPWARD BREAKOUT DETECTED!"
         β”‚
         β”œβ”€β–Ί CloseByTicket(790)  ← Cancel SellStop
         β”‚   └─► OrderDeleteAsync(790)
         β”‚
         β”‚   MT5 Terminal state:
         β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚   β”‚ PENDING ORDERS β”‚ POSITIONS            β”‚
         β”‚   β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
         β”‚   β”‚ (empty)        β”‚ 789: BUY 0.02        β”‚
         β”‚   β”‚                β”‚      @ 1.10017       β”‚
         β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β”œβ”€β–Ί "Holding position for 60 seconds..."
         β”œβ”€β–Ί Task.Delay(60000)
         β”‚
         β”‚   MARKET: Price continues rising...
         β”‚   1.10020 β†’ 1.10040 β†’ 1.10057...
         β”‚
T=35s    β”‚   MT5 Terminal: Price reached 1.10057!
         β”‚   TP TRIGGERED!
         β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚   β”‚ Position closed automatically    β”‚
         β”‚   β”‚ Entry: 1.10017                   β”‚
         β”‚   β”‚ Exit:  1.10057                   β”‚
         β”‚   β”‚ Profit: +40 pts Γ— 0.02 = +$8.00  β”‚
         β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
T=66s    β”œβ”€β–Ί Task.Delay(60000) finished
         β”‚
         β”œβ”€β–Ί CloseAll("EURUSD")
         β”‚   └─► Position already closed (TP triggered)
         β”‚
T=67s    β”œβ”€β–Ί GetBalanceAsync()           β†’ $10008.00
         β”œβ”€β–Ί profit = 10008.00 - 10000.00 = +$8.00
         β”‚
         └─► RETURN profit = 8.00

TOTAL: Caught news breakout WITHOUT predicting direction!
       Profit: +$8.00

πŸ“Š What the result is made ofΒΆ

Profit calculation (upward breakout, TP triggered)ΒΆ

INITIAL BALANCE: $10000.00

STRADDLE PLACED:
- BuyStop @ 1.10017 (SL: 1.09997, TP: 1.10057)
- SellStop @ 1.09985 (canceled after upward breakout)

UPWARD BREAKOUT:
- BuyStop triggered @ 1.10017
- Opened position BUY 0.02 lots

TP TRIGGERED:
- Exit @ 1.10057
- Pips: (1.10057 - 1.10017) / 0.00001 = 40 points

PROFIT CALCULATION:
- Profit = Pips Γ— Volume Γ— PointValue
- PointValue for EURUSD (0.02 lots) = 0.02 Γ— $10 = $0.20 per point
- Profit = 40 Γ— $0.20 = +$8.00

FINAL BALANCE: $10008.00
PROFIT = $8.00

return 8.00;

P/L calculation (scenario "BOTH" - both orders triggered)ΒΆ

EXTREME VOLATILITY:

News caused whipsaw (jerked both directions):

1. Price fell to 1.09985 β†’ SellStop triggered
2. Price rose to 1.10017 β†’ BuyStop triggered

TWO POSITIONS OPENED:
- BUY 0.02 @ 1.10017
- SELL 0.02 @ 1.09985

FINAL PRICE (after 30 sec): 1.10005

CLOSING:

BUY position:
  Entry: 1.10017
  Exit:  1.10005
  Pips:  (1.10005 - 1.10017) / 0.00001 = -12 points
  P/L:   -12 Γ— $0.20 = -$2.40

SELL position:
  Entry: 1.09985
  Exit:  1.10005
  Pips:  (1.09985 - 1.10005) / 0.00001 = -20 points
  P/L:   -20 Γ— $0.20 = -$4.00

TOTAL:
  BUY:  -$2.40
  SELL: -$4.00
  ─────────────
  TOTAL: -$6.40

FINAL BALANCE: $9993.60
PROFIT = -$6.40

This is the WORST scenario (whipsaw) - both orders triggered, both in loss.
Probability: ~5-10% for strong news.

P/L calculation (timeout - breakout didn't happen)ΒΆ

WEAK NEWS:

News released, but data within forecast.
Price moves in narrow range:
1.10000 β†’ 1.10005 β†’ 1.09998 β†’ 1.10003...

NO ORDER TRIGGERED:
- BuyStop @ 1.10017 (price didn't reach)
- SellStop @ 1.09985 (price didn't reach)

TIMEOUT (180 seconds):
- Both orders STILL pending
- direction = ""
- executedOrder = null
- pendingOrder = null

CANCELING BOTH ORDERS:
- CloseByTicket(789) β†’ BuyStop canceled
- CloseByTicket(790) β†’ SellStop canceled

FINAL BALANCE: $10000.00
PROFIT = $0.00

Orders didn't execute β†’ no losses!

🧩 Components and their roles¢

1. NewsStraddleOrchestratorΒΆ

Role: News straddle strategy coordinator

Tasks:

  • Manages timing (countdown until news)
  • Places symmetric straddle (BuyStop + SellStop)
  • MONITORS every SECOND (faster than regular PendingBreakout)
  • Detects 3 breakout scenarios (UPWARD, DOWNWARD, BOTH)
  • Cancels opposite order (OCO mechanism)
  • Handles timeout

2. Key timing parametersΒΆ

public int SecondsBeforeNews { get; set; } = 60;
// ↑ Countdown until news
// Launch orchestrator 60 seconds before exact time

public int MaxWaitAfterNewsSeconds { get; set; } = 180;
// ↑ Maximum time waiting for breakout
// News acts fast β†’ 3 minutes is enough

3. MT5Sugar Extension MethodsΒΆ

// Straddle placement:
BuyStopPoints(priceOffsetPoints: +15)   // Above price
SellStopPoints(priceOffsetPoints: -15)  // Below price

// Cancellation:
CloseByTicket(ticket)  // Cancel pending order
CloseAll(symbol)       // Close all positions

4. Breakout detection logicΒΆ

KEY MECHANISM: Check ticket presence in pending list

OpenedOrdersTicketsAsync() returns list of ONLY pending orders.
When order EXECUTES β†’ it becomes POSITION β†’ disappears from list.

if (!buyStillPending && sellStillPending)
   β†’ BuyStop DISAPPEARED (executed) β†’ UPWARD breakout

else if (buyStillPending && !sellStillPending)
   β†’ SellStop DISAPPEARED (executed) β†’ DOWNWARD breakout

else if (!buyStillPending && !sellStillPending)
   β†’ BOTH DISAPPEARED β†’ BOTH (extreme volatility)

πŸ” Final Dependency DiagramΒΆ

β”‚  USER CODE                                                  
β”‚  var orch = new NewsStraddleOrchestrator(service);          
β”‚  orch.SecondsBeforeNews = 60;  ← Launch 60 sec before       
β”‚  await orch.ExecuteAsync();    ← @ 13:29:00 (NFP @ 13:30)   
└──────────────────────────┬──────────────────────────────────
                           β”‚
                           β–Ό

β”‚  NewsStraddleOrchestrator                                   
β”‚    
β”‚  β”‚  ExecuteAsync() {                                       
β”‚  β”‚    1. Task.Delay(SecondsBeforeNews Γ— 1000)              
β”‚  β”‚    2. SymbolInfoTickAsync() β†’ current price            
β”‚  β”‚    3. BuyStopPoints(+StraddleDistance)                  
β”‚  β”‚    4. SellStopPoints(-StraddleDistance)                 
β”‚  β”‚    5. LOOP (every 1 sec, max 3 min):                    
β”‚  β”‚       - OpenedOrdersTicketsAsync()                      
β”‚  β”‚       - Check: which order disappeared?                 
β”‚  β”‚       - IF one disappeared β†’ CloseByTicket(other)       
β”‚  β”‚    6. Task.Delay(60000)  ← Hold position                
β”‚  β”‚    7. CloseAll()                                        
β”‚  β”‚  }                                                      
β”‚  └─────────────────────────────────────────────────────── 
└──────────────────────────┬──────────────────────────────────
                           β”‚
                           β–Ό

β”‚  MT5Sugar Extension Methods                                 
β”‚  - BuyStopPoints(+offsetPoints)  β†’ above Ask                
β”‚  - SellStopPoints(-offsetPoints) β†’ below Bid                
β”‚  - CloseByTicket(ticket) β†’ OCO cancellation                 
β”‚  - CloseAll(symbol) β†’ final closing                         
└──────────────────────────┬──────────────────────────────────
                           β”‚
                           β–Ό
                    [MT5 Terminal]

🎯 Summary¢

NewsStraddleOrchestrator is made of:

  1. 1 dependency: MT5Service _service

  2. 7 parameters: Symbol, StraddleDistancePoints, Volume, SL, TP, SecondsBeforeNews, MaxWaitAfterNewsSeconds

  3. Key logic:

  4. Countdown until news (Task.Delay)

  5. Symmetric straddle (BuyStop + SellStop)
  6. Monitoring every second (faster than regular breakout!)
  7. 3 scenarios: UPWARD, DOWNWARD, BOTH
  8. OCO mechanism (cancel opposite)

Works through:

  • Timing: launch 60 seconds before news
  • Placing straddle right before news
  • Fast monitoring (1 second instead of 3)
  • Detecting breakout through ticket disappearance from pending list
  • Automatic opposite order cancellation

Returns: - double profit - difference between final and initial balance

Key insight:

Straddle allows catching news volatility WITHOUT predicting direction. By placing orders in both directions, we're guaranteed to catch the movement if it's strong enough. Fast monitoring (every second) is critically important for news - they act INSTANTLY!

Success mathematics:

R:R ratio = 1:2 (SL=20, TP=40)

Even with 40% win rate on news β†’ profitable:
- 6 trades: 4 losses (-$32) + 2 wins (+$32) = $0
- 7 trades: 4 losses (-$32) + 3 wins (+$48) = +$16

News often gives strong directional movements β†’ TP achievable!