Skip to content

NewsStraddleOrchestrator - News Straddle

Description

NewsStraddleOrchestrator is an orchestrator for news trading using the straddle method. Before important news release, it places two pending Stop orders: Buy Stop above and Sell Stop below the current price. When the news comes out and the price moves sharply in either direction, the corresponding order is triggered and the opposite is cancelled.

Operating Principle: "News Trading" - catch volatility from news without predicting direction. Straddle allows catching the movement regardless of whether the price goes up or down after the news (NFP, CPI, central bank decisions, etc.).

File: Examples\Orchestrators\NewsStraddleOrchestrator.cs


Architecture

NEWS STRADDLE ORCHESTRATOR
MT5Service Instance
  ┌─────┼─────┐
  ↓     ↓     ↓
BuyStop SellStop CloseBy
Points  Points   Ticket
(above) (below)  (cancel)

Dependencies

  • MT5Service: Service layer for MT5
  • MT5Sugar Extension Methods: BuyStopPoints, SellStopPoints, CloseByTicket, CloseAll
  • mt5_term_api: gRPC types (OrderSendData, OpenedOrdersTicketsData)

Configuration Parameters

Parameter Type Default Description
Symbol string "EURUSD" Trading instrument
StraddleDistancePoints int 15 Distance of orders from current price (in points)
Volume double 0.02 Volume of each order in lots
StopLossPoints int 20 Stop-loss in points
TakeProfitPoints int 40 Take-profit in points
SecondsBeforeNews int 60 Waiting time before news (sec)
MaxWaitAfterNewsSeconds int 180 Maximum waiting time for breakout after placing orders

Configuration Example

var newsStraddle = new NewsStraddleOrchestrator(service)
{
    Symbol = "EURUSD",
    StraddleDistancePoints = 20,    // Orders at distance of 20 points
    Volume = 0.01,                  // Conservative volume
    StopLossPoints = 25,            // SL = 25 points
    TakeProfitPoints = 60,          // TP = 60 points (R:R = 1:2.4)
    SecondsBeforeNews = 120,        // Wait 2 minutes before news
    MaxWaitAfterNewsSeconds = 300   // Wait for breakout 5 minutes
};

How to Run

You can run this orchestrator using any of the following commands:

dotnet run 10
dotnet run news
dotnet run newsstraddle

Algorithm

Flowchart

START
Get initial balance
Task.Delay(SecondsBeforeNews × 1000)  // Countdown
"NEWS EVENT IMMINENT!"
SymbolInfoTickAsync() → current price
BuyStopPoints(+StraddleDistancePoints)
  → If error → return 0
SellStopPoints(-StraddleDistancePoints)
  → If error → cancel BuyStop, return 0
"STRADDLE ACTIVE"
MONITORING (every second, max MaxWaitAfterNewsSeconds)
  → OpenedOrdersTicketsAsync()
  → Check: BuyStop still pending?
  → Check: SellStop still pending?
  → CONDITIONS:
      If BuyStop executed, SellStop pending
        → UPWARD breakout → cancel SellStop
      If SellStop executed, BuyStop pending
        → DOWNWARD breakout → cancel BuyStop
      If both executed
        → BOTH (extreme volatility!)
      Timeout → cancel both
Task.Delay(60000)  // Hold position 60 sec
CloseAll(Symbol)
Return profit
END

Step-by-Step Description

1. Initialization and countdown (lines 36-47)

var initialBalance = await _service.GetBalanceAsync();

Console.WriteLine($"  Starting balance: ${initialBalance:F2}");
Console.WriteLine($"  Symbol: {Symbol}");
Console.WriteLine($"  Straddle distance: {StraddleDistancePoints} pts");


// │  COUNTDOWN TO NEWS                                      
// │  Simulating waiting before event                        
// └─────────────────────────────────────────────────────────
Console.WriteLine($"  ⏲  Waiting {SecondsBeforeNews}s before news event...\n");
await Task.Delay(SecondsBeforeNews * 1000, ct);

What happens:

  • Waiting before news (default 60 seconds)
  • In real trading: run orchestrator 1-2 minutes before exact news time
  • Example: NFP releases at 13:30 UTC → run orchestrator at 13:29:00

