ParallelLLC/algorithmic_trading
2732
1import logging2import time3from typing import Dict, Any, Optional4from .agent_base import Agent5 6class ExecutionAgent(Agent):7 def __init__(self, config: Dict[str, Any]):8 super().__init__(config)9 self.broker_api = config['execution']['broker_api']10 self.order_size = config['execution']['order_size']11 self.execution_delay = config.get('execution', {}).get('delay_ms', 100)12 self.success_rate = config.get('execution', {}).get('success_rate', 0.95)13 14 # Initialize Alpaca broker if configured15 self.alpaca_broker = None16 if self.broker_api in ['alpaca_paper', 'alpaca_live']:17 try:18 from .alpaca_broker import AlpacaBroker19 self.alpaca_broker = AlpacaBroker(config)20 self.logger.info(f"Alpaca broker initialized for {self.broker_api}")21 except Exception as e:22 self.logger.error(f"Failed to initialize Alpaca broker: {e}")23 self.broker_api = 'paper' # Fallback to simulation24 25 self.logger.info(f"Execution agent initialized with {self.broker_api} broker")26 27 def act(self, signal: Dict[str, Any]) -> Dict[str, Any]:28 """29 Execute trading signal by sending order to broker.30 31 Args:32 signal: Dictionary containing trading signal33 34 Returns:35 Dictionary containing execution result36 """37 try:38 self.logger.info(f"Processing execution signal: {signal['action']} {signal['quantity']} {signal['symbol']}")39 40 # Validate signal41 if not self._validate_signal(signal):42 self.logger.warning("Invalid signal received, skipping execution")43 return self._generate_execution_result(signal, success=False, error="Invalid signal")44 45 # Execute order based on broker type46 if self.broker_api in ['alpaca_paper', 'alpaca_live'] and self.alpaca_broker:47 execution_result = self._execute_alpaca_order(signal)48 else:49 execution_result = self._execute_simulated_order(signal)50 51 # Log execution result52 self.log_action(execution_result)53 54 return execution_result55 56 except Exception as e:57 self.log_error(e, "Error in order execution")58 return self._generate_execution_result(signal, success=False, error=str(e))59 60 def _execute_alpaca_order(self, signal: Dict[str, Any]) -> Dict[str, Any]:61 """Execute order using Alpaca broker"""62 try:63 if signal['action'] == 'hold':64 return self._generate_execution_result(signal, success=True, error=None)65 66 # Place market order with Alpaca67 result = self.alpaca_broker.place_market_order(68 symbol=signal['symbol'],69 quantity=signal['quantity'],70 side=signal['action']71 )72 73 # Convert Alpaca result to our format74 execution_result = {75 'order_id': result.get('order_id'),76 'status': result.get('status', 'unknown'),77 'action': signal['action'],78 'symbol': signal['symbol'],79 'quantity': signal['quantity'],80 'price': result.get('filled_avg_price', signal.get('price', 0)),81 'execution_time': time.time(),82 'commission': self._calculate_commission(signal),83 'total_value': result.get('filled_avg_price', 0) * signal['quantity'] if result.get('filled_avg_price') else 0,84 'success': result.get('success', False),85 'error': result.get('error')86 }87 88 if execution_result['success']:89 self.logger.info(f"Alpaca order executed successfully: {execution_result['order_id']}")90 else:91 self.logger.error(f"Alpaca order failed: {execution_result['error']}")92 93 return execution_result94 95 except Exception as e:96 self.log_error(e, "Error in Alpaca order execution")97 return self._generate_execution_result(signal, success=False, error=str(e))98 99 def _execute_simulated_order(self, signal: Dict[str, Any]) -> Dict[str, Any]:100 """Execute order with broker simulation"""101 try:102 # Simulate execution delay103 time.sleep(self.execution_delay / 1000.0)104 105 # Simulate execution success/failure106 import random107 success = random.random() < self.success_rate108 109 if signal['action'] == 'hold':110 success = True # Hold actions always succeed111 112 if success:113 return self._simulate_successful_execution(signal)114 else:115 return self._simulate_failed_execution(signal)116 117 except Exception as e:118 self.log_error(e, "Error in order execution simulation")119 return self._generate_execution_result(signal, success=False, error=str(e))120 121 def get_account_info(self) -> Dict[str, Any]:122 """Get account information"""123 if self.alpaca_broker:124 return self.alpaca_broker.get_account_info()125 else:126 # Return simulated account info127 return {128 'account_id': 'SIM_ACCOUNT',129 'status': 'ACTIVE',130 'buying_power': 100000.0,131 'cash': 100000.0,132 'portfolio_value': 100000.0,133 'equity': 100000.0,134 'trading_blocked': False135 }136 137 def get_positions(self) -> list:138 """Get current positions"""139 if self.alpaca_broker:140 return self.alpaca_broker.get_positions()141 else:142 # Return simulated positions143 return []144 145 def is_market_open(self) -> bool:146 """Check if market is open"""147 if self.alpaca_broker:148 return self.alpaca_broker.is_market_open()149 else:150 # Assume market is always open for simulation151 return True152 153 def _validate_signal(self, signal: Dict[str, Any]) -> bool:154 """Validate trading signal"""155 try:156 required_fields = ['action', 'symbol', 'quantity']157 158 # Check required fields159 for field in required_fields:160 if field not in signal:161 self.logger.error(f"Missing required field: {field}")162 return False163 164 # Validate action165 if signal['action'] not in ['buy', 'sell', 'hold']:166 self.logger.error(f"Invalid action: {signal['action']}")167 return False168 169 # Validate quantity170 if signal['quantity'] <= 0 and signal['action'] != 'hold':171 self.logger.error(f"Invalid quantity: {signal['quantity']}")172 return False173 174 # Validate symbol175 if not signal['symbol'] or not isinstance(signal['symbol'], str):176 self.logger.error(f"Invalid symbol: {signal['symbol']}")177 return False178 179 return True180 181 except Exception as e:182 self.log_error(e, "Error validating signal")183 return False184 185 def _simulate_successful_execution(self, signal: Dict[str, Any]) -> Dict[str, Any]:186 """Simulate successful order execution"""187 try:188 # Generate execution details189 execution_price = signal.get('price', 0)190 if execution_price == 0:191 # Simulate price slippage192 import random193 slippage = random.uniform(-0.001, 0.001) # ±0.1% slippage194 execution_price = signal.get('price', 100) * (1 + slippage)195 196 execution_time = time.time()197 198 # Calculate fees (simplified)199 commission = self._calculate_commission(signal)200 201 result = {202 'order_id': self._generate_order_id(),203 'status': 'filled',204 'action': signal['action'],205 'symbol': signal['symbol'],206 'quantity': signal['quantity'],207 'price': round(execution_price, 4),208 'execution_time': execution_time,209 'commission': commission,210 'total_value': round(signal['quantity'] * execution_price, 2),211 'success': True,212 'error': None213 }214 215 self.logger.info(f"Order executed successfully: {result['order_id']} - "216 f"{result['action']} {result['quantity']} {result['symbol']} @ {result['price']}")217 218 return result219 220 except Exception as e:221 self.log_error(e, "Error in successful execution simulation")222 return self._generate_execution_result(signal, success=False, error=str(e))223 224 def _simulate_failed_execution(self, signal: Dict[str, Any]) -> Dict[str, Any]:225 """Simulate failed order execution"""226 error_reasons = [227 "Insufficient funds",228 "Market closed",229 "Invalid order",230 "Network timeout",231 "Broker error"232 ]233 234 import random235 error_reason = random.choice(error_reasons)236 237 result = self._generate_execution_result(signal, success=False, error=error_reason)238 239 self.logger.warning(f"Order execution failed: {error_reason}")240 241 return result242 243 def _generate_execution_result(self, signal: Dict[str, Any], success: bool, error: Optional[str] = None) -> Dict[str, Any]:244 """Generate execution result"""245 return {246 'order_id': self._generate_order_id() if success else None,247 'status': 'filled' if success else 'rejected',248 'action': signal.get('action', 'unknown'),249 'symbol': signal.get('symbol', 'unknown'),250 'quantity': signal.get('quantity', 0),251 'price': signal.get('price', 0) if success else 0, # Price is 0 for failed executions252 'execution_time': time.time(),253 'commission': 0,254 'total_value': 0,255 'success': success,256 'error': error257 }258 259 def _calculate_commission(self, signal: Dict[str, Any]) -> float:260 """Calculate commission for the order"""261 try:262 # Simple commission calculation263 base_commission = 1.0 # $1 base commission264 per_share_commission = 0.01 # $0.01 per share265 266 if signal['action'] == 'hold':267 return 0.0268 269 commission = base_commission + (signal['quantity'] * per_share_commission)270 return round(commission, 2)271 272 except Exception as e:273 self.log_error(e, "Error calculating commission")274 return 0.0275 276 def _execute_order(self, signal: Dict[str, Any]) -> Dict[str, Any]:277 """278 Execute a trading order (private method for testing)279 280 Args:281 signal: Trading signal282 283 Returns:284 Execution result285 """286 return self.act(signal)287 288 def _generate_order_id(self) -> str:289 """Generate unique order ID"""290 import uuid291 return f"ORD_{uuid.uuid4().hex[:8].upper()}"292 293 def get_execution_statistics(self) -> Dict[str, Any]:294 """Get execution statistics"""295 # This would typically track real execution statistics296 # For now, return placeholder data297 return {298 'total_orders': 0,299 'successful_orders': 0,300 'failed_orders': 0,301 'success_rate': 0.0,302 'average_execution_time': 0.0,303 'total_commission': 0.0304 }305 