fix: resolve Illegal category error by adding safety checks (v1.5.1)

This commit is contained in:
Gemini CLI
2026-03-05 23:21:15 +01:00
parent 6e6e9db5cc
commit f3ce68de22

View File

@ -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.5.0" self.version = "1.5.1"
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)
@ -170,8 +170,11 @@ class PingPongBot:
df_1d['close'] = df_1d['close'].astype(float) df_1d['close'] = df_1d['close'].astype(float)
self.ma_44_val = df_1d['close'].rolling(window=44).mean().iloc[-1] self.ma_44_val = df_1d['close'].rolling(window=44).mean().iloc[-1]
# Get current price from exchange # Use BTCUSDT for price check if not initialized, otherwise use current symbol
ticker = await asyncio.to_thread(self.session.get_tickers, category="linear", symbol=f"{self.base_coin}USDC") ticker_symbol = self.symbol if self.symbol else f"{self.base_coin}USDT"
ticker_cat = self.category if self.category else "linear"
ticker = await asyncio.to_thread(self.session.get_tickers, category=ticker_cat, symbol=ticker_symbol)
current_price = float(ticker['result']['list'][0]['lastPrice']) current_price = float(ticker['result']['list'][0]['lastPrice'])
self.market_price = current_price self.market_price = current_price
@ -210,6 +213,7 @@ class PingPongBot:
async def close_all_positions(self): async def close_all_positions(self):
"""Closes any active position in the current category/symbol""" """Closes any active position in the current category/symbol"""
try: try:
if not self.category or not self.symbol: return
pos = await asyncio.to_thread(self.session.get_positions, category=self.category, symbol=self.symbol) pos = await asyncio.to_thread(self.session.get_positions, category=self.category, symbol=self.symbol)
if pos['retCode'] == 0: if pos['retCode'] == 0:
for p in pos['result']['list']: for p in pos['result']['list']:
@ -254,12 +258,15 @@ class PingPongBot:
async def update_exchange_data(self): async def update_exchange_data(self):
"""Fetch Price, Balance, Position every 15s""" """Fetch Price, Balance, Position every 15s"""
if not self.category or not self.symbol: return
try: try:
ticker = await asyncio.to_thread(self.session.get_tickers, category=self.category, symbol=self.symbol) ticker = await asyncio.to_thread(self.session.get_tickers, category=self.category, 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 = await asyncio.to_thread(self.session.get_positions, category=self.category, symbol=self.symbol, settleCoin="USDT" if self.category == "linear" else None) # settleCoin is only for linear perpetuals
settle_coin = "USDC" if self.category == "linear" else None
pos = await asyncio.to_thread(self.session.get_positions, category=self.category, symbol=self.symbol, settleCoin=settle_coin)
if pos['retCode'] == 0: if pos['retCode'] == 0:
active = [p for p in pos['result']['list'] if float(p.get('size', 0)) > 0] active = [p for p in pos['result']['list'] if float(p.get('size', 0)) > 0]
self.position = active[0] if active else None self.position = active[0] if active else None
@ -270,13 +277,12 @@ class PingPongBot:
if wallet['retCode'] == 0: if wallet['retCode'] == 0:
res_list = wallet['result']['list'] res_list = wallet['result']['list']
if res_list: if res_list:
# In inverse, we value in BTC, but dashboard usually shows USD.
# We'll stick to totalWalletBalance which is usually USD-equiv in UTA
self.wallet_balance = float(res_list[0].get('totalWalletBalance', 0)) self.wallet_balance = float(res_list[0].get('totalWalletBalance', 0))
except Exception as e: except Exception as e:
logger.error(f"Exchange Sync Error: {e}") logger.error(f"Exchange Sync Error: {e}")
def check_signals(self, df): def check_signals(self, df):
if len(df) < 2: return None
last, prev = df.iloc[-1], df.iloc[-2] last, prev = df.iloc[-1], df.iloc[-2]
rsi_cfg, hurst_cfg = self.config['rsi'], self.config['hurst'] rsi_cfg, hurst_cfg = self.config['rsi'], self.config['hurst']
@ -297,7 +303,7 @@ class PingPongBot:
return "open" if s_open else ("close" if s_close else None) return "open" if s_open else ("close" if s_close else None)
async def execute_trade(self, signal): async def execute_trade(self, signal):
if not signal: return if not signal or not self.market_price: return
last_price = self.market_price last_price = self.market_price
if signal == "close" and self.position: if signal == "close" and self.position:
@ -308,15 +314,14 @@ class PingPongBot:
elif signal == "open": elif signal == "open":
cur_qty = float(self.position['size']) if self.position else 0 cur_qty = float(self.position['size']) if self.position else 0
# Notional calculation differs between Linear and Inverse
if self.category == "linear": if self.category == "linear":
cur_notional = cur_qty * last_price cur_notional = cur_qty * last_price
ping_notional = self.pos_size_margin * self.leverage ping_notional = self.pos_size_margin * self.leverage
qty_to_open = ping_notional / last_price qty_to_open = ping_notional / last_price
else: # Inverse else: # Inverse
cur_notional = cur_qty # Inverse size is in USD usually, but Bybit V5 size is in contracts (USD) cur_notional = cur_qty
ping_notional = self.pos_size_margin * self.leverage ping_notional = self.pos_size_margin * self.leverage
qty_to_open = ping_notional # For Inverse BTCUSD, Qty is USD amount qty_to_open = ping_notional
if (cur_notional + ping_notional) / max(self.wallet_balance, 1) <= self.max_eff_lev: if (cur_notional + ping_notional) / max(self.wallet_balance, 1) <= self.max_eff_lev:
await self.place_order(qty_to_open, is_close=False) await self.place_order(qty_to_open, is_close=False)
@ -324,10 +329,10 @@ class PingPongBot:
self.status_msg = "Max Leverage Reached" self.status_msg = "Max Leverage Reached"
async def place_order(self, qty, is_close=False): async def place_order(self, qty, is_close=False):
if not self.category or not self.symbol: return
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:
# Rounding: Linear BTC needs 3 decimals, Inverse BTCUSD needs integers (USD)
qty_str = str(int(qty)) if self.category == "inverse" else str(round(qty, 3)) qty_str = str(int(qty)) if self.category == "inverse" else str(round(qty, 3))
res = await asyncio.to_thread(self.session.place_order, res = await asyncio.to_thread(self.session.place_order,
@ -371,14 +376,14 @@ class PingPongBot:
async def run(self): async def run(self):
await self.db.connect() await self.db.connect()
await self.update_direction() # Initial point I await self.update_direction()
last_exchange_update = 0 last_exchange_update = 0
while True: while True:
try: try:
now = time.time() now = time.time()
# 1. Periodically check direction (every 2m - Point III.2) # 1. Periodically check direction (every 2m)
if now - self.last_ma_check_time >= 120: if now - self.last_ma_check_time >= 120:
await self.update_direction() await self.update_direction()
self.last_ma_check_time = now self.last_ma_check_time = now