2. Getting current price before news (lines 49-51)

var tick = await _service.SymbolInfoTickAsync(Symbol);

Console.WriteLine($"  📰 NEWS EVENT IMMINENT!");
Console.WriteLine($"  Current: Bid={tick.Bid:F5}, Ask={tick.Ask:F5}\n");

Critically important:

  • Price is obtained IMMEDIATELY before placing orders
  • Ensures straddle symmetry relative to current price

3. Placing Buy Stop (upper straddle order) (lines 53-70)

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;
}

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

Calculation example:

Current Ask price: 1.10000
StraddleDistancePoints: 15
point: 0.00001

BuyStop price = 1.10000 + (15 × 0.00001) = 1.10015

When news comes out and price shoots UP to 1.10015 → order executes

4. Placing Sell Stop (lower straddle order) (lines 72-91)

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...");


    // │  IMPORTANT: Cancel first order!                 
    // └─────────────────────────────────────────────────
    await _service.CloseByTicket(buyStopResult.Order);
    return 0;
}

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

Calculation example:

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

SellStop price = 1.10000 + (-15 × 0.00001) = 1.09985

When news comes out and price shoots DOWN to 1.09985 → order executes

Result:

│  STRADDLE PLACED:                                   
│  1.10015  BUY STOP  (upward breakout)               
│           SL: 1.09995, TP: 1.10055                  
│  1.10000  CURRENT PRICE                             
│  1.09985  SELL STOP (downward breakout)             
│           SL: 1.10005, TP: 1.09945                  
└─────────────────────────────────────────────────────

5. Monitoring for breakout after news (lines 94-136)

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)          
// └─────────────────────────────────────────────────────────
while (DateTime.UtcNow - monitorStart < timeout && !ct.IsCancellationRequested)
{
    await Task.Delay(1000, ct);  // Every second (faster than PendingBreakout!)

    var tickets = await _service.OpenedOrdersTicketsAsync();
    bool buyStillPending = false;
    bool sellStillPending = false;

    foreach (var ticket in tickets.OpenedOrdersTickets)
    {
        if (ticket == (long)buyStopResult.Order) buyStillPending = true;
        if (ticket == (long)sellStopResult.Order) sellStillPending = true;
    }

    // ═══════════════════════════════════════════════════════
    // SCENARIO 1: UPWARD BREAKOUT
    // ═══════════════════════════════════════════════════════
    if (!buyStillPending && sellStillPending)
    {
        executedOrder = buyStopResult.Order;
        pendingOrder = sellStopResult.Order;
        direction = "UPWARD";
        break;
    }

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

    // ═══════════════════════════════════════════════════════
    // SCENARIO 3: BOTH TRIGGERED (rare case!)
    // ═══════════════════════════════════════════════════════
    else if (!buyStillPending && !sellStillPending)
    {
        Console.WriteLine("  ⚡ BOTH ORDERS TRIGGERED - Extreme volatility!");
        direction = "BOTH";
        break;
    }

    // SCENARIO 4: Both still pending → continue waiting
}

Key differences from PendingBreakoutOrchestrator:

  • Check every second (instead of 3 seconds) → news requires fast reaction
  • Additional scenario "BOTH" for extreme volatility
  • Short timeout (3 minutes) → news acts quickly

6. Breakout handling (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}...");

    // 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 (extreme volatility)
// ═══════════════════════════════════════════════════════
else if (direction == "BOTH")
{
    // Price jerked in both directions very fast!
    // TWO positions opened (BUY and SELL simultaneously)
    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);
}

