Riy777 commited on
Commit
ad36615
·
verified ·
1 Parent(s): 754aceb

Update r2.py

Browse files
Files changed (1) hide show
  1. r2.py +127 -71
r2.py CHANGED
@@ -1,22 +1,44 @@
1
- # r2.py (محدث V10.3 - Typing Fix)
2
- import os, traceback, json, time
 
 
 
3
  from datetime import datetime, timedelta
4
  import asyncio
5
  import boto3
6
  from botocore.exceptions import NoCredentialsError, ClientError
7
- from typing import List, Dict, Any, Optional # <-- 🔴 السطر المضاف لإصلاح الخطأ
8
 
 
 
 
9
  R2_ACCOUNT_ID = os.getenv("R2_ACCOUNT_ID")
10
  R2_ACCESS_KEY_ID = os.getenv("R2_ACCESS_KEY_ID")
11
  R2_SECRET_ACCESS_KEY = os.getenv("R2_SECRET_ACCESS_KEY")
12
  BUCKET_NAME = "trading"
13
- INITIAL_CAPITAL = 10.0
14
 
15
- # 🔴 --- (جديد V10.2) أسماء ملفات التعلم --- 🔴
 
 
 
 
16
  WHALE_LEARNING_PENDING_KEY = "learning_whale_pending_records.json"
17
  WHALE_LEARNING_COMPLETED_KEY = "learning_whale_completed_records.json"
18
  WHALE_LEARNING_CONFIG_KEY = "learning_whale_optimal_config.json"
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  class R2Service:
22
  def __init__(self):
@@ -31,6 +53,7 @@ class R2Service:
31
  self.lock_acquired = False
32
  self.BUCKET_NAME = BUCKET_NAME
33
 
 
34
  self._open_trades_warning_printed = False
35
  self._portfolio_warning_printed = False
36
  self._contracts_warning_printed = False
@@ -38,6 +61,9 @@ class R2Service:
38
  except Exception as e:
39
  raise RuntimeError(f"Failed to initialize S3 client: {e}")
40
 
 
 
 
41
  def acquire_lock(self, max_retries=3):
42
  lock_path = "lock.txt"
43
  for attempt in range(max_retries):
@@ -70,10 +96,12 @@ class R2Service:
70
  except Exception as e:
71
  print(f"❌ Failed to release lock: {e}")
72
 
 
 
 
73
  async def save_candidates_async(self, candidates):
74
  """حفظ بيانات المرشحين العشرة في ملف منفصل في R2"""
75
  try:
76
- key = "Candidates.json"
77
  data = {
78
  "timestamp": datetime.now().isoformat(),
79
  "total_candidates": len(candidates),
@@ -81,7 +109,7 @@ class R2Service:
81
  }
82
  data_json = json.dumps(data, indent=2, ensure_ascii=False).encode('utf-8')
83
  self.s3_client.put_object(
84
- Bucket=BUCKET_NAME, Key=key, Body=data_json, ContentType="application/json"
85
  )
86
  print(f"✅ تم حفظ {len(candidates)} مرشح في ملف Candidates في R2")
87
 
@@ -99,8 +127,7 @@ class R2Service:
99
  async def load_candidates_async(self):
100
  """تحميل بيانات المرشحين من R2"""
101
  try:
102
- key = "Candidates.json"
103
- response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
104
  data = json.loads(response['Body'].read())
105
  candidates = data.get('candidates', [])
106
  print(f"✅ تم تحميل {len(candidates)} مرشح من R2")
@@ -112,12 +139,14 @@ class R2Service:
112
  else:
113
  raise
114
 
 
 
 
115
  async def save_llm_prompts_async(self, symbol, prompt_type, prompt_content, analysis_data=None):
116
  """حفظ الـ Prompts المرسلة إلى النموذج الضخم"""
117
  try:
118
- key = "llm_prompts.json"
119
  try:
120
- response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
121
  existing_data = json.loads(response['Body'].read())
122
  except ClientError as e:
123
  if e.response['Error']['Code'] == 'NoSuchKey':
@@ -128,18 +157,19 @@ class R2Service:
128
  new_prompt = {
129
  "timestamp": datetime.now().isoformat(),
130
  "symbol": symbol,
131
- "prompt_type": prompt_type, # 'trading_decision' or 'trade_reanalysis'
132
  "prompt_content": prompt_content,
133
  "analysis_data": analysis_data
134
  }
