bitunix-automated-crypto-trading 2.6.7__py3-none-any.whl → 2.6.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. bitunix_automated_crypto_trading/AsyncThreadRunner.py +81 -81
  2. bitunix_automated_crypto_trading/BitunixApi.py +278 -278
  3. bitunix_automated_crypto_trading/BitunixSignal.py +1099 -1099
  4. bitunix_automated_crypto_trading/BitunixWebSocket.py +254 -254
  5. bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py +74 -74
  6. bitunix_automated_crypto_trading/NotificationManager.py +23 -23
  7. bitunix_automated_crypto_trading/ThreadManager.py +68 -68
  8. bitunix_automated_crypto_trading/TickerManager.py +635 -635
  9. bitunix_automated_crypto_trading/bitunix.py +597 -594
  10. bitunix_automated_crypto_trading/config.py +90 -90
  11. bitunix_automated_crypto_trading/logger.py +84 -84
  12. {bitunix_automated_crypto_trading-2.6.7.dist-info → bitunix_automated_crypto_trading-2.6.8.dist-info}/METADATA +36 -36
  13. bitunix_automated_crypto_trading-2.6.8.dist-info/RECORD +17 -0
  14. bitunix_automated_crypto_trading/config.txt +0 -60
  15. bitunix_automated_crypto_trading/sampleenv.txt +0 -5
  16. bitunix_automated_crypto_trading/static/chart.css +0 -28
  17. bitunix_automated_crypto_trading/static/chart.js +0 -362
  18. bitunix_automated_crypto_trading/static/modal.css +0 -68
  19. bitunix_automated_crypto_trading/static/modal.js +0 -147
  20. bitunix_automated_crypto_trading/static/script.js +0 -166
  21. bitunix_automated_crypto_trading/static/styles.css +0 -118
  22. bitunix_automated_crypto_trading/templates/charts.html +0 -98
  23. bitunix_automated_crypto_trading/templates/login.html +0 -19
  24. bitunix_automated_crypto_trading/templates/main.html +0 -551
  25. bitunix_automated_crypto_trading/templates/modal-chart.html +0 -26
  26. bitunix_automated_crypto_trading/templates/modal-config.html +0 -34
  27. bitunix_automated_crypto_trading/templates/modal-logs.html +0 -15
  28. bitunix_automated_crypto_trading-2.6.7.dist-info/RECORD +0 -31
  29. {bitunix_automated_crypto_trading-2.6.7.dist-info → bitunix_automated_crypto_trading-2.6.8.dist-info}/WHEEL +0 -0
  30. {bitunix_automated_crypto_trading-2.6.7.dist-info → bitunix_automated_crypto_trading-2.6.8.dist-info}/entry_points.txt +0 -0
  31. {bitunix_automated_crypto_trading-2.6.7.dist-info → bitunix_automated_crypto_trading-2.6.8.dist-info}/top_level.txt +0 -0
