| | import logging |
| | import time |
| | from typing import Dict, Any, Optional |
| | from .agent_base import Agent |
| |
|
| | class ExecutionAgent(Agent): |
| | def __init__(self, config: Dict[str, Any]): |
| | super().__init__(config) |
| | self.broker_api = config['execution']['broker_api'] |
| | self.order_size = config['execution']['order_size'] |
| | self.execution_delay = config.get('execution', {}).get('delay_ms', 100) |
| | self.success_rate = config.get('execution', {}).get('success_rate', 0.95) |
| | |
| | |
| | self.alpaca_broker = None |
| | if self.broker_api in ['alpaca_paper', 'alpaca_live']: |
| | try: |
| | from .alpaca_broker import AlpacaBroker |
| | self.alpaca_broker = AlpacaBroker(config) |
| | self.logger.info(f"Alpaca broker initialized for {self.broker_api}") |
| | except Exception as e: |
| | self.logger.error(f"Failed to initialize Alpaca broker: {e}") |
| | self.broker_api = 'paper' |
| | |
| | self.logger.info(f"Execution agent initialized with {self.broker_api} broker") |
| | |
| | def act(self, signal: Dict[str, Any]) -> Dict[str, Any]: |
| | """ |
| | Execute trading signal by sending order to broker. |
| | |
| | Args: |
| | signal: Dictionary containing trading signal |
| | |
| | Returns: |
| | Dictionary containing execution result |
| | """ |
| | try: |
| | self.logger.info(f"Processing execution signal: {signal['action']} {signal['quantity']} {signal['symbol']}") |
| | |
| | |
| | if not self._validate_signal(signal): |
| | self.logger.warning("Invalid signal received, skipping execution") |
| | return self._generate_execution_result(signal, success=False, error="Invalid signal") |
| | |
| | |
| | if self.broker_api in ['alpaca_paper', 'alpaca_live'] and self.alpaca_broker: |
| | execution_result = self._execute_alpaca_order(signal) |
| | else: |
| | execution_result = self._execute_simulated_order(signal) |
| | |
| | |
| | self.log_action(execution_result) |
| | |
| | return execution_result |
| | |
| | except Exception as e: |
| | self.log_error(e, "Error in order execution") |
| | return self._generate_execution_result(signal, success=False, error=str(e)) |
| | |
| | def _execute_alpaca_order(self, signal: Dict[str, Any]) -> Dict[str, Any]: |
| | """Execute order using Alpaca broker""" |
| | try: |
| | if signal['action'] == 'hold': |
| | return self._generate_execution_result(signal, success=True, error=None) |
| | |
| | |
| | result = self.alpaca_broker.place_market_order( |
| | symbol=signal['symbol'], |
| | quantity=signal['quantity'], |
| | side=signal['action'] |
| | ) |
| | |
| | |
| | execution_result = { |
| | 'order_id': result.get('order_id'), |
| | 'status': result.get('status', 'unknown'), |
| | 'action': signal['action'], |
| | 'symbol': signal['symbol'], |
| | 'quantity': signal['quantity'], |
| | 'price': result.get('filled_avg_price', signal.get('price', 0)), |
| | 'execution_time': time.time(), |
| | 'commission': self._calculate_commission(signal), |
| | 'total_value': result.get('filled_avg_price', 0) * signal['quantity'] if result.get('filled_avg_price') else 0, |
| | 'success': result.get('success', False), |
| | 'error': result.get('error') |
| | } |
| | |
| | if execution_result['success']: |
| | self.logger.info(f"Alpaca order executed successfully: {execution_result['order_id']}") |
| | else: |
| | self.logger.error(f"Alpaca order failed: {execution_result['error']}") |
| | |
| | return execution_result |
| | |
| | except Exception as e: |
| | self.log_error(e, "Error in Alpaca order execution") |
| | return self._generate_execution_result(signal, success=False, error=str(e)) |
| | |
| | def _execute_simulated_order(self, signal: Dict[str, Any]) -> Dict[str, Any]: |
| | """Execute order with broker simulation""" |
| | try: |
| | |
| | time.sleep(self.execution_delay / 1000.0) |
| | |
| | |
| | import random |
| | success = random.random() < self.success_rate |
| | |
| | if signal['action'] == 'hold': |
| | success = True |
| | |
| | if success: |
| | return self._simulate_successful_execution(signal) |
| | else: |
| | return self._simulate_failed_execution(signal) |
| | |
| | except Exception as e: |
| | self.log_error(e, "Error in order execution simulation") |
| | return self._generate_execution_result(signal, success=False, error=str(e)) |
| | |
| | def get_account_info(self) -> Dict[str, Any]: |
| | """Get account information""" |
| | if self.alpaca_broker: |
| | return self.alpaca_broker.get_account_info() |
| | else: |
| | |
| | return { |
| | 'account_id': 'SIM_ACCOUNT', |
| | 'status': 'ACTIVE', |
| | 'buying_power': 100000.0, |
| | 'cash': 100000.0, |
| | 'portfolio_value': 100000.0, |
| | 'equity': 100000.0, |
| | 'trading_blocked': False |
| | } |
| | |
| | def get_positions(self) -> list: |
| | """Get current positions""" |
| | if self.alpaca_broker: |
| | return self.alpaca_broker.get_positions() |
| | else: |
| | |
| | return [] |
| | |
| | def is_market_open(self) -> bool: |
| | """Check if market is open""" |
| | if self.alpaca_broker: |
| | return self.alpaca_broker.is_market_open() |
| | else: |
| | |
| | return True |
| |
|
| | def _validate_signal(self, signal: Dict[str, Any]) -> bool: |
| | """Validate trading signal""" |
| | try: |
| | required_fields = ['action', 'symbol', 'quantity'] |
| | |
| | |
| | for field in required_fields: |
| | if field not in signal: |
| | self.logger.error(f"Missing required field: {field}") |
| | return False |
| | |
| | |
| | if signal['action'] not in ['buy', 'sell', 'hold']: |
| | self.logger.error(f"Invalid action: {signal['action']}") |
| | return False |
| | |
| | |
| | if signal['quantity'] <= 0 and signal['action'] != 'hold': |
| | self.logger.error(f"Invalid quantity: {signal['quantity']}") |
| | return False |
| | |
| | |
| | if not signal['symbol'] or not isinstance(signal['symbol'], str): |
| | self.logger.error(f"Invalid symbol: {signal['symbol']}") |
| | return False |
| | |
| | return True |
| | |
| | except Exception as e: |
| | self.log_error(e, "Error validating signal") |
| | return False |
| | |
| | def _simulate_successful_execution(self, signal: Dict[str, Any]) -> Dict[str, Any]: |
| | """Simulate successful order execution""" |
| | try: |
| | |
| | execution_price = signal.get('price', 0) |
| | if execution_price == 0: |
| | |
| | import random |
| | slippage = random.uniform(-0.001, 0.001) |
| | execution_price = signal.get('price', 100) * (1 + slippage) |
| | |
| | execution_time = time.time() |
| | |
| | |
| | commission = self._calculate_commission(signal) |
| | |
| | result = { |
| | 'order_id': self._generate_order_id(), |
| | 'status': 'filled', |
| | 'action': signal['action'], |
| | 'symbol': signal['symbol'], |
| | 'quantity': signal['quantity'], |
| | 'price': round(execution_price, 4), |
| | 'execution_time': execution_time, |
| | 'commission': commission, |
| | 'total_value': round(signal['quantity'] * execution_price, 2), |
| | 'success': True, |
| | 'error': None |
| | } |
| | |
| | self.logger.info(f"Order executed successfully: {result['order_id']} - " |
| | f"{result['action']} {result['quantity']} {result['symbol']} @ {result['price']}") |
| | |
| | return result |
| | |
| | except Exception as e: |
| | self.log_error(e, "Error in successful execution simulation") |
| | return self._generate_execution_result(signal, success=False, error=str(e)) |
| | |
| | def _simulate_failed_execution(self, signal: Dict[str, Any]) -> Dict[str, Any]: |
| | """Simulate failed order execution""" |
| | error_reasons = [ |
| | "Insufficient funds", |
| | "Market closed", |
| | "Invalid order", |
| | "Network timeout", |
| | "Broker error" |
| | ] |
| | |
| | import random |
| | error_reason = random.choice(error_reasons) |
| | |
| | result = self._generate_execution_result(signal, success=False, error=error_reason) |
| | |
| | self.logger.warning(f"Order execution failed: {error_reason}") |
| | |
| | return result |
| | |
| | def _generate_execution_result(self, signal: Dict[str, Any], success: bool, error: Optional[str] = None) -> Dict[str, Any]: |
| | """Generate execution result""" |
| | return { |
| | 'order_id': self._generate_order_id() if success else None, |
| | 'status': 'filled' if success else 'rejected', |
| | 'action': signal.get('action', 'unknown'), |
| | 'symbol': signal.get('symbol', 'unknown'), |
| | 'quantity': signal.get('quantity', 0), |
| | 'price': signal.get('price', 0) if success else 0, |
| | 'execution_time': time.time(), |
| | 'commission': 0, |
| | 'total_value': 0, |
| | 'success': success, |
| | 'error': error |
| | } |
| | |
| | def _calculate_commission(self, signal: Dict[str, Any]) -> float: |
| | """Calculate commission for the order""" |
| | try: |
| | |
| | base_commission = 1.0 |
| | per_share_commission = 0.01 |
| | |
| | if signal['action'] == 'hold': |
| | return 0.0 |
| | |
| | commission = base_commission + (signal['quantity'] * per_share_commission) |
| | return round(commission, 2) |
| | |
| | except Exception as e: |
| | self.log_error(e, "Error calculating commission") |
| | return 0.0 |
| | |
| | def _execute_order(self, signal: Dict[str, Any]) -> Dict[str, Any]: |
| | """ |
| | Execute a trading order (private method for testing) |
| | |
| | Args: |
| | signal: Trading signal |
| | |
| | Returns: |
| | Execution result |
| | """ |
| | return self.act(signal) |
| | |
| | def _generate_order_id(self) -> str: |
| | """Generate unique order ID""" |
| | import uuid |
| | return f"ORD_{uuid.uuid4().hex[:8].upper()}" |
| | |
| | def get_execution_statistics(self) -> Dict[str, Any]: |
| | """Get execution statistics""" |
| | |
| | |
| | return { |
| | 'total_orders': 0, |
| | 'successful_orders': 0, |
| | 'failed_orders': 0, |
| | 'success_rate': 0.0, |
| | 'average_execution_time': 0.0, |
| | 'total_commission': 0.0 |
| | } |
| |
|