crypto-terminal 2.0.0__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.
@@ -0,0 +1,7 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .DS_Store
4
+ venv/
5
+ .venv/
6
+ watchlist.json
7
+ dist/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Owen Alderson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,128 @@
1
+ Metadata-Version: 2.5
2
+ Name: crypto-terminal
3
+ Version: 2.0.0
4
+ Summary: A free, Bloomberg-style crypto terminal for your shell: live Coinbase prices, candles, indicators and alerts.
5
+ Project-URL: Homepage, https://github.com/owen-alderson/crypto-terminal
6
+ Author: Owen Alderson
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: bitcoin,coinbase,crypto,terminal,textual,tui
10
+ Classifier: Environment :: Console
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Office/Business :: Financial :: Investment
13
+ Requires-Python: >=3.10
14
+ Requires-Dist: certifi
15
+ Requires-Dist: httpx>=0.27
16
+ Requires-Dist: textual-plotext>=1.0
17
+ Requires-Dist: textual>=8.0
18
+ Requires-Dist: websockets>=14.0
19
+ Description-Content-Type: text/markdown
20
+
21
+ # crypto-terminal
22
+
23
+ A free, Bloomberg-style crypto terminal that runs in your shell. Live spot prices stream straight from the Coinbase Exchange public websocket: no API key, no account, no polling. Candlestick charts with a live last bar, indicators, price alerts, and search across every Coinbase pair.
24
+
25
+ ![Crypto terminal demo: live candles, indicators, coin search and a price alert](https://raw.githubusercontent.com/owen-alderson/crypto-terminal/main/docs/demo.gif)
26
+
27
+ ![Textual](https://img.shields.io/badge/built%20with-Textual-ffb000) ![Coinbase](https://img.shields.io/badge/data-Coinbase%20Exchange-0052FF)
28
+
29
+ ## Install
30
+
31
+ Requires Python 3.10+.
32
+
33
+ ```bash
34
+ pipx install crypto-terminal
35
+ crypto-terminal
36
+ ```
37
+
38
+ Or `pip install crypto-terminal` into any virtualenv.
39
+
40
+ ## Features
41
+
42
+ - **Live watchlist**: last price, 24h change, and a sparkline of recent ticks. Prices flash green/red on every up/down tick.
43
+ - **Candlestick chart** for the highlighted pair, with volume underneath. Six timeframes (1m to 1d candles). The last candle follows the live price and a new one opens at each candle boundary.
44
+ - **Indicators**: SMA 20, EMA 50 and VWAP (resets at the UTC day) over the candles, RSI 14 in its own panel.
45
+ - **Coin search**: `add solana` lists every online Solana pair (USD, USDC, USDT, EUR, GBP and BTC quotes first). Exact pairs like `add SOL-BTC` add directly. Sub-cent and BTC-quoted prices keep 5 significant figures.
46
+ - **Price alerts**: on a level (`>` / `<`) or a percentage move. A firing alert rings the terminal bell, shows a toast, and sends a desktop notification (macOS, or Linux with `notify-send`). Pairs with an alert carry a 🔔.
47
+ - **Status line**: connection state, time since the last tick, UTC clock. Shows `STALE` after 10s without data and reconnects automatically with exponential backoff.
48
+
49
+ Default pairs: BTC-USD, ETH-USD, XRP-USD, SOL-USD, ADA-USD.
50
+
51
+ ## How live is it?
52
+
53
+ Measured on the five default pairs over 60s:
54
+
55
+ | | Quiet (Sunday) | Busy |
56
+ |---|---|---|
57
+ | Price updates | ~5.5 / s | ~12 / s |
58
+ | Bandwidth | ~0.02 Mbps | ~0.04 Mbps |
59
+
60
+ Coinbase pushes an update on every trade (bursts are batched); nothing is polled. A heartbeat arrives every second, so a dead connection is spotted and replaced within seconds. The chart redraws at most twice a second.
61
+
62
+ ## Keys
63
+
64
+ | Key | Action |
65
+ |---|---|
66
+ | `↑` `↓` | Select pair (chart follows) |
67
+ | `1` – `6` | Candles: 1m, 5m, 15m, 1h, 6h, 1d |
68
+ | `/` or `:` | Open command bar (`esc` closes it) |
69
+ | `ctrl+q` | Quit |
70
+
71
+ ## Commands
72
+
73
+ | Command | Effect |
74
+ |---|---|
75
+ | `add SOL-USD` | Add a pair |
76
+ | `add sol` / `add solana` | Search by symbol or name: pick with `↑` `↓`, `enter` adds, `esc` cancels |
77
+ | `rm ADA-USD` | Remove a pair |
78
+ | `ind sma20 ema50 vwap rsi` | Toggle indicators (any subset) |
79
+ | `ind off` | Clear all indicators |
80
+ | `alert BTC-USD > 90000` | Alert when the price reaches 90,000 or more (`<` for at or below) |
81
+ | `alert BTC-USD move 5%` | Alert on a ±5% move from the current price |
82
+ | `alerts` | List alerts with their numbers |
83
+ | `unalert 2` | Remove alert 2 |
84
+ | `quit` | Exit |
85
+
86
+ Alerts fire once and are then removed. The watchlist, indicators and alerts are saved in `~/.config/crypto-terminal/` (or `$XDG_CONFIG_HOME/crypto-terminal/`).
87
+
88
+ ## From source
89
+
90
+ ```bash
91
+ git clone https://github.com/owen-alderson/crypto-terminal.git
92
+ cd crypto-terminal
93
+ python3 -m venv .venv
94
+ source .venv/bin/activate
95
+ pip install -r requirements.txt
96
+ python -m crypto_terminal
97
+ ```
98
+
99
+ ## Architecture
100
+
101
+ ```
102
+ crypto_terminal/
103
+ feed.py websocket client: subscribes to ticker + heartbeat, validates each message,
104
+ reconnects with exponential backoff (1s → 30s); 30s of silence = dead socket
105
+ history.py REST: candles, the product list for search, the live-candle update
106
+ indicators.py SMA, EMA, RSI, VWAP as pure functions
107
+ alerts.py alert rules, trigger check, desktop notification
108
+ widgets.py PriceTable (watchlist), ChartPane (textual-plotext), PairPicker (search results)
109
+ app.py Textual app: layout, command parsing, saved config, status line
110
+ tests/ pytest: parsing, indicators against reference values, search, alerts, reconnect
111
+ against a local websocket server, headless UI tests with Textual's Pilot (no internet)
112
+ ```
113
+
114
+ The feed, the chart fetch and the product list run as Textual async workers. Changing the watchlist restarts the feed worker, and a new chart request cancels the previous one so a slow response can't draw the wrong pair.
115
+
116
+ ## Tests
117
+
118
+ ```bash
119
+ pytest
120
+ ```
121
+
122
+ ## History
123
+
124
+ This repo started as a Streamlit + CoinGecko portfolio dashboard that polled every 60s. That version is preserved at the `v1-streamlit` tag (`git checkout v1-streamlit`).
125
+
126
+ ## License
127
+
128
+ MIT, see [LICENSE](LICENSE). Provided as is, with no warranty: this is a market-data viewer, not financial advice.
@@ -0,0 +1,108 @@
1
+ # crypto-terminal
2
+
3
+ A free, Bloomberg-style crypto terminal that runs in your shell. Live spot prices stream straight from the Coinbase Exchange public websocket: no API key, no account, no polling. Candlestick charts with a live last bar, indicators, price alerts, and search across every Coinbase pair.
4
+
5
+ ![Crypto terminal demo: live candles, indicators, coin search and a price alert](https://raw.githubusercontent.com/owen-alderson/crypto-terminal/main/docs/demo.gif)
6
+
7
+ ![Textual](https://img.shields.io/badge/built%20with-Textual-ffb000) ![Coinbase](https://img.shields.io/badge/data-Coinbase%20Exchange-0052FF)
8
+
9
+ ## Install
10
+
11
+ Requires Python 3.10+.
12
+
13
+ ```bash
14
+ pipx install crypto-terminal
15
+ crypto-terminal
16
+ ```
17
+
18
+ Or `pip install crypto-terminal` into any virtualenv.
19
+
20
+ ## Features
21
+
22
+ - **Live watchlist**: last price, 24h change, and a sparkline of recent ticks. Prices flash green/red on every up/down tick.
23
+ - **Candlestick chart** for the highlighted pair, with volume underneath. Six timeframes (1m to 1d candles). The last candle follows the live price and a new one opens at each candle boundary.
24
+ - **Indicators**: SMA 20, EMA 50 and VWAP (resets at the UTC day) over the candles, RSI 14 in its own panel.
25
+ - **Coin search**: `add solana` lists every online Solana pair (USD, USDC, USDT, EUR, GBP and BTC quotes first). Exact pairs like `add SOL-BTC` add directly. Sub-cent and BTC-quoted prices keep 5 significant figures.
26
+ - **Price alerts**: on a level (`>` / `<`) or a percentage move. A firing alert rings the terminal bell, shows a toast, and sends a desktop notification (macOS, or Linux with `notify-send`). Pairs with an alert carry a 🔔.
27
+ - **Status line**: connection state, time since the last tick, UTC clock. Shows `STALE` after 10s without data and reconnects automatically with exponential backoff.
28
+
29
+ Default pairs: BTC-USD, ETH-USD, XRP-USD, SOL-USD, ADA-USD.
30
+
31
+ ## How live is it?
32
+
33
+ Measured on the five default pairs over 60s:
34
+
35
+ | | Quiet (Sunday) | Busy |
36
+ |---|---|---|
37
+ | Price updates | ~5.5 / s | ~12 / s |
38
+ | Bandwidth | ~0.02 Mbps | ~0.04 Mbps |
39
+
40
+ Coinbase pushes an update on every trade (bursts are batched); nothing is polled. A heartbeat arrives every second, so a dead connection is spotted and replaced within seconds. The chart redraws at most twice a second.
41
+
42
+ ## Keys
43
+
44
+ | Key | Action |
45
+ |---|---|
46
+ | `↑` `↓` | Select pair (chart follows) |
47
+ | `1` – `6` | Candles: 1m, 5m, 15m, 1h, 6h, 1d |
48
+ | `/` or `:` | Open command bar (`esc` closes it) |
49
+ | `ctrl+q` | Quit |
50
+
51
+ ## Commands
52
+
53
+ | Command | Effect |
54
+ |---|---|
55
+ | `add SOL-USD` | Add a pair |
56
+ | `add sol` / `add solana` | Search by symbol or name: pick with `↑` `↓`, `enter` adds, `esc` cancels |
57
+ | `rm ADA-USD` | Remove a pair |
58
+ | `ind sma20 ema50 vwap rsi` | Toggle indicators (any subset) |
59
+ | `ind off` | Clear all indicators |
60
+ | `alert BTC-USD > 90000` | Alert when the price reaches 90,000 or more (`<` for at or below) |
61
+ | `alert BTC-USD move 5%` | Alert on a ±5% move from the current price |
62
+ | `alerts` | List alerts with their numbers |
63
+ | `unalert 2` | Remove alert 2 |
64
+ | `quit` | Exit |
65
+
66
+ Alerts fire once and are then removed. The watchlist, indicators and alerts are saved in `~/.config/crypto-terminal/` (or `$XDG_CONFIG_HOME/crypto-terminal/`).
67
+
68
+ ## From source
69
+
70
+ ```bash
71
+ git clone https://github.com/owen-alderson/crypto-terminal.git
72
+ cd crypto-terminal
73
+ python3 -m venv .venv
74
+ source .venv/bin/activate
75
+ pip install -r requirements.txt
76
+ python -m crypto_terminal
77
+ ```
78
+
79
+ ## Architecture
80
+
81
+ ```
82
+ crypto_terminal/
83
+ feed.py websocket client: subscribes to ticker + heartbeat, validates each message,
84
+ reconnects with exponential backoff (1s → 30s); 30s of silence = dead socket
85
+ history.py REST: candles, the product list for search, the live-candle update
86
+ indicators.py SMA, EMA, RSI, VWAP as pure functions
87
+ alerts.py alert rules, trigger check, desktop notification
88
+ widgets.py PriceTable (watchlist), ChartPane (textual-plotext), PairPicker (search results)
89
+ app.py Textual app: layout, command parsing, saved config, status line
90
+ tests/ pytest: parsing, indicators against reference values, search, alerts, reconnect
91
+ against a local websocket server, headless UI tests with Textual's Pilot (no internet)
92
+ ```
93
+
94
+ The feed, the chart fetch and the product list run as Textual async workers. Changing the watchlist restarts the feed worker, and a new chart request cancels the previous one so a slow response can't draw the wrong pair.
95
+
96
+ ## Tests
97
+
98
+ ```bash
99
+ pytest
100
+ ```
101
+
102
+ ## History
103
+
104
+ This repo started as a Streamlit + CoinGecko portfolio dashboard that polled every 60s. That version is preserved at the `v1-streamlit` tag (`git checkout v1-streamlit`).
105
+
106
+ ## License
107
+
108
+ MIT, see [LICENSE](LICENSE). Provided as is, with no warranty: this is a market-data viewer, not financial advice.
File without changes
@@ -0,0 +1,9 @@
1
+ from .app import TerminalApp
2
+
3
+
4
+ def main():
5
+ TerminalApp().run()
6
+
7
+
8
+ if __name__ == "__main__":
9
+ main()
@@ -0,0 +1,68 @@
1
+ """Price alerts: fire once when a pair reaches a level or moves a percentage from where the alert was set."""
2
+
3
+ import math
4
+ import shutil
5
+ import subprocess
6
+ import sys
7
+ from dataclasses import dataclass
8
+
9
+ from .history import SYMBOL_RE
10
+ from .widgets import fmt_price
11
+
12
+ KINDS = (">", "<", "move")
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class Alert:
17
+ symbol: str
18
+ kind: str # one of KINDS
19
+ level: float # price for > and <, percent for move
20
+ ref: float | None = None # move only: the price when the alert was set
21
+
22
+ def __str__(self):
23
+ if self.kind == "move":
24
+ return f"{self.symbol} moves ±{self.level:g}% from {fmt_price(self.ref)}"
25
+ return f"{self.symbol} {self.kind} {fmt_price(self.level)}"
26
+
27
+
28
+ def valid_level(kind: str, level: float) -> bool:
29
+ # chained comparisons reject NaN, inf, zero and negatives
30
+ return 0 < level < (100 if kind == "move" else math.inf)
31
+
32
+
33
+ def valid_alert(alert: Alert) -> bool:
34
+ """Full check for alerts read back from disk (the file is user-editable)."""
35
+ return (isinstance(alert.symbol, str) and bool(SYMBOL_RE.fullmatch(alert.symbol)) and alert.kind in KINDS
36
+ and valid_level(alert.kind, alert.level)
37
+ and (alert.kind != "move" or (alert.ref is not None and 0 < alert.ref < math.inf)))
38
+
39
+
40
+ def triggered(alert: Alert, price: float) -> bool:
41
+ if alert.kind == ">":
42
+ return price >= alert.level
43
+ if alert.kind == "<":
44
+ return price <= alert.level
45
+ return abs(price - alert.ref) / alert.ref * 100 >= alert.level
46
+
47
+
48
+ def check(alerts: list[Alert], symbol: str, price: float) -> tuple[list[Alert], list[Alert]]:
49
+ """Split alerts into (fired, remaining) for one tick."""
50
+ fired = [a for a in alerts if a.symbol == symbol and triggered(a, price)]
51
+ return fired, [a for a in alerts if a not in fired]
52
+
53
+
54
+ def desktop_notify(title: str, message: str):
55
+ """Best effort: osascript on macOS, notify-send on Linux, silently skipped if neither exists."""
56
+ if sys.platform == "darwin" and shutil.which("osascript"):
57
+ # text goes in as argv, never spliced into the script, so it can't inject AppleScript
58
+ args = ["osascript", "-e", "on run argv", "-e",
59
+ "display notification (item 2 of argv) with title (item 1 of argv)",
60
+ "-e", "end run", title, message]
61
+ elif shutil.which("notify-send"):
62
+ args = ["notify-send", "--", title, message]
63
+ else:
64
+ return
65
+ try:
66
+ subprocess.Popen(args, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
67
+ except OSError:
68
+ pass