Coverage for src/cc_liquid/cli_callbacks.py: 0%

1"""Rich-based CLI callbacks implementation.

2

3 This module depends on Rich and rendering helpers and is intentionally

4 separated from the core `callbacks` definitions to avoid UI deps in core.

5"""

6

7 from typing import Any

8

9 from rich.console import Console

10 from rich.prompt import Confirm

11

12 from .callbacks import CCLiquidCallbacks

13 from .cli_display import display_execution_summary, show_rebalancing_plan

14

15

16 class RichCLICallbacks(CCLiquidCallbacks):

17 """Rich CLI/Terminal UI implementation of `CCLiquidCallbacks`."""

18

19 def __init__(self):

20 self.console = Console()

21

22 def ask_confirmation(self, message: str) -> bool:

23 return Confirm.ask(f"\n[bold yellow]{message}[/bold yellow]")

24

25 def info(self, message: str) -> None:

26 self.console.print(f"[cyan]{message}[/cyan]")

27

28 def warn(self, message: str) -> None:

29 self.console.print(f"[yellow]{message}[/yellow]")

30

31 def error(self, message: str) -> None:

32 self.console.print(f"[red]{message}[/red]")

33

34 def on_config_override(self, overrides: list[str]) -> None:

35 if overrides:

36 self.console.print(

37 f"[yellow]Applied CLI overrides: {', '.join(overrides)}[/yellow]"

38 )

39

40 def on_trade_start(self, idx: int, total: int, trade: dict[str, Any]) -> None:

41 side = "BUY" if trade["is_buy"] else "SELL"

42 side_style = "green" if side == "BUY" else "red"

43 self.console.print(

44 f" [{idx}/{total}] {trade['coin']} [{side_style}]{side}[/{side_style}] "

45 f"{trade['sz']:.4f} @ ${trade['price']:,.4f}...",

46 end="",

47 )

48

49 def on_trade_fill(

50 self, trade: dict[str, Any], fill_data: dict[str, Any], slippage_pct: float

51 ) -> None:

52 avg_px = float(fill_data.get("avgPx", trade["price"]))

53 slippage_style = "green" if slippage_pct <= 0 else "red"

54 self.console.print(

55 f" [green]✓[/green] Filled @ ${avg_px:,.4f} "

56 f"([{slippage_style}]{slippage_pct:+.3f}%[/{slippage_style}])"

57 )

58

59 def on_trade_fail(self, trade: dict[str, Any], reason: str) -> None:

60 self.console.print(f" [red]✗ {reason}[/red]")

61

62 def on_batch_complete(self, success: list[dict], failed: list[dict]) -> None:

63 if not success and not failed:

64 return

65 total = len(success) + len(failed)

66 self.console.print(

67 f"\n[bold]Batch Complete:[/bold] "

68 f"[green]{len(success)}/{total} succeeded[/green]"

69 f"{f' [red]{len(failed)} failed[/red]' if failed else ''}"

70 )

71

72 def show_trade_plan(

73 self,

74 target_positions: dict,

75 trades: list,

76 account_value: float,

77 leverage: float,

78 ) -> None:

79 show_rebalancing_plan(

80 self.console, target_positions, trades, account_value, leverage

81 )

82

83 def show_execution_summary(

84 self,

85 successful_trades: list[dict],

86 all_trades: list[dict],

87 target_positions: dict,

88 account_value: float,

89 ) -> None:

90 display_execution_summary(

91 self.console, successful_trades, all_trades, target_positions, account_value

92 )

93

94 def on_order_submitted(self, order) -> None:

95 strategy = order.strategy.upper().replace("_", " ")

96 self.console.print(

97 f"[cyan]Order submitted:[/cyan] {order.coin} {order.side} "

98 f"({strategy}, ID: {order.order_id[:12]}...)"

99 )

100

101 def on_order_update(self, order, fill_pct: float) -> None:

102 self.console.print(

103 f" {order.coin}: {fill_pct:.0f}% filled "

104 f"({order.filled_size:.2f}/{order.size:.2f})"

105 )

106

107 def show_open_orders(self, orders: list) -> None:

108 from .cli_display import create_open_orders_panel

109 panel = create_open_orders_panel(orders)

110 self.console.print(panel)

111

112 def show_order_history(self, history, days: int) -> None:

113 from .cli_display import create_order_history_panel

114 panel = create_order_history_panel(history, days)

115 self.console.print(panel)