Riy777 commited on
Commit
48d234e
·
1 Parent(s): b6add51

Update r2.py

Browse files
Files changed (1) hide show
  1. r2.py +292 -122
r2.py CHANGED
@@ -1,29 +1,22 @@
1
- # r2.py (V12.4 - Full with Simulation Support)
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
  R2_ACCOUNT_ID = os.getenv("R2_ACCOUNT_ID")
14
  R2_ACCESS_KEY_ID = os.getenv("R2_ACCESS_KEY_ID")
15
  R2_SECRET_ACCESS_KEY = os.getenv("R2_SECRET_ACCESS_KEY")
16
  BUCKET_NAME = "trading"
17
  INITIAL_CAPITAL = 10.0
18
 
19
- # --- مفاتيح الملفات في R2 ---
20
  WHALE_LEARNING_PENDING_KEY = "learning_whale_pending_records.json"
21
  WHALE_LEARNING_COMPLETED_KEY = "learning_whale_completed_records.json"
22
  WHALE_LEARNING_CONFIG_KEY = "learning_whale_optimal_config.json"
23
 
24
- # 🔴 مفاتيح جديدة للمحاكاة
25
- SIM_TRADES_KEY = "simulation_trades.json"
26
- SIM_PORTFOLIO_KEY = "simulation_portfolio.json"
27
 
28
  class R2Service:
29
  def __init__(self):
@@ -38,7 +31,6 @@ class R2Service:
38
  self.lock_acquired = False
39
  self.BUCKET_NAME = BUCKET_NAME
40
 
41
- # أعلام لتجنب تكرار التحذيرات في السجلات
42
  self._open_trades_warning_printed = False
43
  self._portfolio_warning_printed = False
44
  self._contracts_warning_printed = False
@@ -46,9 +38,6 @@ class R2Service:
46
  except Exception as e:
47
  raise RuntimeError(f"Failed to initialize S3 client: {e}")
48
 
49
- # ==================================================================
50
- # 🔒 إدارة القفل (Concurrency Lock)
51
- # ==================================================================
52
  def acquire_lock(self, max_retries=3):
53
  lock_path = "lock.txt"
54
  for attempt in range(max_retries):
@@ -61,7 +50,7 @@ class R2Service:
61
  if e.response['Error']['Code'] == '404':
62
  self.s3_client.put_object(Bucket=BUCKET_NAME, Key=lock_path, Body=b'')
63
  self.lock_acquired = True
64
- # print("✅ Lock acquired.")
65
  return True
66
  else:
67
  raise
@@ -76,54 +65,13 @@ class R2Service:
76
  if self.lock_acquired:
77
  try:
78
  self.s3_client.delete_object(Bucket=BUCKET_NAME, Key=lock_path)
79
- # print("✅ Lock released.")
80
  self.lock_acquired = False
81
  except Exception as e:
82
  print(f"❌ Failed to release lock: {e}")
83
 
84
- # ==================================================================
85
- # 🔴 دعم المحاكاة (Simulation Support) - جديد V12.4
86
- # ==================================================================
87
- async def save_simulation_results(self, trade_history: List[Dict], portfolio_metrics: Dict):
88
- """
89
- حفظ نتائج المحاكاة في ملفات منفصلة في R2 لتجنب تلويث البيانات الحقيقية.
90
- """
91
- try:
92
- # 1. حفظ سجل صفقات المحاكاة
93
- trades_data = json.dumps(trade_history, indent=2).encode('utf-8')
94
- self.s3_client.put_object(
95
- Bucket=self.BUCKET_NAME, Key=SIM_TRADES_KEY, Body=trades_data, ContentType="application/json"
96
- )
97
-
98
- # 2. حفظ مقاييس المحفظة النهائية للمحاكاة
99
- metrics_data = json.dumps(portfolio_metrics, indent=2).encode('utf-8')
100
- self.s3_client.put_object(
101
- Bucket=self.BUCKET_NAME, Key=SIM_PORTFOLIO_KEY, Body=metrics_data, ContentType="application/json"
102
- )
103
-
104
- print(f"✅ [R2] Simulation results saved successfully ({len(trade_history)} trades).")
105
- except Exception as e:
106
- print(f"❌ [R2] Failed to save simulation results: {e}")
107
- traceback.print_exc()
108
-
109
- async def load_simulation_results(self):
110
- """جلب نتائج آخر محاكاة تم إجراؤها"""
111
- try:
112
- t_resp = self.s3_client.get_object(Bucket=self.BUCKET_NAME, Key=SIM_TRADES_KEY)
113
- p_resp = self.s3_client.get_object(Bucket=self.BUCKET_NAME, Key=SIM_PORTFOLIO_KEY)
114
-
115
- trades = json.loads(t_resp['Body'].read())
116
- portfolio = json.loads(p_resp['Body'].read())
117
-
118
- return trades, portfolio
119
- except ClientError:
120
- print("ℹ️ [R2] No previous simulation results found.")
121
- return [], {}
122
-
123
- # ==================================================================
124
- # 💾 الوظائف الأساسية (Core Storage Functions)
125
- # ==================================================================
126
  async def save_candidates_async(self, candidates):
 
