heisbuba commited on
Commit
c62c5c1
·
verified ·
1 Parent(s): dcca137

Update src/services/journal_engine.py

Browse files
Files changed (1) hide show
  1. src/services/journal_engine.py +86 -34
src/services/journal_engine.py CHANGED
@@ -1,9 +1,10 @@
 
1
  import json
2
  import io
3
  import os
4
  import re
 
5
  from googleapiclient.discovery import build
6
- # [FIX] Import MediaIoBaseUpload instead of the non-existent MediaByteArrayUpload
7
  from googleapiclient.http import MediaIoBaseUpload, MediaIoBaseDownload
8
  from google.oauth2.credentials import Credentials
9
  from ..config import get_user_keys, update_user_keys
@@ -20,23 +21,29 @@ class JournalEngine:
20
 
21
  @staticmethod
22
  def get_drive_service(creds):
 
23
  return build('drive', 'v3', credentials=creds)
24
 
25
  @staticmethod
26
  def load_journal(service, file_id):
27
  """Downloads and parses the journal.json from Drive."""
28
- request = service.files().get_media(fileId=file_id)
29
- fh = io.BytesIO()
30
- downloader = MediaIoBaseDownload(fh, request)
31
- done = False
32
- while not done:
33
- status, done = downloader.next_chunk()
34
- return json.loads(fh.getvalue().decode('utf-8'))
 
 
 
 
 
 
35
 
36
  @staticmethod
37
  def save_to_drive(service, file_id, journal_data):
38
  """Uploads the updated journal list back to the hidden AppData folder."""
