hubble-futures 0.2.13__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.
@@ -0,0 +1,303 @@
1
+ """
2
+ Structured Function Call Logging Module
3
+
4
+ This module provides structured logging for function calls and results in container agents.
5
+ It tracks all trading-related function calls with timing, parameters, and results.
6
+
7
+ Usage:
8
+ from hubble_futures import start_function_log, record_function_call, finish_function_call, export_function_log
9
+
10
+ # Start logging at container entry
11
+ start_function_log()
12
+
13
+ # Record function calls
14
+ record_function_call("open_position", {"symbol": "BTCUSDT", "side": "BUY"})
15
+ finish_function_call("open_position", {"order_id": "12345", "status": "filled"})
16
+
17
+ # Export and clear at container exit
18
+ result = export_function_log(clear=True)
19
+ """
20
+
21
+ import contextvars
22
+ import copy
23
+ import time
24
+ from datetime import datetime
25
+ from typing import Any
26
+
27
+
28
+ # Context variable for function log storage (async-safe)
29
+ _log_storage: contextvars.ContextVar[dict] = contextvars.ContextVar("_function_log_storage")
30
+
31
+
32
+ def _create_new_storage() -> dict:
33
+ """Create a new log storage instance with fresh mutable containers."""
34
+ return {
35
+ "function_calls": [],
36
+ "trading_summary": {},
37
+ "warnings": [],
38
+ "errors": [],
39
+ "metadata": {
40
+ "start_time": None,
41
+ "end_time": None,
42
+ "container_id": None,
43
+ },
44
+ }
45
+
46
+
47
+ def _get_log_storage() -> dict:
48
+ """
49
+ Get context-local log storage.
50
+
51
+ Returns the storage for the current context. Must call start_function_log()
52
+ before this to initialize the storage.
53
+
54
+ Raises:
55
+ LookupError: If start_function_log() has not been called
56
+ """
57
+ return _log_storage.get()
58
+
59
+
60
+ def start_function_log(container_id: str | None = None) -> None:
61
+ """
62
+ Initialize function logging for the current container execution.
63
+
64
+ Must be called at the container entry point before any function calls.
65
+ Creates a fresh storage instance to avoid sharing state across async tasks.
66
+
67
+ Args:
68
+ container_id: Optional container identifier for tracking
69
+
70
+ Example:
71
+ start_function_log(container_id="agent-abc-123")
72
+ """
73
+ # Create a fresh storage instance for this context
74
+ storage = _create_new_storage()
75
+ storage["metadata"]["start_time"] = datetime.utcnow().isoformat()
76
+ storage["metadata"]["container_id"] = container_id
77
+ _log_storage.set(storage)
78
+
79
+
80
+ def record_function_call(function_name: str, parameters: dict[str, Any]) -> None:
81
+ """
82
+ Record the start of a function call with parameters.
83
+
84
+ Call this immediately before invoking a trading function.
85
+ Silently does nothing if logging has not been started (non-container scenarios).
86
+
87
+ Args:
88
+ function_name: Name of the function being called (e.g., "open_position")
89
+ parameters: Parameters passed to the function
90
+
91
+ Example:
92
+ record_function_call("open_position", {"symbol": "BTCUSDT", "side": "BUY", "amount": 0.001})
93
+ """
94
+ try:
95
+ storage = _get_log_storage()
96
+ except LookupError:
97
+ # Logging not initialized (non-container scenario), silently ignore
98
+ return
99
+
100
+ call_record = {
101
+ "function": function_name,
102
+ "parameters": parameters,
103
+ "start_time": datetime.utcnow().isoformat(),
104
+ "end_time": None,
105
+ "duration_ms": None,
106
+ "result": None,
107
+ "error": None,
108
+ "status": "pending",
109
+ }
110
+ storage["function_calls"].append(call_record)
111
+
112
+
113
+ def finish_function_call(function_name: str, result: dict[str, Any] | None = None, error: str | None = None) -> None:
114
+ """
115
+ Record the completion of a function call with result or error.
116
+
117
+ Call this immediately after a trading function returns.
118
+ Silently does nothing if logging has not been started (non-container scenarios).
119
+
120
+ Args:
121
+ function_name: Name of the function that completed (must match record_function_call)
122
+ result: Function return value (e.g., {"order_id": "12345", "status": "filled"})
123
+ error: Error message if the function failed
124
+
125
+ Example:
126
+ # Success case
127
+ finish_function_call("open_position", {"order_id": "12345", "status": "filled"})
128
+
129
+ # Error case
130
+ finish_function_call("open_position", error="Insufficient margin")
131
+ """
132
+ try:
133
+ storage = _get_log_storage()
134
+ except LookupError:
135
+ # Logging not initialized (non-container scenario), silently ignore
136
+ return
137
+
138
+ end_time = datetime.utcnow().isoformat()
139
+
140
+ # Find the most recent pending call for this function
141
+ for call in reversed(storage["function_calls"]):
142
+ if call["function"] == function_name and call["status"] == "pending":
143
+ call["end_time"] = end_time
144
+ call["result"] = result
145
+ call["error"] = error
146
+
147
+ # Calculate duration
148
+ if call["start_time"]:
149
+ try:
150
+ start = datetime.fromisoformat(call["start_time"])
151
+ end = datetime.fromisoformat(end_time)
152
+ call["duration_ms"] = int((end - start).total_seconds() * 1000)
153
+ except Exception:
154
+ pass
155
+
156
+ # Set status
157
+ if error:
158
+ call["status"] = "failed"
159
+ else:
160
+ call["status"] = "succeeded"
161
+ break
162
+
163
+
164
+ def set_trading_summary(summary: dict[str, Any]) -> None:
165
+ """
166
+ Set the overall trading summary for this execution.
167
+
168
+ Call this at the end of execution to summarize trading activity.
169
+ Silently does nothing if logging has not been started (non-container scenarios).
170
+
171
+ Args:
172
+ summary: Trading summary dict with keys like:
173
+ - executed: Total number of trades executed
174
+ - orders: List of order IDs
175
+ - final_position: Net position after all trades
176
+ - total_pnl: Total profit/loss
177
+
178
+ Example:
179
+ set_trading_summary({
180
+ "executed": 2,
181
+ "orders": ["12345", "12346"],
182
+ "final_position": {"BTCUSDT": 0.001},
183
+ "total_pnl": 10.50,
184
+ })
185
+ """
186
+ try:
187
+ storage = _get_log_storage()
188
+ except LookupError:
189
+ # Logging not initialized (non-container scenario), silently ignore
190
+ return
191
+ storage["trading_summary"] = summary
192
+
193
+
194
+ def add_warning(message: str) -> None:
195
+ """
196
+ Add a warning message to the log.
197
+ Silently does nothing if logging has not been started (non-container scenarios).
198
+
199
+ Args:
200
+ message: Warning message to record
201
+ """
202
+ try:
203
+ storage = _get_log_storage()
204
+ except LookupError:
205
+ # Logging not initialized (non-container scenario), silently ignore
206
+ return
207
+ storage["warnings"].append({
208
+ "message": message,
209
+ "timestamp": datetime.utcnow().isoformat(),
210
+ })
211
+
212
+
213
+ def add_error(message: str) -> None:
214
+ """
215
+ Add an error message to the log.
216
+ Silently does nothing if logging has not been started (non-container scenarios).
217
+
218
+ Args:
219
+ message: Error message to record
220
+ """
221
+ try:
222
+ storage = _get_log_storage()
223
+ except LookupError:
224
+ # Logging not initialized (non-container scenario), silently ignore
225
+ return
226
+ storage["errors"].append({
227
+ "message": message,
228
+ "timestamp": datetime.utcnow().isoformat(),
229
+ })
230
+
231
+
232
+ def export_function_log(clear: bool = True) -> dict[str, Any]:
233
+ """
234
+ Export the current function log as a structured JSON dict.
235
+
236
+ Call this at container exit to get the complete log.
237
+ Returns a deep copy to prevent external modifications from affecting internal state.
238
+ Returns empty log if logging has not been started.
239
+
240
+ Args:
241
+ clear: If True, clear the log after exporting (default: True)
242
+
243
+ Returns:
244
+ Structured log dict with keys:
245
+ - metadata: Execution metadata (start_time, end_time, container_id)
246
+ - function_calls: List of all function calls with timing and results
247
+ - trading_summary: Trading summary (if set)
248
+ - warnings: List of warnings
249
+ - errors: List of errors
250
+
251
+ Example:
252
+ result = export_function_log(clear=True)
253
+ # Returns: {"metadata": {...}, "function_calls": [...], ...}
254
+ """
255
+ try:
256
+ storage = _get_log_storage()
257
+ storage["metadata"]["end_time"] = datetime.utcnow().isoformat()
258
+
259
+ # Build deep copy to avoid external modifications
260
+ export = copy.deepcopy({
261
+ "metadata": storage["metadata"],
262
+ "function_calls": storage["function_calls"],
263
+ "trading_summary": storage["trading_summary"],
264
+ "warnings": storage["warnings"],
265
+ "errors": storage["errors"],
266
+ })
267
+ except LookupError:
268
+ # Logging not initialized, return empty log
269
+ export = copy.deepcopy(_create_new_storage())
270
+
271
+ # Clear if requested (create fresh mutable containers)
272
+ if clear:
273
+ _log_storage.set(_create_new_storage())
274
+
275
+ return export
276
+
277
+
278
+ def get_function_log() -> dict[str, Any]:
279
+ """
280
+ Get the current function log without clearing it.
281
+ Returns a deep copy to prevent external modifications from affecting internal state.
282
+ Returns empty log if logging has not been started.
283
+
284
+ Returns:
285
+ Current log state (same structure as export_function_log)
286
+ """
287
+ try:
288
+ storage = _get_log_storage()
289
+ return copy.deepcopy({
290
+ "metadata": storage["metadata"],
291
+ "function_calls": storage["function_calls"],
292
+ "trading_summary": storage["trading_summary"],
293
+ "warnings": storage["warnings"],
294
+ "errors": storage["errors"],
295
+ })
296
+ except LookupError:
297
+ # Logging not initialized, return empty log
298
+ return copy.deepcopy(_create_new_storage())
299
+
300
+
301
+ def clear_function_log() -> None:
302
+ """Clear all function log data by creating a fresh storage instance."""
303
+ _log_storage.set(_create_new_storage())
@@ -0,0 +1,8 @@
1
+ """Version information for hubble-futures package."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("hubble-futures")
7
+ except PackageNotFoundError:
8
+ __version__ = "0.2.13"