127
  try:
128
  key = "Candidates.json"
129
  data = {
@@ -135,22 +83,37 @@ class R2Service:
135
  self.s3_client.put_object(
136
  Bucket=BUCKET_NAME, Key=key, Body=data_json, ContentType="application/json"
137
  )
138
- print(f"✅ Candidates saved to R2: {len(candidates)}")
 
 
 
 
 
 
 
 
 
139
  except Exception as e:
140
- print(f"❌ Failed to save Candidates: {e}")
141
 
142
  async def load_candidates_async(self):
 
143
  try:
144
  key = "Candidates.json"
145
  response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
146
  data = json.loads(response['Body'].read())
147
- return data.get('candidates', [])
 
 
148
  except ClientError as e:
149
  if e.response['Error']['Code'] == 'NoSuchKey':
 
150
  return []
151
- raise
 
152
 
153
  async def save_llm_prompts_async(self, symbol, prompt_type, prompt_content, analysis_data=None):
 
154
  try:
155
  key = "llm_prompts.json"
156
  try:
@@ -159,54 +122,123 @@ class R2Service:
159
  except ClientError as e:
160
  if e.response['Error']['Code'] == 'NoSuchKey':
161
  existing_data = {"prompts": []}
162
- else: raise
 
163
 
164
  new_prompt = {
165
  "timestamp": datetime.now().isoformat(),
166
  "symbol": symbol,
167
- "prompt_type": prompt_type,
168
  "prompt_content": prompt_content,
169
  "analysis_data": analysis_data
170
  }
 
171
  existing_data["prompts"].append(new_prompt)
172
- if len(existing_data["prompts"]) > 500: # الاحتفاظ بآخر 500 فقط
173
- existing_data["prompts"] = existing_data["prompts"][-500:]
174
 
 
175
  self.s3_client.put_object(
176
- Bucket=BUCKET_NAME, Key=key, Body=json.dumps(existing_data, indent=2).encode('utf-8'), ContentType="application/json"
177
  )
 
178
  except Exception as e:
179
- print(f"❌ Failed to save LLM prompt for {symbol}: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
  async def get_portfolio_state_async(self):
182
  key = "portfolio_state.json"
183
  try:
184
  response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
185
  state = json.loads(response['Body'].read())
186
- if hasattr(self, '_portfolio_warning_printed'): delattr(self, '_portfolio_warning_printed')
 
 
187
  return state
188
  except ClientError as e:
189
  if e.response['Error']['Code'] == 'NoSuchKey':
190
  if not hasattr(self, '_portfolio_warning_printed'):
191
- print(f"⚠️ Initializing new portfolio with ${INITIAL_CAPITAL:.2f}")
192
  self._portfolio_warning_printed = True
193
  initial_state = {
194
  "current_capital_usd": INITIAL_CAPITAL,
195
  "invested_capital_usd": 0.0,
196
  "initial_capital_usd": INITIAL_CAPITAL,
197
- "total_trades": 0, "winning_trades": 0,
198
- "total_profit_usd": 0.0, "total_loss_usd": 0.0
 
 
199
  }
200
  await self.save_portfolio_state_async(initial_state)
201
  return initial_state
202
- else: raise
 
203
 
204
  async def save_portfolio_state_async(self, state):
 
205
  try:
 
206
  self.s3_client.put_object(
207
- Bucket=BUCKET_NAME, Key="portfolio_state.json",
208
- Body=json.dumps(state, indent=2).encode('utf-8'), ContentType="application/json"
209
  )
 
210
  except Exception as e:
211
  print(f"❌ Failed to save portfolio state: {e}")
212
  raise
@@ -214,113 +246,251 @@ class R2Service:
214
  async def get_open_trades_async(self):
215
  try:
216
  response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key="open_trades.json")
217
- return json.loads(response['Body'].read())
 
 
 
218
  except ClientError as e:
219
- if e.response['Error']['Code'] == 'NoSuchKey': return []
220
- else: raise
 
 
 
 
 
 
221
 
222
  async def save_open_trades_async(self, trades):
223
  try:
 
224
  self.s3_client.put_object(
225
- Bucket=BUCKET_NAME, Key="open_trades.json",
226
- Body=json.dumps(trades, indent=2).encode('utf-8'), ContentType="application/json"
227
  )
 
228
  except Exception as e:
229
  print(f"❌ Failed to save open trades: {e}")
230
  raise
231
 
232
  async def load_contracts_db_async(self):
 
233
  try:
234
- response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key="contracts.json")
235
- return json.loads(response['Body'].read())
 
 
 
 
236
  except ClientError as e:
237
- if e.response['Error']['Code'] == 'NoSuchKey': return {}
238
- else: raise
 
 
 
 
 
239
 
240
  async def save_contracts_db_async(self, data):
 
241
  try:
 
242
  self.s3_client.put_object(
243
- Bucket=BUCKET_NAME, Key="contracts.json",
244
- Body=json.dumps(data, indent=2).encode('utf-8'), ContentType="application/json"
245
  )
 
246
  except Exception as e:
247
- print(f"❌ Failed to save contracts db: {e}")
248
  raise
249
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  async def save_analysis_audit_log_async(self, audit_data):
 
251
  try:
252
  key = "analysis_audit_log.json"
 
 
253
  try:
254
  response = self.s3_client.get_object(Bucket=BUCKET_NAME, Key=key)
255
- history = json.loads(response['Body'].read())
256
- if not isinstance(history, list): history = []
 
 
 
257
  except ClientError as e:
258
- if e.response['Error']['Code'] == 'NoSuchKey': history = []
259
- else: raise
 
 
260
 
 
261
  history.append(audit_data)
262
- if len(history) > 50: history = history[-50:]
263
 
 
 
 
 
 
 
264
  self.s3_client.put_object(
265
- Bucket=BUCKET_NAME, Key=key,
266
- Body=json.dumps(history, indent=2).encode('utf-8'), ContentType="application/json"
267
  )
 
 
268
  except Exception as e:
269
- print(f"❌ Failed to save analysis audit log: {e}")
270
 
271
- # ==================================================================
272
- # 🐋 تعلم الحيتان (Whale Learning)
273
- # ==================================================================
274
  async def _load_json_file_from_r2(self, key: str, default: Any = []) -> Any:
 
275
  try:
276
  response = self.s3_client.get_object(Bucket=self.BUCKET_NAME, Key=key)
277
  return json.loads(response['Body'].read())
278
  except ClientError as e:
279
- if e.response['Error']['Code'] == 'NoSuchKey': return default
280
- else: raise
281
- except Exception: return default
 
 
 
 
 
 
282
 
283
  async def _save_json_file_to_r2(self, key: str, data: Any):
 
284
  try:
 
285
  self.s3_client.put_object(
286
- Bucket=self.BUCKET_NAME, Key=key,
287
- Body=json.dumps(data, indent=2).encode('utf-8'), ContentType="application/json"
288
  )
289
  except Exception as e:
290
- print(f"❌ Failed to save {key}: {e}")
 
291
 
292
  async def save_whale_learning_record_async(self, record: Dict[str, Any]):
 
 
 
 
293
  try:
294
- pending = await self._load_json_file_from_r2(WHALE_LEARNING_PENDING_KEY, default=[])
295
- pending.append(record)
296
- await self._save_json_file_to_r2(WHALE_LEARNING_PENDING_KEY, pending)
297
- except Exception as e: print(f" Whale record save failed: {e}")
 
 
298
 
299
  async def get_pending_whale_learning_records_async(self) -> List[Dict[str, Any]]:
300
- return await self._load_json_file_from_r2(WHALE_LEARNING_PENDING_KEY, default=[])
 
 
 
 
 
 
 
 
301
 
302
  async def update_completed_whale_learning_record_async(self, completed_record: Dict[str, Any]):
 
 
 
 
 
303
  try:
304
- rec_id = completed_record.get("record_id")
305
- if not rec_id: return
 
 
306
 
307
- completed = await self._load_json_file_from_r2(WHALE_LEARNING_COMPLETED_KEY, default=[])
308
- completed.append(completed_record)
309
- if len(completed) > 5000: completed = completed[-5000:]
310
- await self._save_json_file_to_r2(WHALE_LEARNING_COMPLETED_KEY, completed)
 
 
311
 
