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:
GifariKemal
2026-02-06 13:36:46 +07:00
co-authored by Claude Opus 4.6
parent 0d25548ed5
commit 6e0db5274b
2 changed files with 75 additions and 43 deletions
+1 -1
View File
@@ -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
View File
@@ -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(