fix: resolve pybit TypeError and implement async timeouts (v1.3.7)
This commit is contained in:
@ -68,7 +68,7 @@ class DatabaseManager:
|
|||||||
|
|
||||||
class PingPongBot:
|
class PingPongBot:
|
||||||
def __init__(self, config_path="config/ping_pong_config.yaml"):
|
def __init__(self, config_path="config/ping_pong_config.yaml"):
|
||||||
self.version = "1.3.6"
|
self.version = "1.3.7"
|
||||||
with open(config_path, 'r') as f:
|
with open(config_path, 'r') as f:
|
||||||
self.config = yaml.safe_load(f)
|
self.config = yaml.safe_load(f)
|
||||||
|
|
||||||
@ -78,12 +78,10 @@ class PingPongBot:
|
|||||||
if not self.api_key or not self.api_secret:
|
if not self.api_key or not self.api_secret:
|
||||||
raise ValueError("API_KEY and API_SECRET must be set in .env file")
|
raise ValueError("API_KEY and API_SECRET must be set in .env file")
|
||||||
|
|
||||||
# Added timeout to prevent hanging
|
|
||||||
self.session = HTTP(
|
self.session = HTTP(
|
||||||
testnet=False,
|
testnet=False,
|
||||||
api_key=self.api_key,
|
api_key=self.api_key,
|
||||||
api_secret=self.api_secret,
|
api_secret=self.api_secret
|
||||||
request_timeout=10
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.db = DatabaseManager()
|
self.db = DatabaseManager()
|
||||||
@ -159,17 +157,19 @@ class PingPongBot:
|
|||||||
return df
|
return df
|
||||||
|
|
||||||
async def update_exchange_data(self):
|
async def update_exchange_data(self):
|
||||||
|
"""Fetch Price, Balance, Position every 15s with timeout"""
|
||||||
try:
|
try:
|
||||||
ticker = self.session.get_tickers(category="linear", symbol=self.symbol)
|
# Wrap synchronous pybit calls in asyncio.to_thread for better async behavior
|
||||||
|
ticker = await asyncio.to_thread(self.session.get_tickers, category="linear", symbol=self.symbol)
|
||||||
if ticker['retCode'] == 0:
|
if ticker['retCode'] == 0:
|
||||||
self.market_price = float(ticker['result']['list'][0]['lastPrice'])
|
self.market_price = float(ticker['result']['list'][0]['lastPrice'])
|
||||||
|
|
||||||
pos = self.session.get_positions(category="linear", symbol=self.symbol, settleCoin="USDT")
|
pos = await asyncio.to_thread(self.session.get_positions, category="linear", symbol=self.symbol, settleCoin="USDT")
|
||||||
if pos['retCode'] == 0:
|
if pos['retCode'] == 0:
|
||||||
active = [p for p in pos['result']['list'] if float(p['size']) > 0]
|
active = [p for p in pos['result']['list'] if float(p['size']) > 0]
|
||||||
self.position = active[0] if active else None
|
self.position = active[0] if active else None
|
||||||
|
|
||||||
wallet = self.session.get_wallet_balance(category="linear", accountType="UNIFIED", coin="USDT")
|
wallet = await asyncio.to_thread(self.session.get_wallet_balance, category="linear", accountType="UNIFIED", coin="USDT")
|
||||||
if wallet['retCode'] == 0:
|
if wallet['retCode'] == 0:
|
||||||
result_list = wallet['result']['list']
|
result_list = wallet['result']['list']
|
||||||
if result_list:
|
if result_list:
|
||||||
@ -222,7 +222,7 @@ class PingPongBot:
|
|||||||
side = "Sell" if (self.direction == "long" and is_close) or (self.direction == "short" and not is_close) else "Buy"
|
side = "Sell" if (self.direction == "long" and is_close) or (self.direction == "short" and not is_close) else "Buy"
|
||||||
pos_idx = 1 if self.direction == "long" else 2
|
pos_idx = 1 if self.direction == "long" else 2
|
||||||
try:
|
try:
|
||||||
res = self.session.place_order(
|
res = await asyncio.to_thread(self.session.place_order,
|
||||||
category="linear", symbol=self.symbol, side=side, orderType="Market",
|
category="linear", symbol=self.symbol, side=side, orderType="Market",
|
||||||
qty=str(round(qty, 3)), reduceOnly=is_close, positionIdx=pos_idx
|
qty=str(round(qty, 3)), reduceOnly=is_close, positionIdx=pos_idx
|
||||||
)
|
)
|
||||||
@ -235,8 +235,8 @@ class PingPongBot:
|
|||||||
logger.error(f"Trade Error: {e}")
|
logger.error(f"Trade Error: {e}")
|
||||||
|
|
||||||
def print_dashboard(self):
|
def print_dashboard(self):
|
||||||
"""Prints a clean summary to the logs (replaces Live which can hang in Docker)"""
|
"""Prints a clean summary to the logs"""
|
||||||
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
now = datetime.now().strftime("%H:%M:%S")
|
||||||
self.last_heartbeat = datetime.now()
|
self.last_heartbeat = datetime.now()
|
||||||
|
|
||||||
# 1. Header
|
# 1. Header
|
||||||
@ -264,6 +264,7 @@ class PingPongBot:
|
|||||||
pos.add_row("Entry Price", f"${float(self.position['avgPrice']):,.2f}")
|
pos.add_row("Entry Price", f"${float(self.position['avgPrice']):,.2f}")
|
||||||
pos.add_row("Unrealized PnL", f"[bold {'green' if pnl>=0 else 'red'}]${pnl:,.2f}")
|
pos.add_row("Unrealized PnL", f"[bold {'green' if pnl>=0 else 'red'}]${pnl:,.2f}")
|
||||||
else:
|
else:
|
||||||
|
pos_row = "NONE"
|
||||||
pos.add_row("Position", "NONE")
|
pos.add_row("Position", "NONE")
|
||||||
|
|
||||||
pos.add_row("Status", f"[bold blue]{self.status_msg}[/]")
|
pos.add_row("Status", f"[bold blue]{self.status_msg}[/]")
|
||||||
@ -287,7 +288,8 @@ class PingPongBot:
|
|||||||
|
|
||||||
# 1. Exchange Sync (15s)
|
# 1. Exchange Sync (15s)
|
||||||
if now_ts - last_exchange_update >= 15:
|
if now_ts - last_exchange_update >= 15:
|
||||||
await self.update_exchange_data()
|
# Wrapped in timeout to prevent hanging
|
||||||
|
await asyncio.wait_for(self.update_exchange_data(), timeout=10)
|
||||||
last_exchange_update = now_ts
|
last_exchange_update = now_ts
|
||||||
|
|
||||||
# 2. DB Sync (5s)
|
# 2. DB Sync (5s)
|
||||||
@ -311,6 +313,9 @@ class PingPongBot:
|
|||||||
# 3. Print Dashboard
|
# 3. Print Dashboard
|
||||||
self.print_dashboard()
|
self.print_dashboard()
|
||||||
|
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
logger.error("Exchange update timed out")
|
||||||
|
self.status_msg = "Exchange Update Timeout"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Loop Error: {e}")
|
logger.exception(f"Loop Error: {e}")
|
||||||
self.status_msg = f"Error: {str(e)[:50]}"
|
self.status_msg = f"Error: {str(e)[:50]}"
|
||||||
|
|||||||
Reference in New Issue
Block a user