File size: 11,922 Bytes
386790e |
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 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
#!/usr/bin/env python3
"""
Futures Trading Service
========================
سرویس مدیریت معاملات Futures با قابلیت اجرای دستورات، مدیریت موقعیتها و پیگیری سفارشات
"""
from typing import Optional, List, Dict, Any
from datetime import datetime
from sqlalchemy.orm import Session
from sqlalchemy import and_
import uuid
import logging
from database.models import (
Base, FuturesOrder, FuturesPosition, OrderStatus, OrderSide, OrderType
)
logger = logging.getLogger(__name__)
class FuturesTradingService:
"""سرویس اصلی مدیریت معاملات Futures"""
def __init__(self, db_session: Session):
"""
Initialize the futures trading service.
Args:
db_session: SQLAlchemy database session
"""
self.db = db_session
def create_order(
self,
symbol: str,
side: str,
order_type: str,
quantity: float,
price: Optional[float] = None,
stop_price: Optional[float] = None,
exchange: str = "demo"
) -> Dict[str, Any]:
"""
Create and execute a futures trading order.
Args:
symbol: Trading pair (e.g., "BTC/USDT")
side: Order side ("buy" or "sell")
order_type: Order type ("market", "limit", "stop", "stop_limit")
quantity: Order quantity
price: Limit price (required for limit orders)
stop_price: Stop price (required for stop orders)
exchange: Exchange name (default: "demo")
Returns:
Dict containing order details
"""
try:
# Validate inputs
if order_type in ["limit", "stop_limit"] and not price:
raise ValueError(f"Price is required for {order_type} orders")
if order_type in ["stop", "stop_limit"] and not stop_price:
raise ValueError(f"Stop price is required for {order_type} orders")
# Generate order ID
order_id = f"ORD-{uuid.uuid4().hex[:12].upper()}"
# Create order record
order = FuturesOrder(
order_id=order_id,
symbol=symbol.upper(),
side=OrderSide.BUY if side.lower() == "buy" else OrderSide.SELL,
order_type=OrderType[order_type.upper()],
quantity=quantity,
price=price,
stop_price=stop_price,
status=OrderStatus.OPEN if order_type == "market" else OrderStatus.PENDING,
exchange=exchange
)
self.db.add(order)
self.db.commit()
self.db.refresh(order)
# Execute market orders immediately (in demo mode)
if order_type == "market":
self._execute_market_order(order)
logger.info(f"Created order {order_id} for {symbol} {side} {quantity} @ {price or 'MARKET'}")
return self._order_to_dict(order)
except Exception as e:
self.db.rollback()
logger.error(f"Error creating order: {e}", exc_info=True)
raise
def _execute_market_order(self, order: FuturesOrder) -> None:
"""
Execute a market order immediately (demo mode).
Args:
order: The order to execute
"""
try:
# In demo mode, we simulate immediate execution
# In production, this would call exchange API
order.status = OrderStatus.FILLED
order.filled_quantity = order.quantity
# Simulate fill price (in production, use actual market price)
order.average_fill_price = order.price or 50000.0 # Placeholder
order.executed_at = datetime.utcnow()
# Create or update position
self._update_position_from_order(order)
self.db.commit()
except Exception as e:
logger.error(f"Error executing market order: {e}", exc_info=True)
raise
def _update_position_from_order(self, order: FuturesOrder) -> None:
"""
Update position based on filled order.
Args:
order: The filled order
"""
try:
# Find existing open position
position = self.db.query(FuturesPosition).filter(
and_(
FuturesPosition.symbol == order.symbol,
FuturesPosition.is_open == True
)
).first()
if position:
# Update existing position
if position.side == order.side:
# Increase position
total_value = (position.quantity * position.entry_price) + \
(order.filled_quantity * order.average_fill_price)
total_quantity = position.quantity + order.filled_quantity
position.entry_price = total_value / total_quantity if total_quantity > 0 else position.entry_price
position.quantity = total_quantity
else:
# Close or reduce position
if order.filled_quantity >= position.quantity:
# Close position
realized_pnl = (order.average_fill_price - position.entry_price) * position.quantity
if position.side == OrderSide.SELL:
realized_pnl = -realized_pnl
position.realized_pnl += realized_pnl
position.is_open = False
position.closed_at = datetime.utcnow()
else:
# Reduce position
realized_pnl = (order.average_fill_price - position.entry_price) * order.filled_quantity
if position.side == OrderSide.SELL:
realized_pnl = -realized_pnl
position.realized_pnl += realized_pnl
position.quantity -= order.filled_quantity
else:
# Create new position
position = FuturesPosition(
symbol=order.symbol,
side=order.side,
quantity=order.filled_quantity,
entry_price=order.average_fill_price,
current_price=order.average_fill_price,
exchange=order.exchange
)
self.db.add(position)
self.db.commit()
except Exception as e:
logger.error(f"Error updating position: {e}", exc_info=True)
raise
def get_positions(
self,
symbol: Optional[str] = None,
is_open: Optional[bool] = True
) -> List[Dict[str, Any]]:
"""
Retrieve futures positions.
Args:
symbol: Filter by symbol (optional)
is_open: Filter by open status (optional)
Returns:
List of position dictionaries
"""
try:
query = self.db.query(FuturesPosition)
if symbol:
query = query.filter(FuturesPosition.symbol == symbol.upper())
if is_open is not None:
query = query.filter(FuturesPosition.is_open == is_open)
positions = query.order_by(FuturesPosition.opened_at.desc()).all()
return [self._position_to_dict(p) for p in positions]
except Exception as e:
logger.error(f"Error retrieving positions: {e}", exc_info=True)
raise
def get_orders(
self,
symbol: Optional[str] = None,
status: Optional[str] = None,
limit: int = 100
) -> List[Dict[str, Any]]:
"""
List all trading orders.
Args:
symbol: Filter by symbol (optional)
status: Filter by status (optional)
limit: Maximum number of orders to return
Returns:
List of order dictionaries
"""
try:
query = self.db.query(FuturesOrder)
if symbol:
query = query.filter(FuturesOrder.symbol == symbol.upper())
if status:
query = query.filter(FuturesOrder.status == OrderStatus[status.upper()])
orders = query.order_by(FuturesOrder.created_at.desc()).limit(limit).all()
return [self._order_to_dict(o) for o in orders]
except Exception as e:
logger.error(f"Error retrieving orders: {e}", exc_info=True)
raise
def cancel_order(self, order_id: str) -> Dict[str, Any]:
"""
Cancel a specific order.
Args:
order_id: The order ID to cancel
Returns:
Dict containing cancelled order details
"""
try:
order = self.db.query(FuturesOrder).filter(
FuturesOrder.order_id == order_id
).first()
if not order:
raise ValueError(f"Order {order_id} not found")
if order.status in [OrderStatus.FILLED, OrderStatus.CANCELLED]:
raise ValueError(f"Cannot cancel order with status {order.status.value}")
order.status = OrderStatus.CANCELLED
order.cancelled_at = datetime.utcnow()
self.db.commit()
self.db.refresh(order)
logger.info(f"Cancelled order {order_id}")
return self._order_to_dict(order)
except Exception as e:
self.db.rollback()
logger.error(f"Error cancelling order: {e}", exc_info=True)
raise
def _order_to_dict(self, order: FuturesOrder) -> Dict[str, Any]:
"""Convert order model to dictionary."""
return {
"id": order.id,
"order_id": order.order_id,
"symbol": order.symbol,
"side": order.side.value if order.side else None,
"order_type": order.order_type.value if order.order_type else None,
"quantity": order.quantity,
"price": order.price,
"stop_price": order.stop_price,
"status": order.status.value if order.status else None,
"filled_quantity": order.filled_quantity,
"average_fill_price": order.average_fill_price,
"exchange": order.exchange,
"created_at": order.created_at.isoformat() if order.created_at else None,
"updated_at": order.updated_at.isoformat() if order.updated_at else None,
"executed_at": order.executed_at.isoformat() if order.executed_at else None,
"cancelled_at": order.cancelled_at.isoformat() if order.cancelled_at else None
}
def _position_to_dict(self, position: FuturesPosition) -> Dict[str, Any]:
"""Convert position model to dictionary."""
return {
"id": position.id,
"symbol": position.symbol,
"side": position.side.value if position.side else None,
"quantity": position.quantity,
"entry_price": position.entry_price,
"current_price": position.current_price,
"leverage": position.leverage,
"unrealized_pnl": position.unrealized_pnl,
"realized_pnl": position.realized_pnl,
"exchange": position.exchange,
"is_open": position.is_open,
"opened_at": position.opened_at.isoformat() if position.opened_at else None,
"closed_at": position.closed_at.isoformat() if position.closed_at else None,
"updated_at": position.updated_at.isoformat() if position.updated_at else None
}
|