fix: implement precise rounding for spot asset swaps (v1.5.7)
This commit is contained in:
@ -86,7 +86,7 @@ class DatabaseManager:
|
||||
|
||||
class PingPongBot:
|
||||
def __init__(self, config_path="config/ping_pong_config.yaml"):
|
||||
self.version = "1.5.6"
|
||||
self.version = "1.5.7"
|
||||
with open(config_path, 'r') as f:
|
||||
self.config = yaml.safe_load(f)
|
||||
|
||||
@ -213,10 +213,6 @@ class PingPongBot:
|
||||
if self.direction is not None:
|
||||
await self.close_all_positions()
|
||||
|
||||
# Update settings before swap to ensure we trade the right symbol/category if needed
|
||||
old_symbol = self.symbol
|
||||
old_category = self.category
|
||||
|
||||
self.direction = new_direction
|
||||
if self.direction == "long":
|
||||
self.category = "inverse"
|
||||
@ -241,6 +237,7 @@ class PingPongBot:
|
||||
return False
|
||||
|
||||
async def close_all_positions(self):
|
||||
"""Closes any active position in the current category/symbol"""
|
||||
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)
|
||||
@ -253,7 +250,7 @@ class PingPongBot:
|
||||
logger.error(f"Error closing positions: {e}")
|
||||
|
||||
async def swap_assets(self, target_direction):
|
||||
"""Point II: Exchange BTC/USDC on Spot market using UNIFIED account type"""
|
||||
"""Point II: Exchange BTC/USDC on Spot market with proper rounding"""
|
||||
try:
|
||||
logger.info(f"Swapping assets for {target_direction.upper()} mode...")
|
||||
spot_symbol = f"{self.base_coin}USDC"
|
||||
@ -265,26 +262,27 @@ class PingPongBot:
|
||||
logger.info(f"Current Balances: {coins}")
|
||||
|
||||
if target_direction == "short":
|
||||
# SHORT: Need USDC, Sell BTC
|
||||
btc_bal = coins.get(self.base_coin, 0)
|
||||
if btc_bal > 0.0001:
|
||||
# SHORT: Need USDC, Sell BTC (Max 6 decimals for BTCUSDC spot)
|
||||
btc_bal = floor(coins.get(self.base_coin, 0) * 1000000) / 1000000
|
||||
if btc_bal > 0.000001:
|
||||
logger.info(f"Spot: Selling {btc_bal} {self.base_coin} for USDC")
|
||||
res = await asyncio.to_thread(self.session.place_order,
|
||||
category="spot", symbol=spot_symbol, side="Sell", orderType="Market", qty=str(btc_bal)
|
||||
category="spot", symbol=spot_symbol, side="Sell", orderType="Market", qty=f"{btc_bal:.6f}"
|
||||
)
|
||||
logger.info(f"Swap Result: {res['retMsg']}")
|
||||
else:
|
||||
# LONG: Need BTC, Buy BTC with USDC
|
||||
usdc_bal = coins.get("USDC", 0)
|
||||
# LONG: Need BTC, Buy BTC with USDC (Max 4 decimals for USDC amount)
|
||||
usdc_bal = floor(coins.get("USDC", 0) * 10000) / 10000
|
||||
if usdc_bal > 1.0:
|
||||
logger.info(f"Spot: Buying {self.base_coin} with {usdc_bal} USDC")
|
||||
# marketUnit='quote' means spending USDC
|
||||
res = await asyncio.to_thread(self.session.place_order,
|
||||
category="spot", symbol=spot_symbol, side="Buy", orderType="Market",
|
||||
qty=str(usdc_bal), marketUnit="quote"
|
||||
qty=f"{usdc_bal:.4f}", marketUnit="quote"
|
||||
)
|
||||
logger.info(f"Swap Result: {res['retMsg']}")
|
||||
|
||||
await asyncio.sleep(3) # Wait for spot settlement
|
||||
await asyncio.sleep(5) # Wait for spot settlement
|
||||
except Exception as e:
|
||||
logger.error(f"Asset Swap Error: {e}")
|
||||
|
||||
@ -295,12 +293,15 @@ class PingPongBot:
|
||||
if ticker['retCode'] == 0:
|
||||
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=self.settle_coin if self.category == "linear" else None)
|
||||
# settleCoin is only for USDC linear perpetuals
|
||||
settle_coin = "USDC" if (self.category == "linear" and "USDC" in self.symbol) else None
|
||||
pos = await asyncio.to_thread(self.session.get_positions, category=self.category, symbol=self.symbol, settleCoin=settle_coin)
|
||||
if pos['retCode'] == 0:
|
||||
active = [p for p in pos['result']['list'] if float(p.get('size', 0)) > 0]
|
||||
self.position = active[0] if active else None
|
||||
|
||||
wallet = await asyncio.to_thread(self.session.get_wallet_balance, category=self.category, accountType="UNIFIED", coin=self.settle_coin)
|
||||
target_coin = self.settle_coin
|
||||
wallet = await asyncio.to_thread(self.session.get_wallet_balance, category=self.category, accountType="UNIFIED", coin=target_coin)
|
||||
if wallet['retCode'] == 0:
|
||||
res_list = wallet['result']['list']
|
||||
if res_list:
|
||||
@ -311,17 +312,18 @@ class PingPongBot:
|
||||
def check_signals(self, df):
|
||||
if len(df) < 2: return None
|
||||
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'] or {}, self.config['hurst'] or {}
|
||||
|
||||
l_open = (rsi_cfg['enabled_for_open'] and prev['rsi'] < rsi_cfg['oversold'] and last['rsi'] >= rsi_cfg['oversold']) or \
|
||||
(hurst_cfg['enabled_for_open'] and prev['close'] > prev['hurst_lower'] and last['close'] <= last['hurst_lower'])
|
||||
l_close = (rsi_cfg['enabled_for_close'] and prev['rsi'] > rsi_cfg['overbought'] and last['rsi'] <= rsi_cfg['overbought']) or \
|
||||
(hurst_cfg['enabled_for_close'] and prev['close'] < prev['hurst_upper'] and last['close'] >= last['hurst_upper'])
|
||||
# Signals defined by crossover
|
||||
l_open = (rsi_cfg.get('enabled_for_open') and prev['rsi'] < rsi_cfg.get('oversold', 30) and last['rsi'] >= rsi_cfg.get('oversold', 30)) or \
|
||||
(hurst_cfg.get('enabled_for_open') and prev['close'] > prev['hurst_lower'] and last['close'] <= last['hurst_lower'])
|
||||
l_close = (rsi_cfg.get('enabled_for_close') and prev['rsi'] > rsi_cfg.get('overbought', 70) and last['rsi'] <= rsi_cfg.get('overbought', 70)) or \
|
||||
(hurst_cfg.get('enabled_for_close') and prev['close'] < prev['hurst_upper'] and last['close'] >= last['hurst_upper'])
|
||||
|
||||
s_open = (rsi_cfg['enabled_for_open'] and prev['rsi'] > rsi_cfg['overbought'] and last['rsi'] <= rsi_cfg['overbought']) or \
|
||||
(hurst_cfg['enabled_for_open'] and prev['close'] < prev['hurst_upper'] and last['close'] >= last['hurst_upper'])
|
||||
s_close = (rsi_cfg['enabled_for_close'] and prev['rsi'] < rsi_cfg['oversold'] and last['rsi'] >= rsi_cfg['oversold']) or \
|
||||
(hurst_cfg['enabled_for_close'] and prev['close'] > prev['hurst_lower'] and last['close'] <= last['hurst_lower'])
|
||||
s_open = (rsi_cfg.get('enabled_for_open') and prev['rsi'] > rsi_cfg.get('overbought', 70) and last['rsi'] <= rsi_cfg.get('overbought', 70)) or \
|
||||
(hurst_cfg.get('enabled_for_open') and prev['close'] < prev['hurst_upper'] and last['close'] >= last['hurst_upper'])
|
||||
s_close = (rsi_cfg.get('enabled_for_close') and prev['rsi'] < rsi_cfg.get('oversold', 30) and last['rsi'] >= rsi_cfg.get('oversold', 30)) or \
|
||||
(hurst_cfg.get('enabled_for_close') and prev['close'] > prev['hurst_lower'] and last['close'] <= last['hurst_lower'])
|
||||
|
||||
if self.direction == 'long':
|
||||
return "open" if l_open else ("close" if l_close else None)
|
||||
@ -441,6 +443,7 @@ class PingPongBot:
|
||||
|
||||
await asyncio.sleep(5)
|
||||
|
||||
from math import floor
|
||||
if __name__ == "__main__":
|
||||
bot = PingPongBot()
|
||||
asyncio.run(bot.run())
|
||||
|
||||
Reference in New Issue
Block a user