File size: 12,146 Bytes
859af74
 
 
 
 
 
 
 
 
 
 
 
46590b0
 
 
 
 
 
 
 
 
 
 
 
859af74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46590b0
 
 
 
 
859af74
 
 
 
 
 
 
 
 
 
46590b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
859af74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63f74a3
 
 
 
 
 
 
 
 
 
 
 
859af74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
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)
        
        # Initialize Alpaca broker if configured
        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'  # Fallback to simulation
        
        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']}")
            
            # Validate signal
            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")
            
            # Execute order based on broker type
            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)
            
            # Log execution result
            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)
            
            # Place market order with Alpaca
            result = self.alpaca_broker.place_market_order(
                symbol=signal['symbol'],
                quantity=signal['quantity'],
                side=signal['action']
            )
            
            # Convert Alpaca result to our format
            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:
            # Simulate execution delay
            time.sleep(self.execution_delay / 1000.0)
            
            # Simulate execution success/failure
            import random
            success = random.random() < self.success_rate
            
            if signal['action'] == 'hold':
                success = True  # Hold actions always succeed
            
            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 simulated account info
            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 simulated positions
            return []
    
    def is_market_open(self) -> bool:
        """Check if market is open"""
        if self.alpaca_broker:
            return self.alpaca_broker.is_market_open()
        else:
            # Assume market is always open for simulation
            return True

    def _validate_signal(self, signal: Dict[str, Any]) -> bool:
        """Validate trading signal"""
        try:
            required_fields = ['action', 'symbol', 'quantity']
            
            # Check required fields
            for field in required_fields:
                if field not in signal:
                    self.logger.error(f"Missing required field: {field}")
                    return False
            
            # Validate action
            if signal['action'] not in ['buy', 'sell', 'hold']:
                self.logger.error(f"Invalid action: {signal['action']}")
                return False
            
            # Validate quantity
            if signal['quantity'] <= 0 and signal['action'] != 'hold':
                self.logger.error(f"Invalid quantity: {signal['quantity']}")
                return False
            
            # Validate symbol
            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:
            # Generate execution details
            execution_price = signal.get('price', 0)
            if execution_price == 0:
                # Simulate price slippage
                import random
                slippage = random.uniform(-0.001, 0.001)  # ±0.1% slippage
                execution_price = signal.get('price', 100) * (1 + slippage)
            
            execution_time = time.time()
            
            # Calculate fees (simplified)
            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,  # Price is 0 for failed executions
            '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:
            # Simple commission calculation
            base_commission = 1.0  # $1 base commission
            per_share_commission = 0.01  # $0.01 per share
            
            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"""
        # This would typically track real execution statistics
        # For now, return placeholder data
        return {
            'total_orders': 0,
            'successful_orders': 0,
            'failed_orders': 0,
            'success_rate': 0.0,
            'average_execution_time': 0.0,
            'total_commission': 0.0
        }