├── .gitignore
├── README.md
├── alphavantage-creds.txt.sample
├── requirements.txt
├── rtscli-demo.png
├── rtscli.py
├── setup.py
└── tickers.txt.sample
/.gitignore:
--------------------------------------------------------------------------------
1 | tickers.txt
2 | alphavantage-creds.txt
3 |
4 | # Compiled python modules.
5 | *.pyc
6 |
7 | # Setuptools distribution folder.
8 | /dist/
9 |
10 | # Python egg metadata, regenerated from source files by setuptools.
11 | /*.egg-info
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## rtscli - Realtime Stock Ticker CLI
2 |
3 |
4 | - A stock ticker that runs in console
5 | - It grabs info from Google Finance Api every 10 seconds, or if you press R
6 | - It's pretty simple but if you wanna read through a blog post instead: https://aranair.github.io/posts/2017/06/28/building-a-python-cli-stock-ticker-with-urwid/
7 |
8 | **NOTE!!**
9 | This has been changed to use https://www.alphavantage.co because Google Finance does not seem to work reliably anymore (IPs get blocked and it just plain out doesn't work)
10 |
11 | You can get a free API key with a limited number of queries per second and so this has been tweaked to just refresh every 60s now. Put the api-key into `alphavantage-creds.txt` and it should work as usual.
12 |
13 | ## Screenshot
14 |
15 | 
16 |
17 | ## Dependencies
18 |
19 | Currently this is dependent on the list below but the next step is to build this into an executable so
20 | all that stuff with python and pip can be skipped.
21 |
22 | - Python2.7
23 | - pip
24 | - Bunch of other python packages
25 |
26 | ## Install via Pip
27 |
28 | ```
29 | pip install rtscli
30 | ```
31 |
32 | ## Running it
33 |
34 | ```bash
35 | $ cp tickers.txt.sample tickers.txt
36 | $ rtscli
37 | ```
38 |
39 | ## Tickers.txt Sample
40 |
41 | Format: Name, Ticker(Alphavantage format), cost price, shares held
42 |
43 | ```
44 | GLD,GLD,139,1
45 | ```
46 |
47 | ## Downloading and building manually
48 |
49 | ```
50 | $ git clone git@github.com:aranair/rtscli.git
51 | $ pip install -e .
52 | $ rtscli
53 | ```
54 | ## Future Developments
55 |
56 | Not sure if this is of interest to anyone but if you'll like to see anything on this, raise an issue or something.
57 |
58 | ## License
59 |
60 | MIT
61 |
--------------------------------------------------------------------------------
/alphavantage-creds.txt.sample:
--------------------------------------------------------------------------------
1 | xxxxxxxxxxxxxxxx
2 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | urwid
2 | HTMLParser
3 | simplejson
4 |
--------------------------------------------------------------------------------
/rtscli-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aranair/rtscli/1f0b5184e267e8f4b7275b0b9181da06ae46b670/rtscli-demo.png
--------------------------------------------------------------------------------
/rtscli.py:
--------------------------------------------------------------------------------
1 | import urwid
2 | try:
3 | # For Python 3.0 and later
4 | from urllib.request import urlopen
5 | except ImportError:
6 | # Fall back to Python 2's urllib2
7 | from urllib2 import urlopen
8 | try:
9 | # For Python3.0 and later
10 | from html.parser import HTMLParser
11 | except ImportError:
12 | # Fall back to Python 2's HTMLparser
13 | from HTMLparser import HTMLparser
14 | from simplejson import loads
15 | from time import sleep
16 |
17 |
18 | def parse_lines(lines):
19 | for l in lines:
20 | ticker = l.strip().split(",")
21 | yield ticker
22 |
23 | # Read files and get symbols
24 |
25 | with open("tickers.txt") as file:
26 | tickers = list(parse_lines(file.readlines()))
27 |
28 |
29 | with open("alphavantage-creds.txt") as file:
30 | apikey = list(parse_lines(file.readlines()))[0]
31 |
32 |
33 | # Set up color scheme
34 | palette = [
35 | ('titlebar', 'dark red', ''),
36 | ('refresh button', 'dark green,bold', ''),
37 | ('quit button', 'dark red', ''),
38 | ('getting quote', 'dark blue', ''),
39 | ('headers', 'white,bold', ''),
40 | ('change ', 'dark green', ''),
41 | ('change negative', 'dark red', '')]
42 |
43 | header_text = urwid.Text(u' Stock Quotes')
44 | header = urwid.AttrMap(header_text, 'titlebar')
45 |
46 | # Create the menu
47 | menu = urwid.Text([
48 | u'Press (', ('refresh button', u'R'), u') to manually refresh. ',
49 | u'Press (', ('quit button', u'Q'), u') to quit.'
50 | ])
51 |
52 | # Create the quotes box
53 | quote_text = urwid.Text(u'Press (R) to get your first quote!')
54 | quote_filler = urwid.Filler(quote_text, valign='top', top=1, bottom=1)
55 | v_padding = urwid.Padding(quote_filler, left=1, right=1)
56 | quote_box = urwid.LineBox(v_padding)
57 |
58 | # Assemble the widgets
59 | layout = urwid.Frame(header=header, body=quote_box, footer=menu)
60 |
61 | def pos_neg_change(change):
62 | if not change:
63 | return "0"
64 | else:
65 | return ("+{}".format(change) if change >= 0 else str(change))
66 |
67 | def get_color(change):
68 | color = 'change '
69 | if change < 0:
70 | color += 'negative'
71 | return color
72 |
73 | def append_text(l, s, tabsize=10, color='white'):
74 | l.append((color, s.expandtabs(tabsize)))
75 |
76 | def calculate_gain(price_in, current_price, shares):
77 | gain_per_share = float(current_price) - float(price_in)
78 | gain_percent = round(gain_per_share / float(price_in) * 100, 3)
79 |
80 | return gain_per_share * int(shares), gain_percent
81 |
82 | def get_update():
83 | results = []
84 |
85 | try:
86 | for t in tickers:
87 | ticker_sym = t[1]
88 | url = "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol={}&apikey={}".format(ticker_sym, apikey)
89 | res = loads(urlopen(url).read())
90 | results.append(res["Global Quote"])
91 | except Exception as err:
92 | print(err)
93 | return
94 |
95 | updates = [
96 | ('headers', u'Stock \t '.expandtabs(25)),
97 | ('headers', u'Last Price \t Change '.expandtabs(5)),
98 | ('headers', u'\t % Change '.expandtabs(5)),
99 | ('headers', u'\t Gain '.expandtabs(3)),
100 | ('headers', u'\t % Gain \n'.expandtabs(5)) ]
101 |
102 | total_portfolio_change = 0.0
103 |
104 | for i, r in enumerate(results):
105 | change = float(r['09. change'])
106 | percent_change = r['10. change percent']
107 |
108 | append_text(updates, '{} \t '.format(tickers[i][0]), tabsize=25)
109 | append_text(updates, '{} \t '.format(r['05. price']), tabsize=15)
110 | append_text(
111 | updates,
112 | '{} \t {}% \t'.format(
113 | pos_neg_change(change),
114 | percent_change),
115 | tabsize=13,
116 | color=get_color(change))
117 |
118 | gain = gain_percent = ''
119 | if len(tickers[i]) > 2:
120 | gain, gain_percent = calculate_gain(
121 | price_in = tickers[i][2],
122 | current_price = r['05. price'],
123 | shares = tickers[i][3])
124 |
125 | total_portfolio_change += gain
126 |
127 | append_text(
128 | updates,
129 | '{} \t {}% \n'.format(pos_neg_change(gain), pos_neg_change(gain_percent)),
130 | color=get_color(gain))
131 |
132 | append_text(updates, '\n\n\nNet Portfolio Gain: ')
133 | append_text(updates, pos_neg_change(total_portfolio_change), color=get_color(total_portfolio_change))
134 |
135 | return updates
136 |
137 | # Handle key presses
138 | def handle_input(key):
139 | if key == 'R' or key == 'r':
140 | refresh(main_loop, '')
141 |
142 | if key == 'Q' or key == 'q':
143 | raise urwid.ExitMainLoop()
144 |
145 | def refresh(_loop, _data):
146 | main_loop.draw_screen()
147 | quote_box.base_widget.set_text(get_update())
148 | main_loop.set_alarm_in(60, refresh)
149 |
150 | main_loop = urwid.MainLoop(layout, palette, unhandled_input=handle_input)
151 |
152 | def cli():
153 | main_loop.set_alarm_in(0, refresh)
154 | main_loop.run()
155 | cli()
156 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | setup(
4 | name='rtscli',
5 | version='0.3.2',
6 | description='A realtime stocks ticker that runs in CLI',
7 | url='http://github.com/aranair/rtscli',
8 | author='Boa Ho Man',
9 | author_email='boa.homan@gmail.com',
10 | license='MIT',
11 | install_requires=[
12 | 'urwid',
13 | 'HTMLParser',
14 | 'simplejson',
15 | ],
16 | zip_safe=False,
17 | py_modules=['rtscli'],
18 | entry_points={
19 | 'console_scripts': [
20 | 'rtscli = rtscli:cli'
21 | ]
22 | },
23 | )
24 |
--------------------------------------------------------------------------------
/tickers.txt.sample:
--------------------------------------------------------------------------------
1 | GLD,GLD,139,1
2 |
--------------------------------------------------------------------------------