1 # encoding: latin1 2 3 4 import matplotlib.pyplot as plot 5 import matplotlib.ticker 6 import mpl_toolkits.mplot3d 7 import numpy 8 9 10 class Const(): 11 """constant definitions""" 12 13 # range definition indizes 14 min = 0 15 max = 1 16 steps = 2 17 min_max = slice(min, max + 1) 18 19 # coordinate axes 20 x = 0 21 y = 1 22 z = 2 23 xyz = ( x, y, z ) 24 25 # percent factor 26 percent = 100 27 28 # trade directions 29 long = 1 30 short = -1 31 32 # distinguished capital value 33 start_capital = 1.0 34 35 # distinguished z axis value 36 in_x_y_plane = 0.0 37 38 # drawing order 39 top = 3 40 41 42 class Config(): 43 """holds user configurable data""" 44 view_from_front = True 45 axes = { 46 'extra_x' : ( -10.0, 510.0 ) , 47 'label_adjustments' : ( 2, 6, 2) , # extra padding lines 48 'labels' : ( 'Anfangshebel' , 49 'relative Bewegung\nim Underlying' , 50 'effektiver Hebel' ) , 51 'margins' : ( [ 0.02, 0.1, 0.875, 0.75 ] 52 if view_from_front else 53 [ 0.05, 0.1, 0.95 , 0.8 ] ) , 54 'ranges' : ( ( 1.0 , 500.0 , 1500 ) , # x: start gearing range 55 ( -0.02, 0.02, 1501 ) , # y: underlying movement range 56 ( 0.0 , 1000.0 ) ) } # z: effective gearing range 57 azimuth = 105.0 if view_from_front else -105.0 58 colors = { 59 'alpha_contour' : 0.5 , 60 'alpha_wireframe' : 0.33 , 61 'colormap' : matplotlib.cm.jet , 62 'limits' : ( -500.0, 1200.0 ) } 63 elevation = 25.0 64 epsilon = numpy.finfo(numpy.double).eps 65 dpi = 96 66 figsize = ( 8.0, 6.5 ) if view_from_front else ( 8.0, 6.0 ) 67 gearing_treat_as_inf = colors['limits'][Const.max] + 1.0 68 strides_areas = ( 10, 8 ) 69 title = unicode('Ungünstige Zunahme des Hebels gegen die Traderichtung' 70 + ('\n\n\n' if view_from_front else '\n'), 'latin1') 71 trade_direction = Const.long 72 73 74 def main(): 75 """modules main function""" 76 plot_graph(* get_all_effective_gearings()) 77 78 79 def get_all_effective_gearings(): 80 """compute effective gearings for all points of a meshgrid 81 for ranges of start gearings and underlying movements 82 """ 83 start_gearings, underlying_movements = numpy.meshgrid( 84 numpy.linspace(* Config.axes['ranges'][Const.x]) , 85 numpy.linspace(* Config.axes['ranges'][Const.y]) ) 86 return (start_gearings, underlying_movements, 87 effective_gearing(start_gearings, underlying_movements)) 88 89 90 @ numpy.vectorize 91 def effective_gearing(start_gearing, underlying_movement, trade_direction = Config.trade_direction): 92 """compute effective gearing for one single combination 93 of start gearings and underlying movements 94 95 trade_direction considers the minimal numerical differences for long and short trades 96 """ 97 asset_value = (Const.start_capital + underlying_movement) * start_gearing 98 own_capital = Const.start_capital + underlying_movement * start_gearing * trade_direction 99 if own_capital <= Config.epsilon: 100 result = Config.gearing_treat_as_inf 101 # don't use inf or nan alrady here for nicer coloring of contour plots 102 # this area is later clipped with NaN for wireframe 103 else: 104 result = asset_value / own_capital 105 if result > Config.gearing_treat_as_inf: # the same as in last comment above 106 result = Config.gearing_treat_as_inf 107 return result 108 109 110 def plot_graph(x, y, z): 111 """plot a chart of effective gearings 112 in dependency from start gearing and underlying movement 113 """ 114 strides = [ Config.axes['ranges'][dimension][Const.steps] / Config.strides_areas[dimension] 115 for dimension in ( Const.x, Const.y ) ] 116 117 figure = plot.figure(figsize = Config.figsize, dpi = Config.dpi, facecolor = 'white') 118 axes = figure.add_axes(Config.axes['margins'], projection = '3d') 119 axes.view_init (elev = Config.elevation, azim = Config.azimuth) 120 121 axes.plot(Config.axes['extra_x'], (0.0, 0.0), color = 'black', zorder = Const.top) 122 123 contour_properties = { 124 'alpha' : Config.colors['alpha_contour'] , 125 'cmap' : Config.colors['colormap' ] , 126 'vmin' : Config.colors['limits' ][Const.min] , 127 'vmax' : Config.colors['limits' ][Const.max] } 128 129 axes.contourf (x, y, z, zdir = 'z', ** contour_properties) 130 contour_properties ['offset'] = Const.in_x_y_plane 131 axes.contourf (x, y, z, zdir = 'z', ** contour_properties) 132 133 z = clip_z_data(z) # clip at first here, because data is neede for contour plots 134 # must clip manually, because mplot3d has clipping deficencies 135 136 axes.plot_wireframe (x, y, z, 137 alpha = Config.colors['alpha_wireframe'], cstride = strides[Const.x], rstride = strides[Const.y]) 138 axes.set_title (Config.title) 139 140 # label and limits for axes 141 for dimension in Const.xyz: 142 method_for_dimension(axes, 'set_{}label', dimension)( 143 '\n' * Config.axes['label_adjustments'][dimension] + Config.axes['labels'][dimension]) 144 # positioning of the label with newline prefeixes is a bugfix 145 # because positioning attributes don't work in current release of mplot3d () 146 method_for_dimension(axes, 'set_{}lim', dimension)(* Config.axes['ranges'][dimension][Const.min_max]) 147 148 axes.yaxis.set_major_formatter(PercentFormatter()) 149 150 # adjust y tick label positions 151 plot.draw() # without tis axes.get_yticklabels() will not supply the right values 152 labels_texts = [ label.get_text() for label in axes.get_yticklabels() ] 153 154 axes.set_yticklabels(labels_texts, 155 horizontalalignment = 'left' if Config.view_from_front else 'right', 156 verticalalignment = 'bottom') 157 158 plot.show() 159 160 161 @ numpy.vectorize 162 def clip_z_data(z): 163 """clip z data for fitting the axes as a mplot3d workaround""" 164 min, max = Config.axes['ranges'][Const.z] 165 return z if min <= z <= max else numpy.nan 166 167 168 def method_for_dimension(object, method_name_emplate, dimension): 169 """get a method for the required dimension 170 171 method_name_emplate: string template for string format() method with a '{}' at the position of the dimension 172 """ 173 return object.__getattribute__(method_name_emplate.format(chr(dimension + ord('x')))) 174 175 176 class PercentFormatter(matplotlib.ticker.Formatter): 177 """formats axes tick labels in percents""" 178 179 def __call__(self, x, pos = None): 180 return '%.1f %%' % (x * Const.percent) 181 182 183 if __name__ == '__main__': 184 main()