import uuid from datetime import datetime from app.models.schemas import ( AccountInfo, OrderRequest, OrderResponse, OrderSide, OrderStatus, OrderType, Position, PositionSide, TradeRecord, ) from app.services.market_data import market_data_service class OrderManager: def __init__(self): self._orders: dict[str, OrderResponse] = {} self._positions: dict[str, Position] = {} self._trades: list[TradeRecord] = [] self._account = AccountInfo() @property def account(self) -> AccountInfo: self._update_account() return self._account def get_orders(self, symbol: str | None = None) -> list[OrderResponse]: orders = list(self._orders.values()) if symbol: orders = [o for o in orders if o.symbol == symbol] return sorted(orders, key=lambda o: o.created_at, reverse=True) def get_positions(self) -> list[Position]: self._update_positions() return list(self._positions.values()) def get_trades(self, limit: int = 100) -> list[TradeRecord]: return self._trades[-limit:] def place_order(self, req: OrderRequest) -> OrderResponse: order_id = f"ORD-{uuid.uuid4().hex[:8].upper()}" now = datetime.utcnow() order = OrderResponse( order_id=order_id, symbol=req.symbol, side=req.side, order_type=req.order_type, quantity=req.quantity, price=req.price, status=OrderStatus.PENDING, strategy_id=req.strategy_id, created_at=now, updated_at=now, ) if req.order_type == OrderType.MARKET: self._execute_order(order) else: self._orders[order_id] = order return order def cancel_order(self, order_id: str) -> OrderResponse | None: order = self._orders.get(order_id) if order and order.status == OrderStatus.PENDING: order.status = OrderStatus.CANCELLED order.updated_at = datetime.utcnow() return order return None def _execute_order(self, order: OrderResponse): current_price = market_data_service.get_current_price(order.symbol) if current_price is None: order.status = OrderStatus.REJECTED self._orders[order.order_id] = order return exec_price = current_price if order.price and order.order_type == OrderType.LIMIT: if order.side == OrderSide.BUY and current_price > order.price: self._orders[order.order_id] = order return if order.side == OrderSide.SELL and current_price < order.price: self._orders[order.order_id] = order return exec_price = order.price order.filled_quantity = order.quantity order.avg_price = exec_price order.status = OrderStatus.FILLED order.updated_at = datetime.utcnow() self._orders[order.order_id] = order trade = TradeRecord( trade_id=f"TRD-{uuid.uuid4().hex[:8].upper()}", order_id=order.order_id, symbol=order.symbol, side=order.side, quantity=order.quantity, price=exec_price, strategy_id=order.strategy_id, timestamp=datetime.utcnow(), ) self._update_position(trade) self._trades.append(trade) def _update_position(self, trade: TradeRecord): pos_key = trade.symbol cost = trade.price * trade.quantity if pos_key in self._positions: pos = self._positions[pos_key] if (trade.side == OrderSide.BUY and pos.side == PositionSide.LONG) or \ (trade.side == OrderSide.SELL and pos.side == PositionSide.SHORT): total_cost = pos.avg_price * pos.quantity + trade.price * trade.quantity pos.quantity += trade.quantity pos.avg_price = round(total_cost / pos.quantity, 2) if pos.quantity else 0 else: if trade.quantity >= pos.quantity: pnl = (trade.price - pos.avg_price) * pos.quantity if pos.side == PositionSide.SHORT: pnl = -pnl trade.pnl = round(pnl, 2) self._account.realized_pnl += pnl remaining = trade.quantity - pos.quantity if remaining > 0: new_side = PositionSide.LONG if trade.side == OrderSide.BUY else PositionSide.SHORT self._positions[pos_key] = Position( symbol=trade.symbol, side=new_side, quantity=remaining, avg_price=trade.price, leverage=pos.leverage, ) else: del self._positions[pos_key] else: pnl = (trade.price - pos.avg_price) * trade.quantity if pos.side == PositionSide.SHORT: pnl = -pnl trade.pnl = round(pnl, 2) self._account.realized_pnl += pnl pos.quantity -= trade.quantity else: side = PositionSide.LONG if trade.side == OrderSide.BUY else PositionSide.SHORT self._positions[pos_key] = Position( symbol=trade.symbol, side=side, quantity=trade.quantity, avg_price=trade.price, ) margin_change = cost / self._account.positions[0].leverage if self._account.positions else cost / 10 self._account.used_margin += margin_change / 10 self._account.available_balance = ( self._account.total_balance - self._account.used_margin + self._account.unrealized_pnl ) def _update_positions(self): for pos in self._positions.values(): current_price = market_data_service.get_current_price(pos.symbol) if current_price: pos.current_price = current_price if pos.side == PositionSide.LONG: pos.unrealized_pnl = round((current_price - pos.avg_price) * pos.quantity, 2) else: pos.unrealized_pnl = round((pos.avg_price - current_price) * pos.quantity, 2) pos.margin = round(pos.avg_price * pos.quantity / pos.leverage, 2) def _update_account(self): self._update_positions() total_unrealized = sum(p.unrealized_pnl for p in self._positions.values()) total_margin = sum(p.margin for p in self._positions.values()) self._account.unrealized_pnl = round(total_unrealized, 2) self._account.used_margin = round(total_margin, 2) self._account.available_balance = round( self._account.total_balance - total_margin + total_unrealized + self._account.realized_pnl, 2 ) self._account.positions = list(self._positions.values()) def check_pending_orders(self): for order in list(self._orders.values()): if order.status != OrderStatus.PENDING: continue current_price = market_data_service.get_current_price(order.symbol) if current_price is None: continue if order.order_type == OrderType.LIMIT: if order.side == OrderSide.BUY and current_price <= order.price: self._execute_order(order) elif order.side == OrderSide.SELL and current_price >= order.price: self._execute_order(order) elif order.order_type == OrderType.STOP: if order.side == OrderSide.BUY and current_price >= order.stop_price: self._execute_order(order) elif order.side == OrderSide.SELL and current_price <= order.stop_price: self._execute_order(order) order_manager = OrderManager()