2021-04-26 18:51:21 +00:00
# Copyright (C) 2020 Daniel Richardson richardson.daniel@hotmail.co.uk
#
# This file is part of stockTicker project for justinodunn.
#
# stockTicker can not be copied and/or distributed without the express
# permission of Daniel Richardson
2021-04-28 19:39:30 +00:00
2021-05-01 10:39:20 +00:00
import sys , select
2021-04-26 18:51:21 +00:00
import os
import threading
from PIL import Image , ImageDraw , ImageFont
import time
import csv
2021-04-28 19:39:30 +00:00
import requests
2021-04-26 18:51:21 +00:00
2021-04-27 18:29:14 +00:00
from rgbmatrix import RGBMatrix , RGBMatrixOptions
2021-04-28 19:39:30 +00:00
import finnhub
2021-04-27 18:29:14 +00:00
2021-05-01 10:39:20 +00:00
def getInput ( Block = False ) :
if Block or select . select ( [ sys . stdin ] , [ ] , [ ] , 0 ) == ( [ sys . stdin ] , [ ] , [ ] ) :
msg = sys . stdin . read ( 1 )
#sys.stdin.flush()
else :
msg = ' '
return msg
2021-04-26 19:27:34 +00:00
class StockTicker ( ) :
def __init__ ( self ) :
#Define global resources
self . symbols = [ ]
2021-04-27 18:29:14 +00:00
#self.ApiKeys = [["b1be6696d54342bdb5c8f59a052854fd","237d183faf984e7eba708a647c55153a","3012b3cb19784ed4be2b02e9907e53cb"],["f835e85df2a74df1904fe6dc53bcc17a","41ec268496744c34a96e3c408081300b","2d566fc71ade46d688713b13730c5219"]]
2021-04-28 19:39:30 +00:00
#self.ApiKeys = [["6172b84d1f464ad88b95ed52e4391bce","979f9b8c9ee748fbbb57cb490ccaaf42","593fa818a44144699b75433aafd92d15"]]
self . ApiKey = " c24qddqad3ickpckgg80 "
self . SandboxApiKey = " sandbox_c24qddqad3ickpckgg8g "
2021-04-26 19:27:34 +00:00
self . retrievedList = [ ]
self . greenORred = ( 255 , 255 , 255 )
self . ListStocks = [ ]
self . blank = Image . open ( ' logos/blank.png ' )
self . ApiCalledError = False
self . keySwapper = 0
self . displayTime = 60
self . speedDisplay = 25
self . running = True
self . Delay = 20
self . parseCSV = 0
self . symbolLength = 0
2021-04-27 18:29:14 +00:00
self . brightness = 1.0
# Configuration for the matrix
options = RGBMatrixOptions ( )
options . rows = 64
options . cols = 64
options . chain_length = 1
options . parallel = 1
options . hardware_mapping = ' adafruit-hat ' # If you have an Adafruit HAT: 'adafruit-hat'
2021-04-28 19:39:30 +00:00
options . gpio_slowdown = 3
2021-04-27 18:29:14 +00:00
self . matrix = RGBMatrix ( options = options )
2021-05-01 11:08:15 +00:00
self . finnhubClient = finnhub . Client ( api_key = self . ApiKey )
2021-04-27 18:29:14 +00:00
def openImage ( self , image_file ) :
image = Image . open ( image_file )
# Make image fit our screen.
#image.thumbnail((self.matrix.width, self.matrix.height), Image.ANTIALIAS)
image = image . convert ( ' RGB ' )
return image
2021-05-01 10:39:20 +00:00
def SetImage ( self , image , offset_x = 0 , offset_y = 0 , unsafe = False ) :
2021-04-27 18:29:14 +00:00
if ( image . mode != " RGB " ) :
raise Exception ( " Currently, only RGB mode is supported for SetImage(). Please create images with mode ' RGB ' or convert first with image = image.convert( ' RGB ' ). Pull requests to support more modes natively are also welcome :) " )
if unsafe :
2021-05-01 10:39:20 +00:00
#In unsafe mode we directly acceshow to send commands to running python processs the underlying PIL image array
2021-04-27 18:29:14 +00:00
#in cython, which is considered unsafe pointer accecss,
#however it's super fast and seems to work fine
#https://groups.google.com/forum/#!topic/cython-users/Dc1ft5W6KM4
img_width , img_height = image . size
2021-04-28 19:39:30 +00:00
print ( ' args ' , img_width , img_height , offset_x , offset_y , image )
2021-04-27 18:29:14 +00:00
self . matrix . SetPixelsPillow ( offset_x , offset_y , img_width , img_height , image )
else :
# First implementation of a SetImage(). OPTIMIZE_ME: A more native
# implementation that directly reads the buffer and calls the underlying
# C functions can certainly be faster.
img_width , img_height = image . size
pixels = image . load ( )
for x in range ( max ( 0 , - offset_x ) , min ( img_width , self . matrix . width - offset_x ) ) :
for y in range ( max ( 0 , - offset_y ) , min ( img_height , self . matrix . height - offset_y ) ) :
( r , g , b ) = pixels [ x , y ]
2021-05-01 10:39:20 +00:00
self . matrix . SetPixel ( x + offset_x , y + offset_y , r * self . brightness , g * self . brightness , b * self . brightness )
2021-04-26 19:27:34 +00:00
2021-04-27 18:29:14 +00:00
2021-04-27 18:59:25 +00:00
def ScrollImage ( self , image_file , offset_x = 0 , offset_y = 0 , delay = 0.05 ) :
2021-04-27 18:29:14 +00:00
image = self . openImage ( image_file )
2021-04-27 18:59:25 +00:00
img_width , img_height = image . size
2021-04-27 18:29:14 +00:00
2021-04-27 18:59:25 +00:00
print ( offset_x , offset_y )
while offset_x > - img_width :
offset_x - = 1
self . SetImage ( image , offset_x = offset_x , offset_y = offset_y )
2021-04-27 18:29:14 +00:00
time . sleep ( delay )
2021-04-27 18:59:25 +00:00
def ScrollImageTransition ( self , image_files , offset_x = 0 , offset_y = 0 , delay = 0.05 ) :
# use two image files and switch between them with a seemless transition
current_img = 1
2021-05-01 10:39:20 +00:00
#while True:
2021-04-27 18:59:25 +00:00
2021-04-28 19:39:30 +00:00
2021-05-01 10:39:20 +00:00
if current_img == 1 :
image = self . openImage ( image_files [ 0 ] )
elif current_img == 2 :
image = self . openImage ( image_files [ 1 ] )
img_width , img_height = image . size
2021-04-27 18:59:25 +00:00
2021-05-01 10:39:20 +00:00
while offset_x > - img_width :
offset_x - = 1
self . SetImage ( image , offset_x = offset_x , offset_y = offset_y )
time . sleep ( delay )
try :
if getInput ( ) == ' K ' :
self . resetMatrix ( )
break
except KeyboardInterrupt :
sys . stdout . flush ( )
pass
2021-04-27 18:59:25 +00:00
2021-05-01 10:39:20 +00:00
if current_img == 1 :
current_img = 2
elif current_img == 2 :
current_img = 1
offset_x = 0
2021-04-27 18:59:25 +00:00
2021-04-27 18:29:14 +00:00
2021-04-26 19:27:34 +00:00
#Get the logo from file that is the same as ticker name
def getLogo ( self , Ticker ) :
try :
Logo = Image . open ( ' logos/ ' + Ticker + ' .png ' )
except :
print ( ' Could not find logo for: ' + Ticker + ' using default ' )
Logo = Image . open ( ' logos/default.png ' )
return Logo
#Using change between min and day price give appropriate arrow
#and set the overall change colour
def getArrow ( self , CHANGE ) :
self . greenORred
if ( CHANGE > 0 ) :
Arrow = Image . open ( ' logos/up.png ' )
self . greenORred = ( 0 , 255 , 0 )
return Arrow , CHANGE
Arrow = Image . open ( ' logos/down.png ' )
self . greenORred = ( 255 , 0 , 0 )
CHANGE = ( CHANGE * - 1 )
2021-04-26 18:51:21 +00:00
return Arrow , CHANGE
2021-04-26 19:27:34 +00:00
def get_text_dimensions ( self , text_string , font ) :
canvas = Image . new ( ' RGB ' , ( 100 , 100 ) )
draw = ImageDraw . Draw ( canvas )
monospace = font
text = text_string
white = ( 255 , 255 , 255 )
draw . text ( ( 10 , 10 ) , text , font = monospace , fill = white )
bbox = canvas . getbbox ( )
width = bbox [ 2 ] - bbox [ 0 ]
height = bbox [ 3 ] - bbox [ 1 ]
return width , height
#Draw Ticker, current and change onto one image
def textToImage ( self , TICKER , CURRENT , CHANGE , ARROW ) :
2021-05-01 11:08:15 +00:00
font = ImageFont . load ( " /home/pi/Desktop/stock_ticker/fonts/10x20.pil " )
2021-04-26 19:27:34 +00:00
img = Image . new ( ' RGB ' , ( 150 , 32 ) )
d = ImageDraw . Draw ( img )
2021-04-28 19:39:30 +00:00
d . text ( ( 4 , 0 ) , TICKER , fill = ( 255 , 255 , 255 ) , font = font )
2021-04-26 19:27:34 +00:00
d . text ( ( 4 , 13 ) , CURRENT , fill = self . greenORred , font = font )
text_width_current , text_height = self . get_text_dimensions ( CURRENT , font )
img . paste ( ARROW , ( ( text_width_current + 9 ) , 18 ) )
d . text ( ( ( text_width_current + 29 ) , 13 ) , CHANGE , fill = self . greenORred , font = font )
text_width_change , text_height = self . get_text_dimensions ( CHANGE , font )
newWidth = ( text_width_current + 29 ) + ( text_width_change )
img . crop ( ( 0 , 0 , newWidth , 32 ) )
return img
#Stitch the logo & prices picture into one image
def stitchImage ( self , IMAGES ) :
widths , heights = zip ( * ( i . size for i in IMAGES ) )
total_width = sum ( widths )
max_height = max ( heights )
new_im = Image . new ( ' RGB ' , ( total_width , max_height ) )
x_offset = 0
2021-04-28 19:39:30 +00:00
for im in IMAGES :
2021-04-26 19:27:34 +00:00
new_im . paste ( im , ( x_offset , 0 ) )
x_offset + = im . size [ 0 ]
return new_im
2021-05-01 11:08:15 +00:00
2021-04-26 19:27:34 +00:00
#Get current prices and day prices for the ticker then format the response into an array
2021-04-28 19:39:30 +00:00
def getStockPrices ( self , SYMBOLS ) :
2021-04-26 19:27:34 +00:00
try :
2021-04-28 19:39:30 +00:00
2021-04-26 19:27:34 +00:00
symbols_list = SYMBOLS . split ( " , " )
2021-04-28 19:39:30 +00:00
quotes = [ self . finnhubClient . quote ( symbol ) for symbol in symbols_list ]
current_prices = [ quote [ ' c ' ] for quote in quotes ]
opening_prices = [ quote [ ' o ' ] for quote in quotes ]
for i , symbol in enumerate ( symbols_list ) :
self . retrievedList . append ( [ symbol , current_prices [ i ] , opening_prices [ i ] ] )
print ( self . retrievedList )
'''
2021-04-26 19:27:34 +00:00
td = TDClient ( apikey = APIKEY )
tmin = td . time_series ( symbol = SYMBOLS , interval = " 1min " , outputsize = ' 1 ' ) . as_json ( )
tday = td . time_series ( symbol = SYMBOLS , interval = " 1day " , outputsize = ' 2 ' ) . as_json ( )
for i in range ( len ( symbols_list ) ) :
currentMinPriceList = tmin [ symbols_list [ i ] ]
currentDayPriceList = list ( tday [ symbols_list [ i ] ] )
del currentDayPriceList [ 0 ]
currentMinPriceClose = tmin [ symbols_list [ i ] ] [ 0 ] [ ' close ' ]
currentDayPriceClose = currentDayPriceList [ 0 ] [ ' close ' ]
self . retrievedList . append ( [ ] )
self . retrievedList [ i ] . append ( symbols_list [ i ] )
self . retrievedList [ i ] . append ( currentMinPriceClose )
self . retrievedList [ i ] . append ( currentDayPriceClose )
2021-04-28 19:39:30 +00:00
'''
except Exception as e :
2021-04-26 19:27:34 +00:00
print ( " Could not fetch data - API CALLS REACHED? - Will display old image " )
2021-04-28 19:39:30 +00:00
print ( e )
self . ApiCalledError = True
2021-05-01 10:39:20 +00:00
def resetMatrix ( self ) :
for x in range ( self . matrix . width ) :
for y in range ( self . matrix . height ) :
self . matrix . SetPixel ( x , y , 0 , 0 , 0 )
2021-04-26 19:27:34 +00:00
#Connect all the pieces togeather creating 1 long final stock image
2021-05-01 11:08:15 +00:00
def getFullStockImage ( self ) :
2021-04-26 19:27:34 +00:00
self . ApiCalledError = False
self . ListStocks = [ ]
self . retrievedList = [ ]
start = time . time ( )
print ( ' elapsed time: ' + str ( ( time . time ( ) - start ) ) )
j = 0
while ( j < 3 ) :
2021-04-28 19:39:30 +00:00
2021-04-27 18:29:14 +00:00
# NT: this weird API key thing doesnt work, when I hard code the API key in the above function it does
2021-04-28 19:39:30 +00:00
self . getStockPrices ( self . symbols [ ( j + self . parseCSV ) ] [ 0 ] )
2021-04-26 19:27:34 +00:00
if ( self . ApiCalledError == False ) :
j + = 1
print ( ' elapsed time after api call: ' + str ( ( time . time ( ) - start ) ) )
for i in range ( len ( self . retrievedList ) ) :
print ( ' elapsed timethrough loop: ' + str ( ( time . time ( ) - start ) ) )
Change = float ( self . retrievedList [ i ] [ 1 ] ) - float ( self . retrievedList [ i ] [ 2 ] ) #TEXT
Ticker = self . retrievedList [ i ] [ 0 ] #TEXT
Current = ' %.2f ' % float ( self . retrievedList [ i ] [ 1 ] ) #TEXT
Logo = self . getLogo ( self . retrievedList [ i ] [ 0 ] )
Arrow , Change = self . getArrow ( Change )
Change = ' %.2f ' % Change
MidFrame = self . textToImage ( Ticker , Current , Change , Arrow ) #IMAGE THE TEXT
StitchedStock = self . stitchImage ( [ Logo , MidFrame ] )
self . ListStocks . append ( self . blank )
self . ListStocks . append ( StitchedStock )
2021-04-28 19:39:30 +00:00
2021-04-26 19:27:34 +00:00
self . retrievedList = [ ]
else :
break
2021-04-28 19:39:30 +00:00
2021-04-26 19:27:34 +00:00
print ( ' elapsed time loop ended: ' + str ( ( time . time ( ) - start ) ) )
if ( self . ApiCalledError == False ) :
FinalDisplayImage = self . stitchImage ( self . ListStocks )
FinalDisplayImage . save ( ' final.ppm ' )
print ( self . retrievedList )
#Send the final stitched image to the display for set amount of time
def displayMatrix ( self ) :
#os.system("sudo ./demo -D1 final.ppm -t " + str(displayTime) +" -m "+ str(speedDisplay) +" --led-gpio-mapping=adafruit-hat --led-rows=32 --led-cols=256")
2021-04-28 19:39:30 +00:00
#os.system("sudo ./demo -D1 final.ppm -t " + str(self.displayTime) +" -m "+ str(self.speedDisplay) +" --led-gpio-mapping=adafruit-hat --led-rows=64 --led-cols=64 --led-slowdown-gpio=4 ")
2021-05-01 11:08:15 +00:00
self . ScrollImageTransition ( [ ' final.ppm ' , ' final.ppm ' ] , offset_x = 0 , offset_y = 0 , delay = 0.02 )
2021-04-26 19:27:34 +00:00
#Retrieve symbols from the CSV file
def GetSymbols ( self ) :
2021-04-26 18:51:21 +00:00
2021-04-26 19:27:34 +00:00
self . symbols = [ ]
CSV = csv . reader ( open ( ' csv/tickers.csv ' , ' r ' ) )
z = 0
for row in CSV :
self . symbols . append ( [ ] )
endLine = str ( row [ 0 ] + ' , ' + row [ 1 ] + ' , ' + row [ 2 ] + ' , ' + row [ 3 ] + ' , ' + row [ 4 ] + ' , ' + row [ 5 ] + ' , ' + row [ 6 ] + ' , ' + row [ 7 ] + ' , ' + row [ 8 ] + ' , ' + row [ 9 ] + ' , ' + row [ 10 ] )
self . symbols [ z ] . append ( endLine )
z + = 1
print ( self . symbols )
#Main run definition called by server
def runStockTicker ( self , RUNTIME , DELAY , SPEEDTIME ) :
self . parseCSV = 0
self . Delay = DELAY
self . displayTime = RUNTIME
self . speedDisplay = SPEEDTIME
self . GetSymbols ( )
self . symbolLength = ( len ( self . symbols ) - 3 )
self . GetfullStockImage ( )
self . keySwapper + = 1
self . running = True
self . parseCSV + = 3
2021-04-27 18:29:14 +00:00
2021-04-26 19:27:34 +00:00
while ( True ) :
if ( self . running == True ) :
if ( self . keySwapper < = 1 ) :
if ( self . parseCSV < = self . symbolLength ) :
2021-04-28 19:39:30 +00:00
#th = threading.Thread(target=self.displayMatrix)
#th.start()
self . displayMatrix ( )
2021-04-26 19:27:34 +00:00
time . sleep ( ( int ( self . displayTime ) - int ( self . Delay ) ) )
self . GetfullStockImage ( )
self . keySwapper + = 1
th . join ( )
self . parseCSV + = 3
else :
self . parseCSV = 0
2021-04-26 18:51:21 +00:00
else :
2021-04-26 19:27:34 +00:00
self . keySwapper = 0
2021-04-26 18:51:21 +00:00
else :
2021-04-26 19:27:34 +00:00
break ;
#Change running to false stopping refresh at next checkpoint
def stopStockTicker ( self ) :
self . keySwapper = 0
self . running = False
print ( ' MATRIX DISPLAY STOP CALLED ' )
2021-05-01 10:39:20 +00:00
if __name__ == ' __main__ ' :
#print(sys.stdin.readlines())
stock_ticker = StockTicker ( )
2021-05-01 11:08:15 +00:00
stock_ticker . GetSymbols ( )
stock_ticker . getFullStockImage ( )
stock_ticker . displayMatrix ( )
2021-05-01 10:39:20 +00:00
while True :
msg = getInput ( )
2021-05-01 11:08:15 +00:00
if msg == ' S ' :
stock_ticker . getFullStockImage ( )
2021-05-01 10:39:20 +00:00
stock_ticker . displayMatrix ( )