Skip to content

MT5Service - Trading Methods (Mid-Level API)ΒΆ

6 methods for managing trading operations with clean Go types instead of protobuf

🧩 API Layer: MID-LEVEL - wrappers over MT5Account with convenient DTO structures

Implementation:

These methods are implemented in examples/mt5/MT5Service.go, which wraps package/Helpers/MT5Account.go low-level API with convenient helpers for ease of use.

Demo files:

  • examples/demos/service/04_service_demo.go - comprehensive examples of all service wrapper methods

  • examples/demos/service/05_service_streaming.go - streaming methods examples


🎯 Why These Methods Exist¢

Problem: In MT5Account, trading operations return complex protobuf objects with nested structures:

// Low-level (MT5Account)
data, err := account.OrderSend(ctx, &pb.OrderSendRequest{...})
if data.ReturnedCode == 10009 {
    fmt.Printf("Order: %d, Deal: %d\n", data.Order, data.Deal)
}
// data.RetCodeExternal, data.Comment - all scattered across fields

Solution: MT5Service returns clean DTO structures with clear fields:

// Mid-level (MT5Service)
result, err := service.PlaceOrder(ctx, req)
if result.ReturnedCode == 10009 {
    fmt.Printf("Order: %d, Deal: %d\n", result.Order, result.Deal)
}
// Everything in one OrderResult structure

Advantages:

  • βœ… Clean Go structures instead of protobuf Data
  • βœ… OrderResult and OrderCheckResult instead of nested MqlTrade*
  • βœ… Clear field names (ReturnedCode, Margin, MarginFree)
  • βœ… Automatic unpacking of nested structures
  • βœ… Simple methods for margin and profit calculation

πŸ“‹ All 6 MethodsΒΆ

Method Returns Low-Level Equivalent
PlaceOrder(ctx, req) *OrderResult OrderSend(ctx, req) + unpacking
ModifyOrder(ctx, req) *OrderResult OrderModify(ctx, req) + unpacking
CloseOrder(ctx, req) uint32 OrderClose(ctx, req) + .ReturnedCode
CheckOrder(ctx, req) *OrderCheckResult OrderCheck(ctx, req) + extracting MqlTradeCheckResult
CalculateMargin(ctx, req) float64 OrderCalcMargin(ctx, req) + .Margin
CalculateProfit(ctx, req) float64 OrderCalcProfit(ctx, req) + .Profit

πŸ“¦ DTO StructuresΒΆ

OrderResultΒΆ

type OrderResult struct {
    ReturnedCode    uint32  // Return code (10009 = TRADE_RETCODE_DONE)
    Deal            uint64  // Deal ticket (if executed)
    Order           uint64  // Order ticket (if placed)
    Volume          float64 // Executed volume confirmed by broker
    Price           float64 // Execution price confirmed by broker
    Bid             float64 // Current Bid price
    Ask             float64 // Current Ask price
    Comment         string  // Broker comment or error description
    RequestID       uint32  // Request ID set by terminal
    RetCodeExternal int32   // Return code from external trading system
}

Advantage:

All information about the trading operation result in one structure.

OrderCheckResultΒΆ

type OrderCheckResult struct {
    ReturnedCode uint32  // Validation result code (0 = success)
    Balance      float64 // Account balance after deal execution
    Equity       float64 // Account equity after deal execution
    Profit       float64 // Floating profit after deal
    Margin       float64 // Margin requirements for the order
    MarginFree   float64 // Free margin after deal
    MarginLevel  float64 // Margin level after deal (%)
    Comment      string  // Error description (if validation failed)
}

Advantage:

Shows what the account state will be after order execution.


πŸ“– Method SignaturesΒΆ

1) PlaceOrderΒΆ

func (s *MT5Service) PlaceOrder(
    ctx context.Context,
    req *pb.OrderSendRequest,
) (*OrderResult, error)

Sends a market or pending order to MT5 terminal.

Success check:

if result.ReturnedCode == 10009 { // TRADE_RETCODE_DONE
    fmt.Printf("Success! Order: %d, Deal: %d\n", result.Order, result.Deal)
}


2) ModifyOrderΒΆ

func (s *MT5Service) ModifyOrder(
    ctx context.Context,
    req *pb.OrderModifyRequest,
) (*OrderResult, error)

Modifies an existing order or position (changing SL/TP/price).