135
 
136
  existing_data["prompts"].append(new_prompt)
 
137
  if len(existing_data["prompts"]) > 2000:
138
  existing_data["prompts"] = existing_data["prompts"][-2000:]
139
 
140
  data_json = json.dumps(existing_data, indent=2, ensure_ascii=False).encode('utf-8')
141
  self.s3_client.put_object(
142
- Bucket=BUCKET_NAME, Key=key, Body=data_json, ContentType="application/json"
143
  )
144
  print(f"✅ تم حفظ prompt لـ {symbol} ({prompt_type}) في R2")
145
  except Exception as e:
@@ -147,9 +177,8 @@ class R2Service:
147
 
148
  async def save_system_logs_async(self, log_data):
149
  try:
150
- key = "system_logs.json"
151
  try:
152
- response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
153
  existing_logs = json.loads(response['Body'].read())
154
  except ClientError as e:
155
  if e.response['Error']['Code'] == 'NoSuchKey':
@@ -168,22 +197,24 @@ class R2Service:
168
 
169
  data_json = json.dumps(existing_logs, indent=2, ensure_ascii=False).encode('utf-8')
170
  self.s3_client.put_object(
171
- Bucket=BUCKET_NAME, Key=key, Body=data_json, ContentType="application/json"
172
  )
173
  print(f"✅ System log saved: {log_data.get('cycle_started', log_data.get('cycle_completed', 'event'))}")
174
  except Exception as e:
175
  print(f"❌ Failed to save system logs: {e}")
176
 
 
 
 
177
  async def save_learning_data_async(self, learning_data):
178
  try:
179
- key = "learning_data.json"
180
  data = {
181
  "timestamp": datetime.now().isoformat(),
182
  "learning_data": learning_data
183
  }
184
  data_json = json.dumps(data, indent=2, ensure_ascii=False).encode('utf-8')
185
  self.s3_client.put_object(
186
- Bucket=BUCKET_NAME, Key=key, Body=data_json, ContentType="application/json"
187
  )
188
  print("✅ Learning data saved to R2")
189
  except Exception as e:
@@ -191,8 +222,7 @@ class R2Service:
191
 
192
  async def load_learning_data_async(self):
193
  try:
194
- key = "learning_data.json"
195
- response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
196
  data = json.loads(response['Body'].read())
197
  print("✅ Learning data loaded from R2")
198
  return data
@@ -203,10 +233,12 @@ class R2Service:
203
  else:
204
  raise
205
 
 
 
 
206
  async def get_portfolio_state_async(self):
207
- key = "portfolio_state.json"
208
  try:
209
- response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
210
  state = json.loads(response['Body'].read())
211
  if hasattr(self, '_portfolio_warning_printed'):
212
  delattr(self, '_portfolio_warning_printed')
@@ -217,14 +249,19 @@ class R2Service:
217
  if not hasattr(self, '_portfolio_warning_printed'):
218
  print(f"⚠️ No portfolio state file found. Initializing with ${INITIAL_CAPITAL:.2f}")
219
  self._portfolio_warning_printed = True
 
 
220
  initial_state = {
221
  "current_capital_usd": INITIAL_CAPITAL,
222
  "invested_capital_usd": 0.0,
223
  "initial_capital_usd": INITIAL_CAPITAL,
224
  "total_trades": 0,
225
  "winning_trades": 0,
 
226
  "total_profit_usd": 0.0,
227
- "total_loss_usd": 0.0
 
 
228
  }
229
  await self.save_portfolio_state_async(initial_state)
230
  return initial_state
@@ -232,20 +269,23 @@ class R2Service:
232
  raise
233
 
234
  async def save_portfolio_state_async(self, state):
235
- key = "portfolio_state.json"
236
  try:
 
237
  data_json = json.dumps(state, indent=2).encode('utf-8')
238
  self.s3_client.put_object(
239
- Bucket=BUCKET_NAME, Key=key, Body=data_json, ContentType="application/json"
240
  )
241
  print(f"💾 Portfolio state saved: Current Capital ${state.get('current_capital_usd', 0):.2f}")
242
  except Exception as e:
243
  print(f"❌ Failed to save portfolio state: {e}")