312
- pending = await self._load_json_file_from_r2(WHALE_LEARNING_PENDING_KEY, default=[])
313
- updated_pending = [r for r in pending if r.get("record_id") != rec_id]
314
- await self._save_json_file_to_r2(WHALE_LEARNING_PENDING_KEY, updated_pending)
315
- except Exception as e: print(f" Whale record update failed: {e}")
 
 
 
 
 
 
 
316
 
317
  async def get_all_completed_whale_records_async(self) -> List[Dict[str, Any]]:
318
- return await self._load_json_file_from_r2(WHALE_LEARNING_COMPLETED_KEY, default=[])
 
 
 
 
 
 
 
 
319
 
320
  async def save_whale_learning_config_async(self, config: Dict[str, Any]):
 
 
 
 
321
  await self._save_json_file_to_r2(WHALE_LEARNING_CONFIG_KEY, config)
 
322
 
323
  async def load_whale_learning_config_async(self) -> Dict[str, Any]:
 
 
 
 
324
  return await self._load_json_file_from_r2(WHALE_LEARNING_CONFIG_KEY, default={})
325
 
326
- print("✅ R2 Service V12.4 Loaded (Simulation Support Ready)")
 
 
 
 
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
  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
  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):
 
50
  if e.response['Error']['Code'] == '404':
51
  self.s3_client.put_object(Bucket=BUCKET_NAME, Key=lock_path, Body=b'')
52
  self.lock_acquired = True
53
+ print("✅ Lock acquired.")
54
  return True
55
  else:
56
  raise
 
65
  if self.lock_acquired:
66
  try:
67
  self.s3_client.delete_object(Bucket=BUCKET_NAME, Key=lock_path)
