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.
- crypto_terminal-2.0.0/.gitignore +7 -0
- crypto_terminal-2.0.0/LICENSE +21 -0
- crypto_terminal-2.0.0/PKG-INFO +128 -0
- crypto_terminal-2.0.0/README.md +108 -0
- crypto_terminal-2.0.0/crypto_terminal/__init__.py +0 -0
- crypto_terminal-2.0.0/crypto_terminal/__main__.py +9 -0
- crypto_terminal-2.0.0/crypto_terminal/alerts.py +68 -0
- crypto_terminal-2.0.0/crypto_terminal/app.py +369 -0
- crypto_terminal-2.0.0/crypto_terminal/feed.py +98 -0
- crypto_terminal-2.0.0/crypto_terminal/history.py +113 -0
- crypto_terminal-2.0.0/crypto_terminal/indicators.py +44 -0
- crypto_terminal-2.0.0/crypto_terminal/widgets.py +228 -0
- crypto_terminal-2.0.0/docs/demo.gif +0 -0
- crypto_terminal-2.0.0/pyproject.toml +32 -0
- crypto_terminal-2.0.0/pytest.ini +3 -0
- crypto_terminal-2.0.0/requirements.txt +4 -0
- crypto_terminal-2.0.0/tests/test_alerts.py +97 -0
- crypto_terminal-2.0.0/tests/test_app.py +272 -0
- crypto_terminal-2.0.0/tests/test_candles.py +45 -0
- crypto_terminal-2.0.0/tests/test_feed.py +79 -0
- crypto_terminal-2.0.0/tests/test_indicators.py +46 -0
- crypto_terminal-2.0.0/tests/test_parsing.py +108 -0
- crypto_terminal-2.0.0/tests/test_search.py +59 -0
|
@@ -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
|
+

|
|
26
|
+
|
|
27
|
+
 
|
|
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
|
+

|
|
6
|
+
|
|
7
|
+
 
|
|
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,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
|