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,
+39 -7
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,13 +666,21 @@ 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
for attempt in range(max_retries):
# Re-fetch price each attempt for accuracy
tick = mt5.symbol_info_tick(symbol)
if not tick:
logger.warning(f"Close attempt {attempt + 1}: No tick data for {symbol}")
time.sleep(0.5)
continue
if position.type == mt5.POSITION_TYPE_BUY: if position.type == mt5.POSITION_TYPE_BUY:
close_type = mt5.ORDER_TYPE_SELL close_type = mt5.ORDER_TYPE_SELL
price = mt5.symbol_info_tick(symbol).bid price = tick.bid
else: else:
close_type = mt5.ORDER_TYPE_BUY close_type = mt5.ORDER_TYPE_BUY
price = mt5.symbol_info_tick(symbol).ask price = tick.ask
request = { request = {
"action": mt5.TRADE_ACTION_DEAL, "action": mt5.TRADE_ACTION_DEAL,
@@ -688,8 +698,14 @@ class MT5Connector:
result = mt5.order_send(request) result = mt5.order_send(request)
if result and result.retcode == self.RETCODE_DONE: if result is None:
logger.info(f"Position {ticket} closed") error = mt5.last_error()
logger.warning(f"Close attempt {attempt + 1} failed (None): {error}")
time.sleep(0.5)
continue
if result.retcode == self.RETCODE_DONE:
logger.info(f"Position {ticket} closed @ {result.price}")
return OrderResult( return OrderResult(
success=True, success=True,
order_id=result.order, order_id=result.order,
@@ -699,10 +715,26 @@ class MT5Connector:
volume=result.volume, 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(