Skip to content
4 changes: 2 additions & 2 deletions src/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,10 @@ def place_order(

except ApiError as e:
self.logger.error("Order failed: %s", e)
return None
raise # Re-raise so callers get full error details (status_code, response_body)
except Exception as e:
self.logger.error("Unexpected error placing order: %s", e)
return None
raise

def cancel_order(self, order_id: str) -> bool:
"""
Expand Down
29 changes: 26 additions & 3 deletions src/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,16 @@

class ApiError(Exception):
"""Base exception for API errors."""
pass
def __init__(self, message: str, status_code: int = 0, response_body: str = ""):
super().__init__(message)
self.status_code = status_code
self.response_body = response_body

def __str__(self) -> str:
base = super().__str__()
if self.status_code:
return f"[HTTP {self.status_code}] {base}"
return base


class AuthenticationError(ApiError):
Expand Down Expand Up @@ -222,7 +231,7 @@ def _request(

except requests.exceptions.HTTPError as e:
if e.response is not None and e.response.status_code == 429:
raise RateLimitError("Rate limit exceeded")
raise RateLimitError("Rate limit exceeded", status_code=429)
last_error = e
self.logger.warning(f"HTTP error (attempt {attempt + 1}): {e}")

Expand All @@ -235,7 +244,21 @@ def _request(
self.logger.debug(f"Retrying in {sleep_time}s...")
time.sleep(sleep_time)

raise ApiError(f"Request failed after {self.retry_count} attempts: {last_error}")
# Extract HTTP status code and response body from the last error if available
status_code = 0
response_body = ""
if isinstance(last_error, requests.exceptions.HTTPError) and last_error.response is not None:
status_code = last_error.response.status_code
try:
response_body = last_error.response.text
except Exception:
response_body = ""

raise ApiError(
f"Request failed after {self.retry_count} attempts: {last_error}",
status_code=status_code,
response_body=response_body
)


class ClobClient(ApiClient):
Expand Down
43 changes: 43 additions & 0 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,9 +320,26 @@ async def run_loop(self, scan_interval: int = 5) -> None:
self.logger.info("-" * 60)

loop_count = 0
last_summary_hour = -1

while not shutdown_event.is_set():
loop_count += 1

# Send periodic telegram summary at 00:00, 08:00, 16:00
try:
from datetime import datetime
current_hour = datetime.now().hour
if current_hour in [0, 8, 16] and current_hour != last_summary_hour:
from src.telegram_notifier import send_status_update
pos_summary = self.position_tracker.get_summary()
curr_exposure = pos_summary.get("total_exposure", 0.0)
max_exposure = getattr(self.risk_manager.config, "max_total_exposure", 500.0)
stats = self.stats_tracker.get_performance_summary()

asyncio.create_task(send_status_update(curr_exposure, max_exposure, stats, self.consecutive_failures))
last_summary_hour = current_hour
except Exception as e:
self.logger.error("Failed to schedule periodic telegram summary: %s", e)

try:
# Check circuit breakers
Expand All @@ -336,6 +353,11 @@ async def run_loop(self, scan_interval: int = 5) -> None:

if not can_continue:
self.logger.warning("Circuit breaker triggered: %s", reason)

# Alert on circuit breaker
from src.telegram_notifier import send_error_alert
asyncio.create_task(send_error_alert(reason, "Circuit Breaker Triggered"))

# Wait longer before retrying
await asyncio.sleep(60)
continue
Expand All @@ -360,6 +382,13 @@ async def run_loop(self, scan_interval: int = 5) -> None:
except Exception as e:
self.consecutive_failures += 1
self.logger.error("Error in trading loop: %s", e)

# Send error alert to telegram
try:
from src.telegram_notifier import send_error_alert
asyncio.create_task(send_error_alert(str(e), "Main Trading Loop Exception"))
except Exception as alert_err:
self.logger.error("Failed to send error alert: %s", alert_err)

# If too many failures, increase backoff
if self.consecutive_failures >= 5:
Expand Down Expand Up @@ -476,10 +505,24 @@ async def main():
else:
logger.info("Skipping API connection in dry-run mode")

# Notify Telegram: bot is live
try:
from src.telegram_notifier import send_bot_started
config_summary = f"Exposure limit: ${config.gabagool.max_total_exposure:.0f} | Trade size: ${config.gabagool.max_position_per_market / 2:.0f}/side"
await send_bot_started(dry_run=args.dry_run, config_summary=config_summary)
except Exception as e:
logger.warning("Could not send startup telegram notification: %s", e)

# Run main loop
try:
await gabagool.run_loop(scan_interval=args.scan_interval)
finally:
# Notify Telegram: bot is stopping
try:
from src.telegram_notifier import send_bot_stopped
await send_bot_stopped("Graceful shutdown")
except Exception as e:
logger.warning("Could not send shutdown telegram notification: %s", e)
gabagool.shutdown()


Expand Down
Loading