Skip to content

SimpleScalpingOrchestrator - Fast Scalping

Description

SimpleScalpingOrchestrator is an orchestrator for fast scalping with automatic position sizing based on risk. It opens a market position (BUY or SELL), holds it for maximum 60 seconds, then closes manually if SL/TP didn't trigger.

Principle: "Quick in, quick out" - fast market entry, tight stops (10 points SL), doubled profit (20 points TP), short holding time. Position size is calculated automatically based on specified risk in money.

File: Examples\Orchestrators\SimpleScalpingOrchestrator.cs


Architecture

SIMPLE SCALPING ORCHESTRATOR
    ↓
MT5Service Instance
    ↓
  ┌─────┼─────┐
  ↓     ↓     ↓
BuyMarket SellMarket CloseBy
ByRisk    ByRisk     Ticket
(risk)    (risk)     (manual)

Dependencies

  • MT5Service: Service layer for MT5
  • MT5Sugar Extension Methods: BuyMarketByRisk, SellMarketByRisk, CloseByTicket
  • mt5_term_api: gRPC types (OrderSendData, OpenedOrdersTicketsData)

Configuration Parameters

Parameter Type Default Description
Symbol string "EURUSD" Trading instrument
RiskAmount double 20.0 Maximum risk in dollars ($20)
StopLossPoints int 10 Stop loss in points
TakeProfitPoints int 20 Take profit in points
IsBuy bool true Direction: true = BUY, false = SELL
MaxHoldSeconds int 60 Maximum position holding time (sec)

Configuration example

var scalpingOrchestrator = new SimpleScalpingOrchestrator(service)
{
    Symbol = "GBPUSD",
    RiskAmount = 30.0,      // Risk $30 per trade
    StopLossPoints = 15,    // SL = 15 points
    TakeProfitPoints = 30,  // TP = 30 points (R:R = 1:2)
    IsBuy = false,          // SELL direction
    MaxHoldSeconds = 90     // Hold up to 90 seconds
};

How to Run

You can execute this orchestrator using several command variations:

# Option 1: By number
dotnet run 13

# Option 2: By short name
dotnet run scalping

# Option 3: By full name
dotnet run simplescalping

All three commands will launch the SimpleScalpingOrchestrator with the default configuration or the settings you specify in the code.


Algorithm

Flowchart

START
  ↓
Get initial balance
  ↓
CONDITION: IsBuy == true?
  YES → BuyMarketByRisk(riskMoney, stopPoints, tpPoints)
  NO → SellMarketByRisk(riskMoney, stopPoints, tpPoints)
  ↓
If error → return 0
  ↓
Task.Delay(MaxHoldSeconds × 1000)  // Wait 60 sec
  ↓
OpenedOrdersTicketsAsync()
  ↓
CHECK: Position still open?
  YES → CloseByTicket(ticket)  // Manual close
  NO → "SL/TP already triggered"
  ↓
Get final balance
  ↓
Return profit
END

Step-by-step description

1. Initialization (lines 35-41)

var initialBalance = await _service.GetBalanceAsync();

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

What happens:

  • Initial balance is obtained to calculate P/L
  • Strategy parameters are displayed

2. Opening position with risk-sizing (lines 45-77)

OrderSendData result;

if (IsBuy)
{

    // │  BUY: Use BuyMarketByRisk                           
    // │  Position size calculated AUTOMATICALLY             
    // └─────────────────────────────────────────────────────
    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");

Critically important:

  • Uses BuyMarketByRisk / SellMarketByRisk instead of fixed volume

  • Method automatically calculates position size based on:

  • riskMoney = $20

  • stopPoints = 10 points
  • Current symbol price
  • Result contains calculated Volume in result.Volume field

Calculation example:

Given:
- riskMoney = $20
- stopPoints = 10
- Symbol = EURUSD
- Point value for EURUSD (1.0 lot) = $10

Calculation:
riskPerPoint = riskMoney / stopPoints = $20 / 10 = $2 per point
Volume = riskPerPoint / pointValue = $2 / $10 = 0.2 lots

Result:
result.Volume = 0.20 lots

3. Holding position (lines 79-81)

Console.WriteLine($"  ⏳ Holding for {MaxHoldSeconds}s...\n");


// │  Wait MaxHoldSeconds (default 60 seconds)              
// │  SL or TP may trigger during this time                  
// └─────────────────────────────────────────────────────────
await Task.Delay(MaxHoldSeconds * 1000, ct);

// MaxHoldSeconds = 60 → Task.Delay(60000 ms) = 60 seconds

What happens:

  • Orchestrator "sleeps" for 60 seconds
  • During this time MT5 Terminal monitors the position
  • If price reaches SL or TP → position automatically closes

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)
{
    if (ticket == (long)result.Order)  // Compare ticket
    {
        stillOpen = true;
        break;
    }
}