3) CloseOrderΒΆ

func (s *MT5Service) CloseOrder(
    ctx context.Context,
    req *pb.OrderCloseRequest,
) (uint32, error)

Closes a position or deletes a pending order.

Returns only ReturnedCode (10009 = success). Simpler than PlaceOrder for closing.


4) CheckOrderΒΆ

func (s *MT5Service) CheckOrder(
    ctx context.Context,
    req *pb.OrderCheckRequest,
) (*OrderCheckResult, error)

Validates order before sending to broker.

Use before PlaceOrder to avoid rejections.


5) CalculateMarginΒΆ

func (s *MT5Service) CalculateMargin(
    ctx context.Context,
    req *pb.OrderCalcMarginRequest,
) (float64, error)

Calculates required margin for a potential order.

Use before opening a position to check margin sufficiency.


6) CalculateProfitΒΆ

func (s *MT5Service) CalculateProfit(
    ctx context.Context,
    req *pb.OrderCalcProfitRequest,
) (float64, error)

Calculates potential profit for a hypothetical order.

Useful for profit/risk calculations before placing real orders.


πŸ’‘ Usage ExamplesΒΆ

Example 1: Opening a Market BUY PositionΒΆ

// βœ… MT5Service - returns clean OrderResult (not protobuf)
ctx := context.Background()

// 1. Create order request
req := &pb.OrderSendRequest{
    Symbol:    "EURUSD",
    Action:    pb.ENUM_TRADE_REQUEST_ACTIONS_TRADE_ACTION_DEAL,
    Type:      pb.ENUM_ORDER_TYPE_ORDER_TYPE_BUY,
    Volume:    0.1,
    Price:     0,  // market price
    Sl:        0,
    Tp:        0,
    Deviation: 10,
}

// 2. Send order to broker
result, err := service.PlaceOrder(ctx, req)
if err != nil {
    return fmt.Errorf("order failed: %w", err)
}

// 3. Check result code
if result.ReturnedCode != 10009 {
    fmt.Printf("❌ Error: %s (code: %d)\n", result.Comment, result.ReturnedCode)
    return fmt.Errorf("order rejected")
}

// 4. Success - display execution details
fmt.Printf("βœ… Order executed successfully:\n")
fmt.Printf("   Order ticket: %d\n", result.Order)
fmt.Printf("   Deal ticket:  %d\n", result.Deal)
fmt.Printf("   Price:        %.5f\n", result.Price)
fmt.Printf("   Volume:       %.2f\n", result.Volume)

Advantage over Low-Level:

Returns *OrderResult (clean Go struct) instead of *pb.OrderSendData (protobuf). Same code structure, but cleaner types.


Example 2: Validating Order Before PlacementΒΆ

// ❌ BEFORE (MT5Account) - nested MqlTradeCheckResult structure:
checkReq := &pb.OrderCheckRequest{
    Symbol: "EURUSD",
    Action: pb.ENUM_TRADE_REQUEST_ACTIONS_TRADE_ACTION_DEAL,
    Type:   pb.ENUM_ORDER_TYPE_ORDER_TYPE_BUY,
    Volume: 1.0,
}

// Low-level call
checkData, err := account.OrderCheck(ctx, checkReq)
if err != nil {
    return err
}

// ← Need to access nested structure
result := checkData.MqlTradeCheckResult

// Check validation result
if result.ReturnedCode != 0 {
    fmt.Printf("❌ Invalid order: %s\n", result.Comment)
    return nil
}
fmt.Printf("βœ… Valid! Margin required: %.2f, Free margin after: %.2f\n",
    result.Margin, result.FreeMargin)

// βœ… AFTER (MT5Service) - direct OrderCheckResult:
checkReq := &pb.OrderCheckRequest{
    Symbol: "EURUSD",
    Action: pb.ENUM_TRADE_REQUEST_ACTIONS_TRADE_ACTION_DEAL,
    Type:   pb.ENUM_ORDER_TYPE_ORDER_TYPE_BUY,
    Volume: 1.0,
}

// Mid-level call - returns clean struct directly
result, err := service.CheckOrder(ctx, checkReq)
if err != nil {
    return err
}

// Check validation result
if result.ReturnedCode != 0 {
    fmt.Printf("❌ Invalid order: %s\n", result.Comment)
    return nil
}
fmt.Printf("βœ… Valid! Margin required: %.2f, Free margin after: %.2f\n",
    result.Margin, result.MarginFree)

