Skip to content

Commit f505215

Browse files
authored
Update to 0.0.2
2 parents fd074ae + 37e16da commit f505215

6 files changed

Lines changed: 121 additions & 66 deletions

File tree

cryptochart/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
__version__ = "0.0.1"
1+
__version__ = "0.0.2"
22
from .kraken import KrakenChart
33
__all__ = "KrakenChart",

cryptochart/chart.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,35 @@ def scale(val:float) -> int:
4949
# Drawing labels
5050
for y in range(rows):
5151
label = labelFormat.format( maxVal-(y+1)*stepVal )
52+
colorLeft = "WHITE"
53+
colorRight = "WHITE"
5254
scaleFirst = scale(chartData[0])
5355
scaleLast = scale(chartData[-1])
56+
bestPrice = scale(max(chartData))
57+
worstPrice = scale(min(chartData))
58+
if bestPrice != worstPrice and bestPrice == y:
59+
colorLeft = "GREEN"
60+
elif bestPrice != worstPrice and worstPrice == y:
61+
colorLeft = "RED"
62+
elif scaleFirst == y:
63+
colorLeft = "CYAN"
64+
if bestPrice != worstPrice and bestPrice == y:
65+
colorRight = "GREEN"
66+
elif bestPrice != worstPrice and worstPrice == y:
67+
colorRight = "RED"
68+
elif scaleLast == y:
69+
colorRight = "CYAN"
5470
screen.addstr(
5571
yStart + y,
5672
xStart,
5773
label,
58-
curses.color_pair(COLORS["CYAN" if scaleFirst==y else "WHITE"]),
74+
curses.color_pair(COLORS[colorLeft]),
5975
)
6076
screen.addstr(
6177
yStart + y,
6278
xEnd - labelLength,
6379
label,
64-
curses.color_pair(COLORS["CYAN" if scaleLast==y else "WHITE"]),
80+
curses.color_pair(COLORS[colorRight]),
6581
)
6682
#
6783
screen.addstr(

cryptochart/kraken.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import json
33
from time import time
44
from urllib import request
5+
from threading import Thread, Event
6+
from typing import Any
57
# Third party modules
68
from websocket import create_connection # type: ignore
79
# Local modules
@@ -19,12 +21,15 @@
1921
}
2022

