User Code Sandbox Guide¶
What is this?¶
This is your sandbox for writing custom MT5 trading code in Go. Connection setup is already done - you only need to add your trading logic!
Quick Start¶
- Case already active in main.go
-
Case "18" (also
usercode,user,sandbox,custom) runsusercode.RunUserCode()- nothing needs to be enabled or uncommented; the menu shows it as[18] User Code Sandbox -
Open
usercode/18_usercode.go -
Write your code or uncomment examples
-
Run:
That's it! Your code will execute with full MT5 connection.
How to use¶
Option 1: Uncomment examples¶
The file contains 3 ready-to-use examples:
// Example 1: Get account balance (Sugar - easiest)
// balance, err := sugar.GetBalance()
// if err != nil {
// fmt.Printf("Error: %v\n", err)
// return
// }
// fmt.Printf("Balance: %.2f\n", balance)
Just remove // to activate!
Option 2: Write your own code¶
Add your logic between the markers:
// ═══════════════════════════════════════════════════════════
// YOUR CODE HERE
// ═══════════════════════════════════════════════════════════
// Your trading strategy here...
// TODO: Your code here
Available commands¶
Run your code with:
# From examples/demos directory
go run main.go 18
go run main.go usercode
go run main.go user
go run main.go sandbox
go run main.go custom
What's already configured¶
✓ Connection - MT5 terminal connected via gRPC (sugar.QuickConnect(cfg.MtCluster); the server waits up to 120 s for the terminal)
✓ Disconnect - defer helpers.DisconnectOnExit(sugar)() (registered before connecting) stops the terminal when
RunUserCode returns, and also after helpers.Fatal, a panic or Ctrl+C - you do not need to call sugar.Disconnect()
✓ Configuration - Loaded from config/config.json or environment variables (config.LoadConfig(); API key from api_key, MRPC_API_KEY or --api-key)
✓ MT5Account - Low-level gRPC client (variable: account)
✓ MT5Service - Mid-level wrapper (variable: service)
✓ MT5Sugar - High-level Sugar API (variable: sugar)
✓ Context - context.Background() (variable: ctx) - no timeout; wrap it with context.WithTimeout where you need one
✓ Imports - context, fmt, strings, config, helpers (demo helpers), mt5 (examples/mt5) and pb - add more as you need them
Can I mix API levels?¶
Yes! You can use all three levels in the same file:
// Low-level (direct gRPC protobuf)
req := &pb.AccountSummaryRequest{}
reply, err := account.AccountSummary(ctx, req)
// Mid-level (MT5Service)
accountInfo, err := service.GetAccountSummary(ctx)
// High-level (MT5Sugar)
balance, err := sugar.GetBalance()
All three variables are available simultaneously:
account- for full control (gRPC protobuf)service- for convenient methods (Service layer)sugar- for simplest usage (Sugar API)
Quick reference¶
Get account information¶
// Sugar API (easiest)
balance, err := sugar.GetBalance()
fmt.Printf("Balance: %.2f\n", balance)
// Service API (more details)
accountInfo, err := service.GetAccountSummary(ctx)
fmt.Printf("Balance: %.2f %s\n", accountInfo.Balance, accountInfo.Currency)
fmt.Printf("Equity: %.2f\n", accountInfo.Equity)
// Account API (full control)
req := &pb.AccountSummaryRequest{}
reply, err := account.AccountSummary(ctx, req)
fmt.Printf("Balance: %.2f\n", reply.AccountBalance)
Get current price¶
// Sugar API
bid, _ := sugar.GetBid("EURUSD")
ask, _ := sugar.GetAsk("EURUSD")
fmt.Printf("EURUSD: Bid=%.5f, Ask=%.5f\n", bid, ask)
// Service API
tick, err := service.GetSymbolTick(ctx, "EURUSD")
fmt.Printf("EURUSD Bid: %.5f, Ask: %.5f\n", tick.Bid, tick.Ask)
Open market order¶
// Sugar API (easiest)
ticket, err := sugar.BuyMarket("EURUSD", 0.01)
if err != nil {
fmt.Printf("Order failed: %v\n", err)
} else {
fmt.Printf("Order opened: #%d\n", ticket)
}
// With SL/TP as distances in points (200 = 20 pips on 5-digit EURUSD)
ticket, err = sugar.BuyMarketWithPips("EURUSD", 0.01, 200, 300)
// Calculate position size based on risk (2% with SL 500 points away)
lotSize, _ := sugar.CalculatePositionSize("EURUSD", 2.0, 500)
ticket, err = sugar.BuyMarketWithPips("EURUSD", lotSize, 500, 1000)
BuyMarket, BuyLimit, ... send volumes and prices as given (no rounding) - see Normalization.
CalculateSLTP (and so the ...WithPips methods) rounds SL/TP to the symbol's digits; CalculatePositionSize
rounds the volume down to the volume step.
Place pending order¶
info, _ := sugar.GetSymbolInfo("EURUSD") // Bid, Ask, Point, Digits, ...
// Buy Limit 20 pips (200 points) below current Ask
ticket, err := sugar.BuyLimit("EURUSD", 0.01, info.Ask-200*info.Point)
// Buy Stop 20 pips above current Ask
ticket, err = sugar.BuyStop("EURUSD", 0.01, info.Ask+200*info.Point)
// Sell Limit with absolute price, SL and TP
ticket, err = sugar.SellLimitWithSLTP("EURUSD", 0.01, 1.1050, 1.1070, 1.1030)
// Delete a pending order
_, err = service.CloseOrder(ctx, &pb.OrderCloseRequest{Ticket: ticket})
There are no pip-based pending methods; sugar.CalculateSLTP() turns distances into SL/TP prices for BuyLimitWithSLTP / SellLimitWithSLTP.
Get open positions¶
// Sugar API
positions, err := sugar.GetOpenPositions()
fmt.Printf("Open positions: %d\n", len(positions))
for _, pos := range positions {
fmt.Printf(" #%d %s %.2f lot, Profit: %.2f\n",
pos.Ticket, pos.Symbol, pos.Volume, pos.Profit)
}
// Sugar API filtered by symbol
eurPositions, err := sugar.GetPositionsBySymbol("EURUSD")
// Service API: positions and pending orders, sorted
opened, err := service.GetOpenedOrders(ctx, pb.BMT5_ENUM_OPENED_ORDER_SORT_TYPE_BMT5_OPENED_ORDER_SORT_BY_OPEN_TIME_ASC)
fmt.Printf("Positions: %d, pending orders: %d\n", len(opened.PositionInfos), len(opened.OpenedOrders))
Close positions¶
// Close specific position
err := sugar.ClosePosition(ticket)
// Close all positions for symbol (returns how many were closed; err names every position
// that could not be closed - closed is still valid then)
closed, err := sugar.CloseAllBySymbol("EURUSD")
// Close all positions (all symbols; pending orders are not touched)
closed, err = sugar.CloseAllPositions()
Modify position¶
// Modify SL/TP
err := sugar.ModifyPositionSLTP(ticket, 1.0850, 1.0950)
// Modify only SL or only TP
err = sugar.ModifyPositionSL(ticket, 1.0850)
err = sugar.ModifyPositionTP(ticket, 1.0950)
There is no trailing-stop method: move the SL yourself with ModifyPositionSL, or run the Trailing Stop
orchestrator (go run main.go trailing, source orchestrators/11_trailing_stop.go).
Calculate position size¶
// Calculate position size based on risk (SL distance in points)
volume, err := sugar.CalculatePositionSize("EURUSD", 2.0, 200)
fmt.Printf("Volume for 2%% risk with 200 point (20 pip) SL: %.2f lot\n", volume)
// Then use calculated volume
ticket, err := sugar.BuyMarket("EURUSD", volume)
Return Codes (RetCodes)¶
Always check RetCode after trading operations!
result, err := service.PlaceOrder(ctx, &pb.OrderSendRequest{
Symbol: "EURUSD",
Operation: pb.TMT5_ENUM_ORDER_TYPE_TMT5_ORDER_TYPE_BUY,
Volume: 0.01,
})
if err != nil {
fmt.Printf("Error: %v\n", err) // gRPC error, or the refused request (ApiError with the RetCode)
return
}
// Check RetCode
if result.ReturnedCode == 10009 { // Request completed
fmt.Printf("✅ Order opened: #%d\n", result.Order)
} else if result.ReturnedCode == 10008 { // Order placed
fmt.Printf("✅ Pending order placed: #%d\n", result.Order)
} else {
fmt.Printf("❌ Order failed: %s (code %d)\n",
result.Comment, result.ReturnedCode)
}
Common RetCodes:
10009- Market order executed successfully10008- Pending order placed successfully10019- Insufficient money (insufficient margin)10016- Invalid stops (SL/TP too close to price)10018- Market closed
Full list: RETURN_CODES_REFERENCE.md
Error handling¶
GoMT5 has two types of errors you must check:
1. Returned errors (transport errors and refused requests)¶
result, err := service.PlaceOrder(ctx, req)
if err != nil {
// A network/gRPC error, or an *ApiError from package/Helpers when the terminal refused the
// request - its MqlErrorTradeIntCode() carries the RetCode (e.g. 10014 invalid volume)
fmt.Printf("Error: %v\n", err)
return
}
2. Trading operation errors (RetCode)¶
if !helpers.CheckRetCode(result.ReturnedCode, "Order placement") { // prints the code, its description and a hint
fmt.Printf("Trade rejected: %s\n", result.Comment)
}
Always check BOTH!
Documentation¶
- MT5Account Master Overview - Complete API reference (40+ methods)
- MT5Service API Overview - Mid-level wrapper (36 methods)
- MT5Sugar API Overview - High-level Sugar API (60+ methods)
- Protobuf Inspector Guide - Interactive type explorer
Configuration¶
Method 1: config.json (Recommended)¶
Create examples/demos/config/config.json:
{
"user": 591129415,
"password": "YourPassword",
"host": "mt5.mrpc.pro",
"port": 443,
"grpc_server": "mt5.mrpc.pro:443",
"mt_cluster": "MetaQuotes-Demo",
"api_key": "",
"test_symbol": "EURUSD",
"test_volume": 0.01
}
With user 0 or an empty password (as shipped) a demo account on MetaQuotes-Demo is opened automatically.
Method 2: Environment variables¶
# Linux/Mac
export MT5_USER=591129415
export MT5_PASSWORD="YourPassword"
export MT5_HOST="mt5.mrpc.pro"
export MT5_CLUSTER="MetaQuotes-Demo"
export MRPC_API_KEY="your-api-key"
# Windows PowerShell
$env:MT5_USER="591129415"
$env:MT5_PASSWORD="YourPassword"
$env:MT5_HOST="mt5.mrpc.pro"
See examples/demos/config/config.go for details.
Tips¶
- Start simple - Uncomment one example at a time
-
Use Sugar API - Methods like
BuyMarketWithPips()andCalculatePositionSize()are easier than low-level (their "pips" are points - see Points vs Pips) -
Check RetCode - Always validate trading operations (10009 = success)
- Test on demo - Make sure you're using demo account first!
-
Read documentation - RETURN_CODES_REFERENCE.md explains all error codes
-
Use context - The
ctxvariable iscontext.Background(); addcontext.WithTimeoutfor long calls (Sugar methods have their own timeouts) - Explore types - Use
go run main.go inspectto explore protobuf types - Check examples - See
examples/demos/lowlevel/,service/,sugar/,orchestrators/andpresets/for the 17 working demos (menu items 1-17)
Common mistakes¶
❌ Forgot to check err
✅ Always check errors
ticket, err := sugar.BuyMarket("EURUSD", 0.01)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
❌ Not checking RetCode
✅ Check RetCode for trading operations
result, err := service.PlaceOrder(ctx, req)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
if result.ReturnedCode != 10009 {
fmt.Printf("Trade failed: %s (code %d)\n", result.Comment, result.ReturnedCode)
return
}
Getting help¶
- Protobuf types: Run
go run main.go inspectto explore API types - Error codes: See RETURN_CODES_REFERENCE.md
- Examples: Check
examples/demos/lowlevel/,service/,sugar/andorchestrators/for working code - API documentation: See docs folder for complete reference
Example: Complete trading strategy¶
Here's a complete example demonstrating proper error handling:
func UserCodeMain() error {
// Setup (the same steps RunUserCode() performs)
cfg, err := config.LoadConfig()
if err != nil {
return fmt.Errorf("config error: %w", err)
}
sugar, err := mt5.NewMT5Sugar(cfg.User, cfg.Password, cfg.GrpcServer)
if err != nil {
return fmt.Errorf("setup error: %w", err)
}
sugar.GetAccount().ApiKey = cfg.ApiKey
// Disconnect on every way out - also when QuickConnect fails: the server may already have started the terminal
defer sugar.Disconnect()
if err := sugar.QuickConnect(cfg.MtCluster); err != nil {
return fmt.Errorf("login error: %w", err)
}
// sugar.GetService() and sugar.GetAccount() give the lower levels if you need them
// Get account information
balance, err := sugar.GetBalance()
if err != nil {
return fmt.Errorf("balance error: %w", err)
}
fmt.Printf("Balance: %.2f\n", balance)
// Calculate position size based on risk (1% with SL 500 points = 50 pips away)
volume, err := sugar.CalculatePositionSize("EURUSD", 1.0, 500)
if err != nil {
return fmt.Errorf("position size error: %w", err)
}
// Open position with calculated volume
ticket, err := sugar.BuyMarketWithPips("EURUSD", volume, 500, 1000)
if err != nil {
return fmt.Errorf("order error: %w", err)
}
fmt.Printf("✅ Position opened: #%d with %.2f lot\n", ticket, volume)
return nil
}
Happy trading! 🚀