def plot(data, **kwargs): """ Given open,high,low,close,volume data for a financial instrument (such as a stock, index, currency, future, option, etc.) plot the data. Available plots include ohlc bars, candlestick, and line plots. Also provide visually analysis in the form of common technical studies, such as: moving averages, macd, trading envelope, etc. Also provide ability to plot trading signals, and/or addtional user-defined data. """ dates, opens, highs, lows, closes, volumes = _check_and_prepare_data(data) config = _process_kwargs(kwargs, _valid_kwargs_table()) base = [11.0, 8.5] figscale = config['figscale'] fsize = [d * figscale for d in base] fig = plt.figure() fig.set_size_inches(fsize) # check if we need a lower panel for an additional plot. # if volume=True we will share the lower panel. need_lower_panel = False addplot = config['addplot'] if addplot is not None: if isinstance(addplot, dict): addplot = [ addplot, ] # make list of dict to be consistent elif not _list_of_dict(addplot): raise TypeError('addplot must be `dict`, or `list of dict`, NOT ' + str(type(addplot))) for apdict in addplot: if apdict['panel'] == 'lower': need_lower_panel = True break # fig.add_axes( [left, bottom, width, height] ) ... numbers are fraction of fig if need_lower_panel or config['volume']: if config['volume'] and volumes is None: raise ValueError('Request for volume, but NO volume data.') ax1 = fig.add_axes([0.15, 0.38, 0.70, 0.50]) ax2 = fig.add_axes([0.15, 0.18, 0.70, 0.20], sharex=ax1) else: ax1 = fig.add_axes([0.15, 0.18, 0.70, 0.70]) ax2 = None avg_days_between_points = (dates[-1] - dates[0]) / float(len(dates)) # Default logic for 'no_xgaps': True for intraday data spanning 2 or more days, else False # Caller provided 'no_xgaps' kwarg OVERRIDES default logic. no_xgaps = False # avgerage of 3 or more data points per day we will call intraday data: if avg_days_between_points < 0.33: # intraday if mdates.num2date(dates[-1]).date() != mdates.num2date( dates[0]).date(): # intraday data for more than one day: no_xgaps = True fmtstring = '%b %d, %H:%M' else: # intraday data for a single day fmtstring = '%H:%M' else: # 'daily' data (or could be weekly, etc.) if mdates.num2date(dates[-1]).date().year != mdates.num2date( dates[0]).date().year: fmtstring = '%Y-%b-%d' else: fmtstring = '%b %d' if config[ 'no_xgaps'] is not None: # override whatever was determined above. no_xgaps = config['no_xgaps'] if no_xgaps: formatter = IntegerIndexDateTimeFormatter(dates, fmtstring) xdates = np.arange(len(dates)) else: formatter = mdates.DateFormatter(fmtstring) xdates = dates ax1.xaxis.set_major_formatter(formatter) plt.xticks(rotation=45) ptype = config['type'] collections = None if ptype == 'candle' or ptype == 'candlestick': collections = _construct_candlestick_collections( xdates, opens, highs, lows, closes) elif ptype == 'ohlc' or ptype == 'bars' or ptype == 'ohlc_bars': collections = _construct_ohlc_collections(xdates, opens, highs, lows, closes) elif ptype == 'line': ax1.plot(xdates, closes, color='k') else: raise ValueError('Unrecognized plot type = "' + ptype + '"') if collections is not None: for collection in collections: ax1.add_collection(collection) mavgs = config['mav'] if mavgs is not None: if isinstance(mavgs, int): mavgs = mavgs, # convert to tuple if len(mavgs) > 3: mavgs = mavgs[0:3] # take at most 3 mavcolors = ['turquoise', 'magenta', 'gold'] jj = 0 for mav in mavgs: mavprices = data['Close'].rolling(mav).mean().values ax1.plot(xdates, mavprices, color=mavcolors[jj]) jj += 1 avg_dist_between_points = (xdates[-1] - xdates[0]) / float(len(xdates)) minx = xdates[0] - avg_dist_between_points maxx = xdates[-1] + avg_dist_between_points miny = min([low for low in lows if low != -1]) maxy = max([high for high in highs if high != -1]) corners = (minx, miny), (maxx, maxy) ax1.update_datalim(corners) addplot = config['addplot'] if addplot is not None: if isinstance(addplot, dict): addplot = [ addplot, ] # make list of dict to be consistent elif not _list_of_dict(addplot): raise TypeError('addplot must be `dict`, or `list of dict`, NOT ' + str(type(addplot))) for apdict in addplot: apdata = apdict['data'] if isinstance(apdata, list) and not isinstance(apdata[0], (float, int)): raise TypeError('apdata is list but NOT of float or int') if isinstance(apdata, pd.DataFrame): havedf = True else: havedf = False # must be a single series or array apdata = [ apdata, ] # make it iterable if apdict['panel'] == 'lower': ax = ax2 if config['volume']: ax = ax2.twinx() else: ax = ax1 for column in apdata: if havedf: ydata = apdata.loc[:, column] else: ydata = column if apdict['scatter']: size = apdict['markersize'] mark = apdict['marker'] ax.scatter(xdates, ydata, s=size, marker=mark) else: ax.plot(xdates, ydata) if config['volume']: ax2.bar(xdates, volumes, width=0.7) miny = 0.3 * min(volumes) maxy = 1.1 * max(volumes) ax2.set_ylim(miny, maxy) #ax2.yaxis.set_label_position('right') #ax2.yaxis.tick_right() ax2.xaxis.set_major_formatter(formatter) if need_lower_panel or config['volume']: ax1.spines['bottom'].set_linewidth(0.25) ax2.spines['top'].set_linewidth(0.25) plt.setp(ax1.get_xticklabels(), visible=False) # TODO: ================================================================ # TODO: Investigate: # TODO: =========== # TODO: It appears to me that there may be some or significant overlap # TODO: between what the following functions actually do: # TODO: At the very least, all four of them appear to communicate # TODO: to matplotlib that the xaxis should be treated as dates: # TODO: -> 'ax.autoscale_view()' # TODO: -> 'ax.xaxis_dates()' # TODO: -> 'plt.autofmt_xdates()' # TODO: -> 'fig.autofmt_xdate()' # TODO: ================================================================ #if config['autofmt_xdate']: #print('CALLING fig.autofmt_xdate()') #fig.autofmt_xdate() ax1.autoscale_view() # Is this really necessary?? # really use rcParams: call plt.rc('axes', grid=True) # plt.rc('grid', color='0.75', linestyle='-', linewidth=0.5) ax1.set_ylabel('Price', size='x-large', weight='semibold') if config['volume']: ax2.figure.canvas.draw() # This is needed to calculate offset offset = ax2.yaxis.get_major_formatter().get_offset() ax2.yaxis.offsetText.set_visible(False) vol_label = 'Volume x ' + str(offset) ax2.set_ylabel(vol_label, size='x-large', weight='semibold') plt.show()
def plot(data, **kwargs): """ Given open,high,low,close,volume data for a financial instrument (such as a stock, index, currency, future, option, etc.) plot the data. Available plots include ohlc bars, candlestick, and line plots. Also provide visually analysis in the form of common technical studies, such as: moving averages, macd, trading envelope, etc. Also provide ability to plot trading signals, and/or addtional user-defined data. """ dates, opens, highs, lows, closes, volumes = _check_and_prepare_data(data) config = _process_kwargs(kwargs, _valid_plot_kwargs()) style = config['style'] if isinstance(style, str): style = _styles._get_mpfstyle(style) if isinstance(style, dict): _styles._apply_mpfstyle(style) w, h = config['figratio'] r = float(w) / float(h) if r < 0.25 or r > 4.0: raise ValueError( '"figratio" (aspect ratio) must be between 0.25 and 4.0 (but is ' + str(r) + ')') base = (w, h) figscale = config['figscale'] fsize = [d * figscale for d in base] fig = plt.figure() fig.set_size_inches(fsize) if config['volume'] and volumes is None: raise ValueError('Request for volume, but NO volume data.') # ------------------------------------------------------------- # For now (06-Feb-2020) to keep the code somewhat simpler for # implementing `secondary_y` we are going to ALWAYS create # secondary (twinx) axes, whether we need them or not, and # then they will be available to use later when we are plotting: # ------------------------------------------------------------- need_lower_panel = False addplot = config['addplot'] if addplot is not None: if isinstance(addplot, dict): addplot = [ addplot, ] # make list of dict to be consistent elif not _list_of_dict(addplot): raise TypeError('addplot must be `dict`, or `list of dict`, NOT ' + str(type(addplot))) for apdict in addplot: if apdict['panel'] == 'lower': need_lower_panel = True break # fig.add_axes( [left, bottom, width, height] ) ... numbers are fraction of fig if need_lower_panel or config['volume']: ax1 = fig.add_axes([0.15, 0.38, 0.70, 0.50]) ax2 = fig.add_axes([0.15, 0.18, 0.70, 0.20], sharex=ax1) plt.xticks(rotation=45) # must do this after creation of axis, and # after `sharex`, but must be BEFORE any 'twinx()' ax2.set_axisbelow(True) # so grid does not show through volume bars. ax4 = ax2.twinx() ax4.grid(False) else: ax1 = fig.add_axes([0.15, 0.18, 0.70, 0.70]) plt.xticks( rotation=45 ) # must do this after creation of axis, but before any 'twinx()' ax2 = None ax4 = None ax3 = ax1.twinx() ax3.grid(False) avg_days_between_points = (dates[-1] - dates[0]) / float(len(dates)) # avgerage of 3 or more data points per day we will call intraday data: if avg_days_between_points < 0.33: # intraday if mdates.num2date(dates[-1]).date() != mdates.num2date( dates[0]).date(): # intraday data for more than one day: fmtstring = '%b %d, %H:%M' else: # intraday data for a single day fmtstring = '%H:%M' else: # 'daily' data (or could be weekly, etc.) if mdates.num2date(dates[-1]).date().year != mdates.num2date( dates[0]).date().year: fmtstring = '%Y-%b-%d' else: fmtstring = '%b %d' if config['show_nontrading']: formatter = mdates.DateFormatter(fmtstring) xdates = dates else: formatter = IntegerIndexDateTimeFormatter(dates, fmtstring) xdates = np.arange(len(dates)) ax1.xaxis.set_major_formatter(formatter) ptype = config['type'] collections = None if ptype == 'candle' or ptype == 'candlestick': collections = _construct_candlestick_collections( xdates, opens, highs, lows, closes, marketcolors=style['marketcolors']) elif ptype == 'ohlc' or ptype == 'bars' or ptype == 'ohlc_bars': collections = _construct_ohlc_collections( xdates, opens, highs, lows, closes, marketcolors=style['marketcolors']) elif ptype == 'line': ax1.plot(xdates, closes, color=config['linecolor']) else: raise ValueError('Unrecognized plot type = "' + ptype + '"') if collections is not None: for collection in collections: ax1.add_collection(collection) mavgs = config['mav'] if mavgs is not None: if isinstance(mavgs, int): mavgs = mavgs, # convert to tuple if len(mavgs) > 7: mavgs = mavgs[0:7] # take at most 7 if style['mavcolors'] is not None: mavc = cycle(style['mavcolors']) else: mavc = None for mav in mavgs: mavprices = data['Close'].rolling(mav).mean().values if mavc: ax1.plot(xdates, mavprices, color=next(mavc)) else: ax1.plot(xdates, mavprices) avg_dist_between_points = (xdates[-1] - xdates[0]) / float(len(xdates)) minx = xdates[0] - avg_dist_between_points maxx = xdates[-1] + avg_dist_between_points miny = min([low for low in lows if low != -1]) maxy = max([high for high in highs if high != -1]) corners = (minx, miny), (maxx, maxy) ax1.update_datalim(corners) if config['volume']: vup, vdown = style['marketcolors']['volume'].values() #-- print('vup,vdown=',vup,vdown) vcolors = _updown_colors( vup, vdown, opens, closes, use_prev_close=style['marketcolors']['vcdopcod']) #-- print('len(vcolors),len(opens),len(closes)=',len(vcolors),len(opens),len(closes)) #-- print('vcolors=',vcolors) width = 0.5 * avg_dist_between_points ax2.bar(xdates, volumes, width=width, color=vcolors) miny = 0.3 * min(volumes) maxy = 1.1 * max(volumes) ax2.set_ylim(miny, maxy) ax2.xaxis.set_major_formatter(formatter) used_ax3 = False used_ax4 = False addplot = config['addplot'] if addplot is not None: # Calculate the Order of Magnitude Range # If addplot['secondary_y'] == 'auto', then: If the addplot['data'] # is out of the Order of Magnitude Range, then use secondary_y. # Calculate omrange for Main panel, and for Lower (volume) panel: lo = math.log(max(math.fabs(min(lows)), 1e-7), 10) - 0.5 hi = math.log(max(math.fabs(max(highs)), 1e-7), 10) + 0.5 omrange = {'main': {'lo': lo, 'hi': hi}, 'lower': None} if config['volume']: lo = math.log(max(math.fabs(min(volumes)), 1e-7), 10) - 0.5 hi = math.log(max(math.fabs(max(volumes)), 1e-7), 10) + 0.5 omrange.update(lower={'lo': lo, 'hi': hi}) if isinstance(addplot, dict): addplot = [ addplot, ] # make list of dict to be consistent elif not _list_of_dict(addplot): raise TypeError('addplot must be `dict`, or `list of dict`, NOT ' + str(type(addplot))) for apdict in addplot: apdata = apdict['data'] if isinstance(apdata, list) and not isinstance(apdata[0], (float, int)): raise TypeError('apdata is list but NOT of float or int') if isinstance(apdata, pd.DataFrame): havedf = True else: havedf = False # must be a single series or array apdata = [ apdata, ] # make it iterable for column in apdata: if havedf: ydata = apdata.loc[:, column] else: ydata = column yd = [y for y in ydata if not math.isnan(y)] ymhi = math.log(max(math.fabs(max(yd)), 1e-7), 10) ymlo = math.log(max(math.fabs(min(yd)), 1e-7), 10) secondary_y = False if apdict['secondary_y'] == 'auto': if apdict['panel'] == 'lower': # If omrange['lower'] is not yet set by volume, # then set it here as this is the first ydata # to be plotted on the lower panel, so consider # it to be the 'primary' lower panel axis. if omrange['lower'] is None: omrange.update(lower={'lo': ymlo, 'hi': ymhi}) elif ymlo < omrange['lower']['lo'] or ymhi > omrange[ 'lower']['hi']: secondary_y = True elif ymlo < omrange['main']['lo'] or ymhi > omrange[ 'main']['hi']: secondary_y = True # if secondary_y: # print('auto says USE secondary_y') # else: # print('auto says do NOT use secondary_y') else: secondary_y = apdict['secondary_y'] # print("apdict['secondary_y'] says secondary_y is",secondary_y) if apdict['panel'] == 'lower': ax = ax4 if secondary_y else ax2 else: ax = ax3 if secondary_y else ax1 if ax == ax3: used_ax3 = True if ax == ax4: used_ax4 = True if apdict['scatter']: size = apdict['markersize'] mark = apdict['marker'] color = apdict['color'] ax.scatter(xdates, ydata, s=size, marker=mark, color=color) else: ls = apdict['linestyle'] color = apdict['color'] ax.plot(xdates, ydata, linestyle=ls, color=color) # put the twinx() on the "other" side: if style['y_on_right']: ax1.yaxis.set_label_position('right') ax1.yaxis.tick_right() ax3.yaxis.set_label_position('left') ax3.yaxis.tick_left() if ax2 and ax4: ax2.yaxis.set_label_position('right') ax2.yaxis.tick_right() if ax4 != ax2: ax4.yaxis.set_label_position('left') ax4.yaxis.tick_left() else: ax1.yaxis.set_label_position('left') ax1.yaxis.tick_left() ax3.yaxis.set_label_position('right') ax3.yaxis.tick_right() if ax2 and ax4: ax2.yaxis.set_label_position('left') ax2.yaxis.tick_left() if ax4 != ax2: ax4.yaxis.set_label_position('right') ax4.yaxis.tick_right() if need_lower_panel or config['volume']: ax1.spines['bottom'].set_linewidth(0.25) ax2.spines['top'].set_linewidth(0.25) plt.setp(ax1.get_xticklabels(), visible=False) # TODO: ================================================================ # TODO: Investigate: # TODO: =========== # TODO: It appears to me that there may be some or significant overlap # TODO: between what the following functions actually do: # TODO: At the very least, all four of them appear to communicate # TODO: to matplotlib that the xaxis should be treated as dates: # TODO: -> 'ax.autoscale_view()' # TODO: -> 'ax.xaxis_dates()' # TODO: -> 'plt.autofmt_xdates()' # TODO: -> 'fig.autofmt_xdate()' # TODO: ================================================================ #if config['autofmt_xdate']: #print('CALLING fig.autofmt_xdate()') #fig.autofmt_xdate() ax1.autoscale_view() # Is this really necessary?? ax1.set_ylabel(config['ylabel']) if config['volume']: ax2.figure.canvas.draw() # This is needed to calculate offset offset = ax2.yaxis.get_major_formatter().get_offset() ax2.yaxis.offsetText.set_visible(False) if len(offset) > 0: offset = (' x ' + offset) if config['ylabel_lower'] is None: vol_label = 'Volume' + offset else: if len(offset) > 0: offset = '\n' + offset vol_label = config['ylabel_lower'] + offset ax2.set_ylabel(vol_label) if config['title'] is not None: fig.suptitle(config['title'], size='x-large', weight='semibold') if not used_ax3 and ax3 is not None: ax3.get_yaxis().set_visible(False) if not used_ax4 and ax4 is not None: ax4.get_yaxis().set_visible(False) if config['savefig'] is not None: save = config['savefig'] if isinstance(save, dict): plt.savefig(**save) else: plt.savefig(save) else: # https://stackoverflow.com/a/13361748/1639359 suggests plt.show(block=False) plt.show(block=config['block'])
def plot(data, is_not_set_visible=False, **kwargs): """ Given open,high,low,close,volume data for a financial instrument (such as a stock, index, currency, future, option, etc.) plot the data. Available plots include ohlc bars, candlestick, and line plots. Also provide visually analysis in the form of common technical studies, such as: moving averages, macd, trading envelope, etc. Also provide ability to plot trading signals, and/or addtional user-defined data. """ config = _process_kwargs(kwargs, _valid_plot_kwargs()) dates, opens, highs, lows, closes, volumes = _check_and_prepare_data( data, config) if config["type"] in VALID_PMOVE_TYPES and config["addplot"] is not None: err = "`addplot` is not supported for `type='" + config["type"] + "'`" raise ValueError(err) style = config["style"] if isinstance(style, str): style = _styles._get_mpfstyle(style) if isinstance(style, dict): _styles._apply_mpfstyle(style) w, h = config["figratio"] r = float(w) / float(h) if r < 0.25 or r > 4.0: raise ValueError( '"figratio" (aspect ratio) must be between 0.25 and 4.0 (but is ' + str(r) + ")") base = (w, h) figscale = config["figscale"] fsize = [d * figscale for d in base] fig = plt.figure() fig.set_size_inches(fsize) if config["volume"] and volumes is None: raise ValueError("Request for volume, but NO volume data.") # ------------------------------------------------------------- # For now (06-Feb-2020) to keep the code somewhat simpler for # implementing `secondary_y` we are going to ALWAYS create # secondary (twinx) axes, whether we need them or not, and # then they will be available to use later when we are plotting: # ------------------------------------------------------------- need_lower_panel = False addplot = config["addplot"] if addplot is not None: if isinstance(addplot, dict): addplot = [ addplot, ] # make list of dict to be consistent elif not _list_of_dict(addplot): raise TypeError("addplot must be `dict`, or `list of dict`, NOT " + str(type(addplot))) for apdict in addplot: if apdict["panel"] == "lower": need_lower_panel = True break # fig.add_axes( [left, bottom, width, height] ) ... numbers are fraction of fig if need_lower_panel or config["volume"]: ax1 = fig.add_axes([0.15, 0.38, 0.70, 0.50]) ax2 = fig.add_axes([0.15, 0.18, 0.70, 0.20], sharex=ax1) plt.xticks(rotation=45) # must do this after creation of axis, and # after `sharex`, but must be BEFORE any 'twinx()' ax2.set_axisbelow(True) # so grid does not show through volume bars. ax4 = ax2.twinx() ax4.grid(False) else: ax1 = fig.add_axes([0.15, 0.18, 0.70, 0.70]) plt.xticks( rotation=45 ) # must do this after creation of axis, but before any 'twinx()' ax2 = None ax4 = None ax3 = ax1.twinx() ax3.grid(False) avg_days_between_points = (dates[-1] - dates[0]) / float(len(dates)) # avgerage of 3 or more data points per day we will call intraday data: if avg_days_between_points < 0.33: # intraday if mdates.num2date(dates[-1]).date() != mdates.num2date( dates[0]).date(): # intraday data for more than one day: fmtstring = "%b %d, %H:%M" else: # intraday data for a single day fmtstring = "%H:%M" else: # 'daily' data (or could be weekly, etc.) if (mdates.num2date(dates[-1]).date().year != mdates.num2date( dates[0]).date().year): fmtstring = "%Y-%b-%d" else: fmtstring = "%b %d" ptype = config["type"] if ptype not in VALID_PMOVE_TYPES: if config["show_nontrading"]: formatter = mdates.DateFormatter(fmtstring) xdates = dates else: formatter = IntegerIndexDateTimeFormatter(dates, fmtstring) xdates = np.arange(len(dates)) ax1.xaxis.set_major_formatter(formatter) collections = None if ptype == "candle" or ptype == "candlestick": collections = _construct_candlestick_collections( xdates, opens, highs, lows, closes, marketcolors=style["marketcolors"]) elif ptype == "ohlc" or ptype == "bars" or ptype == "ohlc_bars": collections = _construct_ohlc_collections( xdates, opens, highs, lows, closes, marketcolors=style["marketcolors"]) elif ptype == "renko": ( collections, new_dates, volumes, brick_values, size, ) = _construct_renko_collections( dates, highs, lows, volumes, config["renko_params"], closes, marketcolors=style["marketcolors"], ) elif ptype == "pnf" or ptype == "p&f" or ptype == "pointnfigure": ( collections, new_dates, volumes, brick_values, size, ) = _construct_pointnfig_collections( dates, highs, lows, volumes, config["pointnfig_params"], closes, marketcolors=style["marketcolors"], ) elif ptype == "line": ax1.plot(xdates, closes, color=config["linecolor"]) else: raise ValueError('Unrecognized plot type = "' + ptype + '"') if ptype in VALID_PMOVE_TYPES: formatter = IntegerIndexDateTimeFormatter(new_dates, fmtstring) xdates = np.arange(len(new_dates)) ax1.xaxis.set_major_formatter(formatter) if collections is not None: for collection in collections: ax1.add_collection(collection) mavgs = config["mav"] if mavgs is not None: if isinstance(mavgs, int): mavgs = (mavgs, ) # convert to tuple if len(mavgs) > 7: mavgs = mavgs[0:7] # take at most 7 if style["mavcolors"] is not None: mavc = cycle(style["mavcolors"]) else: mavc = None for mav in mavgs: if ptype in VALID_PMOVE_TYPES: mavprices = pd.Series(brick_values).rolling(mav).mean().values else: mavprices = data["Close"].rolling(mav).mean().values if mavc: ax1.plot(xdates, mavprices, color=next(mavc)) else: ax1.plot(xdates, mavprices) if config["return_calculated_values"] is not None: retdict = config["return_calculated_values"] if ptype == "renko": retdict["renko_bricks"] = brick_values retdict["renko_dates"] = mdates.num2date(new_dates) if config["volume"]: retdict["renko_volumes"] = volumes if mavgs is not None: for i in range(0, len(mavgs)): retdict["mav" + str(mavgs[i])] = mavprices avg_dist_between_points = (xdates[-1] - xdates[0]) / float(len(xdates)) minx = xdates[0] - avg_dist_between_points maxx = xdates[-1] + avg_dist_between_points if ptype not in VALID_PMOVE_TYPES: miny = min([low for low in lows if low != -1]) maxy = max([high for high in highs if high != -1]) else: miny = min([brick for brick in brick_values]) maxy = max([brick + size for brick in brick_values]) corners = (minx, miny), (maxx, maxy) ax1.update_datalim(corners) if config["volume"]: vup, vdown = style["marketcolors"]["volume"].values() # -- print('vup,vdown=',vup,vdown) vcolors = _updown_colors( vup, vdown, opens, closes, use_prev_close=style["marketcolors"]["vcdopcod"]) # -- print('len(vcolors),len(opens),len(closes)=',len(vcolors),len(opens),len(closes)) # -- print('vcolors=',vcolors) width = 0.5 * avg_dist_between_points ax2.bar(xdates, volumes, width=width, color=vcolors) miny = 0.3 * min(volumes) maxy = 1.1 * max(volumes) ax2.set_ylim(miny, maxy) ax2.xaxis.set_major_formatter(formatter) used_ax3 = False used_ax4 = False addplot = config["addplot"] if addplot is not None and ptype not in VALID_PMOVE_TYPES: # Calculate the Order of Magnitude Range # If addplot['secondary_y'] == 'auto', then: If the addplot['data'] # is out of the Order of Magnitude Range, then use secondary_y. # Calculate omrange for Main panel, and for Lower (volume) panel: lo = math.log(max(math.fabs(min(lows)), 1e-7), 10) - 0.5 hi = math.log(max(math.fabs(max(highs)), 1e-7), 10) + 0.5 omrange = {"main": {"lo": lo, "hi": hi}, "lower": None} if config["volume"]: lo = math.log(max(math.fabs(min(volumes)), 1e-7), 10) - 0.5 hi = math.log(max(math.fabs(max(volumes)), 1e-7), 10) + 0.5 omrange.update(lower={"lo": lo, "hi": hi}) if isinstance(addplot, dict): addplot = [ addplot, ] # make list of dict to be consistent elif not _list_of_dict(addplot): raise TypeError("addplot must be `dict`, or `list of dict`, NOT " + str(type(addplot))) for apdict in addplot: apdata = apdict["data"] if isinstance(apdata, list) and not isinstance(apdata[0], (float, int)): raise TypeError("apdata is list but NOT of float or int") if isinstance(apdata, pd.DataFrame): havedf = True else: havedf = False # must be a single series or array apdata = [ apdata, ] # make it iterable for column in apdata: if havedf: ydata = apdata.loc[:, column] else: ydata = column yd = [y for y in ydata if not math.isnan(y)] ymhi = math.log(max(math.fabs(max(yd)), 1e-7), 10) ymlo = math.log(max(math.fabs(min(yd)), 1e-7), 10) secondary_y = False if apdict["secondary_y"] == "auto": if apdict["panel"] == "lower": # If omrange['lower'] is not yet set by volume, # then set it here as this is the first ydata # to be plotted on the lower panel, so consider # it to be the 'primary' lower panel axis. if omrange["lower"] is None: omrange.update(lower={"lo": ymlo, "hi": ymhi}) elif (ymlo < omrange["lower"]["lo"] or ymhi > omrange["lower"]["hi"]): secondary_y = True elif ymlo < omrange["main"]["lo"] or ymhi > omrange[ "main"]["hi"]: secondary_y = True # if secondary_y: # print('auto says USE secondary_y') # else: # print('auto says do NOT use secondary_y') else: secondary_y = apdict["secondary_y"] # print("apdict['secondary_y'] says secondary_y is",secondary_y) if apdict["panel"] == "lower": ax = ax4 if secondary_y else ax2 else: ax = ax3 if secondary_y else ax1 if ax == ax3: used_ax3 = True if ax == ax4: used_ax4 = True if apdict["scatter"]: size = apdict["markersize"] mark = apdict["marker"] color = apdict["color"] ax.scatter(xdates, ydata, s=size, marker=mark, color=color) else: ls = apdict["linestyle"] color = apdict["color"] ax.plot(xdates, ydata, linestyle=ls, color=color) # put the twinx() on the "other" side: if style["y_on_right"]: ax1.yaxis.set_label_position("right") ax1.yaxis.tick_right() ax3.yaxis.set_label_position("left") ax3.yaxis.tick_left() if ax2 and ax4: ax2.yaxis.set_label_position("right") ax2.yaxis.tick_right() if ax4 != ax2: ax4.yaxis.set_label_position("left") ax4.yaxis.tick_left() else: ax1.yaxis.set_label_position("left") ax1.yaxis.tick_left() ax3.yaxis.set_label_position("right") ax3.yaxis.tick_right() if ax2 and ax4: ax2.yaxis.set_label_position("left") ax2.yaxis.tick_left() if ax4 != ax2: ax4.yaxis.set_label_position("right") ax4.yaxis.tick_right() if need_lower_panel or config["volume"]: ax1.spines["bottom"].set_linewidth(0.25) ax2.spines["top"].set_linewidth(0.25) plt.setp(ax1.get_xticklabels(), visible=False) # TODO: ================================================================ # TODO: Investigate: # TODO: =========== # TODO: It appears to me that there may be some or significant overlap # TODO: between what the following functions actually do: # TODO: At the very least, all four of them appear to communicate # TODO: to matplotlib that the xaxis should be treated as dates: # TODO: -> 'ax.autoscale_view()' # TODO: -> 'ax.xaxis_dates()' # TODO: -> 'plt.autofmt_xdates()' # TODO: -> 'fig.autofmt_xdate()' # TODO: ================================================================ # if config['autofmt_xdate']: # print('CALLING fig.autofmt_xdate()') # fig.autofmt_xdate() ax1.autoscale_view() # Is this really necessary?? ax1.set_ylabel(config["ylabel"]) if config["volume"]: ax2.figure.canvas.draw() # This is needed to calculate offset offset = ax2.yaxis.get_major_formatter().get_offset() ax2.yaxis.offsetText.set_visible(False) if len(offset) > 0: offset = " x " + offset if config["ylabel_lower"] is None: vol_label = "Volume" + offset else: if len(offset) > 0: offset = "\n" + offset vol_label = config["ylabel_lower"] + offset ax2.set_ylabel(vol_label) if config["title"] is not None: fig.suptitle(config["title"], size="x-large", weight="semibold") if not used_ax3 and ax3 is not None: ax3.get_yaxis().set_visible(False) if not used_ax4 and ax4 is not None: ax4.get_yaxis().set_visible(False) if config["returnfig"]: axlist = [ax1, ax3] if ax2: axlist.append(ax2) if ax4: axlist.append(ax4) # 自分で追加した if is_not_set_visible: # 枠線を消す + x,y軸の数値(目盛り)消す try: ax1.spines["right"].set_color("none") # 右消し ax1.spines["left"].set_color("none") # 左消し ax1.spines["top"].set_color("none") # 上消し ax1.spines["bottom"].set_color("none") # 下消し ax1.axes.xaxis.set_visible(False) ax1.axes.yaxis.set_visible(False) except: pass try: ax2.spines["right"].set_color("none") # 右消し ax2.spines["left"].set_color("none") # 左消し ax2.spines["top"].set_color("none") # 上消し ax2.spines["bottom"].set_color("none") # 下消し ax2.axes.xaxis.set_visible(False) ax2.axes.yaxis.set_visible(False) except: pass try: ax3.spines["right"].set_color("none") # 右消し ax3.spines["left"].set_color("none") # 左消し ax3.spines["top"].set_color("none") # 上消し ax3.spines["bottom"].set_color("none") # 下消し ax3.axes.xaxis.set_visible(False) ax3.axes.yaxis.set_visible(False) except: pass try: ax4.spines["right"].set_color("none") # 右消し ax4.spines["left"].set_color("none") # 左消し ax4.spines["top"].set_color("none") # 上消し ax4.spines["bottom"].set_color("none") # 下消し ax4.axes.xaxis.set_visible(False) ax4.axes.yaxis.set_visible(False) except: pass if config["savefig"] is not None: save = config["savefig"] if isinstance(save, dict): plt.savefig(**save) else: plt.savefig(save, bbox_inches="tight", pad_inches=0) # 余白削除オプション付けた # plt.savefig(save) elif not config["returnfig"]: # https://stackoverflow.com/a/13361748/1639359 suggests plt.show(block=False) plt.show(block=config["block"]) if config["returnfig"]: return (fig, axlist) # メモリ解放 forで回してるとどんどんメモリ食われるので # https://qiita.com/Masahiro_T/items/bdd0482a8efd84cdd270 plt.clf() plt.close()