// │  CONDITION: Position still open?                        
// └─────────────────────────────────────────────────────────
if (stillOpen)
{
    // SCENARIO 1: Position NOT closed after 60 seconds
    // SL/TP didn't trigger → close manually
    Console.WriteLine($"  Position still open after {MaxHoldSeconds}s - closing manually...");
    await _service.CloseByTicket(result.Order);
    Console.WriteLine("  ✓ Position closed");
}
else
{
    // SCENARIO 2: Position ALREADY closed
    // SL or TP triggered automatically
    Console.WriteLine("  ✓ Position closed automatically (SL/TP hit)");
}

Key logic:

  • OpenedOrdersTicketsAsync() returns list of OPEN positions
  • If our ticket is in the list → position still open → close manually
  • If ticket not in the list → position already closed → do nothing

5. Finalization (lines 106-113)

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

Console.WriteLine($"\n  Final balance: ${finalBalance:F2}");
Console.WriteLine($"  Profit/Loss: ${profit:F2}");

return profit;

Strategy Visualization

Scenario 1: TP triggered (profit)

T=0      BuyMarketByRisk(risk=$20, SL=10pts, TP=20pts)
         │
         ├─► Calculate Volume:
         │   riskPerPoint = $20 / 10 = $2
         │   Volume = $2 / $10 = 0.2 lots
         │
         ├─► Opened position BUY 0.2 lots @ 1.10000
         │   SL: 1.09990 (-10 pts)
         │   TP: 1.10020 (+20 pts)
         │
         │   Price moves:
         │   1.10000 → 1.10005 → 1.10012 → 1.10020...
         │
T=15s    ├─► Price reached 1.10020
         │   TP TRIGGERED!
         │   Position closed automatically
         │   Profit: 20 pts × 0.2 lots = +$40
         │
T=60s    ├─► Task.Delay finished
         │
         ├─► OpenedOrdersTicketsAsync()
         │   → ticket not found (position closed)
         │
         ├─► stillOpen = false
         │
         └─► "Position closed automatically (SL/TP hit)"

Result: Profit +$40

Scenario 2: SL triggered (loss)

T=0      BuyMarketByRisk(risk=$20, SL=10pts, TP=20pts)
         │
         ├─► Opened position BUY 0.2 lots @ 1.10000
         │   SL: 1.09990 (-10 pts)
         │
         │   Price moves:
         │   1.10000 → 1.09998 → 1.09993 → 1.09990...
         │
T=8s     ├─► Price reached 1.09990
         │   SL TRIGGERED!
         │   Position closed automatically
         │   Loss: -10 pts × 0.2 lots = -$20
         │
T=60s    ├─► Task.Delay finished
         │
         ├─► OpenedOrdersTicketsAsync()
         │   → ticket not found (position closed)
         │
         ├─► stillOpen = false
         │
         └─► "Position closed automatically (SL/TP hit)"

Result: Loss -$20 (exactly riskMoney!)

Scenario 3: Manual close (position in flat)

T=0      BuyMarketByRisk(risk=$20, SL=10pts, TP=20pts)
         │
         ├─► Opened position BUY 0.2 lots @ 1.10000
         │   SL: 1.09990
         │   TP: 1.10020
         │
         │   Price moves in range:
         │   1.10000 → 1.10003 → 1.09998 → 1.10005 → 1.10002...
         │
T=60s    ├─► Task.Delay finished
         │   Neither SL nor TP triggered
         │   Current price: 1.10005 (+5 pts in profit)
         │
         ├─► OpenedOrdersTicketsAsync()
         │   → ticket FOUND (position still open)
         │
         ├─► stillOpen = true
         │
         ├─► CloseByTicket(result.Order)
         │   → Close position at current price 1.10005
         │   Profit: +5 pts × 0.2 lots = +$10
         │
         └─► "Position still open after 60s - closing manually"

