Skip to content

🔢 RoundVolumePrice (GoMT4)

Goal: correctly round volume (lots) and price values according to symbol parameters (Digits, VolumeStep, Min/Max lot). This avoids broker rejections when sending/modifying orders.

Real code refs:

  • Account: examples/mt4/MT4Account.go (SymbolParams provides Digits, VolumeStep, Min/Max, Point)
  • Demos: examples/mt4/MT4_service.go (order send/modify examples use these params)

✅ Why we need this

  • Brokers only accept volumes that are multiples of VolumeStep and within [MinLot, MaxLot].
  • Prices must be rounded to the symbol’s Digits (e.g., 5‑digit EURUSD → 1.23456).
  • If you don’t align values, OrderSend or OrderModify will return an error (invalid volume/price).

🔎 1) Read params first

p, err := account.SymbolParams(ctx, symbol)
if err != nil { return err }

fmt.Printf("%s Digits=%d VolumeStep=%.2f MinLot=%.2f MaxLot=%.2f\n",
    symbol, p.GetDigits(), p.GetVolumeStep(), p.GetVolumeMin(), p.GetVolumeMax())

🧮 2) Helpers (convenience functions)

⚠️ Note: these helpers are not auto‑generated by pb, but are commonly added in projects like this one. If you don’t yet have them in your repo, you can safely add them in utils.go (or similar). They simply apply the parameters you already fetch with SymbolParams.

func AlignVolume(v, step, min, max float64) float64 {
    if step <= 0 { return v }
    v = math.Floor(v/step+0.5) * step
    if v < min { v = min }
    if v > max { v = max }
    return v
}

func RoundPrice(p float64, digits int) float64 {
    mul := math.Pow10(digits)
    return math.Round(p*mul) / mul
}

Usage:

vol := AlignVolume(0.13, p.GetVolumeStep(), p.GetVolumeMin(), p.GetVolumeMax())
price := RoundPrice(1.092345, int(p.GetDigits()))

📝 3) Use in OrderSend

side := pb.OrderSendOperationType_OC_OP_BUY

vol := AlignVolume(0.13, p.GetVolumeStep(), p.GetVolumeMin(), p.GetVolumeMax())
price := RoundPrice(q.GetAsk(), int(p.GetDigits()))

resp, err := account.OrderSend(ctx, symbol, side, vol, &price, &slip, nil, nil, &comment, &magic, nil)
if err != nil {
    return fmt.Errorf("OrderSend failed: %w", err)
}

⚠️ Pitfalls

  • Skipping rounding → broker rejects with “invalid price/volume”.
  • Wrong Digits → always take from SymbolParams, not from hardcoded assumptions.
  • Different brokersVolumeStep can differ (0.01 vs 0.1) and MinLot/MaxLot vary.

📎 See also

  • SymbolParams.md — explains where Digits/LotStep come from.
  • PlaceMarketOrder.md, PlacePendingOrder.md — show real order placement using these helpers.
  • ModifyOrder.md — reuses RoundPrice for SL/TP adjustments.