Coverage for src / monte_neo / visualization / charts.py: 86%
58 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-28 16:27 +0200
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-28 16:27 +0200
1"""Chart visualization module.
3Generate charts for indicator visualization.
4"""
6from __future__ import annotations
8from typing import TYPE_CHECKING
10import pandas as pd
12from monte_neo.utils.logger import get_logger
14if TYPE_CHECKING:
15 pass
17logger = get_logger(__name__)
20class ChartGenerator:
21 """Generate trading charts."""
23 def __init__(self) -> None:
24 """Initialize chart generator."""
25 self._has_mplfinance = False
26 try:
27 import mplfinance # noqa
29 self._has_mplfinance = True
30 except ImportError:
31 logger.warning("mplfinance not available, using plotext")
33 def plot_candlestick(
34 self,
35 data: pd.DataFrame,
36 title: str = "Price Chart",
37 save_path: str | None = None,
38 ) -> None:
39 """Plot candlestick chart.
41 Args:
42 data: OHLCV DataFrame.
43 title: Chart title.
44 save_path: Path to save image.
45 """
46 if self._has_mplfinance:
47 self._plot_mpl(data, title, save_path)
48 else:
49 self._plot_terminal(data, title)
51 def _plot_mpl(
52 self,
53 data: pd.DataFrame,
54 title: str,
55 save_path: str | None,
56 ) -> None:
57 """Plot with mplfinance."""
58 import mplfinance as mpf
60 # Prepare data
61 df = data.copy()
62 if not isinstance(df.index, pd.DatetimeIndex):
63 df.index = pd.to_datetime(df.index)
65 kwargs = {
66 "type": "candle",
67 "volume": True,
68 "title": title,
69 "style": "charles",
70 "figsize": (12, 8),
71 }
73 if save_path:
74 kwargs["savefig"] = save_path
75 mpf.plot(df, **kwargs)
76 else:
77 mpf.plot(df, **kwargs)
79 def _plot_terminal(
80 self,
81 data: pd.DataFrame,
82 title: str,
83 ) -> None:
84 """Plot in terminal with plotext."""
85 import plotext as plt
87 plt.clear_figure()
88 plt.title(title)
90 # Use candlestick if OHLC data is available
91 if all(col in data.columns for col in ["open", "high", "low", "close"]):
92 if hasattr(data.index, "astype"):
93 dates = data.index.astype(str).tolist()
94 else:
95 dates = list(range(len(data)))
96 # Plotext expects lists
97 plt.candlestick(
98 dates,
99 data["open"].tolist(),
100 data["high"].tolist(),
101 data["low"].tolist(),
102 data["close"].tolist(),
103 label="OHLC",
104 orientation="vertical"
105 )
106 else:
107 plt.plot(data["close"].values, label="Close")
109 plt.show()
111 def plot_with_signals(
112 self,
113 data: pd.DataFrame,
114 signals: pd.DataFrame,
115 title: str = "Chart with Signals",
116 ) -> None:
117 """Plot chart with entry/exit signals.
119 Args:
120 data: OHLCV DataFrame.
121 signals: Signals DataFrame.
122 title: Chart title.
123 """
124 import plotext as plt
126 plt.clear_figure()
127 plt.title(title)
129 # Price
130 close = data["close"].values
131 plt.plot(close, label="Price")
133 # Entry signals
134 if signals is not None:
135 # Normalize signals to Series if it's a dict or DataFrame
136 if isinstance(signals, dict):
137 sig_series = pd.Series(signals.get("signal", 0), index=data.index)
138 elif isinstance(signals, pd.DataFrame):
139 sig_series = signals["signal"] if "signal" in signals.columns else signals.iloc[:, 0]
140 else:
141 sig_series = pd.Series(signals, index=data.index)
143 entries = sig_series == 1
144 exits = sig_series == -1
146 # Mark entries and exits
147 for i in range(len(sig_series)):
148 if entries.iloc[i]:
149 plt.scatter([i], [close[i]], marker="▲", color="green")
150 elif exits.iloc[i]:
151 plt.scatter([i], [close[i]], marker="▼", color="red")
153 plt.show()