Result: Profit +$10 (less than TP, but still in profit)

Used MT5Sugar Methods

1. BuyMarketByRisk

public static async Task<OrderSendData> BuyMarketByRisk(
    this MT5Service service,
    string symbol,
    int stopPoints,      // SL in points
    double riskMoney,    // Risk in dollars
    int tpPoints = 0,
    string comment = ""
)

Purpose: Opens market BUY position with automatic size calculation based on risk.

Calculation algorithm:

// 1. Get point value for symbol
var symbolInfo = await service.SymbolInfoAsync(symbol);
double pointValue = symbolInfo.Trade_Contract_Size * symbolInfo.Point;
// For EURUSD: 100000 × 0.00001 = $1 per point for 1.0 lot

// 2. Calculate risk per point
double riskPerPoint = riskMoney / stopPoints;
// $20 / 10 = $2 per point

// 3. Calculate Volume
double volume = riskPerPoint / pointValue;
// $2 / $1 = 2.0 lots (for 1.0 base lot)
// But for EURUSD with base $10 per point: $2 / $10 = 0.2 lots

// 4. Get current Ask price
var tick = await service.SymbolInfoTickAsync(symbol);
double askPrice = tick.Ask;

// 5. Calculate SL and TP
double sl = askPrice - (stopPoints × symbolInfo.Point);
double tp = tpPoints > 0 ? askPrice + (tpPoints × symbolInfo.Point) : 0;

// 6. Open position
return await service.BuyMarketAsync(symbol, volume, sl, tp, comment);

Parameters in SimpleScalpingOrchestrator: - symbol: "EURUSD" - stopPoints: 10 (SL = 10 points from entry price) - riskMoney: $20 (maximum loss if SL triggers) - tpPoints: 20 (TP = 20 points from entry price) - comment: "Scalper"

Returns:

OrderSendData {
    ReturnedCode = 10009,  // Success
    Order = 123456789,     // Position ticket
    Volume = 0.2,          // CALCULATED volume
    Price = 1.10000,       // Execution price
    Comment = "Request completed"
}

2. SellMarketByRisk

public static async Task<OrderSendData> SellMarketByRisk(
    this MT5Service service,
    string symbol,
    int stopPoints,
    double riskMoney,
    int tpPoints = 0,
    string comment = ""
)

Purpose: Opens market SELL position with automatic size calculation.

Differences from BuyMarketByRisk:

  • Uses Bid price instead of Ask
  • SL above entry price (for SELL)
  • TP below entry price (for SELL)

SL/TP calculation for SELL:

double bidPrice = tick.Bid;
double sl = bidPrice + (stopPoints × point);  // ABOVE for SELL
double tp = tpPoints > 0 ? bidPrice - (tpPoints × point) : 0;  // BELOW for SELL

3. CloseByTicket

public static async Task CloseByTicket(
    this MT5Service service,
    ulong ticket
)

Purpose: Closes position by ticket number.

Usage in orchestrator:

  • Manual position close after MaxHoldSeconds expires (line 98)

4. OpenedOrdersTicketsAsync

public async Task<OpenedOrdersTicketsData> OpenedOrdersTicketsAsync()

Purpose: Gets list of tickets for open positions and pending orders.

Returns:

OpenedOrdersTicketsData {
    OpenedOrdersTickets: [list of pending orders],
    OpenedPositionTickets: [list of open positions]  ← USED IN ORCHESTRATOR
}

Usage:

  • Check position presence in open list (lines 84-93)
  • If ticket found → position still open
  • If ticket not found → position closed (SL/TP triggered)

Risk Management

Main advantage: Constant risk

TRADITIONAL APPROACH (fixed Volume):
Position 1: 0.1 lots, SL=10pts → risk = 0.1 × 10 × $10 = $10
Position 2: 0.1 lots, SL=20pts → risk = 0.1 × 20 × $10 = $20
Position 3: 0.1 lots, SL=5pts  → risk = 0.1 × 5 × $10 = $5

PROBLEM: Risk changes depending on SL size!

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