244
  raise
245
 
 
 
 
246
  async def get_open_trades_async(self):
247
  try:
248
- response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key="open_trades.json")
249
  trades = json.loads(response['Body'].read())
250
  if hasattr(self, '_open_trades_warning_printed'):
251
  delattr(self, '_open_trades_warning_printed')
@@ -254,7 +294,6 @@ class R2Service:
254
  if e.response['Error']['Code'] == 'NoSuchKey':
255
  if not hasattr(self, '_open_trades_warning_printed'):
256
  print("⚠️ No open trades file found. Starting with an empty list.")
257
- print("💡 This is normal for first-time runs or when all trades are closed.")
258
  self._open_trades_warning_printed = True
259
  return []
260
  else:
@@ -264,17 +303,57 @@ class R2Service:
264
  try:
265
  data_json = json.dumps(trades, indent=2).encode('utf-8')
266
  self.s3_client.put_object(
267
- Bucket=BUCKET_NAME, Key="open_trades.json", Body=data_json, ContentType="application/json"
268
  )
269
  print(f"✅ Open trades saved to R2. Total open trades: {len(trades)}")
270
  except Exception as e:
271
  print(f"❌ Failed to save open trades: {e}")
272
  raise
273
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  async def load_contracts_db_async(self):
275
- key = "contracts.json"
276
  try:
277
- response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
278
  contracts_db = json.loads(response['Body'].read())
279
  if hasattr(self, '_contracts_warning_printed'):
280
  delattr(self, '_contracts_warning_printed')
@@ -290,17 +369,19 @@ class R2Service:
290
  raise
291
 
292
  async def save_contracts_db_async(self, data):
293
- key = "contracts.json"
294
  try:
295
  data_json = json.dumps(data, indent=2).encode('utf-8')
296
  self.s3_client.put_object(
297
- Bucket=BUCKET_NAME, Key=key, Body=data_json, ContentType="application/json"
298
  )
299
  print(f"✅ Contracts database saved to R2 successfully. Total entries: {len(data)}")
300
  except Exception as e:
301
  print(f"❌ Failed to save contracts database to R2: {e}")
302
  raise
303
 
 
 
 
304
  async def get_trade_by_symbol_async(self, symbol):
305
  try:
306
  open_trades = await self.get_open_trades_async()
@@ -341,25 +422,20 @@ class R2Service:
341
  print(f"❌ Failed to get monitored trades: {e}")
342
  return []
343
 
344
- #
345
- # 🔴 دالة جديدة: لحفظ سجل تدقيق التحليل
346
- #
347
  async def save_analysis_audit_log_async(self, audit_data):
348
  """حفظ سجل تدقيق دورة التحليل (يحتفظ بآخر 50 دورة)"""
349
  try:
350
- key = "analysis_audit_log.json"
351
-
352
  # 1. جلب السجل الحالي (إن وجد)
353
  try:
354
- response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
355
  existing_log_data = json.loads(response['Body'].read())
356
  if isinstance(existing_log_data, list):
357
  history = existing_log_data
358
  else:
359
- history = [] # بدء سجل جديد إذا كان التنسيق غير صالح
360
  except ClientError as e:
361
  if e.response['Error']['Code'] == 'NoSuchKey':
362
- history = [] # ملف جديد
363
  else:
364
  raise
365
 
@@ -373,14 +449,16 @@ class R2Service:
373
  # 4. حفظ الملف المحدث
374
  data_json = json.dumps(history, indent=2, ensure_ascii=False).encode('utf-8')
375
  self.s3_client.put_object(
376
- Bucket=BUCKET_NAME, Key=key, Body=data_json, ContentType="application/json"
377
  )
378
  print(f"📊 تم حفظ سجل تدقيق التحليل بنجاح في R2 (إجمالي {len(history)} سجلات)")
379
 
380
  except Exception as e:
381
  print(f"❌ فشل حفظ سجل تدقيق التحليل في R2: {e}")
382
 
383
- # 🔴 --- START OF CHANGE (V10.2 - Whale Learning Storage) --- 🔴
 
 
384
 
385
  async def _load_json_file_from_r2(self, key: str, default: Any = []) -> Any:
386
  """دالة مساعدة لتحميل ملف JSON بأمان."""
