fix: resolve Illegal category error by adding safety checks (v1.5.1)
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.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
|
||||||
|
|||||||
Reference in New Issue
Block a user