TradeChart 2.2.0__tar.gz → 2.3.1__tar.gz
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-2.2.0 → tradechart-2.3.1}/PKG-INFO +23 -11
- {tradechart-2.2.0 → tradechart-2.3.1}/README.md +22 -10
- {tradechart-2.2.0 → tradechart-2.3.1}/TradeChart.egg-info/PKG-INFO +23 -11
- {tradechart-2.2.0 → tradechart-2.3.1}/pyproject.toml +1 -1
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/__init__.py +1 -1
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/charts/heatmap.py +9 -2
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/charts/renderer.py +20 -12
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/config/settings.py +3 -3
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/data/fetcher.py +65 -24
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/data/store.py +36 -1
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart_examples/discord_demo_bot/discord_bot.py +12 -5
- {tradechart-2.2.0 → tradechart-2.3.1}/LICENSE +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/TradeChart.egg-info/SOURCES.txt +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/TradeChart.egg-info/dependency_links.txt +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/TradeChart.egg-info/requires.txt +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/TradeChart.egg-info/top_level.txt +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/setup.cfg +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/charts/__init__.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/charts/indicators.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/charts/themes.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/charts/watermark.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/config/__init__.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/config/logger.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/core/__init__.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/core/engine.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/data/__init__.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/data/groups.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/data/models.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/data/provider_base.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/providers/__init__.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/providers/stooq_provider.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/providers/tradingview_provider.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/providers/yfinance_provider.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/utils/__init__.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/utils/exceptions.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/utils/formatting.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/utils/install.py +0 -0
- {tradechart-2.2.0 → tradechart-2.3.1}/tradechart/utils/validation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: TradeChart
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.1
|
|
4
4
|
Summary: Production-quality financial chart generator — candlestick, line, area, OHLC, Heikin-Ashi, performance heatmaps, and sector group charts.
|
|
5
5
|
Author-email: "TRADELY.DEV" <dev@tradely.dev>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -45,7 +45,7 @@ Requires-Dist: build>=1.0; extra == "dev"
|
|
|
45
45
|
Requires-Dist: twine>=5.0; extra == "dev"
|
|
46
46
|
Dynamic: license-file
|
|
47
47
|
|
|
48
|
-
# TradeChart — Library Edition v2.
|
|
48
|
+
# TradeChart — Library Edition v2.3.1
|
|
49
49
|
|
|
50
50
|
**Python → Financial Charts**
|
|
51
51
|
Generate production-quality candlestick, line, area, OHLC, Heikin-Ashi, and performance heatmap charts from code.
|
|
@@ -116,11 +116,20 @@ Any valid `SECTOR_GROUPS` key (`"mag7"`, `"tech"`, `"finance"`, `"energy"`, …)
|
|
|
116
116
|
|
|
117
117
|
### How it works
|
|
118
118
|
|
|
119
|
-
Once a store path is configured, every call to `chart()`, `data()`, `export()`, `compare()`, or `heatmap()`
|
|
119
|
+
Once a store path is configured, every call to `chart()`, `data()`, `export()`, `compare()`, or `heatmap()` uses a three-level lookup before touching the network:
|
|
120
120
|
|
|
121
121
|
1. **In-memory cache** — sub-millisecond, TTL-based (existing behaviour).
|
|
122
|
-
2. **Disk store** —
|
|
123
|
-
3. **
|
|
122
|
+
2. **Disk store (fresh)** — if the stored file is younger than the bar-resolution threshold, it is served as-is. No network call.
|
|
123
|
+
3. **Disk store (stale) + live fetch** — if the stored file is older than the threshold, the library fetches the current window from a live provider to get the missing bars, then **merges** those new rows on top of the stored history. Historical bars are never discarded or re-requested. The merged result is written back to disk and served.
|
|
124
|
+
|
|
125
|
+
The staleness threshold matches the resolution of the data so charts are never more than one bar behind:
|
|
126
|
+
|
|
127
|
+
| Duration | Bar resolution | Refreshes after |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `"1d"`, `"5d"` | 5 / 15-min bars | 4 hours |
|
|
130
|
+
| `"1mo"`, `"3mo"`, `"6mo"` | Daily bars | 24 hours |
|
|
131
|
+
| `"1y"`, `"2y"`, `"5y"` | Weekly bars | 7 days |
|
|
132
|
+
| `"10y"`, `"max"` | Monthly bars | 30 days |
|
|
124
133
|
|
|
125
134
|
```python
|
|
126
135
|
tc.store("/data/myproject")
|
|
@@ -129,7 +138,10 @@ tc.store("AAPL", "MSFT", "mag7", "3mo") # fetch once, write to disk
|
|
|
129
138
|
# Later — same session or a completely new one:
|
|
130
139
|
tc.chart("AAPL", "3mo") # served from disk, no network call
|
|
131
140
|
tc.data("MSFT", "3mo") # same
|
|
132
|
-
|
|
141
|
+
|
|
142
|
+
# After 24 hours (daily bars): only the new day's bar is fetched and merged
|
|
143
|
+
# in — the months of stored history are kept and reused as-is
|
|
144
|
+
tc.chart("AAPL", "3mo") # delta fetch + merge, then cached
|
|
133
145
|
```
|
|
134
146
|
|
|
135
147
|
### Clearing stored data
|
|
@@ -438,8 +450,8 @@ tc.config(
|
|
|
438
450
|
| `theme` | `str` | `"dark"` | `"dark"`, `"light"`, `"classic"` | Chart colour theme. See [Themes](#themes). |
|
|
439
451
|
| `watermark` | `bool` | `True` | `True`, `False` | Show or hide the TRADELY logo in the bottom-left corner of charts. |
|
|
440
452
|
| `overwrite` | `bool` | `False` | `True`, `False` | `False` — append `_1`, `_2`, … to the filename if it already exists. `True` — overwrite the existing file silently. |
|
|
441
|
-
| `dpi` | `int` | `
|
|
442
|
-
| `fig_size` | `tuple[int, int]` | `(
|
|
453
|
+
| `dpi` | `int` | `100` | `50`–`600` | Output resolution in dots per inch. Higher values produce larger, sharper files. |
|
|
454
|
+
| `fig_size` | `tuple[int, int]` | `(12, 6)` | Any valid `(width, height)` in inches | Matplotlib figure size. Increase for wide monitors or presentations. |
|
|
443
455
|
| `cache_ttl` | `int` | `300` | Any positive integer (seconds) | How long fetched data is kept in memory. `0` effectively disables caching. |
|
|
444
456
|
|
|
445
457
|
---
|
|
@@ -557,7 +569,7 @@ TradeChart tries providers in priority order and falls back automatically on fai
|
|
|
557
569
|
|
|
558
570
|
| Priority | Provider | Requires | Notes |
|
|
559
571
|
|---|---|---|---|
|
|
560
|
-
| 0 | **Disk store** | `tc.store(path)` called once |
|
|
572
|
+
| 0 | **Disk store** | `tc.store(path)` called once | Served from local CSV when fresh. When stale, only the missing bars are fetched from a live provider and merged on top — historical rows are never re-requested. |
|
|
561
573
|
| 1 | **yfinance** | Included by default | Primary live source. Covers stocks, ETFs, indices, crypto, forex. |
|
|
562
574
|
| 2 | **TradingView** | `pip install TradeChart[tradingview]` | Tried if yfinance returns empty or fails. Attempts multiple exchanges automatically (`NASDAQ`, `NYSE`, `AMEX`, `CRYPTO`, `FX`). |
|
|
563
575
|
| 3 | **Stooq** | Nothing — free CSV endpoint | Final fallback. No API key required. Duration mapped to a rolling date range. |
|
|
@@ -569,7 +581,7 @@ If all live providers fail and no disk data exists, a `DataFetchError` is raised
|
|
|
569
581
|
## Notes
|
|
570
582
|
|
|
571
583
|
**Persistent disk store**
|
|
572
|
-
When `tc.store()` is configured, every successful live fetch is written to `tradechart_FetchData/` as a CSV file.
|
|
584
|
+
When `tc.store()` is configured, every successful live fetch is written to `tradechart_FetchData/` as a CSV file. On subsequent requests the library checks whether the stored data is fresh enough (based on bar resolution — 24 hours for daily bars, 4 hours for intraday, etc.). If fresh, it is served directly with no network call. If stale, only the most recent bars are fetched from a live provider and merged on top of the stored history — historical rows are never discarded or re-fetched. Use `tc.clear_cache(disk=True)` to wipe stored files and force a full re-fetch.
|
|
573
585
|
|
|
574
586
|
**In-memory caching**
|
|
575
587
|
Fetched data is also cached in-memory for `cache_ttl` seconds (default 300 s / 5 minutes). All subsequent calls with the same ticker and duration within that window are served from the memory cache. Call `tc.clear_cache()` to force re-evaluation (disk store is still checked before going to the network). Set `tc.config(cache_ttl=0)` to disable the memory cache entirely.
|
|
@@ -590,7 +602,7 @@ The `"heikin_ashi"` chart type converts standard OHLC data to Heikin-Ashi candle
|
|
|
590
602
|
When `mplfinance` is installed (`pip install TradeChart[mplfinance]`), candlestick and Heikin-Ashi charts use it for higher-quality rendering. Otherwise a pure-matplotlib fallback is used automatically — no configuration required.
|
|
591
603
|
|
|
592
604
|
**Output resolution**
|
|
593
|
-
Default DPI is
|
|
605
|
+
Default DPI is 100, producing a ~1200 × 600 px image at the default figure size — well under 300 KB for all supported formats. Increase with `tc.config(dpi=200)` for sharper output, or `tc.config(dpi=300)` for print quality.
|
|
594
606
|
|
|
595
607
|
---
|
|
596
608
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# TradeChart — Library Edition v2.
|
|
1
|
+
# TradeChart — Library Edition v2.3.1
|
|
2
2
|
|
|
3
3
|
**Python → Financial Charts**
|
|
4
4
|
Generate production-quality candlestick, line, area, OHLC, Heikin-Ashi, and performance heatmap charts from code.
|
|
@@ -69,11 +69,20 @@ Any valid `SECTOR_GROUPS` key (`"mag7"`, `"tech"`, `"finance"`, `"energy"`, …)
|
|
|
69
69
|
|
|
70
70
|
### How it works
|
|
71
71
|
|
|
72
|
-
Once a store path is configured, every call to `chart()`, `data()`, `export()`, `compare()`, or `heatmap()`
|
|
72
|
+
Once a store path is configured, every call to `chart()`, `data()`, `export()`, `compare()`, or `heatmap()` uses a three-level lookup before touching the network:
|
|
73
73
|
|
|
74
74
|
1. **In-memory cache** — sub-millisecond, TTL-based (existing behaviour).
|
|
75
|
-
2. **Disk store** —
|
|
76
|
-
3. **
|
|
75
|
+
2. **Disk store (fresh)** — if the stored file is younger than the bar-resolution threshold, it is served as-is. No network call.
|
|
76
|
+
3. **Disk store (stale) + live fetch** — if the stored file is older than the threshold, the library fetches the current window from a live provider to get the missing bars, then **merges** those new rows on top of the stored history. Historical bars are never discarded or re-requested. The merged result is written back to disk and served.
|
|
77
|
+
|
|
78
|
+
The staleness threshold matches the resolution of the data so charts are never more than one bar behind:
|
|
79
|
+
|
|
80
|
+
| Duration | Bar resolution | Refreshes after |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `"1d"`, `"5d"` | 5 / 15-min bars | 4 hours |
|
|
83
|
+
| `"1mo"`, `"3mo"`, `"6mo"` | Daily bars | 24 hours |
|
|
84
|
+
| `"1y"`, `"2y"`, `"5y"` | Weekly bars | 7 days |
|
|
85
|
+
| `"10y"`, `"max"` | Monthly bars | 30 days |
|
|
77
86
|
|
|
78
87
|
```python
|
|
79
88
|
tc.store("/data/myproject")
|
|
@@ -82,7 +91,10 @@ tc.store("AAPL", "MSFT", "mag7", "3mo") # fetch once, write to disk
|
|
|
82
91
|
# Later — same session or a completely new one:
|
|
83
92
|
tc.chart("AAPL", "3mo") # served from disk, no network call
|
|
84
93
|
tc.data("MSFT", "3mo") # same
|
|
85
|
-
|
|
94
|
+
|
|
95
|
+
# After 24 hours (daily bars): only the new day's bar is fetched and merged
|
|
96
|
+
# in — the months of stored history are kept and reused as-is
|
|
97
|
+
tc.chart("AAPL", "3mo") # delta fetch + merge, then cached
|
|
86
98
|
```
|
|
87
99
|
|
|
88
100
|
### Clearing stored data
|
|
@@ -391,8 +403,8 @@ tc.config(
|
|
|
391
403
|
| `theme` | `str` | `"dark"` | `"dark"`, `"light"`, `"classic"` | Chart colour theme. See [Themes](#themes). |
|
|
392
404
|
| `watermark` | `bool` | `True` | `True`, `False` | Show or hide the TRADELY logo in the bottom-left corner of charts. |
|
|
393
405
|
| `overwrite` | `bool` | `False` | `True`, `False` | `False` — append `_1`, `_2`, … to the filename if it already exists. `True` — overwrite the existing file silently. |
|
|
394
|
-
| `dpi` | `int` | `
|
|
395
|
-
| `fig_size` | `tuple[int, int]` | `(
|
|
406
|
+
| `dpi` | `int` | `100` | `50`–`600` | Output resolution in dots per inch. Higher values produce larger, sharper files. |
|
|
407
|
+
| `fig_size` | `tuple[int, int]` | `(12, 6)` | Any valid `(width, height)` in inches | Matplotlib figure size. Increase for wide monitors or presentations. |
|
|
396
408
|
| `cache_ttl` | `int` | `300` | Any positive integer (seconds) | How long fetched data is kept in memory. `0` effectively disables caching. |
|
|
397
409
|
|
|
398
410
|
---
|
|
@@ -510,7 +522,7 @@ TradeChart tries providers in priority order and falls back automatically on fai
|
|
|
510
522
|
|
|
511
523
|
| Priority | Provider | Requires | Notes |
|
|
512
524
|
|---|---|---|---|
|
|
513
|
-
| 0 | **Disk store** | `tc.store(path)` called once |
|
|
525
|
+
| 0 | **Disk store** | `tc.store(path)` called once | Served from local CSV when fresh. When stale, only the missing bars are fetched from a live provider and merged on top — historical rows are never re-requested. |
|
|
514
526
|
| 1 | **yfinance** | Included by default | Primary live source. Covers stocks, ETFs, indices, crypto, forex. |
|
|
515
527
|
| 2 | **TradingView** | `pip install TradeChart[tradingview]` | Tried if yfinance returns empty or fails. Attempts multiple exchanges automatically (`NASDAQ`, `NYSE`, `AMEX`, `CRYPTO`, `FX`). |
|
|
516
528
|
| 3 | **Stooq** | Nothing — free CSV endpoint | Final fallback. No API key required. Duration mapped to a rolling date range. |
|
|
@@ -522,7 +534,7 @@ If all live providers fail and no disk data exists, a `DataFetchError` is raised
|
|
|
522
534
|
## Notes
|
|
523
535
|
|
|
524
536
|
**Persistent disk store**
|
|
525
|
-
When `tc.store()` is configured, every successful live fetch is written to `tradechart_FetchData/` as a CSV file.
|
|
537
|
+
When `tc.store()` is configured, every successful live fetch is written to `tradechart_FetchData/` as a CSV file. On subsequent requests the library checks whether the stored data is fresh enough (based on bar resolution — 24 hours for daily bars, 4 hours for intraday, etc.). If fresh, it is served directly with no network call. If stale, only the most recent bars are fetched from a live provider and merged on top of the stored history — historical rows are never discarded or re-fetched. Use `tc.clear_cache(disk=True)` to wipe stored files and force a full re-fetch.
|
|
526
538
|
|
|
527
539
|
**In-memory caching**
|
|
528
540
|
Fetched data is also cached in-memory for `cache_ttl` seconds (default 300 s / 5 minutes). All subsequent calls with the same ticker and duration within that window are served from the memory cache. Call `tc.clear_cache()` to force re-evaluation (disk store is still checked before going to the network). Set `tc.config(cache_ttl=0)` to disable the memory cache entirely.
|
|
@@ -543,7 +555,7 @@ The `"heikin_ashi"` chart type converts standard OHLC data to Heikin-Ashi candle
|
|
|
543
555
|
When `mplfinance` is installed (`pip install TradeChart[mplfinance]`), candlestick and Heikin-Ashi charts use it for higher-quality rendering. Otherwise a pure-matplotlib fallback is used automatically — no configuration required.
|
|
544
556
|
|
|
545
557
|
**Output resolution**
|
|
546
|
-
Default DPI is
|
|
558
|
+
Default DPI is 100, producing a ~1200 × 600 px image at the default figure size — well under 300 KB for all supported formats. Increase with `tc.config(dpi=200)` for sharper output, or `tc.config(dpi=300)` for print quality.
|
|
547
559
|
|
|
548
560
|
---
|
|
549
561
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: TradeChart
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.1
|
|
4
4
|
Summary: Production-quality financial chart generator — candlestick, line, area, OHLC, Heikin-Ashi, performance heatmaps, and sector group charts.
|
|
5
5
|
Author-email: "TRADELY.DEV" <dev@tradely.dev>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -45,7 +45,7 @@ Requires-Dist: build>=1.0; extra == "dev"
|
|
|
45
45
|
Requires-Dist: twine>=5.0; extra == "dev"
|
|
46
46
|
Dynamic: license-file
|
|
47
47
|
|
|
48
|
-
# TradeChart — Library Edition v2.
|
|
48
|
+
# TradeChart — Library Edition v2.3.1
|
|
49
49
|
|
|
50
50
|
**Python → Financial Charts**
|
|
51
51
|
Generate production-quality candlestick, line, area, OHLC, Heikin-Ashi, and performance heatmap charts from code.
|
|
@@ -116,11 +116,20 @@ Any valid `SECTOR_GROUPS` key (`"mag7"`, `"tech"`, `"finance"`, `"energy"`, …)
|
|
|
116
116
|
|
|
117
117
|
### How it works
|
|
118
118
|
|
|
119
|
-
Once a store path is configured, every call to `chart()`, `data()`, `export()`, `compare()`, or `heatmap()`
|
|
119
|
+
Once a store path is configured, every call to `chart()`, `data()`, `export()`, `compare()`, or `heatmap()` uses a three-level lookup before touching the network:
|
|
120
120
|
|
|
121
121
|
1. **In-memory cache** — sub-millisecond, TTL-based (existing behaviour).
|
|
122
|
-
2. **Disk store** —
|
|
123
|
-
3. **
|
|
122
|
+
2. **Disk store (fresh)** — if the stored file is younger than the bar-resolution threshold, it is served as-is. No network call.
|
|
123
|
+
3. **Disk store (stale) + live fetch** — if the stored file is older than the threshold, the library fetches the current window from a live provider to get the missing bars, then **merges** those new rows on top of the stored history. Historical bars are never discarded or re-requested. The merged result is written back to disk and served.
|
|
124
|
+
|
|
125
|
+
The staleness threshold matches the resolution of the data so charts are never more than one bar behind:
|
|
126
|
+
|
|
127
|
+
| Duration | Bar resolution | Refreshes after |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `"1d"`, `"5d"` | 5 / 15-min bars | 4 hours |
|
|
130
|
+
| `"1mo"`, `"3mo"`, `"6mo"` | Daily bars | 24 hours |
|
|
131
|
+
| `"1y"`, `"2y"`, `"5y"` | Weekly bars | 7 days |
|
|
132
|
+
| `"10y"`, `"max"` | Monthly bars | 30 days |
|
|
124
133
|
|
|
125
134
|
```python
|
|
126
135
|
tc.store("/data/myproject")
|
|
@@ -129,7 +138,10 @@ tc.store("AAPL", "MSFT", "mag7", "3mo") # fetch once, write to disk
|
|
|
129
138
|
# Later — same session or a completely new one:
|
|
130
139
|
tc.chart("AAPL", "3mo") # served from disk, no network call
|
|
131
140
|
tc.data("MSFT", "3mo") # same
|
|
132
|
-
|
|
141
|
+
|
|
142
|
+
# After 24 hours (daily bars): only the new day's bar is fetched and merged
|
|
143
|
+
# in — the months of stored history are kept and reused as-is
|
|
144
|
+
tc.chart("AAPL", "3mo") # delta fetch + merge, then cached
|
|
133
145
|
```
|
|
134
146
|
|
|
135
147
|
### Clearing stored data
|
|
@@ -438,8 +450,8 @@ tc.config(
|
|
|
438
450
|
| `theme` | `str` | `"dark"` | `"dark"`, `"light"`, `"classic"` | Chart colour theme. See [Themes](#themes). |
|
|
439
451
|
| `watermark` | `bool` | `True` | `True`, `False` | Show or hide the TRADELY logo in the bottom-left corner of charts. |
|
|
440
452
|
| `overwrite` | `bool` | `False` | `True`, `False` | `False` — append `_1`, `_2`, … to the filename if it already exists. `True` — overwrite the existing file silently. |
|
|
441
|
-
| `dpi` | `int` | `
|
|
442
|
-
| `fig_size` | `tuple[int, int]` | `(
|
|
453
|
+
| `dpi` | `int` | `100` | `50`–`600` | Output resolution in dots per inch. Higher values produce larger, sharper files. |
|
|
454
|
+
| `fig_size` | `tuple[int, int]` | `(12, 6)` | Any valid `(width, height)` in inches | Matplotlib figure size. Increase for wide monitors or presentations. |
|
|
443
455
|
| `cache_ttl` | `int` | `300` | Any positive integer (seconds) | How long fetched data is kept in memory. `0` effectively disables caching. |
|
|
444
456
|
|
|
445
457
|
---
|
|
@@ -557,7 +569,7 @@ TradeChart tries providers in priority order and falls back automatically on fai
|
|
|
557
569
|
|
|
558
570
|
| Priority | Provider | Requires | Notes |
|
|
559
571
|
|---|---|---|---|
|
|
560
|
-
| 0 | **Disk store** | `tc.store(path)` called once |
|
|
572
|
+
| 0 | **Disk store** | `tc.store(path)` called once | Served from local CSV when fresh. When stale, only the missing bars are fetched from a live provider and merged on top — historical rows are never re-requested. |
|
|
561
573
|
| 1 | **yfinance** | Included by default | Primary live source. Covers stocks, ETFs, indices, crypto, forex. |
|
|
562
574
|
| 2 | **TradingView** | `pip install TradeChart[tradingview]` | Tried if yfinance returns empty or fails. Attempts multiple exchanges automatically (`NASDAQ`, `NYSE`, `AMEX`, `CRYPTO`, `FX`). |
|
|
563
575
|
| 3 | **Stooq** | Nothing — free CSV endpoint | Final fallback. No API key required. Duration mapped to a rolling date range. |
|
|
@@ -569,7 +581,7 @@ If all live providers fail and no disk data exists, a `DataFetchError` is raised
|
|
|
569
581
|
## Notes
|
|
570
582
|
|
|
571
583
|
**Persistent disk store**
|
|
572
|
-
When `tc.store()` is configured, every successful live fetch is written to `tradechart_FetchData/` as a CSV file.
|
|
584
|
+
When `tc.store()` is configured, every successful live fetch is written to `tradechart_FetchData/` as a CSV file. On subsequent requests the library checks whether the stored data is fresh enough (based on bar resolution — 24 hours for daily bars, 4 hours for intraday, etc.). If fresh, it is served directly with no network call. If stale, only the most recent bars are fetched from a live provider and merged on top of the stored history — historical rows are never discarded or re-fetched. Use `tc.clear_cache(disk=True)` to wipe stored files and force a full re-fetch.
|
|
573
585
|
|
|
574
586
|
**In-memory caching**
|
|
575
587
|
Fetched data is also cached in-memory for `cache_ttl` seconds (default 300 s / 5 minutes). All subsequent calls with the same ticker and duration within that window are served from the memory cache. Call `tc.clear_cache()` to force re-evaluation (disk store is still checked before going to the network). Set `tc.config(cache_ttl=0)` to disable the memory cache entirely.
|
|
@@ -590,7 +602,7 @@ The `"heikin_ashi"` chart type converts standard OHLC data to Heikin-Ashi candle
|
|
|
590
602
|
When `mplfinance` is installed (`pip install TradeChart[mplfinance]`), candlestick and Heikin-Ashi charts use it for higher-quality rendering. Otherwise a pure-matplotlib fallback is used automatically — no configuration required.
|
|
591
603
|
|
|
592
604
|
**Output resolution**
|
|
593
|
-
Default DPI is
|
|
605
|
+
Default DPI is 100, producing a ~1200 × 600 px image at the default figure size — well under 300 KB for all supported formats. Increase with `tc.config(dpi=200)` for sharper output, or `tc.config(dpi=300)` for print quality.
|
|
594
606
|
|
|
595
607
|
---
|
|
596
608
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "TradeChart"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.3.1"
|
|
8
8
|
description = "Production-quality financial chart generator — candlestick, line, area, OHLC, Heikin-Ashi, performance heatmaps, and sector group charts."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
@@ -87,7 +87,7 @@ from tradechart.utils.exceptions import (
|
|
|
87
87
|
)
|
|
88
88
|
from tradechart.utils.validation import VALID_DURATIONS
|
|
89
89
|
|
|
90
|
-
__version__ = "2.
|
|
90
|
+
__version__ = "2.3.1"
|
|
91
91
|
__all__ = [
|
|
92
92
|
"terminal", "theme", "watermark", "config",
|
|
93
93
|
"chart", "compare", "heatmap", "data", "export", "clear_cache",
|
|
@@ -345,8 +345,15 @@ class HeatmapRenderer:
|
|
|
345
345
|
stamp_logo(fig, provider=provider)
|
|
346
346
|
|
|
347
347
|
out_file = output_path.with_suffix(f".{fmt}")
|
|
348
|
-
|
|
349
|
-
|
|
348
|
+
pil_kwargs: dict = {}
|
|
349
|
+
if fmt == "png":
|
|
350
|
+
pil_kwargs = {"compress_level": 6, "optimize": True}
|
|
351
|
+
elif fmt in ("jpg", "jpeg", "webp"):
|
|
352
|
+
pil_kwargs = {"quality": 82, "optimize": True}
|
|
353
|
+
save_kwargs: dict = {"dpi": settings.dpi, "facecolor": fig.get_facecolor(), "bbox_inches": "tight"}
|
|
354
|
+
if pil_kwargs:
|
|
355
|
+
save_kwargs["pil_kwargs"] = pil_kwargs
|
|
356
|
+
fig.savefig(out_file, **save_kwargs)
|
|
350
357
|
plt.close(fig)
|
|
351
358
|
log.detail("Saved heatmap → %s", out_file)
|
|
352
359
|
return out_file
|
|
@@ -131,9 +131,9 @@ class ChartRenderer:
|
|
|
131
131
|
|
|
132
132
|
# Wicks — two batch vlines calls instead of N individual plot() calls
|
|
133
133
|
ax_price.vlines(x_arr[up_mask], lows[up_mask], highs[up_mask],
|
|
134
|
-
colors=theme.up_color, linewidth=0.
|
|
134
|
+
colors=theme.up_color, linewidth=0.5)
|
|
135
135
|
ax_price.vlines(x_arr[~up_mask], lows[~up_mask], highs[~up_mask],
|
|
136
|
-
colors=theme.down_color, linewidth=0.
|
|
136
|
+
colors=theme.down_color, linewidth=0.5)
|
|
137
137
|
|
|
138
138
|
# Bodies — two batch bar calls instead of N individual bar() calls
|
|
139
139
|
body_w = max(0.4, min(0.8, 60 / max(len(df), 1)))
|
|
@@ -186,8 +186,7 @@ class ChartRenderer:
|
|
|
186
186
|
stamp_logo(fig, provider=meta.provider)
|
|
187
187
|
|
|
188
188
|
out_file = out.with_suffix(f".{fmt}")
|
|
189
|
-
|
|
190
|
-
facecolor=fig.get_facecolor(), bbox_inches="tight")
|
|
189
|
+
self._savefig(fig, out_file, fmt, get_settings().dpi)
|
|
191
190
|
plt.close(fig)
|
|
192
191
|
log.detail("Saved candlestick chart → %s", out_file)
|
|
193
192
|
return out_file
|
|
@@ -255,8 +254,7 @@ class ChartRenderer:
|
|
|
255
254
|
stamp_logo(fig, provider=meta.provider)
|
|
256
255
|
|
|
257
256
|
out_file = out.with_suffix(f".{fmt}")
|
|
258
|
-
|
|
259
|
-
bbox_inches="tight", facecolor=fig.get_facecolor())
|
|
257
|
+
self._savefig(fig, out_file, fmt, settings.dpi)
|
|
260
258
|
plt.close(fig)
|
|
261
259
|
get_logger().detail("Saved mplfinance chart → %s", out_file)
|
|
262
260
|
return out_file
|
|
@@ -321,8 +319,7 @@ class ChartRenderer:
|
|
|
321
319
|
stamp_logo(fig, provider=meta.provider)
|
|
322
320
|
|
|
323
321
|
out_file = out.with_suffix(f".{fmt}")
|
|
324
|
-
|
|
325
|
-
facecolor=fig.get_facecolor(), bbox_inches="tight")
|
|
322
|
+
self._savefig(fig, out_file, fmt, settings.dpi)
|
|
326
323
|
plt.close(fig)
|
|
327
324
|
get_logger().detail("Saved OHLC chart → %s", out_file)
|
|
328
325
|
return out_file
|
|
@@ -352,8 +349,7 @@ class ChartRenderer:
|
|
|
352
349
|
stamp_logo(fig, provider=meta.provider)
|
|
353
350
|
|
|
354
351
|
out_file = out.with_suffix(f".{fmt}")
|
|
355
|
-
|
|
356
|
-
facecolor=fig.get_facecolor(), bbox_inches="tight")
|
|
352
|
+
self._savefig(fig, out_file, fmt, settings.dpi)
|
|
357
353
|
plt.close(fig)
|
|
358
354
|
get_logger().detail("Saved line chart → %s", out_file)
|
|
359
355
|
return out_file
|
|
@@ -384,8 +380,7 @@ class ChartRenderer:
|
|
|
384
380
|
stamp_logo(fig, provider=meta.provider)
|
|
385
381
|
|
|
386
382
|
out_file = out.with_suffix(f".{fmt}")
|
|
387
|
-
|
|
388
|
-
facecolor=fig.get_facecolor(), bbox_inches="tight")
|
|
383
|
+
self._savefig(fig, out_file, fmt, settings.dpi)
|
|
389
384
|
plt.close(fig)
|
|
390
385
|
get_logger().detail("Saved area chart → %s", out_file)
|
|
391
386
|
return out_file
|
|
@@ -439,6 +434,19 @@ class ChartRenderer:
|
|
|
439
434
|
for spine in ax.spines.values():
|
|
440
435
|
spine.set_visible(False)
|
|
441
436
|
|
|
437
|
+
@staticmethod
|
|
438
|
+
def _savefig(fig: plt.Figure, out_file: Path, fmt: str, dpi: int) -> None:
|
|
439
|
+
"""Save *fig* with format-specific compression to keep file sizes small."""
|
|
440
|
+
pil_kwargs: dict = {}
|
|
441
|
+
if fmt == "png":
|
|
442
|
+
pil_kwargs = {"compress_level": 6, "optimize": True}
|
|
443
|
+
elif fmt in ("jpg", "jpeg", "webp"):
|
|
444
|
+
pil_kwargs = {"quality": 82, "optimize": True}
|
|
445
|
+
kwargs: dict = {"dpi": dpi, "facecolor": fig.get_facecolor(), "bbox_inches": "tight"}
|
|
446
|
+
if pil_kwargs:
|
|
447
|
+
kwargs["pil_kwargs"] = pil_kwargs
|
|
448
|
+
fig.savefig(out_file, **kwargs)
|
|
449
|
+
|
|
442
450
|
@staticmethod
|
|
443
451
|
def _set_date_labels(ax: plt.Axes, df: pd.DataFrame, theme: Theme) -> None:
|
|
444
452
|
n = len(df)
|
|
@@ -32,9 +32,9 @@ class Settings:
|
|
|
32
32
|
inst._theme: ThemeName = "dark"
|
|
33
33
|
inst._watermark_enabled: bool = True
|
|
34
34
|
inst._overwrite: bool = False
|
|
35
|
-
inst._dpi: int =
|
|
36
|
-
inst._fig_width: int =
|
|
37
|
-
inst._fig_height: int =
|
|
35
|
+
inst._dpi: int = 100
|
|
36
|
+
inst._fig_width: int = 12
|
|
37
|
+
inst._fig_height: int = 6
|
|
38
38
|
inst._cache_ttl: int = 300
|
|
39
39
|
inst._disk_store: DiskStore | None = None
|
|
40
40
|
cls._instance = inst
|
|
@@ -5,6 +5,8 @@ from __future__ import annotations
|
|
|
5
5
|
import time
|
|
6
6
|
from typing import Sequence
|
|
7
7
|
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
8
10
|
from tradechart.config.logger import get_logger
|
|
9
11
|
from tradechart.config.settings import get_settings
|
|
10
12
|
from tradechart.data.models import MarketData
|
|
@@ -12,6 +14,19 @@ from tradechart.data.provider_base import BaseProvider
|
|
|
12
14
|
from tradechart.utils.exceptions import DataFetchError
|
|
13
15
|
|
|
14
16
|
|
|
17
|
+
def _merge(stored: pd.DataFrame, fresh: pd.DataFrame) -> pd.DataFrame:
|
|
18
|
+
"""Merge stored history with freshly-fetched rows.
|
|
19
|
+
|
|
20
|
+
Historical rows from *stored* are preserved as-is. Any row whose
|
|
21
|
+
timestamp also appears in *fresh* is overwritten by the fresh value
|
|
22
|
+
(handles end-of-day corrections / partial bars). Rows that exist only
|
|
23
|
+
in *fresh* (new bars) are appended. The result is sorted by date.
|
|
24
|
+
"""
|
|
25
|
+
combined = pd.concat([stored, fresh])
|
|
26
|
+
combined = combined[~combined.index.duplicated(keep="last")]
|
|
27
|
+
return combined.sort_index()
|
|
28
|
+
|
|
29
|
+
|
|
15
30
|
class _Cache:
|
|
16
31
|
"""Simple TTL-based in-memory cache."""
|
|
17
32
|
|
|
@@ -49,25 +64,8 @@ class DataFetcher:
|
|
|
49
64
|
self._cache = _Cache()
|
|
50
65
|
self._log = get_logger()
|
|
51
66
|
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
cached = self._cache.get(ticker, duration)
|
|
55
|
-
if cached is not None:
|
|
56
|
-
self._log.detail("Cache hit for %s/%s", ticker, duration)
|
|
57
|
-
self._log.summary(f"✓ Data loaded from cache ({cached.provider})")
|
|
58
|
-
return cached
|
|
59
|
-
|
|
60
|
-
# 2 — persistent disk store (avoids network for already-fetched data)
|
|
61
|
-
disk_store = get_settings().disk_store
|
|
62
|
-
if disk_store is not None:
|
|
63
|
-
disk_data = disk_store.load(ticker, duration)
|
|
64
|
-
if disk_data is not None:
|
|
65
|
-
self._log.detail("Disk store hit for %s/%s", ticker, duration)
|
|
66
|
-
self._log.summary(f"✓ Data loaded from disk store ({len(disk_data.df)} rows)")
|
|
67
|
-
self._cache.put(disk_data) # promote to memory cache
|
|
68
|
-
return disk_data
|
|
69
|
-
|
|
70
|
-
# 3 — live provider chain
|
|
67
|
+
def _fetch_live(self, ticker: str, duration: str) -> MarketData:
|
|
68
|
+
"""Try each provider in order and return the first successful result."""
|
|
71
69
|
errors: list[str] = []
|
|
72
70
|
for provider in self._providers:
|
|
73
71
|
self._log.section(f"Trying provider: {provider.name}")
|
|
@@ -79,10 +77,6 @@ class DataFetcher:
|
|
|
79
77
|
errors.append(msg)
|
|
80
78
|
continue
|
|
81
79
|
data.clean().downsample()
|
|
82
|
-
self._cache.put(data)
|
|
83
|
-
# 4 — persist to disk store for future sessions
|
|
84
|
-
if disk_store is not None:
|
|
85
|
-
disk_store.save(data)
|
|
86
80
|
self._log.detail("Fetched %d rows from %s", len(data.df), provider.name)
|
|
87
81
|
self._log.summary(f"✓ Data fetched via {provider.name} ({len(data.df)} rows)")
|
|
88
82
|
return data
|
|
@@ -90,12 +84,59 @@ class DataFetcher:
|
|
|
90
84
|
msg = f"{provider.name} failed: {exc}"
|
|
91
85
|
self._log.detail(msg)
|
|
92
86
|
errors.append(msg)
|
|
93
|
-
|
|
94
87
|
raise DataFetchError(
|
|
95
88
|
f"All data providers failed for {ticker}/{duration}.\n"
|
|
96
89
|
+ "\n".join(f" • {e}" for e in errors)
|
|
97
90
|
)
|
|
98
91
|
|
|
92
|
+
def fetch(self, ticker: str, duration: str) -> MarketData:
|
|
93
|
+
# 1 — in-memory cache (fastest)
|
|
94
|
+
cached = self._cache.get(ticker, duration)
|
|
95
|
+
if cached is not None:
|
|
96
|
+
self._log.detail("Cache hit for %s/%s", ticker, duration)
|
|
97
|
+
self._log.summary(f"✓ Data loaded from cache ({cached.provider})")
|
|
98
|
+
return cached
|
|
99
|
+
|
|
100
|
+
disk_store = get_settings().disk_store
|
|
101
|
+
|
|
102
|
+
# 2 — persistent disk store
|
|
103
|
+
disk_data: MarketData | None = None
|
|
104
|
+
if disk_store is not None:
|
|
105
|
+
disk_data = disk_store.load(ticker, duration)
|
|
106
|
+
if disk_data is not None and not disk_store.is_stale(ticker, duration):
|
|
107
|
+
# Data is fresh — serve directly, no network call needed
|
|
108
|
+
self._log.detail("Disk store hit for %s/%s", ticker, duration)
|
|
109
|
+
self._log.summary(f"✓ Data loaded from disk store ({len(disk_data.df)} rows)")
|
|
110
|
+
self._cache.put(disk_data)
|
|
111
|
+
return disk_data
|
|
112
|
+
# disk_data may be stale (or absent) — fall through to live fetch,
|
|
113
|
+
# then merge the new rows on top of the stored history
|
|
114
|
+
|
|
115
|
+
# 3 — live provider chain (only fetches what the provider returns for
|
|
116
|
+
# this duration, typically the most recent N bars)
|
|
117
|
+
fresh = self._fetch_live(ticker, duration)
|
|
118
|
+
|
|
119
|
+
# 4 — merge fresh rows onto stored history, then persist
|
|
120
|
+
if disk_store is not None:
|
|
121
|
+
if disk_data is not None:
|
|
122
|
+
merged_df = _merge(disk_data.df, fresh.df)
|
|
123
|
+
result = MarketData(
|
|
124
|
+
ticker=ticker, duration=duration,
|
|
125
|
+
provider=fresh.provider, df=merged_df,
|
|
126
|
+
)
|
|
127
|
+
self._log.detail(
|
|
128
|
+
"Merged disk (%d rows) + live (%d rows) → %d rows",
|
|
129
|
+
len(disk_data.df), len(fresh.df), len(merged_df),
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
result = fresh
|
|
133
|
+
disk_store.save(result)
|
|
134
|
+
self._cache.put(result)
|
|
135
|
+
return result
|
|
136
|
+
|
|
137
|
+
self._cache.put(fresh)
|
|
138
|
+
return fresh
|
|
139
|
+
|
|
99
140
|
def clear_cache(self) -> None:
|
|
100
141
|
"""Flush the in-memory data cache."""
|
|
101
142
|
self._cache.clear()
|
|
@@ -2,12 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import time
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
import pandas as pd
|
|
8
9
|
|
|
9
10
|
from tradechart.data.models import MarketData
|
|
10
11
|
|
|
12
|
+
# Maximum age (seconds) for a stored file before it is considered stale and
|
|
13
|
+
# re-fetched from a live provider. Keyed by the user-facing duration string;
|
|
14
|
+
# the value matches the bar resolution so a daily-bar file never goes more
|
|
15
|
+
# than one trading day without refreshing.
|
|
16
|
+
_MAX_AGE: dict[str, int] = {
|
|
17
|
+
"1d": 4 * 3_600, # intraday (5-min bars) → 4 hours
|
|
18
|
+
"5d": 4 * 3_600, # intraday (15-min bars) → 4 hours
|
|
19
|
+
"1mo": 24 * 3_600, # daily bars → 1 day
|
|
20
|
+
"3mo": 24 * 3_600, # daily bars → 1 day
|
|
21
|
+
"6mo": 24 * 3_600, # daily bars → 1 day
|
|
22
|
+
"1y": 7 * 24 * 3_600, # weekly bars → 1 week
|
|
23
|
+
"2y": 7 * 24 * 3_600,
|
|
24
|
+
"5y": 7 * 24 * 3_600,
|
|
25
|
+
"10y": 30 * 24 * 3_600, # monthly bars → 30 days
|
|
26
|
+
"max": 30 * 24 * 3_600,
|
|
27
|
+
}
|
|
28
|
+
|
|
11
29
|
|
|
12
30
|
class DiskStore:
|
|
13
31
|
"""Saves and loads MarketData as CSV files under a dedicated folder.
|
|
@@ -52,8 +70,25 @@ class DiskStore:
|
|
|
52
70
|
"""Return True if data for this (ticker, duration) pair is on disk."""
|
|
53
71
|
return self._file_path(ticker, duration).exists()
|
|
54
72
|
|
|
73
|
+
def is_stale(self, ticker: str, duration: str) -> bool:
|
|
74
|
+
"""Return True if the stored file is older than the bar-resolution threshold.
|
|
75
|
+
|
|
76
|
+
A fresh file is served as-is. A stale file still holds valid
|
|
77
|
+
historical rows — the caller should merge fresh provider data on top
|
|
78
|
+
rather than discarding it.
|
|
79
|
+
"""
|
|
80
|
+
path = self._file_path(ticker, duration)
|
|
81
|
+
if not path.exists():
|
|
82
|
+
return True
|
|
83
|
+
max_age = _MAX_AGE.get(duration, 24 * 3_600)
|
|
84
|
+
return time.time() - path.stat().st_mtime > max_age
|
|
85
|
+
|
|
55
86
|
def load(self, ticker: str, duration: str) -> MarketData | None:
|
|
56
|
-
"""Load a previously stored dataset
|
|
87
|
+
"""Load a previously stored dataset regardless of age.
|
|
88
|
+
|
|
89
|
+
Returns *None* only if the file is missing or unreadable. Staleness
|
|
90
|
+
is a separate concern handled by :meth:`is_stale`.
|
|
91
|
+
"""
|
|
57
92
|
path = self._file_path(ticker, duration)
|
|
58
93
|
if not path.exists():
|
|
59
94
|
return None
|
|
@@ -100,6 +100,7 @@ MAX_QUEUE_SIZE = max(1, int(os.getenv("MAX_QUEUE_SIZE", "20")))
|
|
|
100
100
|
tc.theme(os.getenv("THEME", "dark"))
|
|
101
101
|
tc.terminal(os.getenv("TERMINAL", "none"))
|
|
102
102
|
tc.watermark(os.getenv("WATERMARK", "True").lower() == "true")
|
|
103
|
+
tc.store(DATA_DIR) # persist fetched data to TradeChartBot_Data/tradechart_FetchData/
|
|
103
104
|
|
|
104
105
|
# ============================================================
|
|
105
106
|
# BUILT-IN SECTOR GROUPS (mirroring tc.SECTOR_GROUPS)
|
|
@@ -565,7 +566,8 @@ async def cmd_heatmap(
|
|
|
565
566
|
# ============================================================
|
|
566
567
|
|
|
567
568
|
@client.tree.command(name="clearcache", description="MOD ONLY: Clear the chart data cache")
|
|
568
|
-
|
|
569
|
+
@app_commands.describe(disk="Also wipe the persistent disk store and force a full re-fetch (default: False)")
|
|
570
|
+
async def cmd_clearcache(interaction: discord.Interaction, disk: bool = False) -> None:
|
|
569
571
|
if interaction.guild is None:
|
|
570
572
|
await interaction.response.send_message("This bot only works in servers.", ephemeral=True)
|
|
571
573
|
return
|
|
@@ -574,8 +576,9 @@ async def cmd_clearcache(interaction: discord.Interaction) -> None:
|
|
|
574
576
|
await interaction.response.send_message("Only mods can clear the cache.", ephemeral=True)
|
|
575
577
|
return
|
|
576
578
|
|
|
577
|
-
tc.clear_cache()
|
|
578
|
-
|
|
579
|
+
tc.clear_cache(disk=disk)
|
|
580
|
+
label = EMOJI_GREEN + " Cache cleared (memory + disk store)." if disk else EMOJI_GREEN + " Memory cache cleared."
|
|
581
|
+
await interaction.response.send_message(label, ephemeral=True)
|
|
579
582
|
|
|
580
583
|
|
|
581
584
|
# ============================================================
|
|
@@ -929,8 +932,12 @@ async def cmd_help(interaction: discord.Interaction) -> None:
|
|
|
929
932
|
inline=False,
|
|
930
933
|
)
|
|
931
934
|
embed.add_field(
|
|
932
|
-
name="/clearcache (mod only)",
|
|
933
|
-
value=
|
|
935
|
+
name="/clearcache [disk] (mod only)",
|
|
936
|
+
value=(
|
|
937
|
+
"Clear cached chart data. Without `disk:True` only the in-memory cache is cleared "
|
|
938
|
+
"and stored data on disk is still reused. Use `disk:True` to wipe the disk store "
|
|
939
|
+
"and force a full re-fetch from the network."
|
|
940
|
+
),
|
|
934
941
|
inline=False,
|
|
935
942
|
)
|
|
936
943
|
embed.add_field(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|