File size: 8,151 Bytes
590a501 | 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 | 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()
|