RISK-SIZING APPROACH (BuyMarketByRisk):
Position 1: riskMoney=$20, SL=10pts → Volume = $20/(10×$10) = 0.2 lots
Position 2: riskMoney=$20, SL=20pts → Volume = $20/(20×$10) = 0.1 lots
Position 3: riskMoney=$20, SL=5pts  → Volume = $20/(5×$10) = 0.4 lots

RESULT: Risk ALWAYS $20, regardless of SL size!

R:R ratio calculation

Default:
- StopLossPoints = 10
- TakeProfitPoints = 20
- RiskAmount = $20

Risk/Reward Ratio:
Risk = $20 (if SL triggers)
Reward = $40 (if TP triggers)
R:R = 1:2 ✓

With 40% win rate:
- 6 trades: 4 losses (-$80) + 2 wins (+$80) = $0 (break-even)
- 7 trades: 4 losses (-$80) + 3 wins (+$120) = +$40 (profit)

Conclusion: 43% win rate sufficient for profitability!

Recommendations

  1. Risk size:
  2. No more than 1-2% of deposit per trade
  3. For $10000 deposit: recommended risk $100-200
  4. Default $20 suitable for $1000-2000 deposit

  5. SL/TP ratio:

  6. Minimum 1:2 (as default)
  7. Optimal 1:2.5 or 1:3
  8. Never use 1:1 or worse

  9. Holding time:

  10. MaxHoldSeconds = 60 for M1 scalping
  11. MaxHoldSeconds = 180 for M5 trading
  12. MaxHoldSeconds = 300 for M15 trading

  13. Direction selection:

  14. Use technical analysis to determine IsBuy
  15. Don't trade against strong trend
  16. Combine with indicators (MA, RSI, Stochastic)

Usage Examples

Example 1: Conservative scalping

var service = new MT5Service(account);

var conservativeScalp = new SimpleScalpingOrchestrator(service)
{
    Symbol = "EURUSD",
    RiskAmount = 15.0,      // Low risk
    StopLossPoints = 8,     // Very tight SL
    TakeProfitPoints = 20,  // R:R = 1:2.5
    IsBuy = true,
    MaxHoldSeconds = 45     // Short time
};

var profit = await conservativeScalp.ExecuteAsync();
Console.WriteLine($"Scalping completed: ${profit:F2}");

Example 2: Aggressive scalping

var aggressiveScalp = new SimpleScalpingOrchestrator(service)
{
    Symbol = "GBPUSD",
    RiskAmount = 50.0,      // High risk
    StopLossPoints = 12,
    TakeProfitPoints = 36,  // R:R = 1:3
    IsBuy = false,          // SELL
    MaxHoldSeconds = 90
};

var profit = await aggressiveScalp.ExecuteAsync();

Example 3: Use in loop (multiple trades)

var totalProfit = 0.0;
var trades = 0;

for (int i = 0; i < 10; i++)
{
    // Determine direction (e.g., by indicator)
    bool direction = await GetTrendDirection("EURUSD");

    var scalper = new SimpleScalpingOrchestrator(service)
    {
        Symbol = "EURUSD",
        RiskAmount = 20.0,
        StopLossPoints = 10,
        TakeProfitPoints = 20,
        IsBuy = direction,
        MaxHoldSeconds = 60
    };

    var profit = await scalper.ExecuteAsync();
    totalProfit += profit;
    trades++;

    Console.WriteLine($"Trade {trades}: ${profit:F2}");

    // Pause between trades
    await Task.Delay(30000);  // 30 seconds
}

Console.WriteLine($"\nTotal: {trades} trades, ${totalProfit:F2} profit");

Example 4: Adaptive risk based on balance

var balance = await service.GetBalanceAsync();
var dynamicRisk = balance * 0.01;  // 1% of balance

var adaptiveScalp = new SimpleScalpingOrchestrator(service)
{
    Symbol = "EURUSD",
    RiskAmount = dynamicRisk,  // Automatically adapts to balance
    StopLossPoints = 10,
    TakeProfitPoints = 20,
    IsBuy = true,
    MaxHoldSeconds = 60
};

var profit = await adaptiveScalp.ExecuteAsync();

Error Handling

Position opening error (lines 70-74)

