limit max positions
This commit is contained in:
@ -143,6 +143,10 @@ class TradingExecutor:
|
||||
self.max_open_orders = 2 # Maximum number of open orders allowed
|
||||
self.open_orders_count = 0 # Current count of open orders
|
||||
|
||||
# Rate limiting for open orders sync (30 seconds)
|
||||
self.last_open_orders_sync = datetime.min
|
||||
self.open_orders_sync_interval = 30 # seconds
|
||||
|
||||
# Trading symbols
|
||||
self.symbols = self.config.get('symbols', ['ETH/USDT', 'BTC/USDT'])
|
||||
|
||||
@ -332,21 +336,42 @@ class TradingExecutor:
|
||||
logger.debug(f"LOCK RELEASED: {action} for {symbol}")
|
||||
|
||||
def _get_open_orders_count(self) -> int:
|
||||
"""Get current count of open orders across all symbols"""
|
||||
"""Get current count of open orders across all symbols with rate limiting"""
|
||||
try:
|
||||
if self.simulation_mode:
|
||||
return 0
|
||||
|
||||
# Check if enough time has passed since last sync
|
||||
current_time = datetime.now()
|
||||
time_since_last_sync = (current_time - self.last_open_orders_sync).total_seconds()
|
||||
|
||||
if time_since_last_sync < self.open_orders_sync_interval:
|
||||
# Return cached count if within rate limit
|
||||
logger.debug(f"Using cached open orders count ({self.open_orders_count}) - rate limited")
|
||||
return self.open_orders_count
|
||||
|
||||
# Update last sync time
|
||||
self.last_open_orders_sync = current_time
|
||||
|
||||
total_open_orders = 0
|
||||
for symbol in self.symbols:
|
||||
open_orders = self.exchange.get_open_orders(symbol)
|
||||
total_open_orders += len(open_orders)
|
||||
try:
|
||||
open_orders = self.exchange.get_open_orders(symbol)
|
||||
total_open_orders += len(open_orders)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error getting open orders for {symbol}: {e}")
|
||||
# Continue with other symbols
|
||||
continue
|
||||
|
||||
# Update cached count
|
||||
self.open_orders_count = total_open_orders
|
||||
logger.debug(f"Updated open orders count: {total_open_orders}")
|
||||
|
||||
return total_open_orders
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting open orders count: {e}")
|
||||
return 0
|
||||
return self.open_orders_count # Return cached value on error
|
||||
|
||||
def _can_place_new_order(self) -> bool:
|
||||
"""Check if we can place a new order based on open order limit"""
|
||||
@ -359,7 +384,7 @@ class TradingExecutor:
|
||||
return can_place
|
||||
|
||||
def sync_open_orders(self) -> Dict[str, Any]:
|
||||
"""Synchronize open orders with exchange and update internal state
|
||||
"""Synchronize open orders with exchange and update internal state with rate limiting
|
||||
|
||||
Returns:
|
||||
dict: Sync result with status and order details
|
||||
@ -373,6 +398,24 @@ class TradingExecutor:
|
||||
'count': 0
|
||||
}
|
||||
|
||||
# Check rate limiting
|
||||
current_time = datetime.now()
|
||||
time_since_last_sync = (current_time - self.last_open_orders_sync).total_seconds()
|
||||
|
||||
if time_since_last_sync < self.open_orders_sync_interval:
|
||||
# Return cached result if within rate limit
|
||||
logger.debug(f"Open order sync rate limited - using cached data")
|
||||
return {
|
||||
'status': 'rate_limited',
|
||||
'message': f'Rate limited - last sync was {time_since_last_sync:.1f}s ago',
|
||||
'orders': [],
|
||||
'count': self.open_orders_count,
|
||||
'cached': True
|
||||
}
|
||||
|
||||
# Update last sync time
|
||||
self.last_open_orders_sync = current_time
|
||||
|
||||
sync_result = {
|
||||
'status': 'started',
|
||||
'orders': [],
|
||||
@ -408,7 +451,7 @@ class TradingExecutor:
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error syncing orders for {symbol}: {e}"
|
||||
logger.error(error_msg)
|
||||
logger.warning(error_msg) # Changed to warning since this is expected with rate limits
|
||||
sync_result['errors'].append(error_msg)
|
||||
|
||||
# Update internal state
|
||||
@ -505,11 +548,30 @@ class TradingExecutor:
|
||||
logger.warning(f"Maximum concurrent positions reached: {len(self.positions)}")
|
||||
return False
|
||||
|
||||
# SYNC OPEN ORDERS BEFORE CHECKING LIMITS
|
||||
# This ensures we have accurate position data before making decisions
|
||||
if not self.simulation_mode:
|
||||
try:
|
||||
logger.debug(f"Syncing open orders before trade execution for {symbol}")
|
||||
sync_result = self.sync_open_orders()
|
||||
if sync_result.get('status') == 'success':
|
||||
logger.debug(f"Open orders synced successfully: {sync_result.get('count', 0)} orders")
|
||||
else:
|
||||
logger.warning(f"Open orders sync failed: {sync_result.get('message', 'Unknown error')}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Error syncing open orders: {e}")
|
||||
|
||||
# Check open order limit
|
||||
if not self._can_place_new_order():
|
||||
logger.warning(f"Maximum open orders reached: {self._get_open_orders_count()}/{self.max_open_orders}")
|
||||
return False
|
||||
|
||||
# Check position size limit before opening new positions
|
||||
if action in ['BUY', 'SHORT'] and symbol not in self.positions:
|
||||
if not self._check_position_size_limit():
|
||||
logger.warning(f"Position size limit reached - cannot open new position for {symbol}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _execute_buy(self, symbol: str, confidence: float, current_price: float) -> bool:
|
||||
@ -1153,25 +1215,30 @@ class TradingExecutor:
|
||||
return False
|
||||
|
||||
def _calculate_position_size(self, confidence: float, current_price: float) -> float:
|
||||
"""Calculate position size - use 100% of account balance for short-term scalping"""
|
||||
"""Calculate position size - limited to 20% of account balance by default"""
|
||||
# Get account balance (simulation or real)
|
||||
account_balance = self._get_account_balance_for_sizing()
|
||||
|
||||
# Use 100% of account balance since we're holding for seconds/minutes only
|
||||
# Scale by confidence: 70-100% of balance based on confidence (0.7-1.0 range)
|
||||
# Get maximum position size limit (default 20% of balance)
|
||||
max_position_percentage = self.mexc_config.get('max_position_percentage', 0.20)
|
||||
max_position_value = account_balance * max_position_percentage
|
||||
|
||||
# Calculate desired position size based on confidence
|
||||
# Scale by confidence: 70-100% of max position size based on confidence (0.7-1.0 range)
|
||||
confidence_multiplier = max(0.7, min(1.0, confidence))
|
||||
position_value = account_balance * confidence_multiplier
|
||||
desired_position_value = max_position_value * confidence_multiplier
|
||||
|
||||
# Apply reduction based on consecutive losses (risk management)
|
||||
reduction_factor = self.mexc_config.get('consecutive_loss_reduction_factor', 0.8)
|
||||
adjusted_reduction_factor = reduction_factor ** self.consecutive_losses
|
||||
position_value *= adjusted_reduction_factor
|
||||
final_position_value = desired_position_value * adjusted_reduction_factor
|
||||
|
||||
logger.debug(f"Position calculation: account=${account_balance:.2f}, "
|
||||
f"max_position=${max_position_value:.2f} ({max_position_percentage*100:.0f}%), "
|
||||
f"confidence_mult={confidence_multiplier:.2f}, "
|
||||
f"position=${position_value:.2f}, confidence={confidence:.2f}")
|
||||
f"final_position=${final_position_value:.2f}, confidence={confidence:.2f}")
|
||||
|
||||
return position_value
|
||||
return final_position_value
|
||||
|
||||
def _get_account_balance_for_sizing(self) -> float:
|
||||
"""Get account balance for position sizing calculations"""
|
||||
@ -1188,6 +1255,64 @@ class TradingExecutor:
|
||||
logger.warning(f"Failed to get live account balance: {e}, using simulation default")
|
||||
return self.mexc_config.get('simulation_account_usd', 100.0)
|
||||
|
||||
def _check_position_size_limit(self) -> bool:
|
||||
"""Check if total open position value exceeds the maximum allowed percentage of balance"""
|
||||
try:
|
||||
# Get account balance
|
||||
account_balance = self._get_account_balance_for_sizing()
|
||||
|
||||
# Get maximum position percentage (default 20%)
|
||||
max_position_percentage = self.mexc_config.get('max_position_percentage', 0.20)
|
||||
max_position_value = account_balance * max_position_percentage
|
||||
|
||||
# Calculate total current position value
|
||||
total_position_value = 0.0
|
||||
|
||||
# Add existing positions
|
||||
for symbol, position in self.positions.items():
|
||||
# Get current price for the symbol
|
||||
try:
|
||||
ticker = self.exchange.get_ticker(symbol) if self.exchange else None
|
||||
current_price = ticker['last'] if ticker and 'last' in ticker else position.entry_price
|
||||
except Exception:
|
||||
# Fallback to entry price if we can't get current price
|
||||
current_price = position.entry_price
|
||||
|
||||
# Calculate position value
|
||||
position_value = position.quantity * current_price
|
||||
total_position_value += position_value
|
||||
|
||||
logger.debug(f"Existing position {symbol}: {position.quantity:.6f} @ ${current_price:.2f} = ${position_value:.2f}")
|
||||
|
||||
# Add potential value from open orders that could become positions
|
||||
if not self.simulation_mode and self.exchange:
|
||||
try:
|
||||
open_orders = self.exchange.get_open_orders()
|
||||
for order in open_orders:
|
||||
if order.get('side', '').lower() in ['buy', 'sell']:
|
||||
# Estimate the value if this order gets filled
|
||||
order_quantity = float(order.get('quantity', 0))
|
||||
order_price = float(order.get('price', 0))
|
||||
if order_price > 0:
|
||||
order_value = order_quantity * order_price
|
||||
total_position_value += order_value
|
||||
logger.debug(f"Open order {order.get('symbol')}: {order_quantity:.6f} @ ${order_price:.2f} = ${order_value:.2f}")
|
||||
except Exception as e:
|
||||
logger.debug(f"Error calculating open order values: {e}")
|
||||
|
||||
# Check if we would exceed the limit
|
||||
if total_position_value >= max_position_value:
|
||||
logger.warning(f"Position size limit reached: ${total_position_value:.2f} >= ${max_position_value:.2f} ({max_position_percentage*100:.0f}% of balance)")
|
||||
return False
|
||||
|
||||
logger.debug(f"Position size check passed: ${total_position_value:.2f} < ${max_position_value:.2f} ({max_position_percentage*100:.0f}% of balance)")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking position size limit: {e}")
|
||||
# Allow trade if we can't check the limit
|
||||
return True
|
||||
|
||||
def update_positions(self, symbol: str, current_price: float):
|
||||
"""Update position P&L with current market price"""
|
||||
if symbol in self.positions:
|
||||
|
Reference in New Issue
Block a user