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
        }