Code reduction: 15% (removed checkData.MqlTradeCheckResult indirection)


Example 3: Calculating Margin Before Opening PositionΒΆ

// ❌ BEFORE (MT5Account) - extract .Margin from data:
// 1. Create margin calculation request
req := &pb.OrderCalcMarginRequest{
    Symbol: "EURUSD",
    Action: pb.ENUM_TRADE_REQUEST_ACTIONS_TRADE_ACTION_DEAL,
    Type:   pb.ENUM_ORDER_TYPE_ORDER_TYPE_BUY,
    Volume: 1.0,
    Price:  0,
}

// 2. Calculate required margin
data, err := account.OrderCalcMargin(ctx, req)
if err != nil {
    return err
}
requiredMargin := data.Margin  // ← unpacking

// 3. Get current free margin
freeMarginData, _ := account.AccountInfoDouble(ctx, &pb.AccountInfoDoubleRequest{
    PropertyId: pb.AccountInfoDoublePropertyType_ACCOUNT_MARGIN_FREE,
})
freeMargin := freeMarginData.GetRequestedValue()  // ← unpacking

// 4. Check sufficiency
if freeMargin < requiredMargin {
    fmt.Printf("❌ Insufficient margin: %.2f (need: %.2f)\n", freeMargin, requiredMargin)
}

// βœ… AFTER (MT5Service) - returns float64 directly:
// 1. Create margin calculation request
req := &pb.OrderCalcMarginRequest{
    Symbol: "EURUSD",
    Action: pb.ENUM_TRADE_REQUEST_ACTIONS_TRADE_ACTION_DEAL,
    Type:   pb.ENUM_ORDER_TYPE_ORDER_TYPE_BUY,
    Volume: 1.0,
    Price:  0,
}

// 2. Calculate required margin (returns float64 directly)
requiredMargin, err := service.CalculateMargin(ctx, req)
if err != nil {
    return err
}

// 3. Get current free margin (also returns float64 directly)
freeMargin, _ := service.GetAccountDouble(ctx,
    pb.AccountInfoDoublePropertyType_ACCOUNT_MARGIN_FREE)

// 4. Check sufficiency
if freeMargin < requiredMargin {
    fmt.Printf("❌ Insufficient margin: %.2f (need: %.2f)\n",
        freeMargin, requiredMargin)
}

Code reduction: 25% (no data.Margin and .GetRequestedValue() calls)


Example 4: Complete Pre-Placement ValidationΒΆ

// βœ… MT5Service - compact and clear code
func SafePlaceOrder(service *mt5.MT5Service, symbol string, volume float64) error {
    ctx := context.Background()

    // 1. Validate order
    checkReq := &pb.OrderCheckRequest{
        Symbol: symbol,
        Action: pb.ENUM_TRADE_REQUEST_ACTIONS_TRADE_ACTION_DEAL,
        Type:   pb.ENUM_ORDER_TYPE_ORDER_TYPE_BUY,
        Volume: volume,
    }

    // Run validation check
    checkResult, err := service.CheckOrder(ctx, checkReq)
    if err != nil {
        return fmt.Errorf("order check failed: %w", err)
    }

    // Check validation result code
    if checkResult.ReturnedCode != 0 {
        return fmt.Errorf("invalid order: %s", checkResult.Comment)
    }

    // Display predicted account state
    fmt.Printf("βœ… Order valid:\n")
    fmt.Printf("   Margin required: %.2f\n", checkResult.Margin)
    fmt.Printf("   Free margin after: %.2f\n", checkResult.MarginFree)
    fmt.Printf("   Margin level after: %.2f%%\n", checkResult.MarginLevel)

    // 2. Place order
    orderReq := &pb.OrderSendRequest{
        Symbol:    symbol,
        Action:    pb.ENUM_TRADE_REQUEST_ACTIONS_TRADE_ACTION_DEAL,
        Type:      pb.ENUM_ORDER_TYPE_ORDER_TYPE_BUY,
        Volume:    volume,
        Price:     0,
        Deviation: 10,
    }

    // Send order to broker
    result, err := service.PlaceOrder(ctx, orderReq)
    if err != nil {
        return fmt.Errorf("order placement failed: %w", err)
    }

    // Check execution result
    if result.ReturnedCode != 10009 {
        return fmt.Errorf("order rejected: %s", result.Comment)
    }

    // Display execution details
    fmt.Printf("βœ… Order placed successfully:\n")
    fmt.Printf("   Order: %d\n", result.Order)
    fmt.Printf("   Deal: %d\n", result.Deal)
    fmt.Printf("   Price: %.5f\n", result.Price)
    fmt.Printf("   Volume: %.2f\n", result.Volume)

    return nil
}