39
- # [FIX] Use MediaIoBaseUpload with a BytesIO stream
40
  media = MediaIoBaseUpload(
41
  io.BytesIO(json.dumps(journal_data).encode('utf-8')),
42
  mimetype='application/json',
@@ -47,34 +54,39 @@ class JournalEngine:
47
  @staticmethod
48
  def initialize_journal(service):
49
  """Finds or creates 'journal.json' in the appDataFolder."""
50
- response = service.files().list(
51
- spaces='appDataFolder',
52
- fields='files(id, name)',
53
- pageSize=1
54
- ).execute()
55
-
56
- files = response.get('files', [])
57
- if files:
58
- return files[0]['id']
59
-
60
- # Create it if it doesn't exist
61
- file_metadata = {
62
- 'name': 'journal.json',
63
- 'parents': ['appDataFolder']
64
- }
65
- # [FIX] Use MediaIoBaseUpload here too
66
- media = MediaIoBaseUpload(
67
- io.BytesIO(json.dumps([]).encode('utf-8')),
68
- mimetype='application/json',
69
- resumable=True
70
- )
71
- file = service.files().create(body=file_metadata, media_body=media, fields='id').execute()
72
- return file.get('id')
 
 
 
 
73
 
74
  @staticmethod
75
  def parse_pnl(pnl_str):
76
  """Helper to extract a numeric value from strings like '+$500' or '-10%'."""
77
  try:
 
78
  clean = re.sub(r'[^\d\.-]', '', str(pnl_str))
79
  return float(clean) if clean else 0.0
80
  except ValueError:
@@ -90,13 +102,53 @@ class JournalEngine:
90
  total = len(journal_data)
91
 
92
  winrate = (len(wins) / total) * 100 if total > 0 else 0
 
 
93
  best_trade = max(journal_data, key=lambda x: cls.parse_pnl(x.get('pnl', 0)), default={})
94
 
95
- biases = [t.get('bias', 'Neutral') for t in journal_data if t.get('bias')]
 
96
  main_bias = max(set(biases), key=biases.count) if biases else "Neutral"
97
 
98
  return {
99
  "winrate": f"{winrate:.0f}%",
100
  "best_trade": best_trade.get('ticker', '--'),
101
  "bias": main_bias
102
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
  import json
3
  import io
4
  import os
5
  import re
6
+ import uuid
7
  from googleapiclient.discovery import build
 
8
  from googleapiclient.http import MediaIoBaseUpload, MediaIoBaseDownload
9
  from google.oauth2.credentials import Credentials
10
  from ..config import get_user_keys, update_user_keys
 
21
 
22
  @staticmethod
23
  def get_drive_service(creds):
24
+ """Builds the Drive API client."""
25
  return build('drive', 'v3', credentials=creds)
26
 
27
  @staticmethod
28
  def load_journal(service, file_id):
29
  """Downloads and parses the journal.json from Drive."""
30
+ try:
31
+ request = service.files().get_media(fileId=file_id)
32
+ fh = io.BytesIO()
33
+ downloader = MediaIoBaseDownload(fh, request)
34
+ done = False
35
+ while not done:
36
+ status, done = downloader.next_chunk()
37
+
38
+ content = fh.getvalue().decode('utf-8')
39
+ return json.loads(content) if content else []
40
+ except Exception as e:
41
+ print(f"⚠️ Journal Load Error: {e}")
42
+ return []
43
 
44
  @staticmethod
45
  def save_to_drive(service, file_id, journal_data):
46
  """Uploads the updated journal list back to the hidden AppData folder."""
 
47
  media = MediaIoBaseUpload(
48
  io.BytesIO(json.dumps(journal_data).encode('utf-8')),
49
  mimetype='application/json',
 
54
  @staticmethod
55
  def initialize_journal(service):
56
  """Finds or creates 'journal.json' in the appDataFolder."""
57
+ try:
58
+ response = service.files().list(
59
+ q="name='journal.json' and 'appDataFolder' in parents",
60
+ spaces='appDataFolder',
61
+ fields='files(id, name)',
62
+ pageSize=1
63
+ ).execute()
64
+
65
+ files = response.get('files', [])
66
+ if files:
67
+ return files[0]['id']
68
+
69
+ # Create it if it doesn't exist
70
+ file_metadata = {
71
+ 'name': 'journal.json',
72
+ 'parents': ['appDataFolder']
73
+ }
74
+ media = MediaIoBaseUpload(
75
+ io.BytesIO(json.dumps([]).encode('utf-8')),
76
+ mimetype='application/json',
77
+ resumable=True
78
+ )
79
+ file = service.files().create(body=file_metadata, media_body=media, fields='id').execute()
80
+ return file.get('id')
81
+ except Exception as e:
82
+ print(f"⚠️ Journal Init Error: {e}")
83
+ return None
84
 
85
  @staticmethod
86
  def parse_pnl(pnl_str):
87
  """Helper to extract a numeric value from strings like '+$500' or '-10%'."""
88
  try:
89
+ # Remove currency symbols ($) and percentages (%), keep minus signs and decimals
90
  clean = re.sub(r'[^\d\.-]', '', str(pnl_str))
91
  return float(clean) if clean else 0.0
92
  except ValueError:
 
102
  total = len(journal_data)
103
 
104
  winrate = (len(wins) / total) * 100 if total > 0 else 0
105
+
106
+ # Find best trade based on numeric PnL
107
  best_trade = max(journal_data, key=lambda x: cls.parse_pnl(x.get('pnl', 0)), default={})
108
 
109
+ # Determine dominant bias
110
+ biases = [t.get('bias') for t in journal_data if t.get('bias')]
111
  main_bias = max(set(biases), key=biases.count) if biases else "Neutral"
112
 
113
  return {
114
  "winrate": f"{winrate:.0f}%",
115
  "best_trade": best_trade.get('ticker', '--'),
116
  "bias": main_bias
117
+ }
118
+
119
+ @classmethod
120
+ def save_trade(cls, service, file_id, trade_data):
121
+ """
122
+ Handles the Upsert (Update or Insert) logic.
123
+ - If 'id' exists in trade_data -> Update existing entry.
124
+ - If 'id' is missing/empty -> Generate UUID and Append.
125
+ """
126
+ # 1. Load current history
127
+ journal = cls.load_journal(service, file_id)
128
+
129
+ # 2. Check for ID
130
+ trade_id = trade_data.get('id')
131
+
132
+ if trade_id:
133
+ # --- UPDATE MODE ---
134
+ updated = False
135
+ for i, existing_trade in enumerate(journal):
136
+ if existing_trade.get('id') == trade_id:
137
+ # Preserve any fields not in the form if necessary,
138
+ # but here we overwrite with the new form state.
139
+ journal[i] = trade_data
140
+ updated = True
141
+ break
142
+
143
+ # Safety fallback: If ID provided but not found, treat as new
144
+ if not updated:
145
+ trade_data['id'] = str(uuid.uuid4())
146
+ journal.append(trade_data)
147
+ else:
148
+ # --- CREATE MODE ---
149
+ trade_data['id'] = str(uuid.uuid4())
150
+ journal.append(trade_data)
151
+
152
+ # 3. Save back to Drive
153
+ cls.save_to_drive(service, file_id, journal)
154
+ return True