lucky-loster's picture
Upload folder using huggingface_hub
590a501 verified
Raw
History Blame Contribute Delete
8.15 kB
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()