From 3644c219d4f7edbf45c42ac6585cfb0fe5b94b1a Mon Sep 17 00:00:00 2001 From: rob Date: Sun, 1 Mar 2026 17:58:24 -0400 Subject: [PATCH] Fix int64 JSON serialization error in SocketIO responses - Add utils.py with sanitize_for_json() function to convert numpy types (int64, float64, bool_, ndarray) to native Python types - Handle inf/nan values by converting to None - Update backtesting.py to use shared sanitize_for_json utility - Update app.py strategy_events emit to sanitize data before sending - Remove empty root BrighterTrading.db and add *.db to gitignore Co-Authored-By: Claude Opus 4.5 --- .gitignore | 1 + BrighterTrading.db | 0 src/app.py | 5 ++-- src/backtesting.py | 26 +++++++------------- src/utils.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 20 deletions(-) delete mode 100644 BrighterTrading.db create mode 100644 src/utils.py diff --git a/.gitignore b/.gitignore index 9fb1953..f2653f8 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ flask_session/ # Ignore databases data/ data/*.db +*.db # Ignore configuration files config/ diff --git a/BrighterTrading.db b/BrighterTrading.db deleted file mode 100644 index e69de29..0000000 diff --git a/src/app.py b/src/app.py index b4402f0..3451331 100644 --- a/src/app.py +++ b/src/app.py @@ -18,6 +18,7 @@ from email_validator import validate_email, EmailNotValidError # noqa: E402 # Local application imports from BrighterTrades import BrighterTrades # noqa: E402 +from utils import sanitize_for_json # noqa: E402 # Set up logging log_level_name = os.getenv('BRIGHTER_LOG_LEVEL', 'INFO').upper() @@ -128,11 +129,11 @@ def strategy_execution_loop(): # Emit events to the user's room user_name = brighter_trades.users.get_username(user_id=user_id) if user_name: - socketio.emit('strategy_events', { + socketio.emit('strategy_events', sanitize_for_json({ 'strategy_id': strategy_id, 'mode': mode, 'events': events - }, room=user_name) + }), room=user_name) except Exception as e: logger.warning(f"Could not get price for {symbol}: {e}") diff --git a/src/backtesting.py b/src/backtesting.py index 4d5c545..83d21df 100644 --- a/src/backtesting.py +++ b/src/backtesting.py @@ -17,6 +17,7 @@ from mapped_strategy import MappedStrategy import numpy as np import pandas as pd import signal +from utils import sanitize_for_json # Configure logging logger = logging.getLogger(__name__) @@ -821,26 +822,15 @@ class Backtester: } def sanitize_results(self, results): - import math - for key, value in results.items(): - if isinstance(value, float) and (math.isinf(value) or math.isnan(value)): - results[key] = None # Replace `inf` or `nan` with `None` - elif isinstance(value, list): - results[key] = self.sanitize_results_in_list(value) - elif isinstance(value, dict): - results[key] = self.sanitize_results(value) - return results + """ + Recursively sanitize a dictionary for JSON serialization. + Converts numpy types to native Python types and handles inf/nan. + """ + return sanitize_for_json(results) def sanitize_results_in_list(self, results_list): - sanitized_list = [] - for item in results_list: - if isinstance(item, dict): - sanitized_list.append(self.sanitize_results(item)) - elif isinstance(item, float) and (math.isinf(item) or math.isnan(item)): - sanitized_list.append(None) - else: - sanitized_list.append(item) - return sanitized_list + """Sanitize a list for JSON serialization.""" + return sanitize_for_json(results_list) def update_strategy_stats(self, tbl_key: str, results: dict): """ diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 0000000..512a3e0 --- /dev/null +++ b/src/utils.py @@ -0,0 +1,60 @@ +""" +Utility functions for BrighterTrading. +""" +import math +import numpy as np + + +def sanitize_for_json(value): + """ + Convert a value to a JSON-serializable type. + Handles numpy types, inf, nan, and nested structures. + + :param value: Any value that needs to be JSON serialized + :return: JSON-serializable value + """ + # Handle None + if value is None: + return None + # Handle numpy integer types + if isinstance(value, np.integer): + return int(value) + # Handle numpy floating types + elif isinstance(value, np.floating): + float_val = float(value) + if math.isinf(float_val) or math.isnan(float_val): + return None + return float_val + # Handle numpy boolean + elif isinstance(value, np.bool_): + return bool(value) + # Handle numpy arrays + elif isinstance(value, np.ndarray): + return [sanitize_for_json(v) for v in value.tolist()] + # Handle regular floats with inf/nan + elif isinstance(value, float): + if math.isinf(value) or math.isnan(value): + return None + return value + # Handle nested dicts + elif isinstance(value, dict): + return {k: sanitize_for_json(v) for k, v in value.items()} + # Handle lists + elif isinstance(value, list): + return [sanitize_for_json(v) for v in value] + # Handle tuples + elif isinstance(value, tuple): + return tuple(sanitize_for_json(v) for v in value) + # Return other types as-is (str, int, bool, etc.) + return value + + +def sanitize_dict_for_json(data: dict) -> dict: + """ + Sanitize a dictionary for JSON serialization. + Convenience wrapper around sanitize_for_json for dictionaries. + + :param data: Dictionary to sanitize + :return: JSON-serializable dictionary + """ + return sanitize_for_json(data)