Skip to content

OnPositionProfit HOW

Example from file: examples/demos/lowlevel/03_streaming_methods.go

The OnPositionProfit() method is used to receive profit and loss (P&L) updates for all open positions in real-time.

This stream is particularly useful for:

  • displaying current profit on a monitoring dashboard;
  • analyzing position dynamics;
  • automated risk management.

The stream sends one update every TimerPeriodMilliseconds (0 = the default, 1000 ms). Without open positions the updates are empty (all three lists empty) unless IgnoreEmptyData is true, which skips them.


🧩 Code example

profitReq := &pb.OnPositionProfitRequest{
    TimerPeriodMilliseconds: 1000,  // one update per second (0 = the default, 1000 ms)
    IgnoreEmptyData:         false, // false = also send updates with no position in them
}

profitCtx, profitCancel := context.WithCancel(ctx)
profitChan, profitErrChan := account.OnPositionProfit(profitCtx, profitReq)

fmt.Printf("Streaming position P&L updates (max %d events or %d seconds)...\n", MAX_EVENTS, MAX_SECONDS)
fmt.Println("  NOTE: This stream sends an update every TimerPeriodMilliseconds; with no open position the")
fmt.Println("        update is empty (set IgnoreEmptyData: true to skip empty updates)")

eventCount = 0
timeout = time.After(MAX_SECONDS * time.Second)

streamProfit:
for {
    select {
    case profitData, ok := <-profitChan:
        if !ok {
            fmt.Println("  Stream closed by server")
            break streamProfit
        }
        eventCount++
        totalPositions := len(profitData.NewPositions) + len(profitData.UpdatedPositions) + len(profitData.DeletedPositions)
        fmt.Printf("  Event #%d: Type=%v Total=%d (New=%d Updated=%d Deleted=%d)\n",
            eventCount, profitData.Type, totalPositions,
            len(profitData.NewPositions),
            len(profitData.UpdatedPositions),
            len(profitData.DeletedPositions))

        // Show first 3 updated positions
        maxShow := 3
        if len(profitData.UpdatedPositions) > 0 {
            if len(profitData.UpdatedPositions) < maxShow {
                maxShow = len(profitData.UpdatedPositions)
            }
            for i := 0; i < maxShow; i++ {
                pos := profitData.UpdatedPositions[i]
                fmt.Printf("    Updated Position: Ticket=%d Symbol=%s Profit=%.2f\n",
                    pos.Ticket,
                    pos.PositionSymbol,
                    pos.Profit)
            }
        }

        if eventCount >= MAX_EVENTS {
            fmt.Printf("  ✓ Received %d events, stopping stream\n", MAX_EVENTS)
            break streamProfit
        }

    case err := <-profitErrChan:
        if err != nil {
            helpers.PrintShortError(err, "Stream error")
            break streamProfit
        }

    case <-timeout:
        fmt.Printf("  ⏱ Timeout after %d seconds (received %d events)\n", MAX_SECONDS, eventCount)
        break streamProfit
    }
}
profitCancel() // closes this stream on the server

🟢 Detailed Code Explanation

1️. Subscribe to Stream

profitReq := &pb.OnPositionProfitRequest{
    TimerPeriodMilliseconds: 1000,  // one update per second (0 = the default, 1000 ms)
    IgnoreEmptyData:         false, // false = also send updates with no position in them
}

The request sets the update period (TimerPeriodMilliseconds) and whether empty updates are sent (IgnoreEmptyData). There is no symbol filter: the subscription covers all positions on the account (filter by PositionSymbol on the client side).


2️. Start the Stream

profitCtx, profitCancel := context.WithCancel(ctx)
profitChan, profitErrChan := account.OnPositionProfit(profitCtx, profitReq)

The stream gets its own context, derived from ctx: profitCancel() after the loop closes this stream on the server at once. Without it the stream would stay open, unread, until ctx itself ends — and every later stream of the demo would pile up next to it.

The method returns two channels:

  • profitChan — position data stream;
  • profitErrChan — connection error stream.

3️. Informational Message

fmt.Println("  NOTE: This stream sends an update every TimerPeriodMilliseconds; with no open position the")
fmt.Println("        update is empty (set IgnoreEmptyData: true to skip empty updates)")

Explains that updates arrive periodically: with no open position they are empty (Total=0), unless IgnoreEmptyData skips them.


4️. Main Event Reception Loop

for {
    select {
    case profitData, ok := <-profitChan:

Uses select to simultaneously listen to three channels: events, errors, and timer.


5️. Process Received Data

totalPositions := len(profitData.NewPositions) + len(profitData.UpdatedPositions) + len(profitData.DeletedPositions)
fmt.Printf("  Event #%d: Type=%v Total=%d (New=%d Updated=%d Deleted=%d)\n", ...)

Each event shows how many positions are:

  • new (NewPositions),
  • updated (UpdatedPositions),
  • deleted (DeletedPositions).

6️. Display Updated Positions

for i := 0; i < maxShow; i++ {
    pos := profitData.UpdatedPositions[i]
    fmt.Printf("    Updated Position: Ticket=%d Symbol=%s Profit=%.2f\n", ...)
}

Shows several of the first updated positions with their tickets, symbols, and profit.


7️. Stream Termination

The stream closes by event limit (MAX_EVENTS), timeout (MAX_SECONDS), or connection error.


📦 What the Server Returns

message OnPositionProfitData {
  MT5_SUB_ENUM_EVENT_GROUP_TYPE type = 1;  // OrderProfit
  repeated OnPositionProfitPositionInfo new_positions = 2;
  repeated OnPositionProfitPositionInfo updated_positions = 3;
  repeated OnPositionProfitPositionInfo deleted_positions = 4;
  OnEventAccountInfo account_info = 5;
  string terminal_instance_guid_id = 6;
}

message OnPositionProfitPositionInfo {
  int32 index = 1;
  int64 ticket = 2;
  double profit = 3;
  string position_symbol = 4;
}

💡 Example Output

Streaming position P&L updates (max 5 events or 10 seconds)...
  NOTE: This stream sends an update every TimerPeriodMilliseconds; with no open position the
        update is empty (set IgnoreEmptyData: true to skip empty updates)
  Event #1: Type=OrderProfit Total=1 (New=0 Updated=1 Deleted=0)
    Updated Position: Ticket=123456 Symbol=EURUSD Profit=12.45
  Event #2: Type=OrderProfit Total=1 (New=0 Updated=1 Deleted=0)
    Updated Position: Ticket=123456 Symbol=EURUSD Profit=14.10
  ✓ Received 5 events, stopping stream

🧠 What It's Used For

The OnPositionProfit() method is used for:

  • tracking P&L on positions in real-time;
  • displaying profit dynamics in the interface;
  • calculating total account results without additional requests;
  • building trading panels and analytical tools.

💬 In Simple Terms

OnPositionProfit() is a stream that reports: "EURUSD position is now +12.45, was +10.30 a minute ago". It constantly updates profit for all positions, without repeated server requests.