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 +262 -0
- tradechart/charts/__init__.py +8 -0
- tradechart/charts/indicators.py +74 -0
- tradechart/charts/renderer.py +441 -0
- tradechart/charts/themes.py +75 -0
- tradechart/charts/watermark.py +116 -0
- tradechart/config/__init__.py +6 -0
- tradechart/config/logger.py +79 -0
- tradechart/config/settings.py +141 -0
- tradechart/core/__init__.py +5 -0
- tradechart/core/engine.py +227 -0
- tradechart/data/__init__.py +6 -0
- tradechart/data/fetcher.py +88 -0
- tradechart/data/models.py +71 -0
- tradechart/data/provider_base.py +20 -0
- tradechart/providers/__init__.py +7 -0
- tradechart/providers/stooq_provider.py +62 -0
- tradechart/providers/tradingview_provider.py +69 -0
- tradechart/providers/yfinance_provider.py +38 -0
- tradechart/utils/__init__.py +21 -0
- tradechart/utils/exceptions.py +27 -0
- tradechart/utils/formatting.py +20 -0
- tradechart/utils/validation.py +99 -0
- tradechart-2.0.0.dist-info/METADATA +269 -0
- tradechart-2.0.0.dist-info/RECORD +28 -0
- tradechart-2.0.0.dist-info/WHEEL +5 -0
- tradechart-2.0.0.dist-info/licenses/LICENSE +21 -0
- tradechart-2.0.0.dist-info/top_level.txt +1 -0
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)
|