@@ -410,10 +488,7 @@ class R2Service:
410
  traceback.print_exc()
411
 
412
  async def save_whale_learning_record_async(self, record: Dict[str, Any]):
413
- """
414
- (جديد V10.2)
415
- يحفظ سجلاً "معلقاً" جديداً في ملف PENDING.
416
- """
417
  try:
418
  pending_records = await self._load_json_file_from_r2(WHALE_LEARNING_PENDING_KEY, default=[])
419
  pending_records.append(record)
@@ -423,10 +498,7 @@ class R2Service:
423
  print(f"❌ [R2Service] فشل في save_whale_learning_record_async: {e}")
424
 
425
  async def get_pending_whale_learning_records_async(self) -> List[Dict[str, Any]]:
426
- """
427
- (جديد V10.2)
428
- يجلب جميع السجلات "المعلقة" من ملف PENDING.
429
- """
430
  try:
431
  return await self._load_json_file_from_r2(WHALE_LEARNING_PENDING_KEY, default=[])
432
  except Exception as e:
@@ -434,11 +506,7 @@ class R2Service:
434
  return []
435
 
436
  async def update_completed_whale_learning_record_async(self, completed_record: Dict[str, Any]):
437
- """
438
- (جديد V10.2)
439
- 1. يحفظ السجل "المكتمل" في ملف COMPLETED.
440
- 2. يزيل السجل من ملف PENDING.
441
- """
442
  try:
443
  record_id = completed_record.get("record_id")
444
  if not record_id:
@@ -465,10 +533,7 @@ class R2Service:
465
  print(f"❌ [R2Service] فشل في update_completed_whale_learning_record_async: {e}")
466
 
467
  async def get_all_completed_whale_records_async(self) -> List[Dict[str, Any]]:
468
- """
469
- (جديد V10.2)
470
- يجلب *جميع* السجلات المكتملة (لتحليل الارتباط).
471
- """
472
  try:
473
  return await self._load_json_file_from_r2(WHALE_LEARNING_COMPLETED_KEY, default=[])
474
  except Exception as e:
@@ -476,21 +541,12 @@ class R2Service:
476
  return []
477
 
478
  async def save_whale_learning_config_async(self, config: Dict[str, Any]):
479
- """
480
- (جديد V10.2)
481
- يحفظ ملف الإعدادات (الأوزان) الذي نتج عن التعلم.
482
- """
483
  await self._save_json_file_to_r2(WHALE_LEARNING_CONFIG_KEY, config)
484
  print(f"✅ [R2Service] تم حفظ إعدادات تعلم الحيتان المثلى.")
485
 
486
  async def load_whale_learning_config_async(self) -> Dict[str, Any]:
487
- """
488
- (جديد V10.2)
489
- يجلب ملف الإعدادات (الأوزان) الذي نتج عن التعلم.
490
- """
491
  return await self._load_json_file_from_r2(WHALE_LEARNING_CONFIG_KEY, default={})
492
 
493
- # 🔴 --- END OF CHANGE --- 🔴
494
-
495
-
496
- print("✅ Enhanced R2 Service Loaded - Comprehensive Logging System with Candidates Support")
 
1
+ # r2.py (V11.0 - GEM-Architect: Full Stack Restoration & Accounting Fix)
2
+ import os
3
+ import traceback
4
+ import json
5
+ import time
6
  from datetime import datetime, timedelta
7
  import asyncio
8
  import boto3
9
  from botocore.exceptions import NoCredentialsError, ClientError
10
+ from typing import List, Dict, Any, Optional
11
 
12
+ # ==============================================================================
13
+ # ⚙️ التكوين والإعدادات
14
+ # ==============================================================================
15
  R2_ACCOUNT_ID = os.getenv("R2_ACCOUNT_ID")
16
  R2_ACCESS_KEY_ID = os.getenv("R2_ACCESS_KEY_ID")
17
  R2_SECRET_ACCESS_KEY = os.getenv("R2_SECRET_ACCESS_KEY")
18
  BUCKET_NAME = "trading"
 
19
 
20
+ # الرصيد الافتراضي عند إنشاء محفظة جديدة (تم تحديثه ليكون منطقياً للمحاكاة)
21
+ INITIAL_CAPITAL = 100.0
22
+
23
+ # 📁 مفاتيح الملفات في R2 (File Keys)
24
+ # 1. التعلم العميق للحيتان
25
  WHALE_LEARNING_PENDING_KEY = "learning_whale_pending_records.json"