if (result.ReturnedCode != 10009)
{
    Console.WriteLine($"  ✗ Order failed: {result.Comment}");
    return 0;  // Emergency exit
}

Possible causes:

  • Insufficient funds (even after Volume calculation)
  • Market closed
  • Incorrect symbol
  • Minimum/maximum lot exceeded

Exception in main block (lines 115-120)

catch (Exception ex)
{
    Console.WriteLine($"\n  ✗ Error: {ex.Message}");
    return 0;
}

Problem: No emergency position close!

Improvement:

catch (Exception ex)
{
    Console.WriteLine($"\n  ✗ Error: {ex.Message}");

    // Attempt to close position on error
    if (result != null && result.ReturnedCode == 10009)
    {
        try
        {
            await _service.CloseByTicket(result.Order);
            Console.WriteLine("  Emergency close executed");
        }
        catch { }
    }

    return 0;
}


Console Output

Output example (TP triggered)

+============================================================+
|  SIMPLE SCALPING ORCHESTRATOR                             |
+============================================================+

  Starting balance: $10000.00
  Symbol: EURUSD
  Direction: BUY
  Risk: $20.00
  SL: 10 pts | TP: 20 pts
  Max hold: 60s

  Opening position...
  ✓ Position opened: #123456789
  Volume: 0.20 lots

  ⏳ Holding for 60s...

  ✓ Position closed automatically (SL/TP hit)

  Final balance: $10040.00
  Profit/Loss: $40.00

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

Output example (manual close)

+============================================================+
|  SIMPLE SCALPING ORCHESTRATOR                             |
+============================================================+

  Starting balance: $10000.00
  Symbol: EURUSD
  Direction: SELL
  Risk: $20.00
  SL: 10 pts | TP: 20 pts
  Max hold: 60s

  Opening position...
  ✓ Position opened: #123456790
  Volume: 0.20 lots

  ⏳ Holding for 60s...

  Position still open after 60s - closing manually...
  ✓ Position closed

  Final balance: $10010.00
  Profit/Loss: $10.00

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

When to Use Simple Scalping

✅ Suitable Conditions

  • M1-M5 timeframes: Very short-term trading
  • High liquidity: London/New York sessions
  • Narrow spreads: EURUSD, GBPUSD, USDJPY in active hours
  • Clear signals: Level breakout, support/resistance bounce
  • Low volatility: Avoid news

❌ Unsuitable Conditions

  • Asian session: Low volatility, wide spreads
  • Before/after news: Unpredictable movements
  • Exotic pairs: High commissions, low liquidity
  • H1+ timeframes: Too long holding for scalping

Optimization

Possible improvements

  1. Dynamic SL based on ATR:

    var atr = await GetATR(Symbol, period: 14);
    StopLossPoints = (int)(atr * 0.5);  // 50% of ATR
    TakeProfitPoints = StopLossPoints * 2;  // Preserve R:R 1:2
    

  2. Partial closing:

    // After reaching 50% of TP
    await Task.Delay(30000);  // 30 sec
    var positions = await _service.PositionsAsync(Symbol);
    if (positions.Count > 0)
    {
        await _service.ClosePartial(positions[0].Ticket, percent: 50);
        await _service.MoveToBreakeven(positions[0].Ticket);
    }
    

  3. Trailing stop:

    await Task.Delay(30000);
    // If position in profit > 10 points
    await _service.TrailStart(result.Order, trailPoints: 5);
    

  4. Time filter:

    var hour = DateTime.UtcNow.Hour;
    if (hour >= 7 && hour <= 16)  // London/NY sessions
    {
        await ExecuteAsync();
    }
    



Summary

SimpleScalpingOrchestrator is a basic fast scalping strategy with risk management:

✅ Pros:

  • Automatic risk-sizing - position size always matches specified risk
  • Excellent R:R ratio (1:2 by default)
  • Ease of use and configuration
  • Short holding time (minimize exposure)
  • Suitable for automation

❌ Cons:

  • Requires active market (narrow spreads)
  • High frequency trades → commission accumulation
  • Dependence on entry accuracy
  • Not suitable for trending markets (better to use trailing)

Recommendation: Use as a basic building block for more complex strategies. Combine with technical analysis for direction selection. Always test on demo account before real trading.