Riy777 commited on
Commit
490e45e
·
1 Parent(s): 431cfca

Create trade_manger.py

Browse files
Files changed (1) hide show
  1. trade_manger.py +390 -0
trade_manger.py ADDED
@@ -0,0 +1,390 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from datetime import datetime, timedelta
3
+ from helpers import safe_float_conversion, _apply_patience_logic
4
+
5
+ class TradeManager:
6
+ def __init__(self, r2_service, learning_engine=None, data_manager=None):
7
+ self.r2_service = r2_service
8
+ self.learning_engine = learning_engine
9
+ self.data_manager = data_manager
10
+ self.monitoring_tasks = {}
11
+ self.is_running = False
12
+
13
+ async def open_trade(self, symbol, decision, current_price):
14
+ """فتح صفقة جديدة باستخدام رأس المال المتاح بالكامل"""
15
+ try:
16
+ portfolio_state = await self.r2_service.get_portfolio_state_async()
17
+ available_capital = portfolio_state.get("current_capital_usd", 0)
18
+
19
+ if available_capital < 1:
20
+ print(f"❌ رأس مال غير كافٍ (${available_capital:.2f}) لفتح صفقة جديدة.")
21
+ return None
22
+
23
+ expected_target_minutes = decision.get('expected_target_minutes', 15)
24
+ expected_target_minutes = max(5, min(expected_target_minutes, 45))
25
+ expected_target_time = (datetime.now() + timedelta(minutes=expected_target_minutes)).isoformat()
26
+
27
+ strategy = decision.get('strategy')
28
+ if not strategy or strategy == 'unknown':
29
+ strategy = 'GENERIC'
30
+ print(f"⚠️ الاستراتيجية مفقودة أو غير معروفة. تعيين إلى GENERIC لـ {symbol}")
31
+
32
+ trades = await self.r2_service.get_open_trades_async()
33
+ new_trade = {
34
+ "id": str(int(datetime.now().timestamp())),
35
+ "symbol": symbol,
36
+ "entry_price": current_price,
37
+ "entry_timestamp": datetime.now().isoformat(),
38
+ "decision_data": decision,
39
+ "status": "OPEN",
40
+ "stop_loss": decision.get("stop_loss"),
41
+ "take_profit": decision.get("take_profit"),
42
+ "trade_type": decision.get("trade_type"),
43
+ "position_size_usd": available_capital,
44
+ "expected_target_minutes": expected_target_minutes,
45
+ "expected_target_time": expected_target_time,
46
+ "is_monitored": True,
47
+ "strategy": strategy
48
+ }
49
+
50
+ trades.append(new_trade)
51
+ await self.r2_service.save_open_trades_async(trades)
52
+
53
+ original_expected = decision.get('expected_target_minutes', 15)
54
+ if original_expected > 45:
55
+ print(f"⚠️ النموذج طلب {original_expected} دقيقة، تم التحديد إلى 45 دقيقة لتناسق الاستراتيجية")
56
+
57
+ print(f"✅ صفقة جديدة لـ {symbol} محفوظة بحجم ${available_capital:.2f}. الاستراتيجية: {strategy}. النتائج المتوقعة في {expected_target_minutes} دقيقة.")
58
+
59
+ portfolio_state["invested_capital_usd"] = available_capital
60
+ portfolio_state["current_capital_usd"] = 0.0
61
+ portfolio_state["total_trades"] = portfolio_state.get("total_trades", 0) + 1
62
+ await self.r2_service.save_portfolio_state_async(portfolio_state)
63
+
64
+ await self.r2_service.save_system_logs_async({
65
+ "new_trade_opened": True,
66
+ "symbol": symbol,
67
+ "position_size": available_capital,
68
+ "expected_minutes": expected_target_minutes,
69
+ "trade_type": decision.get("trade_type", "LONG"),
70
+ "strategy": strategy
71
+ })
72
+
73
+ return new_trade
74
+
75
+ except Exception as e:
76
+ print(f"❌ فشل فتح صفقة جديدة: {e}")
77
+ raise
78
+
79
+ async def close_trade(self, trade_to_close, close_price, reason="إغلاق بالنظام"):
80
+ """إغلاق صفقة وتحديث المحفظة وتحليل النتيجة"""
81
+ try:
82
+ trade_to_close['status'] = 'CLOSED'
83
+ trade_to_close['close_price'] = close_price
84
+ trade_to_close['close_timestamp'] = datetime.now().isoformat()
85
+ trade_to_close['is_monitored'] = False
86
+
87
+ entry_price = trade_to_close['entry_price']
88
+ position_size = trade_to_close['position_size_usd']
89
+ trade_type = trade_to_close.get('trade_type', 'LONG')
90
+ strategy = trade_to_close.get('strategy', 'unknown')
91
+
92
+ pnl = 0.0
93
+ pnl_percent = 0.0
94
+
95
+ if entry_price and entry_price > 0 and close_price and close_price > 0:
96
+ try:
97
+ if trade_type == 'LONG':
98
+ pnl_percent = ((close_price - entry_price) / entry_price) * 100
99
+ pnl = position_size * (pnl_percent / 100)
100
+ elif trade_type == 'SHORT':
101
+ pnl_percent = ((entry_price - close_price) / entry_price) * 100
102
+ pnl = position_size * (pnl_percent / 100)
103
+
104
+ print(f"💰 حساب PnL: الدخول=${entry_price:.6f}, الخروج=${close_price:.6f}, "
105
+ f"الحجم=${position_size:.2f}, النوع={trade_type}, "
106
+ f"PnL=${pnl:.4f} ({pnl_percent:+.4f}%)")
107
+
108
+ except (TypeError, ZeroDivisionError) as calc_error:
109
+ print(f"⚠️ خطأ في حساب PnL: {calc_error}")
110
+ pnl = 0.0
111
+ pnl_percent = 0.0
112
+ else:
113
+ print(f"⚠️ أسعار غير صالحة لحساب PnL: الدخول={entry_price}, الخروج={close_price}")
114
+
115
+ trade_to_close['pnl_usd'] = pnl
116
+ trade_to_close['pnl_percent'] = pnl_percent
117
+
118
+ await self._archive_closed_trade(trade_to_close)
119
+ await self._update_trade_summary(trade_to_close)
120
+
121
+ portfolio_state = await self.r2_service.get_portfolio_state_async()
122
+ current_capital = portfolio_state.get("current_capital_usd", 0)
123
+ invested_capital = portfolio_state.get("invested_capital_usd", 0)
124
+
125
+ new_capital = current_capital + position_size + pnl
126
+
127
+ portfolio_state["current_capital_usd"] = new_capital
128
+ portfolio_state["invested_capital_usd"] = 0.0
129
+
130
+ if pnl > 0:
131
+ portfolio_state["winning_trades"] = portfolio_state.get("winning_trades", 0) + 1
132
+ portfolio_state["total_profit_usd"] = portfolio_state.get("total_profit_usd", 0.0) + pnl
133
+ elif pnl < 0:
134
+ portfolio_state["total_loss_usd"] = portfolio_state.get("total_loss_usd", 0.0) + abs(pnl)
135
+
136
+ await self.r2_service.save_portfolio_state_async(portfolio_state)
137
+
138
+ print(f"📈 PnL الصفقة: ${pnl:.4f} ({pnl_percent:+.4f}%). "
139
+ f"رأس المال الجديد المتاح: ${new_capital:.4f}. الاستراتيجية: {strategy}")
140
+
141
+ open_trades = await self.r2_service.get_open_trades_async()
142
+ trades_to_keep = [t for t in open_trades if t.get('id') != trade_to_close.get('id')]
143
+ await self.r2_service.save_open_trades_async(trades_to_keep)
144
+
145
+ print(f"✅ تم إغلاق صفقة {trade_to_close.get('symbol')} وأرشفتها بنجاح. الاستراتيجية: {strategy}")
146
+
147
+ await self.r2_service.save_system_logs_async({
148
+ "trade_closed": True,
149
+ "symbol": trade_to_close.get('symbol'),
150
+ "entry_price": entry_price,
151
+ "close_price": close_price,
152
+ "pnl_usd": pnl,
153
+ "pnl_percent": pnl_percent,
154
+ "new_capital": new_capital,
155
+ "strategy": strategy,
156
+ "position_size": position_size,
157
+ "trade_type": trade_type,
158
+ "reason": reason
159
+ })
160
+
161
+ if self.learning_engine and self.learning_engine.initialized:
162
+ await self.learning_engine.analyze_trade_outcome(trade_to_close, reason)
163
+
164
+ return True
165
+
166
+ except Exception as e:
167
+ print(f"❌ فشل إغلاق الصفقة: {e}")
168
+ raise
169
+
170
+ async def update_trade(self, trade_to_update, re_analysis_decision):
171
+ """تحديث الصفقة بمعطيات جديدة من إعادة التحليل"""
172
+ try:
173
+ if re_analysis_decision.get('new_stop_loss'):
174
+ trade_to_update['stop_loss'] = re_analysis_decision['new_stop_loss']
175
+ if re_analysis_decision.get('new_take_profit'):
176
+ trade_to_update['take_profit'] = re_analysis_decision['new_take_profit']
177
+
178
+ new_expected_minutes = re_analysis_decision.get('new_expected_minutes')
179
+ if new_expected_minutes:
180
+ new_expected_minutes = max(5, min(new_expected_minutes, 45))
181
+ trade_to_update['expected_target_minutes'] = new_expected_minutes
182
+ trade_to_update['expected_target_time'] = (datetime.now() + timedelta(minutes=new_expected_minutes)).isoformat()
183
+ print(f"⏰ تم تحديث وقت الصفقة المتوقع إلى {new_expected_minutes} دقيقة.")
184
+
185
+ original_strategy = trade_to_update.get('strategy')
186
+ if not original_strategy or original_strategy == 'unknown':
187
+ original_strategy = re_analysis_decision.get('strategy', 'GENERIC')
188
+
189
+ trade_to_update['strategy'] = original_strategy
190
+ trade_to_update['decision_data'] = re_analysis_decision
191
+ trade_to_update['is_monitored'] = True
192
+
193
+ open_trades = await self.r2_service.get_open_trades_async()
194
+ for i, trade in enumerate(open_trades):
195
+ if trade.get('id') == trade_to_update.get('id'):
196
+ open_trades[i] = trade_to_update
197
+ break
198
+
199
+ await self.r2_service.save_open_trades_async(open_trades)
200
+ print(f"✅ تم تحديث صفقة {trade_to_update.get('symbol')} بنجاح. الاستراتيجية: {original_strategy}")
201
+
202
+ await self.r2_service.save_system_logs_async({
203
+ "trade_updated": True,
204
+ "symbol": trade_to_update.get('symbol'),
205
+ "new_expected_minutes": new_expected_minutes,
206
+ "action": "UPDATE_TRADE",
207
+ "strategy": original_strategy
208
+ })
209
+
210
+ return True
211
+
212
+ except Exception as e:
213
+ print(f"❌ فشل تحديث الصفقة: {e}")
214
+ raise
215
+
216
+ async def immediate_close_trade(self, symbol, close_price, reason="المراقبة الفورية"):
217
+ """إغلاق فوري لصفقة بدون إعادة تحليل كاملة"""
218
+ try:
219
+ open_trades = await self.r2_service.get_open_trades_async()
220
+ trade_to_close = None
221
+
222
+ for trade in open_trades:
223
+ if trade['symbol'] == symbol and trade['status'] == 'OPEN':
224
+ trade_to_close = trade
225
+ break
226
+
227
+ if not trade_to_close:
228
+ print(f"❌ لم يتم العثور على صفقة مفتوحة لـ {symbol}")
229
+ return False
230
+
231
+ await self.close_trade(trade_to_close, close_price, reason)
232
+ print(f"🚨 إغلاق فوري: {symbol} عند {close_price} - {reason}")
233
+
234
+ return True
235
+
236
+ except Exception as e:
237
+ print(f"❌ فشل الإغلاق الفوري لصفقة {symbol}: {e}")
238
+ return False
239
+
240
+ async def start_trade_monitoring(self):
241
+ """بدء مراقبة الصفقات المفتوحة"""
242
+ self.is_running = True
243
+ while self.is_running:
244
+ try:
245
+ open_trades = await self.r2_service.get_open_trades_async()
246
+ for trade in open_trades:
247
+ symbol = trade['symbol']
248
+ if symbol not in self.monitoring_tasks:
249
+ asyncio.create_task(self._monitor_single_trade(trade))
250
+ self.monitoring_tasks[symbol] = trade
251
+
252
+ current_symbols = {trade['symbol'] for trade in open_trades}
253
+ for symbol in list(self.monitoring_tasks.keys()):
254
+ if symbol not in current_symbols:
255
+ del self.monitoring_tasks[symbol]
256
+
257
+ await asyncio.sleep(10)
258
+ except Exception as error:
259
+ print(f"خطأ في مراقبة الصفقات: {error}")
260
+ await asyncio.sleep(30)
261
+
262
+ async def _monitor_single_trade(self, trade):
263
+ """مراقبة صفقة فردية"""
264
+ symbol = trade['symbol']
265
+ while symbol in self.monitoring_tasks and self.is_running:
266
+ try:
267
+ if not self.data_manager:
268
+ await asyncio.sleep(15)
269
+ continue
270
+
271
+ current_price = await self.data_manager.get_latest_price_async(symbol)
272
+ if not current_price:
273
+ await asyncio.sleep(15)
274
+ continue
275
+
276
+ entry_price = trade['entry_price']
277
+ stop_loss = trade.get('stop_loss')
278
+ take_profit = trade.get('take_profit')
279
+ should_close, close_reason = False, ""
280
+
281
+ if stop_loss and current_price <= stop_loss:
282
+ should_close, close_reason = True, f"وصول وقف الخسارة: {current_price} <= {stop_loss}"
283
+ elif take_profit and current_price >= take_profit:
284
+ should_close, close_reason = True, f"وصول جني الأرباح: {current_price} >= {take_profit}"
285
+
286
+ if not should_close and current_price > entry_price:
287
+ dynamic_stop = current_price * 0.98
288
+ if dynamic_stop > (stop_loss or 0):
289
+ trade['stop_loss'] = dynamic_stop
290
+
291
+ if should_close:
292
+ if self.r2_service.acquire_lock():
293
+ try:
294
+ await self.immediate_close_trade(symbol, current_price, close_reason)
295
+ finally:
296
+ self.r2_service.release_lock()
297
+
298
+ if symbol in self.monitoring_tasks:
299
+ del self.monitoring_tasks[symbol]
300
+ break
301
+
302
+ await asyncio.sleep(15)
303
+ except Exception as error:
304
+ print(f"خطأ في مراقبة {symbol}: {error}")
305
+ await asyncio.sleep(30)
306
+
307
+ def stop_monitoring(self):
308
+ """إيقاف مراقبة الصفقات"""
309
+ self.is_running = False
310
+ self.monitoring_tasks.clear()
311
+
312
+ async def _archive_closed_trade(self, closed_trade):
313
+ """أرشفة الصفقة المغلقة"""
314
+ try:
315
+ key = "closed_trades_history.json"
316
+ try:
317
+ response = self.r2_service.s3_client.get_object(Bucket="trading", Key=key)
318
+ history = json.loads(response['Body'].read())
319
+ except Exception:
320
+ history = []
321
+
322
+ history.append(closed_trade)
323
+
324
+ data_json = json.dumps(history, indent=2).encode('utf-8')
325
+ self.r2_service.s3_client.put_object(
326
+ Bucket="trading", Key=key, Body=data_json, ContentType="application/json"
327
+ )
328
+ print(f"📚 تم أرشفة الصفقة. إجمالي الصفقات المؤرشفة: {len(history)}")
329
+ except Exception as e:
330
+ print(f"❌ فشل أرشفة الصفقة: {e}")
331
+
332
+ async def _update_trade_summary(self, closed_trade):
333
+ """تحديث إحصائيات التداول"""
334
+ try:
335
+ key = "trade_summary.json"
336
+ try:
337
+ response = self.r2_service.s3_client.get_object(Bucket="trading", Key=key)
338
+ summary = json.loads(response['Body'].read())
339
+ except Exception:
340
+ summary = {
341
+ "total_trades": 0, "winning_trades": 0, "losing_trades": 0,
342
+ "total_profit_usd": 0.0, "total_loss_usd": 0.0, "win_percentage": 0.0,
343
+ "avg_profit_per_trade": 0.0, "avg_loss_per_trade": 0.0,
344
+ "largest_win": 0.0, "largest_loss": 0.0
345
+ }
346
+
347
+ pnl = closed_trade.get('pnl_usd', 0.0)
348
+
349
+ summary['total_trades'] += 1
350
+ if pnl >= 0:
351
+ summary['winning_trades'] += 1
352
+ summary['total_profit_usd'] += pnl
353
+ if pnl > summary.get('largest_win', 0):
354
+ summary['largest_win'] = pnl
355
+ else:
356
+ summary['losing_trades'] += 1
357
+ summary['total_loss_usd'] += abs(pnl)
358
+ if abs(pnl) > summary.get('largest_loss', 0):
359
+ summary['largest_loss'] = abs(pnl)
360
+
361
+ if summary['total_trades'] > 0:
362
+ summary['win_percentage'] = (summary['winning_trades'] / summary['total_trades']) * 100
363
+
364
+ if summary['winning_trades'] > 0:
365
+ summary['avg_profit_per_trade'] = summary['total_profit_usd'] / summary['winning_trades']
366
+ if summary['losing_trades'] > 0:
367
+ summary['avg_loss_per_trade'] = summary['total_loss_usd'] / summary['losing_trades']
368
+
369
+ data_json = json.dumps(summary, indent=2).encode('utf-8')
370
+ self.r2_service.s3_client.put_object(
371
+ Bucket="trading", Key=key, Body=data_json, ContentType="application/json"
372
+ )
373
+ print(f"📊 تم تحديث ملخص التداول. معدل الربح: {summary['win_percentage']:.2f}%")
374
+
375
+ except Exception as e:
376
+ print(f"❌ فشل تحديث ملخص التداول: {e}")
377
+
378
+ async def get_open_trades(self):
379
+ """الحصول على الصفقات المفتوحة"""
380
+ return await self.r2_service.get_open_trades_async()
381
+
382
+ async def get_trade_by_symbol(self, symbol):
383
+ """الحصول على صفقة بالرمز"""
384
+ open_trades = await self.get_open_trades()
385
+ for trade in open_trades:
386
+ if trade['symbol'] == symbol and trade['status'] == 'OPEN':
387
+ return trade
388
+ return None
389
+
390
+ print("✅ Trade Manager محمل - إدارة موحدة للصفقات")