From 6861d0f20b80bb9bb9c720c4ef01ea090193df67 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sun, 10 Aug 2025 13:05:41 +0300 Subject: [PATCH] stability and error cleaning. rule to read specs --- .cursor/rules/specs.mdc | 5 ++++ core/exchanges/bybit_interface.py | 8 +++++++ core/exchanges/bybit_rest_client.py | 37 ++++++++++++++++++++++++++--- core/trading_executor.py | 9 ++++--- 4 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 .cursor/rules/specs.mdc diff --git a/.cursor/rules/specs.mdc b/.cursor/rules/specs.mdc new file mode 100644 index 0000000..7e95633 --- /dev/null +++ b/.cursor/rules/specs.mdc @@ -0,0 +1,5 @@ +--- +description: use .kiro\specs cnotent as project guideline and specifications. they may change as project develops, but will give you a good starting point and broad understanding of the project we are working on. Also, when you find problems proceed to fixing them without asking. We are discovering problems so we fix them :) +globs: +alwaysApply: false +--- diff --git a/core/exchanges/bybit_interface.py b/core/exchanges/bybit_interface.py index 9b5e293..5d678e9 100644 --- a/core/exchanges/bybit_interface.py +++ b/core/exchanges/bybit_interface.py @@ -98,6 +98,14 @@ class BybitInterface(ExchangeInterface): testnet=self.test_mode ) + # Sync server time for REST client and prefer larger recv_window + try: + self.rest_client.sync_server_time() + # Increase REST recv window defensively + self.rest_client.recv_window_ms = max(self.rest_client.recv_window_ms, 20000) + except Exception: + pass + # Test pybit connection first try: account_info = self.session.get_wallet_balance(accountType="UNIFIED") diff --git a/core/exchanges/bybit_rest_client.py b/core/exchanges/bybit_rest_client.py index bd0315e..e3a275c 100644 --- a/core/exchanges/bybit_rest_client.py +++ b/core/exchanges/bybit_rest_client.py @@ -40,6 +40,9 @@ class BybitRestClient: # Rate limiting self.last_request_time = 0 self.min_request_interval = 0.1 # 100ms between requests + # Recv window and server time sync + self.recv_window_ms = 20000 # 20 seconds to tolerate clock drift + self.time_delta_ms = 0 # server_time_ms - local_time_ms # Request session for connection pooling self.session = requests.Session() @@ -61,7 +64,7 @@ class BybitRestClient: HMAC-SHA256 signature """ # Bybit signature format: timestamp + api_key + recv_window + params - recv_window = "5000" # 5 seconds + recv_window = str(self.recv_window_ms) param_str = f"{timestamp}{self.api_key}{recv_window}{params}" signature = hmac.new( @@ -86,7 +89,7 @@ class BybitRestClient: 'X-BAPI-API-KEY': self.api_key, 'X-BAPI-SIGN': signature, 'X-BAPI-TIMESTAMP': timestamp, - 'X-BAPI-RECV-WINDOW': '5000', + 'X-BAPI-RECV-WINDOW': str(self.recv_window_ms), 'Content-Type': 'application/json' } @@ -116,7 +119,9 @@ class BybitRestClient: self._rate_limit() url = f"{self.base_url}{endpoint}" - timestamp = str(int(time.time() * 1000)) + # Apply server time delta + local_ms = int(time.time() * 1000) + timestamp = str(local_ms + self.time_delta_ms) if params is None: params = {} @@ -165,6 +170,20 @@ class BybitRestClient: error_msg = result.get('retMsg', 'Unknown error') error_code = result.get('retCode', 'Unknown') logger.error(f"Bybit Error {error_code}: {error_msg}") + # Handle time drift (10002) by syncing server time and retrying once + if signed and str(error_code) == '10002': + try: + self.sync_server_time() + # increase recv window defensively + self.recv_window_ms = max(self.recv_window_ms, 30000) + # Retry once + return self._make_request(method, endpoint, params, signed) + except Exception: + pass + # Handle rate limit (10016) by brief backoff and retry + if str(error_code) == '10016': + time.sleep(0.25) + return self._make_request(method, endpoint, params, signed) raise Exception(f"Bybit Error {error_code}: {error_msg}") return result @@ -173,6 +192,18 @@ class BybitRestClient: """Get server time (public endpoint).""" return self._make_request('GET', '/v5/market/time') + def sync_server_time(self) -> None: + """Sync local-server time delta to reduce 10002 invalid timestamp errors.""" + try: + res = self.get_server_time() + server_ts = int(res.get('result', {}).get('timeSecond', 0)) * 1000 + if server_ts > 0: + local_ms = int(time.time() * 1000) + self.time_delta_ms = server_ts - local_ms + logger.info(f"Bybit server time synced. Delta: {self.time_delta_ms} ms") + except Exception as e: + logger.warning(f"Failed to sync Bybit server time: {e}") + def get_account_info(self) -> Dict[str, Any]: """Get account information (private endpoint).""" return self._make_request('GET', '/v5/account/wallet-balance', diff --git a/core/trading_executor.py b/core/trading_executor.py index b479a8f..b2e4c9e 100644 --- a/core/trading_executor.py +++ b/core/trading_executor.py @@ -2513,7 +2513,7 @@ class TradingExecutor: 'error': str(e) } - def execute_trade(self, symbol: str, action: str, quantity: float) -> bool: + def execute_trade(self, symbol: str, action: str, quantity: float, current_price: Optional[float] = None) -> bool: """Execute a trade directly (compatibility method for dashboard) Args: @@ -2525,10 +2525,9 @@ class TradingExecutor: bool: True if trade executed successfully """ try: - # Get current price - current_price = None - # Always get real current price - never use simulated data - current_price = self._get_real_current_price(symbol) + # Get current price (allow explicit price or fallback to real fetch) + if current_price is None: + current_price = self._get_real_current_price(symbol) if current_price is None: logger.error(f"Failed to get real current price for {symbol}") return False