1+ from .state import State
2+
3+ class ColorTheme :
4+ black = (0 , 0 , 0 )
5+ white = (255 , 255 , 255 )
6+ red = (255 , 10 , 0 )
7+ lightblue = (100 , 100 , 255 )
8+ green = (0 , 240 , 0 )
9+
10+ background = black
11+ up_candle = green
12+ down_candle = red
13+ wick = white
14+ text = white
15+
16+ class PygameRender :
17+ def __init__ (
18+ self ,
19+ window_size : int = 100 ,
20+ screen_width : int = 1024 ,
21+ screen_height : int = 768 ,
22+ top_bottom_offset : int = 25 ,
23+ candle_spacing : int = 1 ,
24+ color_theme = ColorTheme (),
25+ frame_rate : int = 30 ,
26+ ):
27+
28+ # pygame window settings
29+ self .screen_width = screen_width
30+ self .screen_height = screen_height
31+ self .top_bottom_offset = top_bottom_offset
32+ self .candle_spacing = candle_spacing
33+ self .window_size = window_size
34+ self .color_theme = color_theme
35+ self .frame_rate = frame_rate
36+
37+ self .candle_width = self .screen_width // self .window_size - self .candle_spacing
38+ self .chart_height = self .screen_height - 2 * self .top_bottom_offset
39+
40+ self ._states = []
41+
42+ try :
43+ import pygame
44+ self .pygame = pygame
45+ except ImportError :
46+ raise ImportError ('Please install pygame (pip install pygame)' )
47+
48+ self .pygame .init ()
49+ self .pygame .display .init ()
50+ self .screen_shape = (self .screen_width , self .screen_height )
51+ self .window = self .pygame .display .set_mode (self .screen_shape , self .pygame .RESIZABLE )
52+ self .clock = self .pygame .time .Clock ()
53+
54+ def reset (self ):
55+ self ._states = []
56+
57+ def _map_price_to_window (self , price , max_low , max_high ):
58+ max_range = max_high - max_low
59+ value = int (self .chart_height - (price - max_low ) / max_range * self .chart_height ) + self .top_bottom_offset
60+ return value
61+
62+ def _prerender (func ):
63+ """ Decorator for input data validation and pygame window rendering"""
64+ def wrapper (self , info : dict , rgb_array : bool = False ):
65+ self ._states += info .get ('states' , [])
66+
67+ if not self ._states or not bool (self .window ._pixels_address ):
68+ return
69+
70+ for event in self .pygame .event .get ():
71+ if event .type == self .pygame .QUIT :
72+ self .pygame .quit ()
73+ return
74+
75+ if event .type == self .pygame .VIDEORESIZE :
76+ self .screen_shape = (event .w , event .h )
77+
78+ # self.screen.fill(self.color_theme.background)
79+ canvas = func (self , info )
80+ canvas = self .pygame .transform .scale (canvas , self .screen_shape )
81+ # The following line copies our drawings from `canvas` to the visible window
82+ self .window .blit (canvas , canvas .get_rect ())
83+ self .pygame .display .update ()
84+ self .clock .tick (self .frame_rate )
85+
86+ if rgb_array :
87+ return self .pygame .surfarray .array3d (canvas )
88+
89+ return wrapper
90+
91+ @_prerender
92+ def render (self , info : dict ):
93+
94+ canvas = self .pygame .Surface ((self .screen_width , self .screen_height ))
95+ canvas .fill (self .color_theme .background )
96+
97+ max_high = max ([state .high for state in self ._states ])
98+ max_low = min ([state .low for state in self ._states ])
99+
100+ candle_offset = self .candle_spacing
101+
102+ for state in self ._states [- self .window_size :]:
103+
104+ assert isinstance (state , State ) == True # check if state is a State object
105+
106+ # Calculate candle coordinates
107+ candle_y_open = self ._map_price_to_window (state .open , max_low , max_high )
108+ candle_y_close = self ._map_price_to_window (state .close , max_low , max_high )
109+ candle_y_high = self ._map_price_to_window (state .high , max_low , max_high )
110+ candle_y_low = self ._map_price_to_window (state .low , max_low , max_high )
111+
112+ # Determine candle color
113+ if state .open < state .close :
114+ # up candle
115+ candle_color = self .color_theme .up_candle
116+ candle_body_y = candle_y_close
117+ candle_body_height = candle_y_open - candle_y_close
118+ else :
119+ # down candle
120+ candle_color = self .color_theme .down_candle
121+ candle_body_y = candle_y_open
122+ candle_body_height = candle_y_close - candle_y_open
123+
124+ # Draw candlestick wicks
125+ self .pygame .draw .line (canvas , self .color_theme .wick , (candle_offset + self .candle_width // 2 , candle_y_high ), (candle_offset + self .candle_width // 2 , candle_y_low ))
126+
127+ # Draw candlestick body
128+ self .pygame .draw .rect (canvas , candle_color , (candle_offset , candle_body_y , self .candle_width , candle_body_height ))
129+
130+ # Move to the next candle
131+ candle_offset += self .candle_width + self .candle_spacing
132+
133+ return canvas
0 commit comments