limit max positions
This commit is contained in:
@@ -927,11 +927,9 @@ class TradingOrchestrator:
|
||||
try:
|
||||
current_time = datetime.now()
|
||||
|
||||
# Check if enough time has passed since last decision
|
||||
if symbol in self.last_decision_time:
|
||||
time_since_last = (current_time - self.last_decision_time[symbol]).total_seconds()
|
||||
if time_since_last < self.decision_frequency:
|
||||
return None
|
||||
# EXECUTE EVERY SIGNAL: Remove decision frequency limit
|
||||
# Allow immediate execution of every signal from the decision model
|
||||
logger.debug(f"Processing signal for {symbol} - no frequency limit applied")
|
||||
|
||||
# Get current market data
|
||||
current_price = self.data_provider.get_current_price(symbol)
|
||||
@@ -1333,43 +1331,15 @@ class TradingOrchestrator:
|
||||
current_position_pnl, symbol
|
||||
)
|
||||
|
||||
# Apply aggressiveness-based confidence thresholds
|
||||
if best_action in ['BUY', 'SELL']:
|
||||
# For entry signals, use entry aggressiveness
|
||||
if not self._has_open_position(symbol):
|
||||
if best_confidence < entry_threshold:
|
||||
best_action = 'HOLD'
|
||||
reasoning['entry_threshold_applied'] = True
|
||||
reasoning['entry_threshold'] = entry_threshold
|
||||
# For exit signals, use exit aggressiveness
|
||||
else:
|
||||
if best_confidence < exit_threshold:
|
||||
best_action = 'HOLD'
|
||||
reasoning['exit_threshold_applied'] = True
|
||||
reasoning['exit_threshold'] = exit_threshold
|
||||
else:
|
||||
# Standard threshold for HOLD
|
||||
if best_confidence < self.confidence_threshold:
|
||||
best_action = 'HOLD'
|
||||
reasoning['threshold_applied'] = True
|
||||
# EXECUTE EVERY SIGNAL: Remove confidence thresholds and signal accumulation
|
||||
# The decision model has already aggregated all model outputs (CNN, DQN, transformer, etc.)
|
||||
# So we trust its decision and execute every signal
|
||||
reasoning['execute_every_signal'] = True
|
||||
reasoning['models_aggregated'] = [pred.model_name for pred in predictions]
|
||||
reasoning['aggregated_confidence'] = best_confidence
|
||||
|
||||
# Signal accumulation check - require multiple confident signals
|
||||
if best_action in ['BUY', 'SELL']:
|
||||
required_signals = 3 # Require 3 confident signals
|
||||
recent_decisions = self.get_recent_decisions(symbol, limit=5)
|
||||
|
||||
# Count recent signals in the same direction
|
||||
same_direction_count = sum(1 for d in recent_decisions
|
||||
if d.action == best_action and d.confidence > entry_threshold)
|
||||
|
||||
if same_direction_count < required_signals:
|
||||
best_action = 'HOLD'
|
||||
reasoning['signal_accumulation'] = True
|
||||
reasoning['required_signals'] = required_signals
|
||||
reasoning['current_signals'] = same_direction_count
|
||||
logger.info(f"Signal accumulation: {same_direction_count}/{required_signals} signals for {best_action}")
|
||||
else:
|
||||
logger.info(f"Signal accumulation satisfied: {same_direction_count}/{required_signals} signals for {best_action}")
|
||||
logger.info(f"EXECUTING EVERY SIGNAL: {best_action} (confidence: {best_confidence:.3f}) "
|
||||
f"from aggregated models: {reasoning['models_aggregated']}")
|
||||
|
||||
# Add P&L-based decision adjustment
|
||||
best_action, best_confidence = self._apply_pnl_feedback(
|
||||
|
@@ -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