"""Order execution logic.""" import structlog from datetime import datetime, timezone from decimal import Decimal from typing import Any, Optional from shared.broker import RedisBroker from shared.db import Database from shared.events import OrderEvent from shared.models import Order, OrderStatus, OrderType, Signal from shared.notifier import TelegramNotifier from order_executor.risk_manager import RiskManager logger = structlog.get_logger() class OrderExecutor: """Executes orders on an exchange with risk gating.""" def __init__( self, exchange: Any, risk_manager: RiskManager, broker: RedisBroker, db: Database, notifier: TelegramNotifier, dry_run: bool = True, ) -> None: self.exchange = exchange self.risk_manager = risk_manager self.broker = broker self.db = db self.notifier = notifier self.dry_run = dry_run async def execute(self, signal: Signal) -> Optional[Order]: """Run risk checks and place an order for the given signal.""" # Fetch buying power from Alpaca balance = await self.exchange.get_buying_power() # Fetch current positions positions = {} # Compute daily PnL (not tracked at executor level — use 0 unless provided) daily_pnl = Decimal(0) # Run risk checks result = self.risk_manager.check( signal=signal, balance=balance, positions=positions, daily_pnl=daily_pnl, ) if not result.allowed: logger.warning("risk_check_rejected", signal_id=str(signal.id), reason=result.reason) return None # Build the order model order = Order( signal_id=signal.id, symbol=signal.symbol, side=signal.side, type=OrderType.MARKET, price=signal.price, quantity=signal.quantity, status=OrderStatus.PENDING, ) if self.dry_run: order.status = OrderStatus.FILLED order.filled_at = datetime.now(timezone.utc) logger.info( "order_filled_dry_run", side=str(order.side), quantity=str(order.quantity), symbol=order.symbol, ) else: try: await self.exchange.submit_order( symbol=signal.symbol, qty=float(signal.quantity), side=signal.side.value.lower(), type="market", ) order.status = OrderStatus.FILLED order.filled_at = datetime.now(timezone.utc) logger.info( "order_filled", side=str(order.side), quantity=str(order.quantity), symbol=order.symbol, ) except Exception as exc: order.status = OrderStatus.FAILED logger.error("order_failed", signal_id=str(signal.id), error=str(exc)) # Persist to DB await self.db.insert_order(order) # Publish order event event = OrderEvent(data=order) await self.broker.publish("orders", event.to_dict()) # Notify via Telegram await self.notifier.send_order(order) return order