├── .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 | ![Demo](https://github.com/aranair/rtscli/blob/master/rtscli-demo.png?raw=true "Demo") 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 | --------------------------------------------------------------------------------