stability and error cleaning. rule to read specs
This commit is contained in:
5
.cursor/rules/specs.mdc
Normal file
5
.cursor/rules/specs.mdc
Normal file
@@ -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
|
||||||
|
---
|
@@ -98,6 +98,14 @@ class BybitInterface(ExchangeInterface):
|
|||||||
testnet=self.test_mode
|
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
|
# Test pybit connection first
|
||||||
try:
|
try:
|
||||||
account_info = self.session.get_wallet_balance(accountType="UNIFIED")
|
account_info = self.session.get_wallet_balance(accountType="UNIFIED")
|
||||||
|
@@ -40,6 +40,9 @@ class BybitRestClient:
|
|||||||
# Rate limiting
|
# Rate limiting
|
||||||
self.last_request_time = 0
|
self.last_request_time = 0
|
||||||
self.min_request_interval = 0.1 # 100ms between requests
|
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
|
# Request session for connection pooling
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
@@ -61,7 +64,7 @@ class BybitRestClient:
|
|||||||
HMAC-SHA256 signature
|
HMAC-SHA256 signature
|
||||||
"""
|
"""
|
||||||
# Bybit signature format: timestamp + api_key + recv_window + params
|
# 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}"
|
param_str = f"{timestamp}{self.api_key}{recv_window}{params}"
|
||||||
|
|
||||||
signature = hmac.new(
|
signature = hmac.new(
|
||||||
@@ -86,7 +89,7 @@ class BybitRestClient:
|
|||||||
'X-BAPI-API-KEY': self.api_key,
|
'X-BAPI-API-KEY': self.api_key,
|
||||||
'X-BAPI-SIGN': signature,
|
'X-BAPI-SIGN': signature,
|
||||||
'X-BAPI-TIMESTAMP': timestamp,
|
'X-BAPI-TIMESTAMP': timestamp,
|
||||||
'X-BAPI-RECV-WINDOW': '5000',
|
'X-BAPI-RECV-WINDOW': str(self.recv_window_ms),
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +119,9 @@ class BybitRestClient:
|
|||||||
self._rate_limit()
|
self._rate_limit()
|
||||||
|
|
||||||
url = f"{self.base_url}{endpoint}"
|
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:
|
if params is None:
|
||||||
params = {}
|
params = {}
|
||||||
@@ -165,6 +170,20 @@ class BybitRestClient:
|
|||||||
error_msg = result.get('retMsg', 'Unknown error')
|
error_msg = result.get('retMsg', 'Unknown error')
|
||||||
error_code = result.get('retCode', 'Unknown')
|
error_code = result.get('retCode', 'Unknown')
|
||||||
logger.error(f"Bybit Error {error_code}: {error_msg}")
|
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}")
|
raise Exception(f"Bybit Error {error_code}: {error_msg}")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -173,6 +192,18 @@ class BybitRestClient:
|
|||||||
"""Get server time (public endpoint)."""
|
"""Get server time (public endpoint)."""
|
||||||
return self._make_request('GET', '/v5/market/time')
|
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]:
|
def get_account_info(self) -> Dict[str, Any]:
|
||||||
"""Get account information (private endpoint)."""
|
"""Get account information (private endpoint)."""
|
||||||
return self._make_request('GET', '/v5/account/wallet-balance',
|
return self._make_request('GET', '/v5/account/wallet-balance',
|
||||||
|
@@ -2513,7 +2513,7 @@ class TradingExecutor:
|
|||||||
'error': str(e)
|
'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)
|
"""Execute a trade directly (compatibility method for dashboard)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -2525,10 +2525,9 @@ class TradingExecutor:
|
|||||||
bool: True if trade executed successfully
|
bool: True if trade executed successfully
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Get current price
|
# Get current price (allow explicit price or fallback to real fetch)
|
||||||
current_price = None
|
if current_price is None:
|
||||||
# Always get real current price - never use simulated data
|
current_price = self._get_real_current_price(symbol)
|
||||||
current_price = self._get_real_current_price(symbol)
|
|
||||||
if current_price is None:
|
if current_price is None:
|
||||||
logger.error(f"Failed to get real current price for {symbol}")
|
logger.error(f"Failed to get real current price for {symbol}")
|
||||||
return False
|
return False
|
||||||
|
Reference in New Issue
Block a user