fix: spread dict access and close_position retry logic
- Fix 'dict' has no attribute 'spread' by using .get("spread", 0)
- Add 3-retry loop to close_position() with fresh price each attempt
- Match retry pattern from send_order() for consistency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
0d25548ed5
commit
6e0db5274b
+1
-1
@@ -1220,7 +1220,7 @@ class TradingBot:
|
|||||||
regime=regime,
|
regime=regime,
|
||||||
volatility=volatility,
|
volatility=volatility,
|
||||||
session=session_status.get("session", "unknown"),
|
session=session_status.get("session", "unknown"),
|
||||||
spread=self.mt5.get_symbol_info(self.config.symbol).spread if hasattr(self.mt5, 'get_symbol_info') else 0,
|
spread=self.mt5.get_symbol_info(self.config.symbol).get("spread", 0) if hasattr(self.mt5, 'get_symbol_info') else 0,
|
||||||
atr=0, # ATR calculated in main loop, not available here
|
atr=0, # ATR calculated in main loop, not available here
|
||||||
smc_signal=signal.signal_type,
|
smc_signal=signal.signal_type,
|
||||||
smc_confidence=signal.confidence,
|
smc_confidence=signal.confidence,
|
||||||
|
|||||||
+65
-33
@@ -639,15 +639,17 @@ class MT5Connector:
|
|||||||
volume: Optional[float] = None,
|
volume: Optional[float] = None,
|
||||||
deviation: int = 20,
|
deviation: int = 20,
|
||||||
magic: int = 123456,
|
magic: int = 123456,
|
||||||
|
max_retries: int = 3,
|
||||||
) -> OrderResult:
|
) -> OrderResult:
|
||||||
"""
|
"""
|
||||||
Close an open position.
|
Close an open position with retry logic.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
ticket: Position ticket
|
ticket: Position ticket
|
||||||
volume: Volume to close (None for full close)
|
volume: Volume to close (None for full close)
|
||||||
deviation: Maximum price deviation
|
deviation: Maximum price deviation
|
||||||
magic: Magic number
|
magic: Magic number
|
||||||
|
max_retries: Maximum retry attempts
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
OrderResult with execution details
|
OrderResult with execution details
|
||||||
@@ -664,45 +666,75 @@ class MT5Connector:
|
|||||||
symbol = position.symbol
|
symbol = position.symbol
|
||||||
pos_volume = volume or position.volume
|
pos_volume = volume or position.volume
|
||||||
|
|
||||||
# Determine close direction
|
result = None
|
||||||
if position.type == mt5.POSITION_TYPE_BUY:
|
for attempt in range(max_retries):
|
||||||
close_type = mt5.ORDER_TYPE_SELL
|
# Re-fetch price each attempt for accuracy
|
||||||
price = mt5.symbol_info_tick(symbol).bid
|
tick = mt5.symbol_info_tick(symbol)
|
||||||
else:
|
if not tick:
|
||||||
close_type = mt5.ORDER_TYPE_BUY
|
logger.warning(f"Close attempt {attempt + 1}: No tick data for {symbol}")
|
||||||
price = mt5.symbol_info_tick(symbol).ask
|
time.sleep(0.5)
|
||||||
|
continue
|
||||||
|
|
||||||
request = {
|
if position.type == mt5.POSITION_TYPE_BUY:
|
||||||
"action": mt5.TRADE_ACTION_DEAL,
|
close_type = mt5.ORDER_TYPE_SELL
|
||||||
"symbol": symbol,
|
price = tick.bid
|
||||||
"volume": float(pos_volume),
|
else:
|
||||||
"type": close_type,
|
close_type = mt5.ORDER_TYPE_BUY
|
||||||
"position": ticket,
|
price = tick.ask
|
||||||
"price": price,
|
|
||||||
"deviation": deviation,
|
|
||||||
"magic": magic,
|
|
||||||
"comment": "AI Bot Close",
|
|
||||||
"type_time": mt5.ORDER_TIME_GTC,
|
|
||||||
"type_filling": mt5.ORDER_FILLING_IOC,
|
|
||||||
}
|
|
||||||
|
|
||||||
result = mt5.order_send(request)
|
request = {
|
||||||
|
"action": mt5.TRADE_ACTION_DEAL,
|
||||||
|
"symbol": symbol,
|
||||||
|
"volume": float(pos_volume),
|
||||||
|
"type": close_type,
|
||||||
|
"position": ticket,
|
||||||
|
"price": price,
|
||||||
|
"deviation": deviation,
|
||||||
|
"magic": magic,
|
||||||
|
"comment": "AI Bot Close",
|
||||||
|
"type_time": mt5.ORDER_TIME_GTC,
|
||||||
|
"type_filling": mt5.ORDER_FILLING_IOC,
|
||||||
|
}
|
||||||
|
|
||||||
if result and result.retcode == self.RETCODE_DONE:
|
result = mt5.order_send(request)
|
||||||
logger.info(f"Position {ticket} closed")
|
|
||||||
return OrderResult(
|
if result is None:
|
||||||
success=True,
|
error = mt5.last_error()
|
||||||
order_id=result.order,
|
logger.warning(f"Close attempt {attempt + 1} failed (None): {error}")
|
||||||
retcode=result.retcode,
|
time.sleep(0.5)
|
||||||
comment=result.comment,
|
continue
|
||||||
price=result.price,
|
|
||||||
volume=result.volume,
|
if result.retcode == self.RETCODE_DONE:
|
||||||
)
|
logger.info(f"Position {ticket} closed @ {result.price}")
|
||||||
|
return OrderResult(
|
||||||
|
success=True,
|
||||||
|
order_id=result.order,
|
||||||
|
retcode=result.retcode,
|
||||||
|
comment=result.comment,
|
||||||
|
price=result.price,
|
||||||
|
volume=result.volume,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Non-retryable errors
|
||||||
|
if result.retcode in [
|
||||||
|
self.RETCODE_INVALID_VOLUME,
|
||||||
|
self.RETCODE_INVALID_STOPS,
|
||||||
|
self.RETCODE_TRADE_DISABLED,
|
||||||
|
]:
|
||||||
|
return OrderResult(
|
||||||
|
success=False,
|
||||||
|
retcode=result.retcode,
|
||||||
|
comment=result.comment,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Retryable: requote, reject, invalid price, invalid request
|
||||||
|
logger.warning(f"Close attempt {attempt + 1} for #{ticket}: {result.retcode} - {result.comment}")
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
return OrderResult(
|
return OrderResult(
|
||||||
success=False,
|
success=False,
|
||||||
retcode=result.retcode if result else None,
|
retcode=result.retcode if result else None,
|
||||||
comment=result.comment if result else "Close failed",
|
comment=result.comment if result else "Max retries exceeded",
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_open_positions(
|
def get_open_positions(
|
||||||
|
|||||||
Reference in New Issue
Block a user