2123
class KrakenChart(MainModule):
24+
thr:Thread
25+
closeEvent:Event
2226
def __init__(self, currency:str="btc") -> None:
2327
if currency not in CURRENCIES:
2428
raise Exception("Currency {} not supported".format(currency))
25-
self.currency = currency
29+
self.currency = currency
30+
self.closeEvent = Event()
2631
super().__init__()
27-
def _fetchFirst(self) -> None:
32+
def _fetchHistory(self) -> None:
2833
resCtx = request.Request(url='https://api.kraken.com/0/public/OHLC?pair={}&since={}'.format(
2934
CURRENCIES[self.currency]["ticker"],
3035
int(time()-61440),
@@ -34,21 +39,19 @@ def _fetchFirst(self) -> None:
3439
self.prices.append(float(data[1]))
3540
self.lastTimestamp = int(res["result"]["last"] // 60 * 60)
3641
def _fetchThreadLoop(self) -> None:
37-
ws = create_connection("wss://ws.kraken.com/")
38-
ws.send(json.dumps({
39-
"event": "subscribe",
40-
"pair": [
41-
CURRENCIES[self.currency]["pair"],
42-
],
43-
"subscription": {
44-
"name": "spread",
45-
}
46-
# "subscription": {
47-
# "interval": 1,
48-
# "name": "ohlc",
49-
# }
50-
}))
42+
ws:Any = None
5143
while not self.closeEvent.is_set():
44+
if ws is None:
45+
ws = create_connection("wss://ws.kraken.com/")
46+
ws.send(json.dumps({
47+
"event": "subscribe",
48+
"pair": [
49+
CURRENCIES[self.currency]["pair"],
50+
],
51+
"subscription": {
52+
"name": "spread",
53+
}
54+
}))
5255
rawResult = ws.recv()
5356
try:
5457
res = json.loads(rawResult)
@@ -59,4 +62,13 @@ def _fetchThreadLoop(self) -> None:
5962
"""
6063
{'connectionID': 15419748703498491903, 'event': 'systemStatus', 'status': 'online', 'version': '1.8.7'}
6164
"""
65+
if self.tickerCounter%15 == 0:
66+
ws = None
6267
ws.close()
68+
def _start(self) -> None:
69+
self._fetchHistory()
70+
self.thr = Thread(target=self._fetchThreadLoop, daemon=True)
71+
self.thr.start()
72+
def _close(self) -> None:
73+
self.closeEvent.set()
74+
self.thr.join(5)

cryptochart/main.py

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Builtin modules
22
import curses
33
from time import sleep, time
4-
from threading import Thread, Event, Lock
54
from typing import Dict, List, Any, TYPE_CHECKING
5+
from threading import Lock
66
from dataclasses import dataclass
77
# Third party modules
88
# Local modules
@@ -39,81 +39,112 @@ class MainModule:
3939
prices:List[float]
4040
priceCaches:List[float]
4141
lastTimestamp:float
42-
closeEvent:Event
42+
tickerCounter:int
4343
pricesLock:Lock
4444
terminalSize:YX
4545
screen:Any
4646
currency:str
47-
thr:Thread
4847
def __init__(self, currency:str="btc") -> None:
4948
self.terminalSize = YX(0, 0)
50-
self.closeEvent = Event()
5149
self.pricesLock = Lock()
5250
self.lastTimestamp = 0
51+
self.tickerCounter = 0
5352
self.prices = []
5453
self.priceCaches = []
5554
def _write(self, text:str, color:str="WHITE") -> None:
5655
self.screen.addstr(text, curses.color_pair(COLORS[color]))
5756
def _writeN(self, text:str, length:int, color:str="WHITE") -> None:
5857
self.screen.addnstr(text, length, curses.color_pair(COLORS[color]))
59-
def _fetchFirst(self) -> None: ...
58+
def _fetchHistory(self) -> None: ...
6059
def _fetchThreadLoop(self) -> None: ...
60+
def _start(self) -> None: ...
61+
def _close(self) -> None: ...
6162
def _loop(self, screen:Any) -> None:
6263
try:
6364
try:
64-
curses.start_color()
6565
curses.use_default_colors()
6666
for i in range(curses.COLORS):
6767
curses.init_pair(i+1, i, -1)
6868
screen.attron( curses.color_pair(COLORS["WHITE"]) )
6969
curses.curs_set(0)
7070
except:
71-
curses.initscr()
71+
pass
7272
screen.timeout(0)
7373
screen.clear()
7474
screen.addstr("Loading prices...")
7575
screen.refresh()
7676
self.screen = screen
77+
doRefresh = False
78+
lastData = 0.0, 0
79+
lastTS = 0
7780
while True:
78-
sleep(1)
79-
screen.clear()
81+
sleep(0.1)
82+
c = self.screen.getch()
83+
if c == ord('q'):
84+
break
85+
doRefresh = False
8086
tmpTerminalSize = YX(*screen.getmaxyx())
8187
if tmpTerminalSize != self.terminalSize:
8288
self.terminalSize = tmpTerminalSize
89+
doRefresh = True
8390
if self.terminalSize.x <= 30 or self.terminalSize.y <= 15:
91+
screen.clear()
8492
self._writeN("No place to print", 17, "RED")
8593
screen.refresh()
8694
continue
8795
with self.pricesLock:
8896
if time()-self.lastTimestamp > 60:
89-
if not self.priceCaches:
97+
if self.tickerCounter%5 == 0:
98+
self.prices.clear()
99+
self._fetchHistory()
90100
self.prices.append(self.prices[-1])
91101
else:
92-
self.prices.append(sum(self.priceCaches) / len(self.priceCaches))
93-
self.priceCaches.clear()
102+
if not self.priceCaches:
103+
self.prices.append(self.prices[-1])
104+
else:
105+
self.prices.append(sum(self.priceCaches) / len(self.priceCaches))
106+
self.priceCaches.clear()
107+
self.tickerCounter += 1
94108
self.lastTimestamp = int(time() // 60 * 60)
95-
else:
96-
if self.priceCaches:
97-
self.prices[-1] = sum(self.priceCaches) / len(self.priceCaches)
109+
doRefresh = True
110+
elif self.priceCaches:
111+
pcs = sum(self.priceCaches)
112+
pcl = len(self.priceCaches)
113+
if lastData != (pcs, pcl):
114+
self.prices[-1] = pcs / pcl
98115
self.priceCaches.clear()
116+
lastData = pcs, pcl
117+
doRefresh = True
118+
if lastTS != int(time()%60):
119+
lastTS = int(time()%60)
120+
doRefresh = True
121+
if not doRefresh:
122+
continue
123+
screen.clear()
99124
# Parsing cache
100125
screen.move(0, 0)
101126
self._write("BTC price: ")
102127
self._write("{:.2F}".format(self.prices[-1]), "RED" if self.prices[-1] < self.prices[-2] else "GREEN")
103-
self._write(" USD ")
128+
self._write(" USD | ")
104129
self._write("{}".format(60-int(time()-self.lastTimestamp)), "YELLOW")
105130
self._write(" seconds left for the next tick. [{} tickers in cache]\n".format(len(self.prices)))
106131
self._write("Changes: ")
107-
for i, tDiff in enumerate([
108-
TickerDiff(self.prices[-1], self.prices[-2]),
109-
TickerDiff(self.prices[-1], self.prices[-16]),
110-
TickerDiff(self.prices[-1], self.prices[-31]),
111-
TickerDiff(self.prices[-1], self.prices[-61]),
132+
for i, (d, tDiff) in enumerate([
133+
("1 min: ", TickerDiff(self.prices[-1], self.prices[-2])),
134+
("15 min: ", TickerDiff(self.prices[-1], self.prices[-16])),
135+
("30 min: ", TickerDiff(self.prices[-1], self.prices[-31])),
136+
("1 hour: ", TickerDiff(self.prices[-1], self.prices[-61])),
137+
("12 hour: ", TickerDiff(self.prices[-1], self.prices[-721])),
112138
]):
113139
if i > 0:
114-
self._write(" : ")
115-
self._write("{sign}{val:.2F} ({sign}{perc:.2F}%)".format(**tDiff.__dict__), tDiff.color)
116-
self._write(" (1min:15min:30min:1hour)")
140+
d = " | " + d
141+
text = "{sign}{val:.2F} ({sign}{perc:.2F}%)".format(**tDiff.__dict__)
142+
cy, cx = screen.getyx()
143+
if len(text)+len(d) >= (self.terminalSize.x-cx):
144+
self._write("\n")
145+
d = " "+d[3:]
146+
self._write(d)
147+
self._write(text, tDiff.color)
117148
cy, cx = screen.getyx()
118149
writeChart(
119150
screen,
@@ -128,11 +159,8 @@ def _loop(self, screen:Any) -> None:
128159
except KeyboardInterrupt:
129160
return
130161
finally:
131-
self.closeEvent.set()
132-
self.thr.join(5)
162+
self._close()
133163
curses.endwin()
134164
def start(self) -> None:
135-
self._fetchFirst()
136-
self.thr = Thread(target=self._fetchThreadLoop, daemon=True)
137-
self.thr.start()
165+
self._start()
138166
curses.wrapper(self._loop)

screenshot.png

52.5 KB
Loading

setup.py

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,29 @@
11
#!/usr/bin/env python3
22
import os
3-
import cryptochart
43
try:
5-
from setuptools import setup
4+
from setuptools import setup # type: ignore
65
except ImportError:
76
from distutils.core import setup
87

98
pwd = os.path.abspath(os.path.dirname(__file__))
109

1110
setup(
12-
name = "python-cryptochart",
13-
version = cryptochart.__version__,
14-
description = "Terminal chart for crypto currencies",
15-
keywords = "crypto currency bitcoin chart ascii terminal",
16-
author = "Andor `iFA` Rajci - Fusions Solutions KFT",
17-
author_email = "ifa@fusionsolutions.io",
18-
url = "https://github.com/FusionSolutions/python-cryptochart",
19-
license = "GPL-3",
20-
packages=["cryptochart"],
21-
long_description=open(os.path.join(pwd, "README.md")).read(),
22-
long_description_content_type="text/markdown",
23-
zip_safe=False,
24-
python_requires=">=3.7.0",
25-
install_requires=["websocket-client"],
26-
package_data={ "":["py.typed"] },
27-
classifiers=[ # https://pypi.org/pypi?%3Aaction=list_classifiers
11+
name = "python-cryptochart",
12+
version = "0.0.2",
13+
description = "Terminal chart for crypto currencies",
14+
keywords = "crypto currency bitcoin chart ascii terminal",
15+
author = "Andor `iFA` Rajci - Fusions Solutions KFT",
16+
author_email = "ifa@fusionsolutions.io",
17+
url = "https://github.com/FusionSolutions/python-cryptochart",
18+
license = "GPL-3",
19+
packages = ["cryptochart"],
20+
long_description = open(os.path.join(pwd, "README.md")).read(),
21+
long_description_content_type = "text/markdown",
22+
zip_safe = False,
23+
python_requires = ">=3.7.0",
24+
install_requires = ["websocket-client"],
25+
package_data = { "":["py.typed"] },
26+
classifiers = [ # https://pypi.org/pypi?%3Aaction=list_classifiers
2827
"Development Status :: 4 - Beta",
2928
"Topic :: Terminals",
3029
"Programming Language :: Python :: 3 :: Only",

0 commit comments

Comments
 (0)