|
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 |
|
} |
|
|