68
+ print("✅ Lock released.")
69
  self.lock_acquired = False
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 = {
 
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
+
88
+ # عرض معلومات المرشحين المحفوظين
89
+ print("📊 المرشحون المحفوظون:")
90
+ for i, candidate in enumerate(candidates):
91
+ symbol = candidate.get('symbol', 'Unknown')
92
+ score = candidate.get('enhanced_final_score', 0)
93
+ strategy = candidate.get('target_strategy', 'GENERIC')
94
+ print(f" {i+1}. {symbol}: {score:.3f} - {strategy}")
95
+
96
  except Exception as e:
97
+ print(f"❌ فشل حفظ المرشحين في R2: {e}")
98
 
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")
107
+ return candidates
108
  except ClientError as e:
109
  if e.response['Error']['Code'] == 'NoSuchKey':
110
+ print("⚠️ لا يوجد ملف مرشحين سابق")
111
  return []
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:
 
122
  except ClientError as e:
123
  if e.response['Error']['Code'] == 'NoSuchKey':
124
  existing_data = {"prompts": []}
125
+ else:
126
+ raise
127
 
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:
146
+ print(f"❌ فشل حفظ prompt لـ {symbol}: {e}")
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':
156
+ existing_logs = {"logs": []}
157
+ else:
158
+ raise
159
+
160
+ log_entry = {
161
+ "timestamp": datetime.now().isoformat(),
162
+ **log_data
163
+ }
164
+
165
+ existing_logs["logs"].append(log_entry)
166
+ if len(existing_logs["logs"]) > 2000:
167
+ existing_logs["logs"] = existing_logs["logs"][-2000:]
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:
190
+ print(f"❌ Failed to save learning data: {e}")
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
199
+ except ClientError as e:
200
+ if e.response['Error']['Code'] == 'NoSuchKey':
201
+ print("⚠️ No learning data found. Starting fresh.")
202
+ return {}
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')
213
+ print(f"💰 Portfolio state loaded: Current Capital ${state.get('current_capital_usd', 0):.2f}")
214
  return state
215
  except ClientError as e:
216
  if e.response['Error']['Code'] == 'NoSuchKey':
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
231
+ else:
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
 
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')
252
+ return trades
253
  except ClientError as e:
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:
261
+ raise
262
 
263
  async def save_open_trades_async(self, trades):
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')
281
+ print(f"💾 Contracts database loaded from R2. Total entries: {len(contracts_db)}")
282
+ return contracts_db
283
  except ClientError as e:
284
+ if e.response['Error']['Code'] == 'NoSuchKey':
285
+ if not hasattr(self, '_contracts_warning_printed'):
286
+ print("⚠️ No existing contracts database found. Initializing new one.")
287
+ self._contracts_warning_printed = True
288
+ return {}
289
+ else:
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()
307
+ for trade in open_trades:
308
+ if trade['symbol'] == symbol and trade['status'] == 'OPEN':
309
+ return trade
310
+ return None
311
+ except Exception as e:
312
+ print(f"❌ Failed to get trade by symbol {symbol}: {e}")
313
+ return None
314
+
315
+ async def update_trade_monitoring_status_async(self, symbol, is_monitored):
316
+ try:
317
+ open_trades = await self.get_open_trades_async()
318
+ updated = False
319
+ for trade in open_trades:
320
+ if trade['symbol'] == symbol:
321
+ trade['is_monitored'] = is_monitored
322
+ updated = True
323
+ break
324
+ if updated:
325
+ await self.save_open_trades_async(open_trades)
326
+ status = "ENABLED" if is_monitored else "DISABLED"
327
+ print(f"✅ Real-time monitoring {status} for {symbol}")
328
+ else:
329
+ print(f"⚠️ Trade {symbol} not found for monitoring status update")
330
+ return updated
331
+ except Exception as e:
332
+ print(f"❌ Failed to update monitoring status for {symbol}: {e}")
333
+ return False
334
+
335
+ async def get_monitored_trades_async(self):
336
+ try:
337
+ open_trades = await self.get_open_trades_async()
338
+ monitored_trades = [trade for trade in open_trades if trade.get('is_monitored', False)]
339
+ return monitored_trades
340
+ except Exception as e:
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
 
366
+ # 2. إضافة الدورة الحالية
367
  history.append(audit_data)
 
368
 
369
+ # 3. الحفاظ على آخر 50 سجل فقط
370
+ if len(history) > 50:
371
+ history = history[-50:]
372
+
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 بأمان."""
387
  try:
388
  response = self.s3_client.get_object(Bucket=self.BUCKET_NAME, Key=key)
389
  return json.loads(response['Body'].read())
390
  except ClientError as e:
391
+ if e.response['Error']['Code'] == 'NoSuchKey':
392
+ print(f"ℹ️ [R2Service] لم يتم العثور على ملف '{key}'. سيتم استخدام القيمة الافتراضية.")
393
+ return default
394
+ else:
395
+ print(f"❌ [R2Service] خطأ ClientError أثناء تحميل '{key}': {e}")
396
+ raise
397
+ except Exception as e:
398
+ print(f"❌ [R2Service] خطأ عام أثناء تحميل '{key}': {e}")
399
+ return default
400
 
401
  async def _save_json_file_to_r2(self, key: str, data: Any):
402
+ """دالة مساعدة لحفظ ملف JSON بأمان."""
403
  try:
404
+ data_json = json.dumps(data, indent=2, ensure_ascii=False).encode('utf-8')
405
  self.s3_client.put_object(
406
+ Bucket=self.BUCKET_NAME, Key=key, Body=data_json, ContentType="application/json"
 
407
  )
408
  except Exception as e:
409
+ print(f"❌ [R2Service] فشل حفظ الملف '{key}' إلى R2: {e}")
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)
420
+ await self._save_json_file_to_r2(WHALE_LEARNING_PENDING_KEY, pending_records)
421
+ print(f" [R2Service] تم حفظ سجل تعلم الحيتان (PENDING) لـ {record['symbol']}.")
422
+ except Exception as e:
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:
433
+ print(f"❌ [R2Service] فشل في get_pending_whale_learning_records_async: {e}")
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:
445
+ print("❌ [R2Service] لا يمكن تحديث سجل مكتمل بدون record_id.")
446
+ return
447
 
448
+ # 1. الحفظ في ملف المكتمل (يحتفظ بآخر 5000 سجل مكتمل)
449
+ completed_records = await self._load_json_file_from_r2(WHALE_LEARNING_COMPLETED_KEY, default=[])
450
+ completed_records.append(completed_record)
451
+ if len(completed_records) > 5000:
452
+ completed_records = completed_records[-5000:]
453
+ await self._save_json_file_to_r2(WHALE_LEARNING_COMPLETED_KEY, completed_records)
454
 
455
+ # 2. الإزالة من ملف المعلق
456
+ pending_records = await self._load_json_file_from_r2(WHALE_LEARNING_PENDING_KEY, default=[])
457
+ updated_pending_records = [
458
+ rec for rec in pending_records if rec.get("record_id") != record_id
459
+ ]
460
+ await self._save_json_file_to_r2(WHALE_LEARNING_PENDING_KEY, updated_pending_records)
461
+
462
+ print(f"✅ [R2Service] تم نقل سجل تعلم الحيتان (COMPLETED) لـ {completed_record['symbol']} (ID: {record_id}).")
463
+
464
+ except Exception as e:
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:
475
+ print(f"❌ [R2Service] فشل في get_all_completed_whale_records_async: {e}")
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")