β
Can Open Position (CanOpenPosition)ΒΆ
Sugar method: Comprehensive validation check - verifies if you can open a position with specified volume BEFORE attempting to trade.
API Information:
- Method:
sugar.CanOpenPosition(symbol, volume) - Package:
mt5(MT5Sugar) - Underlying calls:
IsSymbolAvailable(),GetSymbolInfo(),GetFreeMargin(),service.CalculateMargin() - Timeout: 5 seconds
- Returns: Three values (bool, string, error)
π Method SignatureΒΆ
π½ InputΒΆ
| Parameter | Type | Description |
|---|---|---|
symbol |
string |
Trading symbol (e.g., "EURUSD", "GBPUSD") |
volume |
float64 |
Desired lot size (e.g., 0.1, 1.0) |
β¬οΈ OutputΒΆ
| Return | Type | Description |
|---|---|---|
can |
bool |
true if position can be opened, false if not |
reason |
string |
Explanation if can't open (empty string if can open) |
error |
error |
Error if validation check itself failed |
Return patterns:
// Success - can open
(true, "", nil)
// Blocked - validation failed (not an error, just can't open)
(false, "insufficient margin: need 500.00, have 300.00", nil)
(false, "volume 0.15 not a multiple of step 0.01", nil)
(false, "symbol INVALID is not available", nil)
// Error - validation check failed
(false, "", fmt.Errorf("connection timeout"))
π¬ Just the EssentialsΒΆ
- What it is: Pre-flight check before trading - validates EVERYTHING.
- Why you need it: ALWAYS call this before trading to avoid order rejections.
- Sanity check: Checks 6 things: symbol availability, volume limits, volume step, margin requirement, free margin sufficiency.
π― Validation Checks PerformedΒΆ
This method validates:
-
β Symbol availability - Is symbol tradeable?
-
β Volume minimum - Is volume >= VolumeMin?
-
β Volume maximum - Is volume <= VolumeMax?
-
β Volume step - Is volume a multiple of VolumeStep?
-
β Margin calculation - Can we calculate required margin?
-
β Free margin - Do we have enough free margin?
If ANY check fails β returns (false, reason, nil)
π Usage ExamplesΒΆ
1) Basic usage - validate before tradingΒΆ
symbol := "EURUSD"
volume := 0.1
canOpen, reason, err := sugar.CanOpenPosition(symbol, volume)
if err != nil {
fmt.Printf("Validation error: %v\n", err)
return
}
if !canOpen {
fmt.Printf("β Cannot open position: %s\n", reason)
return
}
fmt.Println("β
Validation passed - opening position...")
ticket, _ := sugar.BuyMarket(symbol, volume)
fmt.Printf("Position #%d opened\n", ticket)
2) Complete trading workflow with validationΒΆ
symbol := "EURUSD"
riskPercent := 2.0
stopLoss := 50.0
takeProfit := 100.0
// Step 1: Calculate position size
lotSize, err := sugar.CalculatePositionSize(symbol, riskPercent, stopLoss)
if err != nil {
fmt.Printf("Position size calculation failed: %v\n", err)
return
}
// Step 2: Validate BEFORE trading
canOpen, reason, err := sugar.CanOpenPosition(symbol, lotSize)
if err != nil {
fmt.Printf("Validation error: %v\n", err)
return
}
if !canOpen {
fmt.Printf("β Cannot open position: %s\n", reason)
fmt.Printf(" Symbol: %s\n", symbol)
fmt.Printf(" Lot size: %.2f\n", lotSize)
return
}
// Step 3: All checks passed - safe to trade
fmt.Println("β
All validations passed")
ticket, err := sugar.BuyMarketWithPips(symbol, lotSize, stopLoss, takeProfit)
if err != nil {
fmt.Printf("Order failed: %v\n", err)
return
}
fmt.Printf("β
Position #%d opened successfully\n", ticket)
fmt.Printf(" Lot size: %.2f\n", lotSize)
fmt.Printf(" SL: %.0f pips, TP: %.0f pips\n", stopLoss, takeProfit)
3) Handle different failure reasonsΒΆ
func OpenPositionSafely(sugar *mt5.MT5Sugar, symbol string, volume float64) {
canOpen, reason, err := sugar.CanOpenPosition(symbol, volume)
if err != nil {
fmt.Printf("π΄ Validation check failed: %v\n", err)
return
}
if !canOpen {
// Parse the reason and provide helpful feedback
if strings.Contains(reason, "margin") {
fmt.Printf("π° Insufficient margin: %s\n", reason)
fmt.Println(" β Reduce position size or close other positions")
} else if strings.Contains(reason, "minimum") {
fmt.Printf("π Volume too small: %s\n", reason)
info, _ := sugar.GetSymbolInfo(symbol)
fmt.Printf(" β Minimum lot size: %.2f\n", info.VolumeMin)
} else if strings.Contains(reason, "maximum") {
fmt.Printf("π Volume too large: %s\n", reason)
info, _ := sugar.GetSymbolInfo(symbol)
fmt.Printf(" β Maximum lot size: %.2f\n", info.VolumeMax)
} else if strings.Contains(reason, "step") {
fmt.Printf("βοΈ Invalid volume step: %s\n", reason)
info, _ := sugar.GetSymbolInfo(symbol)
fmt.Printf(" β Volume must be multiple of %.2f\n", info.VolumeStep)
} else if strings.Contains(reason, "available") {
fmt.Printf("β Symbol unavailable: %s\n", reason)
fmt.Println(" β Check symbol name or market hours")
} else {
fmt.Printf("β οΈ Cannot open: %s\n", reason)
}
return
}
// Validation passed - open position
fmt.Println("β
Opening position...")
ticket, _ := sugar.BuyMarket(symbol, volume)
fmt.Printf(" Position #%d opened\n", ticket)
}
// Usage:
OpenPositionSafely(sugar, "EURUSD", 0.1)
4) Validate multiple positions before batch tradingΒΆ
type TradeRequest struct {
Symbol string
Volume float64
}
func ValidateMultiplePositions(
sugar *mt5.MT5Sugar,
requests []TradeRequest,
) []TradeRequest {
validRequests := []TradeRequest{}
fmt.Println("Validating positions...")
fmt.Println("βββββββββββββββββββββββββββββββββββββββββ")
for i, req := range requests {
canOpen, reason, err := sugar.CanOpenPosition(req.Symbol, req.Volume)
if err != nil {
fmt.Printf("%d. %s %.2f lots - β οΈ Error: %v\n",
i+1, req.Symbol, req.Volume, err)
continue
}
if !canOpen {
fmt.Printf("%d. %s %.2f lots - β %s\n",
i+1, req.Symbol, req.Volume, reason)
continue
}
fmt.Printf("%d. %s %.2f lots - β
Valid\n",
i+1, req.Symbol, req.Volume)
validRequests = append(validRequests, req)
}
fmt.Println("βββββββββββββββββββββββββββββββββββββββββ")
fmt.Printf("Valid: %d/%d positions\n", len(validRequests), len(requests))
return validRequests
}
// Usage:
requests := []TradeRequest{
{"EURUSD", 0.1},
{"GBPUSD", 0.2},
{"USDJPY", 0.15},
{"INVALID", 0.1},
}
validRequests := ValidateMultiplePositions(sugar, requests)
// Open only valid positions
for _, req := range validRequests {
ticket, _ := sugar.BuyMarket(req.Symbol, req.Volume)
fmt.Printf("Opened %s: #%d\n", req.Symbol, ticket)
}
5) Pre-trade checklist displayΒΆ
func ShowPreTradeChecklist(
sugar *mt5.MT5Sugar,
symbol string,
volume float64,
) bool {
fmt.Println("βββββββββββββββββββββββββββββββββββββββββ")
fmt.Println("β PRE-TRADE VALIDATION CHECK β")
fmt.Println("βββββββββββββββββββββββββββββββββββββββββ")
fmt.Printf("Symbol: %s\n", symbol)
fmt.Printf("Lot size: %.2f\n\n", volume)
// Check symbol availability
available, _ := sugar.IsSymbolAvailable(symbol)
if available {
fmt.Println("β
Symbol available")
} else {
fmt.Println("β Symbol not available")
return false
}
// Get symbol info
info, _ := sugar.GetSymbolInfo(symbol)
// Check volume minimum
if volume >= info.VolumeMin {
fmt.Printf("β
Volume >= minimum (%.2f)\n", info.VolumeMin)
} else {
fmt.Printf("β Volume < minimum (%.2f)\n", info.VolumeMin)
return false
}
// Check volume maximum
if volume <= info.VolumeMax {
fmt.Printf("β
Volume <= maximum (%.2f)\n", info.VolumeMax)
} else {
fmt.Printf("β Volume > maximum (%.2f)\n", info.VolumeMax)
return false
}
// Check volume step
steps := volume / info.VolumeStep
if steps == float64(int(steps)) {
fmt.Printf("β
Volume is multiple of step (%.2f)\n", info.VolumeStep)
} else {
fmt.Printf("β Volume not multiple of step (%.2f)\n", info.VolumeStep)
return false
}
// Check margin
requiredMargin, _ := sugar.CalculateRequiredMargin(symbol, volume)
freeMargin, _ := sugar.GetFreeMargin()
fmt.Printf(" Required margin: $%.2f\n", requiredMargin)
fmt.Printf(" Free margin: $%.2f\n", freeMargin)
if requiredMargin <= freeMargin {
fmt.Println("β
Sufficient margin")
} else {
fmt.Println("β Insufficient margin")
return false
}
fmt.Println("\nβ
ALL CHECKS PASSED - Ready to trade!")
return true
}
// Usage:
if ShowPreTradeChecklist(sugar, "EURUSD", 0.1) {
ticket, _ := sugar.BuyMarket("EURUSD", 0.1)
fmt.Printf("\nPosition opened: #%d\n", ticket)
}
6) Retry with adjusted volumeΒΆ
func OpenPositionWithAdjustment(
sugar *mt5.MT5Sugar,
symbol string,
initialVolume float64,
) (uint64, error) {
volume := initialVolume
// Try with initial volume
canOpen, reason, err := sugar.CanOpenPosition(symbol, volume)
if err != nil {
return 0, err
}
if canOpen {
fmt.Printf("β
Opening %.2f lots...\n", volume)
return sugar.BuyMarket(symbol, volume)
}
// If insufficient margin, try with reduced volume
if strings.Contains(reason, "margin") {
fmt.Printf("β οΈ Insufficient margin for %.2f lots\n", volume)
// Get max capacity
maxLots, _ := sugar.GetMaxLotSize(symbol)
if maxLots == 0 {
return 0, fmt.Errorf("no margin available")
}
// Try with max capacity
volume = maxLots
fmt.Printf(" Trying with reduced volume: %.2f lots\n", volume)
canOpen, reason, err = sugar.CanOpenPosition(symbol, volume)
if err != nil {
return 0, err
}
if canOpen {
return sugar.BuyMarket(symbol, volume)
}
}
return 0, fmt.Errorf("cannot open position: %s", reason)
}
// Usage:
ticket, err := OpenPositionWithAdjustment(sugar, "EURUSD", 1.0)
if err != nil {
fmt.Printf("Failed: %v\n", err)
} else {
fmt.Printf("Success: #%d\n", ticket)
}
7) Comprehensive validation wrapperΒΆ
type ValidationResult struct {
CanOpen bool
Reason string
RequiredMargin float64
FreeMargin float64
MarginRatio float64
}
func ValidatePositionDetailed(
sugar *mt5.MT5Sugar,
symbol string,
volume float64,
) (*ValidationResult, error) {
result := &ValidationResult{}
// Run validation
canOpen, reason, err := sugar.CanOpenPosition(symbol, volume)
if err != nil {
return nil, err
}
result.CanOpen = canOpen
result.Reason = reason
// Get margin details
requiredMargin, _ := sugar.CalculateRequiredMargin(symbol, volume)
freeMargin, _ := sugar.GetFreeMargin()
result.RequiredMargin = requiredMargin
result.FreeMargin = freeMargin
if freeMargin > 0 {
result.MarginRatio = (requiredMargin / freeMargin) * 100
}
return result, nil
}
func ShowValidationReport(result *ValidationResult, symbol string, volume float64) {
fmt.Println("βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ")
fmt.Println("β POSITION VALIDATION REPORT β")
fmt.Println("βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ")
fmt.Printf("Symbol: %s\n", symbol)
fmt.Printf("Desired volume: %.2f lots\n\n", volume)
fmt.Printf("Required margin: $%.2f\n", result.RequiredMargin)
fmt.Printf("Available margin: $%.2f\n", result.FreeMargin)
fmt.Printf("Margin usage: %.1f%%\n\n", result.MarginRatio)
if result.CanOpen {
fmt.Println("β
VALIDATION PASSED")
fmt.Println(" Position can be opened")
if result.MarginRatio > 70 {
fmt.Println("\nβ οΈ Warning: High margin usage (>70%)")
fmt.Println(" Consider reducing position size")
}
} else {
fmt.Println("β VALIDATION FAILED")
fmt.Printf(" Reason: %s\n", result.Reason)
}
}
// Usage:
result, err := ValidatePositionDetailed(sugar, "EURUSD", 0.5)
if err != nil {
fmt.Printf("Validation error: %v\n", err)
return
}
ShowValidationReport(result, "EURUSD", 0.5)
if result.CanOpen {
ticket, _ := sugar.BuyMarket("EURUSD", 0.5)
fmt.Printf("\nPosition opened: #%d\n", ticket)
}
8) Margin safety validatorΒΆ
func ValidateWithMarginSafety(
sugar *mt5.MT5Sugar,
symbol string,
volume float64,
maxMarginPercent float64,
) (bool, string) {
// First, standard validation
canOpen, reason, err := sugar.CanOpenPosition(symbol, volume)
if err != nil {
return false, fmt.Sprintf("validation error: %v", err)
}
if !canOpen {
return false, reason
}
// Additional check: margin usage threshold
requiredMargin, _ := sugar.CalculateRequiredMargin(symbol, volume)
freeMargin, _ := sugar.GetFreeMargin()
marginUsage := (requiredMargin / freeMargin) * 100
if marginUsage > maxMarginPercent {
return false, fmt.Sprintf(
"margin usage %.1f%% exceeds limit %.1f%%",
marginUsage, maxMarginPercent,
)
}
return true, ""
}
// Usage with 50% margin limit:
canOpen, reason := ValidateWithMarginSafety(sugar, "EURUSD", 0.5, 50.0)
if !canOpen {
fmt.Printf("β Cannot open: %s\n", reason)
} else {
fmt.Println("β
Validation passed with margin safety check")
ticket, _ := sugar.BuyMarket("EURUSD", 0.5)
fmt.Printf("Position opened: #%d\n", ticket)
}
9) Batch validation with statisticsΒΆ
func BatchValidateAndReport(sugar *mt5.MT5Sugar, requests []TradeRequest) {
passCount := 0
failCount := 0
errorCount := 0
failureReasons := make(map[string]int)
fmt.Println("βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ")
fmt.Println("β BATCH VALIDATION REPORT β")
fmt.Println("βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ")
fmt.Printf("Total requests: %d\n\n", len(requests))
for i, req := range requests {
canOpen, reason, err := sugar.CanOpenPosition(req.Symbol, req.Volume)
status := ""
if err != nil {
status = fmt.Sprintf("β οΈ Error: %v", err)
errorCount++
} else if canOpen {
status = "β
Pass"
passCount++
} else {
status = fmt.Sprintf("β %s", reason)
failCount++
// Track failure reason
failureReasons[reason]++
}
fmt.Printf("%d. %s %.2f lots - %s\n",
i+1, req.Symbol, req.Volume, status)
}
fmt.Println("\nβββββββββββββββββββββββββββββββββββββββββββββββββββββββββ")
fmt.Printf("β
Passed: %d (%.1f%%)\n",
passCount, float64(passCount)/float64(len(requests))*100)
fmt.Printf("β Failed: %d (%.1f%%)\n",
failCount, float64(failCount)/float64(len(requests))*100)
fmt.Printf("β οΈ Errors: %d\n", errorCount)
if len(failureReasons) > 0 {
fmt.Println("\nFailure breakdown:")
for reason, count := range failureReasons {
fmt.Printf(" β’ %s: %d times\n", reason, count)
}
}
}
// Usage:
requests := []TradeRequest{
{"EURUSD", 0.1},
{"GBPUSD", 0.2},
{"USDJPY", 100.0}, // Too large
{"INVALID", 0.1}, // Invalid symbol
{"XAUUSD", 0.01},
}
BatchValidateAndReport(sugar, requests)
10) Advanced position validatorΒΆ
type PositionValidator struct {
sugar *mt5.MT5Sugar
maxMarginUsagePercent float64
maxPositions int
}
func NewPositionValidator(
sugar *mt5.MT5Sugar,
maxMarginPercent float64,
maxPositions int,
) *PositionValidator {
return &PositionValidator{
sugar: sugar,
maxMarginUsagePercent: maxMarginPercent,
maxPositions: maxPositions,
}
}
func (pv *PositionValidator) Validate(
symbol string,
volume float64,
) (bool, string, error) {
// Check 1: Standard validation
canOpen, reason, err := pv.sugar.CanOpenPosition(symbol, volume)
if err != nil {
return false, "", err
}
if !canOpen {
return false, reason, nil
}
// Check 2: Maximum positions limit
posCount, _ := pv.sugar.GetOpenPositionsCount()
if posCount >= pv.maxPositions {
return false, fmt.Sprintf(
"maximum positions reached (%d/%d)",
posCount, pv.maxPositions,
), nil
}
// Check 3: Margin usage threshold
requiredMargin, _ := pv.sugar.CalculateRequiredMargin(symbol, volume)
freeMargin, _ := pv.sugar.GetFreeMargin()
marginUsage := (requiredMargin / freeMargin) * 100
if marginUsage > pv.maxMarginUsagePercent {
return false, fmt.Sprintf(
"margin usage %.1f%% exceeds limit %.1f%%",
marginUsage, pv.maxMarginUsagePercent,
), nil
}
// Check 4: Symbol not already in portfolio (optional rule)
openPositions, _ := pv.sugar.GetOpenPositions()
for _, pos := range openPositions {
if pos.Symbol == symbol {
return false, fmt.Sprintf(
"position already exists for %s (ticket #%d)",
symbol, pos.Ticket,
), nil
}
}
return true, "", nil
}
func (pv *PositionValidator) ValidateAndExecute(
symbol string,
volume float64,
direction string,
) (uint64, error) {
canOpen, reason, err := pv.Validate(symbol, volume)
if err != nil {
return 0, fmt.Errorf("validation error: %w", err)
}
if !canOpen {
return 0, fmt.Errorf("validation failed: %s", reason)
}
// Execute trade
if direction == "BUY" {
return pv.sugar.BuyMarket(symbol, volume)
} else {
return pv.sugar.SellMarket(symbol, volume)
}
}
func (pv *PositionValidator) ShowRules() {
fmt.Println("βββββββββββββββββββββββββββββββββββββββββ")
fmt.Println("β POSITION VALIDATOR RULES β")
fmt.Println("βββββββββββββββββββββββββββββββββββββββββ")
fmt.Printf("Max margin usage: %.1f%%\n", pv.maxMarginUsagePercent)
fmt.Printf("Max positions: %d\n", pv.maxPositions)
fmt.Println("Symbol uniqueness: Enforced")
fmt.Println("Broker limits: Enforced")
}
// Usage:
validator := NewPositionValidator(sugar, 50.0, 5)
validator.ShowRules()
// Validate and execute
ticket, err := validator.ValidateAndExecute("EURUSD", 0.1, "BUY")
if err != nil {
fmt.Printf("Failed: %v\n", err)
} else {
fmt.Printf("β
Position opened: #%d\n", ticket)
}
π Related MethodsΒΆ
π¦ Methods used internally:
IsSymbolAvailable()- Check symbol is tradeableGetSymbolInfo()- Get volume limitsGetFreeMargin()- Get available marginservice.CalculateMargin()- Calculate required margin
π¬ Complementary sugar methods:
CalculatePositionSize()- Calculate risk-based lot size βGetMaxLotSize()- Get maximum tradeable volumeCalculateRequiredMargin()- Get margin neededBuyMarket() / SellMarket()- Execute trades after validation
Recommended workflow:
// 1. Calculate lot size
lotSize, _ := sugar.CalculatePositionSize("EURUSD", 2.0, 50)
// 2. ALWAYS validate before trading
canOpen, reason, _ := sugar.CanOpenPosition("EURUSD", lotSize)
if !canOpen {
fmt.Println("Cannot trade:", reason)
return
}
// 3. Trade
ticket, _ := sugar.BuyMarket("EURUSD", lotSize)
β οΈ Common PitfallsΒΆ
1) Not calling before tradingΒΆ
// β WRONG - trading without validation
ticket, _ := sugar.BuyMarket("EURUSD", 0.1)
// Order might get rejected!
// β
CORRECT - validate first
canOpen, reason, _ := sugar.CanOpenPosition("EURUSD", 0.1)
if !canOpen {
fmt.Println("Cannot trade:", reason)
return
}
ticket, _ := sugar.BuyMarket("EURUSD", 0.1)
2) Ignoring the reason when falseΒΆ
// β WRONG - not checking why it failed
canOpen, _, _ := sugar.CanOpenPosition("EURUSD", 0.1)
if !canOpen {
fmt.Println("Can't trade") // Not helpful!
}
// β
CORRECT - use the reason
canOpen, reason, _ := sugar.CanOpenPosition("EURUSD", 0.1)
if !canOpen {
fmt.Printf("Can't trade: %s\n", reason) // Informative!
}
3) Confusing error vs blockedΒΆ
// β WRONG - treating blocked as error
canOpen, reason, err := sugar.CanOpenPosition("EURUSD", 0.1)
if err != nil || !canOpen {
// These are different things!
}
// β
CORRECT - handle separately
canOpen, reason, err := sugar.CanOpenPosition("EURUSD", 0.1)
if err != nil {
// Actual error - validation check failed
fmt.Printf("Error: %v\n", err)
return
}
if !canOpen {
// Not an error - just can't open (with reason why)
fmt.Printf("Blocked: %s\n", reason)
return
}
4) Not checking error returnΒΆ
// β WRONG - ignoring error
canOpen, reason, _ := sugar.CanOpenPosition("EURUSD", 0.1)
// β
CORRECT - check error
canOpen, reason, err := sugar.CanOpenPosition("EURUSD", 0.1)
if err != nil {
fmt.Printf("Validation error: %v\n", err)
return
}
5) Validating but not using resultΒΆ
// β WRONG - validating but ignoring result
sugar.CanOpenPosition("EURUSD", 0.1)
sugar.BuyMarket("EURUSD", 0.1) // Might still fail!
// β
CORRECT - use validation result
canOpen, reason, _ := sugar.CanOpenPosition("EURUSD", 0.1)
if !canOpen {
fmt.Println("Cannot trade:", reason)
return
}
sugar.BuyMarket("EURUSD", 0.1)
π Pro TipsΒΆ
-
ALWAYS call this - Make it mandatory before every trade
-
Check all three returns - (bool, string, error) all matter
-
Use the reason - Provides specific feedback on what's wrong
-
False β Error -
falsemeans "can't open" (not a system error) -
Validate calculated sizes - Even CalculatePositionSize() results should be validated
-
Fast check - Lightweight, no trade execution, safe to call frequently
-
Multi-check - Validates 6 different things in one call
π Return Value MeaningsΒΆ
Three return values:
(true, "", nil)
β β
All checks passed, safe to trade
(false, "reason", nil)
β β Can't open (not an error, just blocked)
β "reason" explains what's wrong
(false, "", error)
β π΄ Validation check itself failed
β System error, not a trading validation issue
Common reasons when false:
- "insufficient margin: need X, have Y"
- "volume X below minimum Y"
- "volume X exceeds maximum Y"
- "volume X not a multiple of step Y"
- "symbol X is not available"
See also: CalculatePositionSize.md, GetMaxLotSize.md, CalculateRequiredMargin.md