7. Final close (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;

Strategy Visualization

Scenario 1: UPWARD Breakout (typical NFP scenario)

T=0      START
         SecondsBeforeNews = 60
         ├─► Task.Delay(60000) → waiting for news
T=60s    ├─► "NEWS EVENT IMMINENT!"
         │   Current price: Bid=1.10000, Ask=1.10002
         ├─► BuyStopPoints(+15)  → order @ 1.10017
         ├─► SellStopPoints(-15) → order @ 1.09985
         │   STRADDLE PLACED:
         │   │ BUY STOP  @ 1.10017              
         │   │ CURRENT   @ 1.10000              
         │   │ SELL STOP @ 1.09985              
         │   └──────────────────────────────────
T=61s    ├─► NEWS RELEASED! (NFP better than forecast)
         │   Price shoots UP:
         │   1.10000 → 1.10010 → 1.10018...
T=62s    ├─► BUY STOP TRIGGERED @ 1.10017!
         │   Opened BUY position 0.02 lots
         │   SL: 1.09997, TP: 1.10057
         ├─► Monitoring detected:
         │   buyStillPending = false (disappeared from list)
         │   sellStillPending = true
         │   → "UPWARD BREAKOUT DETECTED!"
         │   → Cancel SELL STOP @ 1.09985
         │   STATE:
         │   │ Position: BUY 0.02 @ 1.10017     
         │   │ SL: 1.09997, TP: 1.10057         
         │   └──────────────────────────────────
         │   Price continues to rise:
         │   1.10018 → 1.10035 → 1.10050 → 1.10057...
T=90s    ├─► TP HIT @ 1.10057!
         │   Profit: +40 pts × 0.02 lots = +$8.00
T=120s   ├─► Task.Delay(60000) finished
         ├─► CloseAll("EURUSD")
         │   └─► Position already closed (TP hit earlier)
         └─► Result: Profit +$8.00

TOTAL: Caught movement from news WITHOUT predicting direction!

Scenario 2: DOWNWARD Breakout

T=60s    STRADDLE PLACED:
         BUY STOP  @ 1.10017
         SELL STOP @ 1.09985

T=61s    NEWS: NFP worse than forecast
         Price shoots down:
         1.10000 → 1.09995 → 1.09985 → 1.09980...

T=62s    SELL STOP TRIGGERED @ 1.09985!
         Opened SELL position 0.02 lots
         SL: 1.10005, TP: 1.09945

         → "DOWNWARD BREAKOUT DETECTED!"
         → Cancel BUY STOP @ 1.10017

         Price continues to fall:
         1.09980 → 1.09960 → 1.09945...

T=85s    TP HIT @ 1.09945!
         Profit: +40 pts × 0.02 lots = +$8.00

Result: Profit +$8.00

Scenario 3: Extreme volatility (BOTH orders triggered)

T=60s    STRADDLE PLACED:
         BUY STOP  @ 1.10017
         SELL STOP @ 1.09985

T=61s    NEWS: Unexpected data!
         Price sharply DOWN:
         1.10000 → 1.09985 (SELL STOP triggered!)

T=62s    Then sharply UP (correction):
         1.09985 → 1.10000 → 1.10017 (BUY STOP triggered!)


         │ ⚡ BOTH ORDERS TRIGGERED!                 
         │ Position 1: SELL 0.02 @ 1.09985          
         │ Position 2: BUY  0.02 @ 1.10017          
         │ This is HEDGE (mutually neutralize)!     
         └──────────────────────────────────────────

T=92s    Task.Delay(30000) finished

         CloseAll("EURUSD")
         ├─► Closed SELL @ current price 1.10010
         │   P/L: (1.09985 - 1.10010) × 0.02 = -$5
         └─► Closed BUY @ current price 1.10010
             P/L: (1.10010 - 1.10017) × 0.02 = -$1.40

         Total: -$5 + (-$1.40) = -$6.40

Result: Loss -$6.40 (spreads and commissions on whipsaw)

Scenario 4: No breakout (weak news)

T=60s    STRADDLE PLACED:
         BUY STOP  @ 1.10017
         SELL STOP @ 1.09985

T=61s    NEWS: Data within forecast range
         Price moves in narrow range:
         1.10000 → 1.10005 → 1.09998 → 1.10003...

T=180s   NO ORDERS TRIGGERED!
         (Price didn't reach either 1.10017 or 1.09985)

         → "No breakout after 180s"
         → Cancel BUY STOP
         → Cancel SELL STOP

Result: Profit $0.00 (orders not executed)

When to Use News Straddle

✅ Best News Events

  1. Non-Farm Payrolls (NFP):
  2. USA, first Friday of month, 13:30 UTC
  3. Highest volatility
  4. Recommended parameters:

    StraddleDistancePoints = 20
    StopLossPoints = 30
    TakeProfitPoints = 70
    

  5. Central Bank Decisions:

  6. Fed (FOMC), ECB, BoE
  7. Huge movements on rate decisions
  8. Recommended parameters:

    StraddleDistancePoints = 25
    StopLossPoints = 35
    TakeProfitPoints = 90
    

  9. CPI (Inflation Data):

  10. USA, mid-month, 13:30 UTC
  11. Strong movements, especially on surprises
  12. Recommended parameters:

    StraddleDistancePoints = 18
    StopLossPoints = 25
    TakeProfitPoints = 60
    

  13. GDP:

  14. Quarterly data
  15. Medium volatility
  16. Recommended parameters:
    StraddleDistancePoints = 15
    StopLossPoints = 20
    TakeProfitPoints = 50
    

❌ Bad News Events

  • Secondary data: Consumer Confidence, Housing Starts.

  • Central bank speeches: Unpredictable volatility timing.

  • Corporate earnings: More for stocks, not forex.

  • Geopolitical events: Unpredictable, may cause whipsaw.


Risk Management

Choosing StraddleDistancePoints

Too close (5-10 pts):
  ✗ False triggers from noise before news
  ✗ Both orders may trigger (whipsaw)

Optimal (15-25 pts):
  ✓ Protection from noise
  ✓ Catches real breakouts
  ✓ Rarely both orders trigger

Too far (30+ pts):
  ✗ Will miss weak news
  ✗ Late execution (less movement before TP)

Recommendation:
StraddleDistancePoints = ATR(14) × 0.3-0.5
For EURUSD with ATR=12: use 15-20 pts

R:R Calculation

Default:
- StraddleDistancePoints = 15
- StopLossPoints = 20
- TakeProfitPoints = 40

Risk/Reward:
- Risk: 20 pts
- Reward: 40 pts
- R:R = 1:2 ✓

Why this is good:
- Even with 40% win rate → profitable
- News often gives strong directional movements
- TP reachable within 1-3 minutes after news

Maximum Risk

Worst Case: Both orders triggered (whipsaw)

Position 1: SELL 0.02 @ 1.09985, SL 1.10005
Position 2: BUY 0.02 @ 1.10017, SL 1.09997

If both SL trigger:
  SELL SL: -20 pts × 0.02 = -$4.00
  BUY SL:  -20 pts × 0.02 = -$4.00
  ────────────────────────────────
  Maximum loss: -$8.00

Probability: ~5-10% (rarely both SL trigger simultaneously)

Recommendations

  1. Position volume:
  2. Conservative: 0.01 lots (risk $2-4)
  3. Moderate: 0.02 lots (risk $4-8)
  4. Aggressive: 0.05 lots (risk $10-20)

  5. Placement timing:

  6. Run orchestrator 1-2 minutes before exact news time
  7. Too early → risk of triggering from noise
  8. Too late → miss start of movement

  9. Calendar check:

    // Always check economic calendar!
    // Use only HIGH IMPACT news
    

  10. Spread:

  11. Spread widens before news!
  12. EURUSD: usually 0.5-1 point, during news can be 3-5 points
  13. Account for this in StraddleDistancePoints

Usage Examples

Example 1: NFP Trading

// Non-Farm Payrolls - first Friday of month, 13:30 UTC
// Run at 13:29:00

var nfpStraddle = new NewsStraddleOrchestrator(service)
{
    Symbol = "EURUSD",
    StraddleDistancePoints = 20,    // Wide (strong volatility)
    Volume = 0.01,                  // Conservative
    StopLossPoints = 30,            // Wide SL
    TakeProfitPoints = 70,          // Ambitious TP (R:R = 1:2.3)
    SecondsBeforeNews = 60,         // Wait 1 minute
    MaxWaitAfterNewsSeconds = 240   // 4 minutes for breakout
};

var profit = await nfpStraddle.ExecuteAsync();
Console.WriteLine($"NFP Trade: ${profit:F2}");

Example 2: CPI Trading

// CPI - mid-month, 13:30 UTC
// Run at 13:29:30

var cpiStraddle = new NewsStraddleOrchestrator(service)
{
    Symbol = "EURUSD",
    StraddleDistancePoints = 18,
    Volume = 0.02,
    StopLossPoints = 25,
    TakeProfitPoints = 60,          // R:R = 1:2.4
    SecondsBeforeNews = 30,         // Short wait
    MaxWaitAfterNewsSeconds = 180
};

var profit = await cpiStraddle.ExecuteAsync();

Example 3: FOMC Decision

// FOMC - 8 times per year, ~19:00 UTC
// Run at 18:58:00

var fomcStraddle = new NewsStraddleOrchestrator(service)
{
    Symbol = "EURUSD",
    StraddleDistancePoints = 25,    // VERY wide
    Volume = 0.01,
    StopLossPoints = 40,            // Protection from whipsaw
    TakeProfitPoints = 100,         // R:R = 1:2.5
    SecondsBeforeNews = 120,        // 2 minutes before
    MaxWaitAfterNewsSeconds = 300   // 5 minutes
};

var profit = await fomcStraddle.ExecuteAsync();

Optimization

Possible Improvements

  1. Adaptive distance based on ATR:

    var atr = await GetATR(Symbol, period: 14);
    StraddleDistancePoints = (int)(atr * 0.4);
    

  2. Asymmetric straddle (if there's bias):

    // Expect upward breakout more than downward
    buyStopDistance = 15;   // Closer
    sellStopDistance = 25;  // Further
    

  3. Partial close on profit:

    // After +20 pts close 50%, trail the rest
    await Task.Delay(10000);  // 10 sec
    var positions = await _service.PositionsAsync(Symbol);
    if (positions.Count > 0)
    {
        await _service.ClosePartial(positions[0].Ticket, percent: 50);
        await _service.TrailStart(positions[0].Ticket, trailPoints: 10);
    }
    

  4. Cancel orders if spread too wide:

    var spread = tick.Ask - tick.Bid;
    if (spread > 0.0005)  // 5 points for EURUSD
    {
        Console.WriteLine("Spread too wide - aborting");
        return 0;
    }
    



Console Output

Output example (upward breakout)

+============================================================+
|  NEWS STRADDLE ORCHESTRATOR                               |
+============================================================+

  Starting balance: $10000.00
  Symbol: EURUSD
  Straddle distance: 15 pts
  Volume: 0.02 lots
  SL: 20 pts | TP: 40 pts

  ⏲  Waiting 60s before news event...

  📰 NEWS EVENT IMMINENT!
  Current: Bid=1.10000, Ask=1.10002

  Placing BUY STOP (upper straddle)...
  ✓ BUY STOP: #123456789

  Placing SELL STOP (lower straddle)...
  ✓ SELL STOP: #123456790

  ✅ STRADDLE ACTIVE - Waiting for news spike!

  🚀 UPWARD BREAKOUT DETECTED!
  Position opened: #123456789
  Canceling opposite order #123456790...
  ✓ Opposite order canceled

  ⏳ Holding position for 60 seconds...

  Closing all remaining positions...
  ✓ All closed

  Final balance: $10008.00
  Profit/Loss: $8.00
  Direction: UPWARD

+============================================================+

Summary

NewsStraddleOrchestrator is a powerful strategy for news trading:

Pros:

  • No need to predict direction - catch movement in any direction
  • Excellent R:R ratio (1:2 and better)
  • Automation of news trading
  • Works on most volatile events
  • OCO mechanism (cancel opposite order)

Cons:

  • Whipsaw risk (both orders trigger)
  • Wide spreads during news
  • Requires precise timing (run 1-2 min before news)
  • Not all news gives strong movements
  • Stress from high volatility

Recommendation: Use ONLY for HIGH IMPACT news (NFP, CPI, FOMC). Always check economic calendar. Start with small lots (0.01). Test on demo account before real trading. Avoid trading all news indiscriminately - choose 2-3 most important per month.