| 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() |
|
|