""" actions.py — Action handler for dry/confirm/live modes. ======================================================== Every action the bot takes (place order, cancel order, update TP) goes through this handler. The mode controls what actually happens. MODES: ------ dryrun → Logs what it would do. Returns None. No API calls made. confirm → Prints the action, waits for y/n input. Only proceeds on 'y'. live → Executes immediately, no prompts. All three modes log the action clearly so you can audit what happened. WHY THIS MATTERS: ----------------- Before trusting the bot with live orders, run in dryrun and confirm modes. Watch the logs for several days. Only switch to live when you're confident the logic is correct and the orders it wants to place match your expectations. """ import logging from typing import Optional from client import CapitalClient import config log = logging.getLogger("maxbot.actions") class ActionHandler: def __init__(self, mode: str, client: CapitalClient): self.mode = mode self.client = client self.actions_taken = 0 self.orders_placed = 0 self.orders_cancelled = 0 self.tp_updates = 0 def _log_action(self, symbol: str, description: str): """Print a highly visible action log line.""" log.warning(f"") log.warning(f"{'━' * 50}") log.warning(f" {symbol} {description}") log.warning(f"{'━' * 50}") def _confirm(self, description: str) -> bool: if self.mode == "dryrun": log.info(f"[DRY RUN] Would: {description}") return False if self.mode == "confirm": print(f"\n ACTION: {description}") try: answer = input(" Proceed? (y/n): ").strip().lower() except (EOFError, KeyboardInterrupt): return False return answer == "y" return True # live # ───────────────────────────────────────── # ACTIONS # ───────────────────────────────────────── def place_limit_order(self, size: float, limit_price: float, take_profit: float) -> Optional[dict]: desc = ( f"PLACE ORDER entry ${limit_price:.2f} " f"size {size} TP ${take_profit:.2f}" ) if self._confirm(desc): result = self.client.place_limit_order(size, limit_price, take_profit) self._log_action("✚", desc) self.actions_taken += 1 self.orders_placed += 1 return result return None def cancel_order(self, deal_id: str, price: float) -> Optional[dict]: desc = f"CANCEL ORDER ${price:.2f}" if self._confirm(desc): result = self.client.cancel_order(deal_id) self._log_action("✖", desc) self.actions_taken += 1 self.orders_cancelled += 1 return result return None def enable_tp(self, deal_id: str, entry_price: float, take_profit: float) -> Optional[dict]: desc = ( f"ENABLE TP position ${entry_price:.2f} " f"→ TP ${take_profit:.2f}" ) if self._confirm(desc): result = self.client.update_position_tp(deal_id, take_profit) self._log_action("✔", desc) self.actions_taken += 1 self.tp_updates += 1 return result return None def disable_tp(self, deal_id: str, entry_price: float) -> Optional[dict]: desc = f"DISABLE TP position ${entry_price:.2f} (manual close)" if self._confirm(desc): result = self.client.update_position_tp(deal_id, None) self._log_action("⊘", desc) self.actions_taken += 1 self.tp_updates += 1 return result return None def summary(self) -> str: return ( f"Session summary: " f"{self.actions_taken} total actions | " f"{self.orders_placed} placed | " f"{self.orders_cancelled} cancelled | " f"{self.tp_updates} TP updates" )