26
  WHALE_LEARNING_COMPLETED_KEY = "learning_whale_completed_records.json"
27
  WHALE_LEARNING_CONFIG_KEY = "learning_whale_optimal_config.json"
28
 
29
+ # 2. المحاسبة والتاريخ
30
+ PORTFOLIO_STATE_KEY = "portfolio_state.json"
31
+ OPEN_TRADES_KEY = "open_trades.json"
32
+ CLOSED_TRADES_KEY = "closed_trades_history.json"
33
+
34
+ # 3. النظام والمرشحين
35
+ CANDIDATES_KEY = "Candidates.json"
36
+ CONTRACTS_DB_KEY = "contracts.json"
37
+ SYSTEM_LOGS_KEY = "system_logs.json"
38
+ LLM_PROMPTS_KEY = "llm_prompts.json"
39
+ LEARNING_DATA_KEY = "learning_data.json"
40
+ ANALYSIS_AUDIT_KEY = "analysis_audit_log.json"
41
+
42
 
43
  class R2Service:
44
  def __init__(self):
 
53
  self.lock_acquired = False
54
  self.BUCKET_NAME = BUCKET_NAME
55
 
56
+ # منع تكرار طباعة التحذيرات
57
  self._open_trades_warning_printed = False
58
  self._portfolio_warning_printed = False
59
  self._contracts_warning_printed = False
 
61
  except Exception as e:
62
  raise RuntimeError(f"Failed to initialize S3 client: {e}")
63
 
64
+ # ==============================================================================
65
+ # 🔒 إدارة القفل (Lock Mechanism)
66
+ # ==============================================================================
67
  def acquire_lock(self, max_retries=3):
68
  lock_path = "lock.txt"
69
  for attempt in range(max_retries):
 
96
  except Exception as e:
97
  print(f"❌ Failed to release lock: {e}")
98
 
99
+ # ==============================================================================
100
+ # 📊 إدارة المرشحين (Candidates)
101
+ # ==============================================================================
102
  async def save_candidates_async(self, candidates):
103
  """حفظ بيانات المرشحين العشرة في ملف منفصل في R2"""
104
  try:
 
105
  data = {
106
  "timestamp": datetime.now().isoformat(),
107
  "total_candidates": len(candidates),
 
109
  }
110
  data_json = json.dumps(data, indent=2, ensure_ascii=False).encode('utf-8')
111
  self.s3_client.put_object(
112
+ Bucket=BUCKET_NAME, Key=CANDIDATES_KEY, Body=data_json, ContentType="application/json"
113
  )
114
  print(f"✅ تم حفظ {len(candidates)} مرشح في ملف Candidates في R2")
115
 
 
127
  async def load_candidates_async(self):
128
  """تحميل بيانات المرشحين من R2"""
129
  try:
130
+ response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=CANDIDATES_KEY)
 
131
  data = json.loads(response['Body'].read())
132
  candidates = data.get('candidates', [])
133
  print(f"✅ تم تحميل {len(candidates)} مرشح من R2")
 
139
  else:
140
  raise
141
 
142
+ # ==============================================================================
143
+ # 📝 السجلات والتعلم (Logs & Debugging)
144
+ # ==============================================================================
145
  async def save_llm_prompts_async(self, symbol, prompt_type, prompt_content, analysis_data=None):
146
  """حفظ الـ Prompts المرسلة إلى النموذج الضخم"""
147
  try:
 
148
  try:
149
+ response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=LLM_PROMPTS_KEY)
150
  existing_data = json.loads(response['Body'].read())
151
  except ClientError as e:
152
  if e.response['Error']['Code'] == 'NoSuchKey':
 
157
  new_prompt = {
158
  "timestamp": datetime.now().isoformat(),
159
  "symbol": symbol,
160
+ "prompt_type": prompt_type,
161
  "prompt_content": prompt_content,
162
  "analysis_data": analysis_data
163
  }
164
 
165
  existing_data["prompts"].append(new_prompt)
166
+ # الاحتفاظ بآخر 2000 سجل فقط
167
  if len(existing_data["prompts"]) > 2000:
168
  existing_data["prompts"] = existing_data["prompts"][-2000:]
169
 
170
  data_json = json.dumps(existing_data, indent=2, ensure_ascii=False).encode('utf-8')
171
  self.s3_client.put_object(
172
+ Bucket=BUCKET_NAME, Key=LLM_PROMPTS_KEY, Body=data_json, ContentType="application/json"
173
  )
174
  print(f"✅ تم حفظ prompt لـ {symbol} ({prompt_type}) في R2")
175
  except Exception as e:
 
177
 
178
  async def save_system_logs_async(self, log_data):
179
  try:
 
180
  try:
181
+ response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=SYSTEM_LOGS_KEY)
182
  existing_logs = json.loads(response['Body'].read())
183
  except ClientError as e:
184
  if e.response['Error']['Code'] == 'NoSuchKey':
 
197
 
198
  data_json = json.dumps(existing_logs, indent=2, ensure_ascii=False).encode('utf-8')
199
  self.s3_client.put_object(
200
+ Bucket=BUCKET_NAME, Key=SYSTEM_LOGS_KEY, Body=data_json, ContentType="application/json"
201
  )
202
  print(f"✅ System log saved: {log_data.get('cycle_started', log_data.get('cycle_completed', 'event'))}")
203
  except Exception as e:
204
  print(f"❌ Failed to save system logs: {e}")
205
 
206
+ # ==============================================================================
207
+ # 🧠 بيانات التعلم العام (General Learning Data)
208
+ # ==============================================================================
209
  async def save_learning_data_async(self, learning_data):
210
  try:
 
211
  data = {
212
  "timestamp": datetime.now().isoformat(),
213
  "learning_data": learning_data
214
  }
215
  data_json = json.dumps(data, indent=2, ensure_ascii=False).encode('utf-8')
216
  self.s3_client.put_object(
217
+ Bucket=BUCKET_NAME, Key=LEARNING_DATA_KEY, Body=data_json, ContentType="application/json"
218
  )
219
  print("✅ Learning data saved to R2")
220
  except Exception as e:
 
222
 
223
  async def load_learning_data_async(self):
224
  try:
225
+ response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=LEARNING_DATA_KEY)
 
226
  data = json.loads(response['Body'].read())
227
  print("✅ Learning data loaded from R2")
228
  return data
 
233
  else:
234
  raise
235
 
236
+ # ==============================================================================
237
+ # 💰 إدارة المحفظة (Accounting & Portfolio) - [معدل للمحاسبة]
238
+ # ==============================================================================
239
  async def get_portfolio_state_async(self):
 
240
  try:
241
+ response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=PORTFOLIO_STATE_KEY)
242
  state = json.loads(response['Body'].read())
243
  if hasattr(self, '_portfolio_warning_printed'):
244
  delattr(self, '_portfolio_warning_printed')
 
249
  if not hasattr(self, '_portfolio_warning_printed'):
250
  print(f"⚠️ No portfolio state file found. Initializing with ${INITIAL_CAPITAL:.2f}")
251
  self._portfolio_warning_printed = True
252
+
253
+ # تهيئة محفظة جديدة نظيفة
254
  initial_state = {
255
  "current_capital_usd": INITIAL_CAPITAL,
256
  "invested_capital_usd": 0.0,
257
  "initial_capital_usd": INITIAL_CAPITAL,
258
  "total_trades": 0,
259
  "winning_trades": 0,
260
+ "losing_trades": 0,
261
  "total_profit_usd": 0.0,
262
+ "total_loss_usd": 0.0,
263
+ "win_rate": 0.0,
264
+ "last_update": datetime.now().isoformat()
265
  }
266
  await self.save_portfolio_state_async(initial_state)
267
  return initial_state
 
269
  raise
270
 
271
  async def save_portfolio_state_async(self, state):
 
272
  try:
273
+ state['last_update'] = datetime.now().isoformat()
274
  data_json = json.dumps(state, indent=2).encode('utf-8')
275
  self.s3_client.put_object(
276
+ Bucket=BUCKET_NAME, Key=PORTFOLIO_STATE_KEY, Body=data_json, ContentType="application/json"
277
  )
278
  print(f"💾 Portfolio state saved: Current Capital ${state.get('current_capital_usd', 0):.2f}")
279
  except Exception as e:
280
  print(f"❌ Failed to save portfolio state: {e}")
281
  raise
282
 
283
+ # ==============================================================================
284
+ # 📈 إدارة الصفقات (Open Trades & History)
285
+ # ==============================================================================
286
  async def get_open_trades_async(self):
287
  try:
288
+ response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=OPEN_TRADES_KEY)
289
  trades = json.loads(response['Body'].read())
290
  if hasattr(self, '_open_trades_warning_printed'):
291
  delattr(self, '_open_trades_warning_printed')
 
294
  if e.response['Error']['Code'] == 'NoSuchKey':
295
  if not hasattr(self, '_open_trades_warning_printed'):
296
  print("⚠️ No open trades file found. Starting with an empty list.")
 
297
  self._open_trades_warning_printed = True
298
  return []
299
  else:
 
303
  try:
304
  data_json = json.dumps(trades, indent=2).encode('utf-8')
305
  self.s3_client.put_object(
306
+ Bucket=BUCKET_NAME, Key=OPEN_TRADES_KEY, Body=data_json, ContentType="application/json"
307
  )
308
  print(f"✅ Open trades saved to R2. Total open trades: {len(trades)}")
309
  except Exception as e:
310
  print(f"❌ Failed to save open trades: {e}")
311
  raise
312
 
313
+ # 🔴 [جديد] أرشفة الصفقات المغلقة 🔴
314
+ async def append_to_closed_trades_history(self, trade_data: Dict[str, Any]):
315
+ """إضافة صفقة مغلقة إلى السجل التاريخي الدائم"""
316
+ try:
317
+ # 1. تحميل السجل القديم
318
+ try:
319
+ response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=CLOSED_TRADES_KEY)
320
+ history = json.loads(response['Body'].read())
321
+ except ClientError as e:
322
+ if e.response['Error']['Code'] == 'NoSuchKey':
323
+ history = []
324
+ else:
325
+ raise
326
+
327
+ # 2. إضافة الصفقة الجديدة
328
+ history.append(trade_data)
329
+
330
+ # 3. حفظ السجل (نحتفظ بآخر 1000 صفقة فقط)
331
+ if len(history) > 1000:
332
+ history = history[-1000:]
333
+
334
+ data_json = json.dumps(history, indent=2).encode('utf-8')
335
+ self.s3_client.put_object(
336
+ Bucket=BUCKET_NAME, Key=CLOSED_TRADES_KEY, Body=data_json, ContentType="application/json"
337
+ )
338
+ print(f"📜 [R2 History] تم أرشفة الصفقة {trade_data.get('symbol')} في السجل التاريخي.")
339
+
340
+ except Exception as e:
341
+ print(f"❌ [R2 Error] فشل حفظ الصفقة في السجل التاريخي: {e}")
342
+
343
+ async def get_closed_trades_history(self):
344
+ """جلب سجل الصفقات التاريخية"""
345
+ try:
346
+ response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=CLOSED_TRADES_KEY)
347
+ return json.loads(response['Body'].read())
348
+ except ClientError:
349
+ return []
350
+
351
+ # ==============================================================================
352
+ # 📜 قاعدة بيانات العقود (Contracts DB)
353
+ # ==============================================================================
354
  async def load_contracts_db_async(self):
 
355
  try:
356
+ response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=CONTRACTS_DB_KEY)
357
  contracts_db = json.loads(response['Body'].read())
358
  if hasattr(self, '_contracts_warning_printed'):
359
  delattr(self, '_contracts_warning_printed')
 
369
  raise
370
 
371
  async def save_contracts_db_async(self, data):
 
372
  try:
373
  data_json = json.dumps(data, indent=2).encode('utf-8')
374
  self.s3_client.put_object(
375
+ Bucket=BUCKET_NAME, Key=CONTRACTS_DB_KEY, Body=data_json, ContentType="application/json"
376
  )
377
  print(f"✅ Contracts database saved to R2 successfully. Total entries: {len(data)}")
378
  except Exception as e:
379
  print(f"❌ Failed to save contracts database to R2: {e}")
380
  raise
381
 
382
+ # ==============================================================================
383
+ # 🛠️ أدوات مساعدة (Utilities)
384
+ # ==============================================================================
385
  async def get_trade_by_symbol_async(self, symbol):