Example 5: Modifying SL/TP of Existing PositionΒΆ

// βœ… MT5Service - position modification
func ModifyPositionSLTP(service *mt5.MT5Service, ticket uint64, sl, tp float64) error {
    ctx := context.Background()

    // 1. Create modification request
    req := &pb.OrderModifyRequest{
        Position: ticket,
        Action:   pb.ENUM_TRADE_REQUEST_ACTIONS_TRADE_ACTION_SLTP,
        Sl:       sl,
        Tp:       tp,
    }

    // 2. Send modification request
    result, err := service.ModifyOrder(ctx, req)
    if err != nil {
        return fmt.Errorf("modify failed: %w", err)
    }

    // 3. Check modification result
    if result.ReturnedCode != 10009 {
        return fmt.Errorf("modify rejected: %s", result.Comment)
    }

    // 4. Success
    fmt.Printf("βœ… Position %d modified: SL=%.5f, TP=%.5f\n", ticket, sl, tp)
    return nil
}

πŸ”§ When to UseΒΆ

βœ… PlaceOrderΒΆ

Use when:

  • Opening market positions (BUY/SELL)
  • Placing pending orders (Limit/Stop)
  • Need Deal and Order ticket information

Example:

result, _ := service.PlaceOrder(ctx, orderReq)
if result.ReturnedCode == 10009 {
    fmt.Printf("Deal: %d\n", result.Deal)
}

βœ… CheckOrderΒΆ

Use when:

  • Validating order before placement
  • Need information about future account state
  • Calculating margin level after execution

Example:

checkResult, _ := service.CheckOrder(ctx, checkReq)
if checkResult.MarginLevel < 200.0 {
    return fmt.Errorf("margin level too low")
}

βœ… CalculateMargin / CalculateProfitΒΆ

Use when:

  • Calculating required margin for position
  • Estimating potential profit/loss
  • Performing risk management calculations

Example:

margin, _ := service.CalculateMargin(ctx, marginReq)
profit, _ := service.CalculateProfit(ctx, profitReq)
riskRewardRatio := profit / (margin * 0.02) // 2% risk

πŸ“Š Performance ComparisonΒΆ

Task Low-Level (lines of code) Mid-Level (lines of code) Reduction
Place order 15-20 lines 15-20 lines 0% (but cleaner types)
Check order 8-10 lines 6-8 lines 15-25%
Calculate margin 4-5 lines 2-3 lines 25-40%
Full validation + placement 30-35 lines 25-30 lines 15-20%

Conclusion: Code is not necessarily shorter, but significantly cleaner and more understandable.


πŸ’‘ RecommendationsΒΆ

  1. Always use CheckOrder before PlaceOrder in production
  2. Check ReturnedCode - 10009 = success, everything else = error
  3. Use CalculateMargin to verify margin before opening position
  4. Read Comment in OrderResult/OrderCheckResult for error details
  5. For closing positions use CloseOrder instead of PlaceOrder

Return codes:

  • 10009 - TRADE_RETCODE_DONE (success)
  • 10004 - TRADE_RETCODE_REQUOTE (requote)
  • 10006 - TRADE_RETCODE_REJECT (rejected)
  • 10013 - TRADE_RETCODE_INVALID_PRICE (invalid price)
  • 10014 - TRADE_RETCODE_INVALID_STOPS (invalid SL/TP)
  • 10019 - TRADE_RETCODE_NO_MONEY (insufficient funds)


🎯 Summary¢

MT5Service Trading methods solve the main task - remove protobuf ceremony:

  • ❌ No need to unpack data.MqlTradeCheckResult
  • ❌ No need to call .Margin or .GetRequestedValue()
  • βœ… Get clean DTOs (*OrderResult, *OrderCheckResult)
  • βœ… Code reads like normal Go
  • βœ… All trading operations in one place