@@ -1,254 +1,254 @@
1
- import websockets
2
- import json
3
- import time
4
- import hashlib
5
- import asyncio
6
- import random
7
- import string
8
- from typing import Callable
9
- import threading
10
- from .logger import Logger
11
- logger = Logger(__name__).get_logger()
12
- import gc
13
-
14
- class BitunixPublicWebSocketClient:
15
- def __init__(self, api_key, secret_key, type):
16
- self.api_key = api_key
17
- self.secret_key = secret_key
18
- self.type = type
19
- self.url = "wss://fapi.bitunix.com/public/"
20
- self.websocket = None
21
- self.running=False
22
- self.loop = asyncio.new_event_loop()
23
- self.loop_thread = threading.Thread(target=self.start_loop)
24
- self.loop_thread.daemon = True # Ensure the thread closes with the program
25
- self.loop_thread.start()
26
-
27
-
28
- def start_loop(self):
29
- asyncio.set_event_loop(self.loop)
30
- self.loop.run_forever()
31
-
32
- def stop_loop(self):
33
- # Stop the loop in a thread-safe way
34
- self.loop.call_soon_threadsafe(self.loop.stop)
35
-
36
- # Wait for the thread to finish
37
- self.loop_thread.join()
38
- logger.info("Event loop stopped cleanly")
39
-
40
-
41
- async def run_websocket(self, process_func: Callable):
42
- self.running = True
43
- while self.running:
44
- try:
45
- async with websockets.connect(self.url, ping_interval=None, open_timeout=30) as self.websocket:
46
- connect_response = await self.websocket.recv()
47
- logger.info(f"{self.url} {self.type} websocket connect Response: {connect_response}")
48
-
49
- self.heartbeat_task = asyncio.create_task(self.send_heartbeat(self.websocket))
50
-
51
- for ticker in self.tickerList:
52
- subscribe_message = self.create_subscription_message(ticker)
53
- if subscribe_message:
54
- await self.websocket.send(subscribe_message)
55
-
56
- self.recv_task = asyncio.create_task(self.receive_messages(process_func))
57
- await self.recv_task
58
- except websockets.exceptions.ConnectionClosedError as e:
59
- logger.warning(f"{self.url} {self.type} WebSocket connection closed. Retrying in 5 seconds: {e}")
60
- await asyncio.sleep(5) # Delay before retrying
61
- except Exception as e:
62
- logger.error(f"{self.url} {self.type} Unexpected error during WebSocket operation: {e}")
63
- await asyncio.sleep(5) # Delay before retrying
64
-
65
- def create_subscription_message(self, ticker):
66
- if self.type == 'depth':
67
- dep = {"symbol": ticker, "ch": "depth_book1"}
68
- return json.dumps({"op": "subscribe", "args": [dep]})
69
- elif self.type == 'kline':
70
- kli = {"symbol": ticker, "ch": "market_kline_1min"}
71
- return json.dumps({"op": "subscribe", "args": [kli]})
72
- elif self.type == 'ticker':
73
- tic = {"symbol": ticker, "ch": "ticker"}
74
- return json.dumps({"op": "subscribe", "args": [tic]})
75
- return None
76
-
77
- async def receive_messages(self, process_func: Callable):
78
- try:
79
- while self.running:
80
- message = await self.websocket.recv()
81
- await process_func(message)
82
- logger.warning(f"{self.url} {self.type} WebSocket connection closed")
83
- except asyncio.CancelledError:
84
- logger.info(f"{self.url} {self.type} WebSocket receive task cancelled")
85
- self.running = False
86
- except websockets.exceptions.ConnectionClosedError as e:
87
- logger.info(f"{self.url} {self.type} Websocket: Connection closed: {e}")
88
- except Exception as e:
89
- logger.info(f"{self.url} {self.type} Websocket: Unexpected error: {e}")
90
- pass
91
- del message, process_func
92
- gc.collect()
93
-
94
- async def send_heartbeat(self, websocket):
95
- try:
96
- while True:
97
- await websocket.send(json.dumps({"op": "ping", "ping": int(time.time())}))
98
- await asyncio.sleep(30)
99
- except asyncio.CancelledError:
100
- logger.info(f"{self.url} {self.type} WebSocket hearbeat task cancelled")
101
- except websockets.exceptions.ConnectionClosed:
102
- logger.info(f"{self.url} {self.type} WebSocket connection for heartbeat is closed")
103
- except Exception as e:
104
- logger.info(f"{self.url} {self.type} Websocket for heartbeat: Unexpected error: {e}")
105
- pass
106
-
107
- async def stop_websocket(self):
108
- try:
109
- self.running = False
110
- # Cancel the receive task
111
- if hasattr(self, "recv_task") and self.recv_task:
112
- self.loop.call_soon_threadsafe(self.recv_task.cancel) # Schedule cancellation
113
-
114
- # Cancel the heartbeat task
115
- if hasattr(self, "heartbeat_task") and self.heartbeat_task:
116
- self.loop.call_soon_threadsafe(self.heartbeat_task.cancel) # Schedule cancellation
117
-
118
- # Close the WebSocket
119
- if self.websocket is not None:
120
- self.loop.call_soon_threadsafe(self.websocket.close) #
121
-
122
- except Exception as e:
123
- logger.error(f"{self.url} {self.type} Unexpected error during WebSocket shutdown: {e}")
124
- finally:
125
- # Stop the event loop
126
- self.loop.call_soon_threadsafe(self.loop.stop)
127
- logger.info(f"{self.url} {self.type} WebSocket thread stopped and resources cleaned up.")
128
-
129
- class BitunixPrivateWebSocketClient:
130
- def __init__(self, api_key, secret_key):
131
- self.api_key = api_key
132
- self.secret_key = secret_key
133
- self.url = "wss://fapi.bitunix.com/private/"
134
- self.websocket = None
135
- self.running=False
136
- self.loop = asyncio.new_event_loop()
137
- self.loop_thread = threading.Thread(target=self.start_loop)
138
- self.loop_thread.daemon = True # Ensure the thread closes with the program
139
- self.loop_thread.start()
140
-
141
-
142
- def start_loop(self):
143
- asyncio.set_event_loop(self.loop)
144
- self.loop.run_forever()
145
-
146
- def stop_loop(self):
147
- # Stop the loop in a thread-safe way
148
- self.loop.call_soon_threadsafe(self.loop.stop)
149
-
150
- # Wait for the thread to finish
151
- self.loop_thread.join()
152
- logger.info("Event loop stopped cleanly")
153
-
154
- async def run_websocket(self, process_func: Callable):
155
- self.running = True
156
- while self.running:
157
- try:
158
- async with websockets.connect(self.url, ping_interval=None, open_timeout=30) as self.websocket:
159
- connect_response = await self.websocket.recv()
160
- logger.info(f"{self.url} websocket connect Response: {connect_response}")
161
-
162
- self.heartbeat_task = asyncio.create_task(self.send_heartbeat(self.websocket))
163
-
164
- nonce = await self.generate_nonce(32)
165
- sign, timestamp = await self.generate_signature(self.api_key, self.secret_key, nonce)
166
- login_request = {
167
- "op": "login",
168
- "args": [
169
- {
170
- "apiKey": self.api_key,
171
- "timestamp": timestamp,
172
- "nonce": nonce,
173
- "sign": sign,
174
- }
175
- ],
176
- }
177
- await self.websocket.send(json.dumps(login_request))
178
- login_response = await self.websocket.recv()
179
- logger.info(f"{self.url} Login Response: {login_response}")
180
-
181
- self.recv_task = asyncio.create_task(self.receive_messages(process_func))
182
- await self.recv_task
183
-
184
- except websockets.exceptions.ConnectionClosedError as e:
185
- logger.warning(f"{self.type} WebSocket connection closed. Retrying in 5 seconds: {e}")
186
- await asyncio.sleep(5) # Delay before retrying
187
- except Exception as e:
188
- logger.error(f"Unexpected error during {self.type} WebSocket operation: {e}")
189
- await asyncio.sleep(5) # Delay before retrying
190
-
191
- async def receive_messages(self, process_func: Callable):
192
- try:
193
- while self.running:
194
- message = await self.websocket.recv()
195
- await process_func(message)
196
- logger.warning(f"{self.type} WebSocket connection closed")
197
- except asyncio.CancelledError:
198
- logger.info(f"{self.url} WebSocket receive task cancelled")
199
- self.running = False
200
- except websockets.exceptions.ConnectionClosedError as e:
201
- logger.info(f"{self.url} Websocket: Connection closed: {e}")
202
- except Exception as e:
203
- logger.info(f"{self.url} Websocket: Unexpected error: {e}")
204
- pass
205
- del message, process_func
206
- gc.collect()
207
-
208
- async def send_heartbeat(self, websocket):
209
- try:
210
- while True:
211
- await websocket.send(json.dumps({"op": "ping", "ping": int(time.time())}))
212
- await asyncio.sleep(30)
213
- except asyncio.CancelledError:
214
- logger.info(f"{self.url} WebSocket hearbeat task cancelled")
215
- except websockets.exceptions.ConnectionClosed:
216
- logger.info(f"{self.url} WebSocket connection for heartbeat is closed")
217
- except Exception as e:
218
- logger.info(f"{self.url} Websocket for heartbeat: Unexpected error: {e}")
219
- pass
220
-
221
- async def stop_websocket(self):
222
- try:
223
- self.running = False
224
- # Cancel the receive task
225
- if hasattr(self, "recv_task") and self.recv_task:
226
- self.loop.call_soon_threadsafe(self.recv_task.cancel) # Schedule cancellation
227
-
228
- # Cancel the heartbeat task
229
- if hasattr(self, "heartbeat_task") and self.heartbeat_task:
230
- self.loop.call_soon_threadsafe(self.heartbeat_task.cancel) # Schedule cancellation
231
-
232
- # Close the WebSocket
233
- if self.websocket is not None:
234
- self.loop.call_soon_threadsafe(self.websocket.close) #
235
-
236
- except Exception as e:
237
- logger.error(f"Unexpected error during {self.url} WebSocket shutdown: {e}")
238
- finally:
239
- # Stop the event loop
240
- self.loop.call_soon_threadsafe(self.loop.stop)
241
- logger.info(f"{self.url} WebSocket thread stopped and resources cleaned up.")
242
-
243
-
244
- async def generate_signature(self, api_key, secret_key, nonce):
245
- timestamp = int(time.time())
246
- pre_sign = f"{nonce}{timestamp}{api_key}"
247
- sign = hashlib.sha256(pre_sign.encode()).hexdigest()
248
- final_sign = hashlib.sha256((sign + secret_key).encode()).hexdigest()
249
- return final_sign, timestamp
250
-
251
- async def generate_nonce(self, length=32):
252
- return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
253
-
254
-
1
+ import websockets
2
+ import json
3
+ import time
4
+ import hashlib
5
+ import asyncio
6
+ import random
7
+ import string
8
+ from typing import Callable
9
+ import threading
10
+ from logger import Logger
11
+ logger = Logger(__name__).get_logger()
12
+ import gc
13
+
14
+ class BitunixPublicWebSocketClient:
15
+ def __init__(self, api_key, secret_key, type):
16
+ self.api_key = api_key
17
+ self.secret_key = secret_key
18
+ self.type = type
19
+ self.url = "wss://fapi.bitunix.com/public/"
20
+ self.websocket = None
21
+ self.running=False
22
+ self.loop = asyncio.new_event_loop()
23
+ self.loop_thread = threading.Thread(target=self.start_loop)
24
+ self.loop_thread.daemon = True # Ensure the thread closes with the program
25
+ self.loop_thread.start()
26
+
27
+
28
+ def start_loop(self):
29
+ asyncio.set_event_loop(self.loop)
30
+ self.loop.run_forever()
31
+
32
+ def stop_loop(self):
33
+ # Stop the loop in a thread-safe way
34
+ self.loop.call_soon_threadsafe(self.loop.stop)
35
+
36
+ # Wait for the thread to finish
37
+ self.loop_thread.join()
38
+ logger.info("Event loop stopped cleanly")
39
+
40
+
41
+ async def run_websocket(self, process_func: Callable):
42
+ self.running = True
43
+ while self.running:
44
+ try:
45
+ async with websockets.connect(self.url, ping_interval=None, open_timeout=30) as self.websocket:
46
+ connect_response = await self.websocket.recv()
47
+ logger.info(f"{self.url} {self.type} websocket connect Response: {connect_response}")
48
+
49
+ self.heartbeat_task = asyncio.create_task(self.send_heartbeat(self.websocket))
50
+
51
+ for ticker in self.tickerList:
52
+ subscribe_message = self.create_subscription_message(ticker)
53
+ if subscribe_message:
54
+ await self.websocket.send(subscribe_message)
55
+
56
+ self.recv_task = asyncio.create_task(self.receive_messages(process_func))
57
+ await self.recv_task
58
+ except websockets.exceptions.ConnectionClosedError as e:
59
+ logger.warning(f"{self.url} {self.type} WebSocket connection closed. Retrying in 5 seconds: {e}")
60
+ await asyncio.sleep(5) # Delay before retrying
61
+ except Exception as e:
62
+ logger.error(f"{self.url} {self.type} Unexpected error during WebSocket operation: {e}")
63
+ await asyncio.sleep(5) # Delay before retrying
64
+
65
+ def create_subscription_message(self, ticker):
66
+ if self.type == 'depth':
67
+ dep = {"symbol": ticker, "ch": "depth_book1"}
68
+ return json.dumps({"op": "subscribe", "args": [dep]})
69
+ elif self.type == 'kline':
70
+ kli = {"symbol": ticker, "ch": "market_kline_1min"}
71
+ return json.dumps({"op": "subscribe", "args": [kli]})
72
+ elif self.type == 'ticker':
73
+ tic = {"symbol": ticker, "ch": "ticker"}
74
+ return json.dumps({"op": "subscribe", "args": [tic]})
75
+ return None
76
+
77
+ async def receive_messages(self, process_func: Callable):
78
+ try:
79
+ while self.running:
80
+ message = await self.websocket.recv()
81
+ await process_func(message)
82
+ logger.warning(f"{self.url} {self.type} WebSocket connection closed")
83
+ except asyncio.CancelledError:
84
+ logger.info(f"{self.url} {self.type} WebSocket receive task cancelled")
85
+ self.running = False
86
+ except websockets.exceptions.ConnectionClosedError as e:
87
+ logger.info(f"{self.url} {self.type} Websocket: Connection closed: {e}")
88
+ except Exception as e:
89
+ logger.info(f"{self.url} {self.type} Websocket: Unexpected error: {e}")
90
+ pass
91
+ del message, process_func
92
+ gc.collect()
93
+
94
+ async def send_heartbeat(self, websocket):
95
+ try:
96
+ while True:
97
+ await websocket.send(json.dumps({"op": "ping", "ping": int(time.time())}))
98
+ await asyncio.sleep(30)
99
+ except asyncio.CancelledError:
100
+ logger.info(f"{self.url} {self.type} WebSocket hearbeat task cancelled")
101
+ except websockets.exceptions.ConnectionClosed:
102
+ logger.info(f"{self.url} {self.type} WebSocket connection for heartbeat is closed")
103
+ except Exception as e:
104
+ logger.info(f"{self.url} {self.type} Websocket for heartbeat: Unexpected error: {e}")
105
+ pass
106
+
107
+ async def stop_websocket(self):
108
+ try:
109
+ self.running = False
110
+ # Cancel the receive task
111
+ if hasattr(self, "recv_task") and self.recv_task:
112
+ self.loop.call_soon_threadsafe(self.recv_task.cancel) # Schedule cancellation
113
+
114
+ # Cancel the heartbeat task
115
+ if hasattr(self, "heartbeat_task") and self.heartbeat_task:
116
+ self.loop.call_soon_threadsafe(self.heartbeat_task.cancel) # Schedule cancellation
117
+
118
+ # Close the WebSocket
119
+ if self.websocket is not None:
120
+ self.loop.call_soon_threadsafe(self.websocket.close) #
121
+
122
+ except Exception as e:
123
+ logger.error(f"{self.url} {self.type} Unexpected error during WebSocket shutdown: {e}")
124
+ finally:
125
+ # Stop the event loop
126
+ self.loop.call_soon_threadsafe(self.loop.stop)
127
+ logger.info(f"{self.url} {self.type} WebSocket thread stopped and resources cleaned up.")
128
+
129
+ class BitunixPrivateWebSocketClient:
130
+ def __init__(self, api_key, secret_key):
131
+ self.api_key = api_key
132
+ self.secret_key = secret_key
133
+ self.url = "wss://fapi.bitunix.com/private/"
134
+ self.websocket = None
135
+ self.running=False
136
+ self.loop = asyncio.new_event_loop()
137
+ self.loop_thread = threading.Thread(target=self.start_loop)
138
+ self.loop_thread.daemon = True # Ensure the thread closes with the program
139
+ self.loop_thread.start()
140
+
141
+
142
+ def start_loop(self):
143
+ asyncio.set_event_loop(self.loop)
144
+ self.loop.run_forever()
145
+
146
+ def stop_loop(self):
147
+ # Stop the loop in a thread-safe way
148
+ self.loop.call_soon_threadsafe(self.loop.stop)
149
+
150
+ # Wait for the thread to finish
151
+ self.loop_thread.join()
152
+ logger.info("Event loop stopped cleanly")
153
+
154
+ async def run_websocket(self, process_func: Callable):
155
+ self.running = True
156
+ while self.running:
157
+ try:
158
+ async with websockets.connect(self.url, ping_interval=None, open_timeout=30) as self.websocket:
159
+ connect_response = await self.websocket.recv()
160
+ logger.info(f"{self.url} websocket connect Response: {connect_response}")
161
+
162
+ self.heartbeat_task = asyncio.create_task(self.send_heartbeat(self.websocket))
163
+
164
+ nonce = await self.generate_nonce(32)
165
+ sign, timestamp = await self.generate_signature(self.api_key, self.secret_key, nonce)
166
+ login_request = {
167
+ "op": "login",
168
+ "args": [
169
+ {
170
+ "apiKey": self.api_key,
171
+ "timestamp": timestamp,
172
+ "nonce": nonce,
173
+ "sign": sign,
174
+ }
175
+ ],
176
+ }
177
+ await self.websocket.send(json.dumps(login_request))
178
+ login_response = await self.websocket.recv()
179
+ logger.info(f"{self.url} Login Response: {login_response}")
180
+
181
+ self.recv_task = asyncio.create_task(self.receive_messages(process_func))
182
+ await self.recv_task
183
+
184
+ except websockets.exceptions.ConnectionClosedError as e:
185
+ logger.warning(f"{self.type} WebSocket connection closed. Retrying in 5 seconds: {e}")
186
+ await asyncio.sleep(5) # Delay before retrying
187
+ except Exception as e:
188
+ logger.error(f"Unexpected error during {self.type} WebSocket operation: {e}")
189
+ await asyncio.sleep(5) # Delay before retrying
190
+
191
+ async def receive_messages(self, process_func: Callable):
192
+ try:
193
+ while self.running:
194
+ message = await self.websocket.recv()
195
+ await process_func(message)
196
+ logger.warning(f"{self.type} WebSocket connection closed")
197
+ except asyncio.CancelledError:
198
+ logger.info(f"{self.url} WebSocket receive task cancelled")
199
+ self.running = False
200
+ except websockets.exceptions.ConnectionClosedError as e:
201
+ logger.info(f"{self.url} Websocket: Connection closed: {e}")
202
+ except Exception as e:
203
+ logger.info(f"{self.url} Websocket: Unexpected error: {e}")
204
+ pass
205
+ del message, process_func
206
+ gc.collect()
207
+
208
+ async def send_heartbeat(self, websocket):
209
+ try:
210
+ while True:
211
+ await websocket.send(json.dumps({"op": "ping", "ping": int(time.time())}))
212
+ await asyncio.sleep(30)
213
+ except asyncio.CancelledError:
214
+ logger.info(f"{self.url} WebSocket hearbeat task cancelled")
215
+ except websockets.exceptions.ConnectionClosed:
216
+ logger.info(f"{self.url} WebSocket connection for heartbeat is closed")
217
+ except Exception as e:
218
+ logger.info(f"{self.url} Websocket for heartbeat: Unexpected error: {e}")
219
+ pass
220
+
221
+ async def stop_websocket(self):
222
+ try:
223
+ self.running = False
224
+ # Cancel the receive task
225
+ if hasattr(self, "recv_task") and self.recv_task:
226
+ self.loop.call_soon_threadsafe(self.recv_task.cancel) # Schedule cancellation
227
+
228
+ # Cancel the heartbeat task
229
+ if hasattr(self, "heartbeat_task") and self.heartbeat_task:
230
+ self.loop.call_soon_threadsafe(self.heartbeat_task.cancel) # Schedule cancellation
231
+
232
+ # Close the WebSocket
233
+ if self.websocket is not None:
234
+ self.loop.call_soon_threadsafe(self.websocket.close) #
235
+
236
+ except Exception as e:
237
+ logger.error(f"Unexpected error during {self.url} WebSocket shutdown: {e}")
238
+ finally:
239
+ # Stop the event loop
240
+ self.loop.call_soon_threadsafe(self.loop.stop)
241
+ logger.info(f"{self.url} WebSocket thread stopped and resources cleaned up.")
242
+
243
+
244
+ async def generate_signature(self, api_key, secret_key, nonce):
245
+ timestamp = int(time.time())
246
+ pre_sign = f"{nonce}{timestamp}{api_key}"
247
+ sign = hashlib.sha256(pre_sign.encode()).hexdigest()
248
+ final_sign = hashlib.sha256((sign + secret_key).encode()).hexdigest()
249
+ return final_sign, timestamp
250
+
251
+ async def generate_nonce(self, length=32):
252
+ return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
253
+
254
+
@@ -1,74 +1,74 @@
1
- import pandas as pd
2
- from jinja2 import Template
3
-
4
- class DataFrameHtmlRenderer:
5
- def __init__(self, hide_columns=None, color_column_mapping=None):
6
- """
7
- Initialize the renderer with columns to hide and column-color mapping.
8
- """
9
- self.hide_columns = hide_columns or []
10
- self.color_column_mapping = color_column_mapping or {}
11
-
12
- def preprocess_styles(self, dataframe):
13
- """
14
- Precomputes the styles for columns that need coloring based on the provided DataFrame.
15
- Returns a DataFrame with styles.
16
- """
17
- style_df = pd.DataFrame(index=dataframe.index, columns=dataframe.columns)
18
- for col, color_col in self.color_column_mapping.items():
19
- if col in dataframe.columns and color_col in dataframe.columns:
20
- style_df[col] = dataframe[color_col] # Copy the color values
21
- return style_df.fillna("") # Fill empty cells with blank strings
22
-
23
- def render_html(self, dataframe):
24
- """
25
- Renders the DataFrame as an HTML string with conditional coloring for specified columns.
26
- """
27
- style_df = self.preprocess_styles(dataframe)
28
-
29
- # Create combined_data for all columns
30
- combined_data = []
31
- for index, row in dataframe.iterrows():
32
- row_data = []
33
- for col in dataframe.columns:
34
- row_data.append({
35
- "value": row[col], # The cell's value
36
- "style": style_df.at[index, col], # The cell's style (if any)
37
- "column": col # The column name for the cell
38
- })
39
- combined_data.append(row_data)
40
-
41
- # Jinja2 HTML template
42
- template = Template("""
43
- <table border="1" style="border-collapse: collapse; width: 100%; font-size: small;">
44
- <thead>
45
- <tr>
46
- {% for column in columns if column not in hide_columns %}
47
- <th>{{ column }}</th>
48
- {% endfor %}
49
- </tr>
50
- </thead>
51
- <tbody>
52
- {% for row in combined_data %}
53
- <tr>
54
- {% for cell in row %}
55
- {% if cell.column not in hide_columns %}
56
- <td {% if cell.style %} style="background-color: {{ cell.style }};" {% endif %}>
57
- {{ cell.value }}
58
- </td>
59
- {% endif %}
60
- {% endfor %}
61
- </tr>
62
- {% endfor %}
63
- </tbody>
64
- </table>
65
- """)
66
-
67
- # Render the HTML
68
- html_output = template.render(
69
- columns=dataframe.columns.tolist(), # All columns
70
- hide_columns=self.hide_columns, # Columns to hide
71
- combined_data=combined_data # Combined data (values + styles)
72
- )
73
- return html_output
74
-
1
+ import pandas as pd
2
+ from jinja2 import Template
3
+
4
+ class DataFrameHtmlRenderer:
5
+ def __init__(self, hide_columns=None, color_column_mapping=None):
6
+ """
7
+ Initialize the renderer with columns to hide and column-color mapping.
8
+ """
9
+ self.hide_columns = hide_columns or []
10
+ self.color_column_mapping = color_column_mapping or {}
11
+
12
+ def preprocess_styles(self, dataframe):
13
+ """
14
+ Precomputes the styles for columns that need coloring based on the provided DataFrame.
15
+ Returns a DataFrame with styles.
16
+ """
17
+ style_df = pd.DataFrame(index=dataframe.index, columns=dataframe.columns)
18
+ for col, color_col in self.color_column_mapping.items():
19
+ if col in dataframe.columns and color_col in dataframe.columns:
20
+ style_df[col] = dataframe[color_col] # Copy the color values
21
+ return style_df.fillna("") # Fill empty cells with blank strings
22
+
23
+ def render_html(self, dataframe):
24
+ """
25
+ Renders the DataFrame as an HTML string with conditional coloring for specified columns.
26
+ """
27
+ style_df = self.preprocess_styles(dataframe)
28
+
29
+ # Create combined_data for all columns
30
+ combined_data = []
31
+ for index, row in dataframe.iterrows():
32
+ row_data = []
33
+ for col in dataframe.columns:
34
+ row_data.append({
35
+ "value": row[col], # The cell's value
36
+ "style": style_df.at[index, col], # The cell's style (if any)
37
+ "column": col # The column name for the cell
38
+ })
39
+ combined_data.append(row_data)
40
+
41
+ # Jinja2 HTML template
42
+ template = Template("""
43
+ <table border="1" style="border-collapse: collapse; width: 100%; font-size: small;">
44
+ <thead>
45
+ <tr>
46
+ {% for column in columns if column not in hide_columns %}
47
+ <th>{{ column }}</th>
48
+ {% endfor %}
49
+ </tr>
50
+ </thead>
51
+ <tbody>
52
+ {% for row in combined_data %}
53
+ <tr>
54
+ {% for cell in row %}
55
+ {% if cell.column not in hide_columns %}
56
+ <td {% if cell.style %} style="background-color: {{ cell.style }};" {% endif %}>
57
+ {{ cell.value }}
58
+ </td>
59
+ {% endif %}
60
+ {% endfor %}
61
+ </tr>
62
+ {% endfor %}
63
+ </tbody>
64
+ </table>
65
+ """)
66
+
67
+ # Render the HTML
68
+ html_output = template.render(
69
+ columns=dataframe.columns.tolist(), # All columns
70
+ hide_columns=self.hide_columns, # Columns to hide
71
+ combined_data=combined_data # Combined data (values + styles)
72
+ )
73
+ return html_output
74
+