386
  try:
387
  open_trades = await self.get_open_trades_async()
 
422
  print(f"❌ Failed to get monitored trades: {e}")
423
  return []
424
 
 
 
 
425
  async def save_analysis_audit_log_async(self, audit_data):
426
  """حفظ سجل تدقيق دورة التحليل (يحتفظ بآخر 50 دورة)"""
427
  try:
 
 
428
  # 1. جلب السجل الحالي (إن وجد)
429
  try:
430
+ response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=ANALYSIS_AUDIT_KEY)
431
  existing_log_data = json.loads(response['Body'].read())
432
  if isinstance(existing_log_data, list):
433
  history = existing_log_data
434
  else:
435
+ history = []
436
  except ClientError as e:
437
  if e.response['Error']['Code'] == 'NoSuchKey':
438
+ history = []
439
  else:
440
  raise
441
 
 
449
  # 4. حفظ الملف المحدث
450
  data_json = json.dumps(history, indent=2, ensure_ascii=False).encode('utf-8')
451
  self.s3_client.put_object(
452
+ Bucket=BUCKET_NAME, Key=ANALYSIS_AUDIT_KEY, Body=data_json, ContentType="application/json"
453
  )
454
  print(f"📊 تم حفظ سجل تدقيق التحليل بنجاح في R2 (إجمالي {len(history)} سجلات)")
455
 
456
  except Exception as e:
457
  print(f"❌ فشل حفظ سجل تدقيق التحليل في R2: {e}")
458
 
459
+ # ==============================================================================
460
+ # 🐋 تعلم الحيتان (Whale Learning) - V10.2
461
+ # ==============================================================================
462
 
463
  async def _load_json_file_from_r2(self, key: str, default: Any = []) -> Any:
464
  """دالة مساعدة لتحميل ملف JSON بأمان."""
 
488
  traceback.print_exc()
489
 
490
  async def save_whale_learning_record_async(self, record: Dict[str, Any]):
491
+ """يحفظ سجلاً 'معلقاً' جديداً في ملف PENDING."""
 
 
 
492
  try:
493
  pending_records = await self._load_json_file_from_r2(WHALE_LEARNING_PENDING_KEY, default=[])
494
  pending_records.append(record)
 
498
  print(f"❌ [R2Service] فشل في save_whale_learning_record_async: {e}")
499
 
500
  async def get_pending_whale_learning_records_async(self) -> List[Dict[str, Any]]:
501
+ """يجلب جميع السجلات 'المعل��ة' من ملف PENDING."""
 
 
 
502
  try:
503
  return await self._load_json_file_from_r2(WHALE_LEARNING_PENDING_KEY, default=[])
504
  except Exception as e:
 
506
  return []
507
 
508
  async def update_completed_whale_learning_record_async(self, completed_record: Dict[str, Any]):
509
+ """يحفظ السجل 'المكتمل' في ملف COMPLETED ويزيله من PENDING."""
 
 
 
 
510
  try:
511
  record_id = completed_record.get("record_id")
512
  if not record_id:
 
533
  print(f"❌ [R2Service] فشل في update_completed_whale_learning_record_async: {e}")
534
 
535
  async def get_all_completed_whale_records_async(self) -> List[Dict[str, Any]]:
536
+ """يجلب جميع السجلات المكتملة."""
 
 
 
537
  try:
538
  return await self._load_json_file_from_r2(WHALE_LEARNING_COMPLETED_KEY, default=[])
539
  except Exception as e:
 
541
  return []
542
 
543
  async def save_whale_learning_config_async(self, config: Dict[str, Any]):
544
+ """يحفظ ملف الإعدادات (الأوزان) الذي نتج عن التعلم."""
 
 
 
545
  await self._save_json_file_to_r2(WHALE_LEARNING_CONFIG_KEY, config)
546
  print(f"✅ [R2Service] تم حفظ إعدادات تعلم الحيتان المثلى.")
547
 
548
  async def load_whale_learning_config_async(self) -> Dict[str, Any]:
549
+ """يجلب ملف الإعدادات (الأوزان)."""
 
 
 
550
  return await self._load_json_file_from_r2(WHALE_LEARNING_CONFIG_KEY, default={})
551
 
552
+ print("✅ Full R2 Service Loaded - Accounting & History Modules Ready")