TradeChart 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tradechart/__init__.py ADDED
@@ -0,0 +1,262 @@
1
+ """
2
+ TradeChart — production-quality financial chart generator.
3
+
4
+ A TRADELY project · https://doc.tradely.dev
5
+
6
+ Public API
7
+ ----------
8
+ tc.terminal(mode) — Set logging verbosity.
9
+ tc.theme(name) — Set chart colour theme.
10
+ tc.watermark(enabled) — Toggle the TRADELY logo watermark.
11
+ tc.config(**kwargs) — Batch-set global options.
12
+ tc.chart(...) — Fetch data and render a chart.
13
+ tc.compare(...) — Overlay multiple tickers on one chart.
14
+ tc.data(...) — Fetch raw OHLCV data as a DataFrame.
15
+ tc.export(...) — Export market data to CSV / JSON / XLSX.
16
+ tc.clear_cache() — Flush the in-memory data cache.
17
+
18
+ Example
19
+ -------
20
+ >>> import tradechart as tc
21
+ >>> tc.terminal("full")
22
+ >>> tc.chart("AAPL", "1mo", "candle", indicators=["sma", "bollinger"])
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ from pathlib import Path
28
+ from typing import Optional
29
+ import threading
30
+
31
+ import pandas as pd
32
+
33
+ from tradechart.config.settings import get_settings
34
+ from tradechart.core.engine import Engine
35
+ from tradechart.utils.exceptions import (
36
+ TradeChartError,
37
+ DataFetchError,
38
+ InvalidTickerError,
39
+ RenderError,
40
+ OutputError,
41
+ ConfigError,
42
+ )
43
+
44
+ __version__ = "2.0.0"
45
+ __all__ = [
46
+ "terminal", "theme", "watermark", "config",
47
+ "chart", "compare", "data", "export", "clear_cache",
48
+ ]
49
+
50
+ _engine: Engine | None = None
51
+ _engine_lock = threading.Lock()
52
+
53
+
54
+ def _get_engine() -> Engine:
55
+ global _engine
56
+ if _engine is None:
57
+ with _engine_lock:
58
+ if _engine is None:
59
+ _engine = Engine()
60
+ return _engine
61
+
62
+
63
+ # ── Configuration ────────────────────────────────────────────────────────────
64
+
65
+ def terminal(feedback_type: str) -> None:
66
+ """Set global logging verbosity.
67
+
68
+ Parameters
69
+ ----------
70
+ feedback_type : ``"full"`` | ``"on_done"`` | ``"none"``
71
+ """
72
+ get_settings().terminal_mode = feedback_type
73
+
74
+
75
+ def theme(name: str) -> None:
76
+ """Set the chart colour theme.
77
+
78
+ Parameters
79
+ ----------
80
+ name : ``"dark"`` | ``"light"`` | ``"classic"``
81
+ """
82
+ get_settings().theme = name
83
+
84
+
85
+ def watermark(enabled: bool = True) -> None:
86
+ """Enable or disable the TRADELY logo watermark on charts.
87
+
88
+ Parameters
89
+ ----------
90
+ enabled : bool
91
+ ``True`` to stamp the logo (default), ``False`` to omit it.
92
+ """
93
+ get_settings().watermark_enabled = enabled
94
+
95
+
96
+ def config(**kwargs) -> dict:
97
+ """Batch-set global options. Returns the current settings as a dict.
98
+
99
+ Accepted keyword arguments
100
+ --------------------------
101
+ terminal : str — logging mode
102
+ theme : str — colour theme
103
+ watermark : bool — logo watermark on/off
104
+ overwrite : bool — allow overwriting existing files
105
+ dpi : int — output resolution (50–600)
106
+ fig_size : tuple[int,int] — figure dimensions in inches
107
+ cache_ttl : int — data cache time-to-live in seconds
108
+
109
+ Example
110
+ -------
111
+ >>> tc.config(theme="light", dpi=200, overwrite=True)
112
+ """
113
+ s = get_settings()
114
+ dispatch = {
115
+ "terminal": lambda v: setattr(s, "terminal_mode", v),
116
+ "theme": lambda v: setattr(s, "theme", v),
117
+ "watermark": lambda v: setattr(s, "watermark_enabled", v),
118
+ "overwrite": lambda v: setattr(s, "overwrite", v),
119
+ "dpi": lambda v: setattr(s, "dpi", v),
120
+ "fig_size": lambda v: setattr(s, "fig_size", v),
121
+ "cache_ttl": lambda v: setattr(s, "cache_ttl", v),
122
+ }
123
+ for key, value in kwargs.items():
124
+ setter = dispatch.get(key)
125
+ if setter is None:
126
+ raise ConfigError(
127
+ f"Unknown config key '{key}'. "
128
+ f"Allowed: {', '.join(sorted(dispatch))}"
129
+ )
130
+ setter(value)
131
+
132
+ return {
133
+ "terminal": s.terminal_mode,
134
+ "theme": s.theme,
135
+ "watermark": s.watermark_enabled,
136
+ "overwrite": s.overwrite,
137
+ "dpi": s.dpi,
138
+ "fig_size": s.fig_size,
139
+ "cache_ttl": s.cache_ttl,
140
+ }
141
+
142
+
143
+ # ── Charting ─────────────────────────────────────────────────────────────────
144
+
145
+ def chart(
146
+ ticker: str,
147
+ duration: str = "1mo",
148
+ chart_type: str = "candle",
149
+ output_location: Optional[str] = None,
150
+ output_name: Optional[str] = None,
151
+ fmt: str = "png",
152
+ indicators: Optional[list[str]] = None,
153
+ show_volume: bool = True,
154
+ ) -> Path:
155
+ """Fetch market data and render a chart image.
156
+
157
+ Parameters
158
+ ----------
159
+ ticker : str
160
+ Instrument symbol — ``"AAPL"``, ``"BTC-USD"``, ``"EURUSD=X"``.
161
+ duration : str
162
+ ``"1d"`` ``"5d"`` ``"1mo"`` ``"3mo"`` ``"6mo"`` ``"1y"`` ``"2y"``
163
+ ``"5y"`` ``"10y"`` ``"max"``
164
+ chart_type : str
165
+ ``"candle"`` ``"line"`` ``"ohlc"`` ``"area"`` ``"heikin_ashi"``
166
+ output_location : str or None
167
+ Directory. Defaults to cwd; created if missing.
168
+ output_name : str or None
169
+ Filename. Defaults to ``{TICKER}_{duration}_{type}.{fmt}``.
170
+ fmt : str
171
+ ``"png"`` ``"jpg"`` ``"svg"`` ``"pdf"`` ``"webp"``
172
+ indicators : list[str] or None
173
+ ``"sma"`` ``"ema"`` ``"bollinger"`` ``"vwap"`` ``"rsi"`` ``"macd"``
174
+ ``"volume"``
175
+ show_volume : bool
176
+ Show volume subplot (default ``True``).
177
+
178
+ Returns
179
+ -------
180
+ pathlib.Path
181
+ Absolute path to the saved chart image.
182
+ """
183
+ return _get_engine().run(
184
+ ticker=ticker, duration=duration, chart_type=chart_type,
185
+ output_location=output_location, output_name=output_name,
186
+ fmt=fmt, indicators=indicators, show_volume=show_volume,
187
+ )
188
+
189
+
190
+ def compare(
191
+ tickers: list[str],
192
+ duration: str = "1mo",
193
+ output_location: Optional[str] = None,
194
+ output_name: Optional[str] = None,
195
+ fmt: str = "png",
196
+ normalise: bool = True,
197
+ ) -> Path:
198
+ """Overlay multiple tickers on a single chart for comparison.
199
+
200
+ Parameters
201
+ ----------
202
+ tickers : list[str]
203
+ 2–8 ticker symbols to compare.
204
+ duration : str
205
+ Shared time span for all tickers.
206
+ normalise : bool
207
+ If ``True`` (default), show percentage change from period start.
208
+ If ``False``, plot raw prices (useful when scales are similar).
209
+
210
+ Returns
211
+ -------
212
+ pathlib.Path
213
+ """
214
+ return _get_engine().compare(
215
+ tickers=tickers, duration=duration,
216
+ output_location=output_location, output_name=output_name,
217
+ fmt=fmt, normalise=normalise,
218
+ )
219
+
220
+
221
+ def data(ticker: str, duration: str = "1mo") -> pd.DataFrame:
222
+ """Fetch raw OHLCV market data without rendering a chart.
223
+
224
+ Parameters
225
+ ----------
226
+ ticker : str
227
+ duration : str
228
+
229
+ Returns
230
+ -------
231
+ pandas.DataFrame
232
+ Columns: Open, High, Low, Close, Volume. DatetimeIndex.
233
+ """
234
+ return _get_engine().fetch_data(ticker, duration)
235
+
236
+
237
+ def export(
238
+ ticker: str,
239
+ duration: str = "1mo",
240
+ fmt: str = "csv",
241
+ output_location: Optional[str] = None,
242
+ output_name: Optional[str] = None,
243
+ ) -> Path:
244
+ """Export market data to a file.
245
+
246
+ Parameters
247
+ ----------
248
+ fmt : ``"csv"`` | ``"json"`` | ``"xlsx"``
249
+
250
+ Returns
251
+ -------
252
+ pathlib.Path
253
+ """
254
+ return _get_engine().export(
255
+ ticker=ticker, duration=duration, fmt=fmt,
256
+ output_location=output_location, output_name=output_name,
257
+ )
258
+
259
+
260
+ def clear_cache() -> None:
261
+ """Flush the in-memory data cache."""
262
+ _get_engine().clear_cache()
@@ -0,0 +1,8 @@
1
+ """Chart rendering layer."""
2
+
3
+ from tradechart.charts.renderer import ChartRenderer
4
+ from tradechart.charts.watermark import stamp_logo, clear_cache as clear_logo_cache
5
+ from tradechart.charts.themes import Theme, get_theme
6
+ from tradechart.charts.indicators import apply_indicators
7
+
8
+ __all__ = ["ChartRenderer", "stamp_logo", "clear_logo_cache", "Theme", "get_theme", "apply_indicators"]
@@ -0,0 +1,74 @@
1
+ """Technical indicator computations added to a DataFrame in-place."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pandas as pd
6
+
7
+
8
+ def add_sma(df: pd.DataFrame, period: int = 20) -> None:
9
+ """Simple Moving Average on Close."""
10
+ df[f"SMA_{period}"] = df["Close"].rolling(window=period).mean()
11
+
12
+
13
+ def add_ema(df: pd.DataFrame, period: int = 20) -> None:
14
+ """Exponential Moving Average on Close."""
15
+ df[f"EMA_{period}"] = df["Close"].ewm(span=period, adjust=False).mean()
16
+
17
+
18
+ def add_bollinger(df: pd.DataFrame, period: int = 20, std_dev: float = 2.0) -> None:
19
+ """Bollinger Bands (upper, middle, lower)."""
20
+ sma = df["Close"].rolling(window=period).mean()
21
+ std = df["Close"].rolling(window=period).std()
22
+ df["BB_Upper"] = sma + std_dev * std
23
+ df["BB_Middle"] = sma
24
+ df["BB_Lower"] = sma - std_dev * std
25
+
26
+
27
+ def add_vwap(df: pd.DataFrame) -> None:
28
+ """Volume-Weighted Average Price (resets each day for intraday data)."""
29
+ typical = (df["High"] + df["Low"] + df["Close"]) / 3
30
+ cum_tp_vol = (typical * df["Volume"]).cumsum()
31
+ cum_vol = df["Volume"].cumsum()
32
+ df["VWAP"] = cum_tp_vol / cum_vol.replace(0, float("nan"))
33
+
34
+
35
+ def add_rsi(df: pd.DataFrame, period: int = 14) -> None:
36
+ """Relative Strength Index (0–100)."""
37
+ delta = df["Close"].diff()
38
+ gain = delta.where(delta > 0, 0.0).rolling(window=period).mean()
39
+ loss = (-delta.where(delta < 0, 0.0)).rolling(window=period).mean()
40
+ rs = gain / loss.replace(0, float("nan"))
41
+ df["RSI"] = 100 - (100 / (1 + rs))
42
+
43
+
44
+ def add_macd(
45
+ df: pd.DataFrame,
46
+ fast: int = 12,
47
+ slow: int = 26,
48
+ signal: int = 9,
49
+ ) -> None:
50
+ """MACD line, signal line, and histogram."""
51
+ ema_fast = df["Close"].ewm(span=fast, adjust=False).mean()
52
+ ema_slow = df["Close"].ewm(span=slow, adjust=False).mean()
53
+ df["MACD"] = ema_fast - ema_slow
54
+ df["MACD_Signal"] = df["MACD"].ewm(span=signal, adjust=False).mean()
55
+ df["MACD_Hist"] = df["MACD"] - df["MACD_Signal"]
56
+
57
+
58
+ # Dispatcher
59
+ _INDICATOR_FNS: dict[str, callable] = {
60
+ "sma": add_sma,
61
+ "ema": add_ema,
62
+ "bollinger": add_bollinger,
63
+ "vwap": add_vwap,
64
+ "rsi": add_rsi,
65
+ "macd": add_macd,
66
+ }
67
+
68
+
69
+ def apply_indicators(df: pd.DataFrame, indicators: list[str]) -> None:
70
+ """Apply each named indicator to *df* in-place."""
71
+ for name in indicators:
72
+ fn = _INDICATOR_FNS.get(name)
73
+ if fn is not None:
74
+ fn(df)