コード例 #1
0
ファイル: _widths.py プロジェクト: matplotlib/mplfinance
def _determine_width_config( xdates, config ):
    '''
    Given x-axis xdates, and `mpf.plot()` kwargs config,
    determine the widths and linewidths for candles,
    volume bars, ohlc bars, etc.
    '''
    datalen = len(xdates)
    avg_dist_between_points = (xdates[-1] - xdates[0]) / float(datalen)

    tweak  = 1.06 if datalen > 100 else 1.03

    adjust = tweak*avg_dist_between_points if config['show_nontrading'] else 1.0

    width_config = {}

    if config['width_adjuster_version'] == 'v0':  # Behave like original version of code:

        width_config['volume_width'    ] = 0.5*avg_dist_between_points
        width_config['volume_linewidth'] = None
        width_config['ohlc_ticksize'   ] = avg_dist_between_points / 2.5
        width_config['ohlc_linewidth'  ] = None
        width_config['candle_width'    ] = avg_dist_between_points / 2.0
        width_config['candle_linewidth'] = None
        width_config['line_width'      ] = None

    else: # config['width_adjuster_version'] == 'v1'

        width_config['volume_width'    ] = _dfinterpolate(_widths,datalen,'vw' ) * adjust
        width_config['volume_linewidth'] = _dfinterpolate(_widths,datalen,'vlw')
        width_config['ohlc_ticksize'   ] = _dfinterpolate(_widths,datalen,'ow' ) * adjust
        width_config['ohlc_linewidth'  ] = _dfinterpolate(_widths,datalen,'olw')
        width_config['candle_width'    ] = _dfinterpolate(_widths,datalen,'cw' ) * adjust
        width_config['candle_linewidth'] = _dfinterpolate(_widths,datalen,'clw')
        width_config['line_width'      ] = _dfinterpolate(_widths,datalen,'lw')

    if config['scale_width_adjustment'] is not None:

        scale = _process_kwargs(config['scale_width_adjustment'],_valid_scale_width_kwargs())
        if scale['volume'] is not None:
            width_config['volume_width']  *= scale['volume']
        if scale['ohlc'] is not None:
            width_config['ohlc_ticksize'] *= scale['ohlc']
        if scale['candle'] is not None:
            width_config['candle_width']  *= scale['candle']
        if scale['lines'] is not None:
            width_config['line_width']    *= scale['lines']
        if scale['volume_linewidth'] is not None:
            width_config['volume_linewidth']  *= scale['volume_linewidth']
        if scale['ohlc_linewidth'] is not None: 
            width_config['ohlc_linewidth'  ]  *= scale['ohlc_linewidth']
        if scale['candle_linewidth'] is not None:
            width_config['candle_linewidth']  *= scale['candle_linewidth']

    if config['update_width_config'] is not None:
     
        update = _process_kwargs(config['update_width_config'],_valid_update_width_kwargs())
        uplist = [ (k,v) for k,v in update.items() if v is not None ]
        width_config.update(uplist)

    return width_config
コード例 #2
0
def _construct_aline_collections(alines, dtix=None):
    """construct arbitrary line collections

    Parameters
    ----------
    alines : sequence
        sequences of segments, which are sequences of lines,
        which are sequences of two or more points ( date[time], price ) or (x,y) 

        date[time] may be (a) pandas.to_datetime parseable string,
                          (b) pandas Timestamp, or
                          (c) python datetime.datetime or datetime.date

    alines may also be a dict, containing
    the following keys:

        'alines'     : the same as defined above: sequence of price, or dates, or segments
        'colors'     : colors for the above alines
        'linestyle'  : line types for the above alines
        'linewidths' : line types for the above alines

    dtix:  date index for the x-axis, used for converting the dates when
           x-values are 'evenly spaced integers' (as when skipping non-trading days)

    Returns
    -------
    ret : list
        lines collections
    """
    if alines is None:
        return None

    if isinstance(alines, dict):
        aconfig = _process_kwargs(alines, _valid_lines_kwargs())
        alines = aconfig['alines']
    else:
        aconfig = _process_kwargs({}, _valid_lines_kwargs())

    #print('aconfig=',aconfig)
    #print('alines=',alines)

    alines = _alines_validator(alines, returnStandardizedValue=True)
    if alines is None:
        raise ValueError('Unable to standardize alines value: ' + str(alines))

    alines = _convert_segment_dates(alines, dtix)

    lw = aconfig['linewidths']
    co = aconfig['colors']
    ls = aconfig['linestyle']
    al = aconfig['alpha']
    lcollection = LineCollection(alines,
                                 colors=co,
                                 linewidths=lw,
                                 linestyles=ls,
                                 antialiaseds=(0, ),
                                 alpha=al)
    return lcollection
コード例 #3
0
def _construct_hline_collections(hlines,minx,maxx):
    """Construct horizontal lines collection

    Parameters
    ----------
    hlines : sequence
        sequence of [price] values at which to draw horizontal lines

    hlines may also be a dict, containing
    the following keys:

        'hlines'     : the same as defined above: sequence of price, or dates, or segments
        'colors'     : colors for the above hlines
        'linestyle'  : line types for the above hlines
        'linewidths' : line types for the above hlines

    minx : the minimum value for x for the horizontal line, already converted to `xdates` format
    maxx : the maximum value for x for the horizontal line, already converted to `xdates` format

    Returns
    -------
    ret : list
        lines collections
    """

    if hlines is None:
        return None

    #print('_construct_hline_collections() called:',
    #      '\nhlines=',hlines,'\nminx,maxx=',minx,maxx)

    # hlines do NOT require converting segment dates, because the dates
    # are not user-specified, but are from already converted minxdt,maxxdt

    if isinstance(hlines,dict):
        hconfig = _process_kwargs(hlines, _valid_lines_kwargs())
        hlines = hconfig['hlines']
    else:
        hconfig = _process_kwargs({}, _valid_lines_kwargs())

    #print('hconfig=',hconfig)
    #print('hlines=',hlines)
    
    lines = []
    if not isinstance(hlines,(list,tuple)):
        hlines = [hlines,] # may be a single price value

    for val in hlines:
        lines.append( [(minx,val),(maxx,val)] )

    lw = hconfig['linewidths']
    co = hconfig['colors']
    ls = hconfig['linestyle']
    al = hconfig['alpha']
    lcollection = LineCollection(lines,colors=co,linewidths=lw,linestyles=ls,antialiaseds=(0,),alpha=al)
    return lcollection
コード例 #4
0
def make_addplot(data, **kwargs):
    '''
    Take data (pd.Series, pd.DataFrame, np.ndarray of floats, list of floats), and
    kwargs (see valid_addplot_kwargs_table) and construct a correctly structured dict
    to be passed into plot() using kwarg `addplot`.  
    NOTE WELL: len(data) here must match the len(data) passed into plot()
    '''
    if not isinstance(data, (pd.Series, pd.DataFrame, np.ndarray, list)):
        raise TypeError('Wrong type for data, in make_addplot()')

    config = _process_kwargs(kwargs, _valid_addplot_kwargs())

    return dict(data=data, **config)
コード例 #5
0
def make_mpf_style(**kwargs):
    config = _process_kwargs(kwargs, _valid_make_mpf_style_kwargs())

    if config['base_mpf_style'] is not None:
        style = _get_mpfstyle(config['base_mpf_style'])
        update = [(k, v) for k, v in config.items() if v is not None]
        style.update(update)
    else:
        style = config

    if style['marketcolors'] is None:
        style['marketcolors'] = _styles['default']['marketcolors']

    return style
コード例 #6
0
ファイル: _styles.py プロジェクト: matplotlib/mplfinance
def make_mpf_style( **kwargs ):
    config = _process_kwargs(kwargs, _valid_make_mpf_style_kwargs())
    if config['rc'] is not None and config['legacy_rc'] is not None:
        raise ValueError('kwargs `rc` and `legacy_rc` may NOT be used together!')

    # -----------
    # March 2021: Found bug that if caller used `base_mpf_style` and `rc` at
    #   the same time, then the caller's `rc` will completely replace the `rc` 
    #   of `base_mpf_style`.  That was never the intention!  Rather it should be
    #   that the caller's `rc` merely adds to and/or modifies the `rc` of the
    #   `base_mpf_style`.  In order to provide a path to "backwards compatibility"
    #   for users who may have depended on the bug behavior (callers `rc` replaces
    #   `rc` of `base_mpf_style`) we provide a new kwarg `legacy_rc` which will
    #   now behave the way that `rc` used to behave.
    # -----------

    if config['base_mpf_style'] is not None:
        style  = _get_mpfstyle(config['base_mpf_style'])
        # Have to handle 'rc' separately, so we don't wipe 
        # out the 'rc' params in the `base_mpf_style` that
        # are not specified in the `make_mpf_style` config:
        if config['rc'] is not None:
            rc = config['rc']
            del config['rc']
            if isinstance(style['rc'],list):
                style['rc'] = dict(style['rc'])
            if style['rc'] is None:
                style['rc'] = {}
            style['rc'].update(rc)
        elif config['legacy_rc'] is not None:
            config['rc'] = config['legacy_rc']
            del config['legacy_rc']
        update = [ (k,v) for k,v in config.items() if v is not None ]
        style.update(update)
    else:
        style  = config

    if style['marketcolors'] is None:
        style['marketcolors'] = _styles['default']['marketcolors']

    return style
コード例 #7
0
def _construct_pointnfig_collections(dates,
                                     highs,
                                     lows,
                                     volumes,
                                     config_pointnfig_params,
                                     closes,
                                     marketcolors=None):
    """Represent the price change with Xs and Os

    Parameters
    ----------
    dates : sequence
        sequence of dates
    highs : sequence
        sequence of high values
    lows : sequence
        sequence of low values
    config_pointnfig_params : kwargs table (dictionary)
        box_size : size of each box
        atr_length : length of time used for calculating atr
    closes : sequence
        sequence of closing values
    marketcolors : dict of colors: up, down, edge, wick, alpha

    Returns
    -------
    ret : tuple
        rectCollection
    """
    pointnfig_params = _process_kwargs(config_pointnfig_params,
                                       _valid_pointnfig_kwargs())
    if marketcolors is None:
        marketcolors = _get_mpfstyle('classic')['marketcolors']
        print('default market colors:', marketcolors)

    box_size = pointnfig_params['box_size']
    atr_length = pointnfig_params['atr_length']

    if box_size == 'atr':
        box_size = _calculate_atr(atr_length, highs, lows, closes)
    else:  # is an integer or float
        total_atr = _calculate_atr(len(closes) - 1, highs, lows, closes)
        upper_limit = 5 * total_atr
        lower_limit = 0.01 * total_atr
        if box_size > upper_limit:
            raise ValueError(
                "Specified box_size may not be larger than (1.5* the Average True Value of the dataset) which has value: "
                + str(upper_limit))
        elif box_size < lower_limit:
            raise ValueError(
                "Specified box_size may not be smaller than (0.01* the Average True Value of the dataset) which has value: "
                + str(lower_limit))

    alpha = marketcolors['alpha']

    uc = mcolors.to_rgba(marketcolors['candle']['up'], alpha)
    dc = mcolors.to_rgba(marketcolors['candle']['down'], alpha)
    tfc = mcolors.to_rgba(marketcolors['edge']['down'],
                          0)  # transparent face color

    cdiff = []
    prev_close_box = closes[0]
    new_volumes = [
    ]  # holds the volumes corresponding with the index.  If more than one index for the same day then they all have the same volume.
    new_dates = []  # holds the dates corresponding with the index
    volume_cache = 0  # holds the volumes for the dates that were skipped
    prev_sign = 0
    current_cdiff_index = -1

    for i in range(len(closes) - 1):
        box_diff = int((closes[i + 1] - prev_close_box) / box_size)
        if box_diff == 0:
            if volumes is not None:
                volume_cache += volumes[i]
            continue
        sign = box_diff / abs(box_diff)
        if sign == prev_sign:
            cdiff[current_cdiff_index] += box_diff
            if volumes is not None:
                new_volumes[current_cdiff_index] += volumes[i] + volume_cache
                volume_cache = 0
        else:
            cdiff.append(box_diff)
            if volumes is not None:
                new_volumes.append(volumes[i] + volume_cache)
                volume_cache = 0
            new_dates.append(dates[i])
            prev_sign = sign
            current_cdiff_index += 1

        prev_close_box += box_diff * box_size

    curr_price = closes[0]

    box_values = []  # y values for the boxes
    circle_patches = [
    ]  # list of circle patches to be used to create the cirCollection
    line_seg = []  # line segments that make up the Xs

    for index, difference in enumerate(cdiff):
        diff = abs(difference)

        sign = (difference / abs(difference))  # -1 or 1
        start_iteration = 0 if sign > 0 else 1

        x = [index] * (diff)
        y = [
            curr_price + (i * box_size * sign)
            for i in range(start_iteration, diff + start_iteration)
        ]

        curr_price += (box_size * sign * (diff))
        box_values.append(sum(y) / len(y))

        for i in range(len(x)):  # x and y have the same length
            height = box_size * 0.85
            width = (50 / box_size) / len(new_dates)
            if height < 0.5:
                width = height

            padding = (box_size * 0.075)
            if sign == 1:  # X
                line_seg.append([(x[i] - width / 2, y[i] + padding),
                                 (x[i] + width / 2, y[i] + height + padding)
                                 ])  # create / part of the X
                line_seg.append([(x[i] - width / 2, y[i] + height + padding),
                                 (x[i] + width / 2, y[i] + padding)
                                 ])  # create \ part of the X
            else:  # O
                circle_patches.append(
                    Ellipse((x[i], y[i] - (height / 2) - padding), width,
                            height))

    useAA = 0,  # use tuple here
    lw = 0.5

    cirCollection = PatchCollection(circle_patches)
    cirCollection.set_facecolor([tfc] * len(circle_patches))
    cirCollection.set_edgecolor([dc] * len(circle_patches))

    xCollection = LineCollection(line_seg,
                                 colors=[uc] * len(line_seg),
                                 linewidths=lw,
                                 antialiaseds=useAA)

    return (cirCollection,
            xCollection), new_dates, new_volumes, box_values, box_size
コード例 #8
0
def _construct_renko_collections(dates,
                                 highs,
                                 lows,
                                 volumes,
                                 config_renko_params,
                                 closes,
                                 marketcolors=None):
    """Represent the price change with bricks

    Parameters
    ----------
    dates : sequence
        sequence of dates
    highs : sequence
        sequence of high values
    lows : sequence
        sequence of low values
    config_renko_params : kwargs table (dictionary)
        brick_size : size of each brick
        atr_length : length of time used for calculating atr
    closes : sequence
        sequence of closing values
    marketcolors : dict of colors: up, down, edge, wick, alpha

    Returns
    -------
    ret : tuple
        rectCollection
    """
    renko_params = _process_kwargs(config_renko_params, _valid_renko_kwargs())
    if marketcolors is None:
        marketcolors = _get_mpfstyle('classic')['marketcolors']
        print('default market colors:', marketcolors)

    brick_size = renko_params['brick_size']
    atr_length = renko_params['atr_length']

    if brick_size == 'atr':
        brick_size = _calculate_atr(atr_length, highs, lows, closes)
    else:  # is an integer or float
        total_atr = _calculate_atr(len(closes) - 1, highs, lows, closes)
        upper_limit = 1.5 * total_atr
        lower_limit = 0.01 * total_atr
        if brick_size > upper_limit:
            raise ValueError(
                "Specified brick_size may not be larger than (1.5* the Average True Value of the dataset) which has value: "
                + str(upper_limit))
        elif brick_size < lower_limit:
            raise ValueError(
                "Specified brick_size may not be smaller than (0.01* the Average True Value of the dataset) which has value: "
                + str(lower_limit))

    alpha = marketcolors['alpha']

    uc = mcolors.to_rgba(marketcolors['candle']['up'], alpha)
    dc = mcolors.to_rgba(marketcolors['candle']['down'], alpha)
    euc = mcolors.to_rgba(marketcolors['edge']['up'], 1.0)
    edc = mcolors.to_rgba(marketcolors['edge']['down'], 1.0)

    cdiff = []
    prev_close_brick = closes[0]
    for i in range(len(closes) - 1):
        brick_diff = int((closes[i + 1] - prev_close_brick) / brick_size)
        cdiff.append(brick_diff)
        prev_close_brick += brick_diff * brick_size

    bricks = []  # holds bricks, 1 for down bricks, -1 for up bricks
    new_dates = []  # holds the dates corresponding with the index
    new_volumes = [
    ]  # holds the volumes corresponding with the index.  If more than one index for the same day then they all have the same volume.

    start_price = closes[0]

    volume_cache = 0  # holds the volumes for the dates that were skipped

    last_diff_sign = 0  # direction the bricks were last going in -1 -> down, 1 -> up
    for i in range(len(cdiff)):
        num_bricks = abs(cdiff[i])
        curr_diff_sign = cdiff[i] / abs(cdiff[i]) if cdiff[i] != 0 else 0
        if last_diff_sign != 0 and num_bricks > 0 and curr_diff_sign != last_diff_sign:
            num_bricks -= 1
        last_diff_sign = curr_diff_sign

        if num_bricks != 0:
            new_dates.extend([dates[i]] * num_bricks)

        if volumes is not None:  # only adds volumes if there are volume values when volume=True
            if num_bricks != 0:
                new_volumes.extend([volumes[i] + volume_cache] * num_bricks)
                volume_cache = 0
            else:
                volume_cache += volumes[i]

        if cdiff[i] > 0:
            bricks.extend([1] * num_bricks)
        else:
            bricks.extend([-1] * num_bricks)

    verts = []
    colors = []
    edge_colors = []
    brick_values = []
    prev_num = -1 if bricks[0] > 0 else 0
    for index, number in enumerate(bricks):
        if number == 1:  # up brick
            colors.append(uc)
            edge_colors.append(euc)
        else:  # down brick
            colors.append(dc)
            edge_colors.append(edc)

        prev_num += number
        brick_y = start_price + (prev_num * brick_size)
        brick_values.append(brick_y)

        x, y = index, brick_y

        verts.append(
            ((x, y), (x, y + brick_size), (x + 1, y + brick_size), (x + 1, y)))

    useAA = 0,  # use tuple here
    lw = None
    rectCollection = PolyCollection(verts,
                                    facecolors=colors,
                                    antialiaseds=useAA,
                                    edgecolors=edge_colors,
                                    linewidths=lw)

    return (rectCollection, ), new_dates, new_volumes, brick_values, brick_size
コード例 #9
0
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()
コード例 #10
0
def _construct_pointnfig_collections(dates,
                                     highs,
                                     lows,
                                     volumes,
                                     config_pointnfig_params,
                                     closes,
                                     marketcolors=None):
    """Represent the price change with Xs and Os

    NOTE: this code assumes if any value open, low, high, close is
    missing they all are missing

    Algorithm Explanation
    ---------------------
    In the first part of the algorithm, we populate the boxes array
    along with adjusting the dates and volumes arrays into the new_dates and
    new_volumes arrays. A single date includes a range from no boxes to many 
    boxes, if a date has no boxes it shall not be included in new_dates, 
    and if it has n boxes then it will be included n times. Volumes use a 
    volume cache to save volume amounts for dates that do not have any boxes
    before adding the cache to the next date that has at least one box.
    We populate the boxes array with each close values difference from the 
    previously created brick divided by the box size.

    The second part of the algorithm has a series of step. First we combine the
    adjacent like signed values in the boxes array (ex. [-1, -2, 3, -4] -> [-3, 3, -4]).
    Next we subtract 1 from the absolute value of each element in boxes except the 
    first to ensure every time there is a trend change (ex. previous box is
    an X, current brick is a O) we draw one less box to account for the price 
    having to move the previous box's amount before creating a box in the 
    opposite direction. Next we adjust volume and dates to combine volume into 
    non 0 box indexes and to only use dates from non 0 box indexes. We then
    remove all 0s from the boxes array and once again combine adjacent similarly
    signed differences in boxes.

    Lastly, we enumerate through the boxes to populate the line_seg and circle_patches
    arrays. line_seg holds the / and \ line segments that make up an X and 
    circle_patches holds matplotlib.patches Ellipse objects for each O. We start
    by filling an x and y array each iteration which contain the x and y 
    coordinates for each box in the column. Then for each coordinate pair in
    x, y we add to either the line_seg array or the circle_patches array 
    depending on the value of sign for the current column (1 indicates 
    line_seg, -1 indicates circle_patches). The height of the boxes take 
    into account padding which separates each box by a small margin in 
    order to increase readability.

    Useful sources:
    https://stackoverflow.com/questions/8750648/point-and-figure-chart-with-matplotlib
    https://www.investopedia.com/articles/technical/03/081303.asp
    
    Parameters
    ----------
    dates : sequence
        sequence of dates
    highs : sequence
        sequence of high values
    lows : sequence
        sequence of low values
    config_pointnfig_params : kwargs table (dictionary)
        box_size : size of each box
        atr_length : length of time used for calculating atr
    closes : sequence
        sequence of closing values
    marketcolors : dict of colors: up, down, edge, wick, alpha

    Returns
    -------
    ret : tuple
        rectCollection
    """
    pointnfig_params = _process_kwargs(config_pointnfig_params,
                                       _valid_pnf_kwargs())
    if marketcolors is None:
        marketcolors = _get_mpfstyle('classic')['marketcolors']
        #print('default market colors:',marketcolors)

    box_size = pointnfig_params['box_size']
    atr_length = pointnfig_params['atr_length']

    if box_size == 'atr':
        if atr_length == 'total':
            box_size = _calculate_atr(len(closes) - 1, highs, lows, closes)
        else:
            box_size = _calculate_atr(atr_length, highs, lows, closes)
    else:  # is an integer or float
        upper_limit = (max(closes) - min(closes)) / 2
        lower_limit = 0.01 * _calculate_atr(
            len(closes) - 1, highs, lows, closes)
        if box_size > upper_limit:
            raise ValueError(
                "Specified box_size may not be larger than (50% of the close price range of the dataset) which has value: "
                + str(upper_limit))
        elif box_size < lower_limit:
            raise ValueError(
                "Specified box_size may not be smaller than (0.01* the Average True Value of the dataset) which has value: "
                + str(lower_limit))

    alpha = marketcolors['alpha']

    uc = mcolors.to_rgba(marketcolors['ohlc']['up'], alpha)
    dc = mcolors.to_rgba(marketcolors['ohlc']['down'], alpha)
    tfc = mcolors.to_rgba(marketcolors['edge']['down'],
                          0)  # transparent face color

    boxes = [
    ]  # each element in an integer representing the number of boxes to be drawn on that indexes column (negative numbers -> Os, positive numbers -> Xs)
    prev_close_box = closes[
        0]  # represents the value of the last box in the previous column
    volume_cache = 0  # holds the volumes for the dates that were skipped
    temp_volumes, temp_dates = [], [
    ]  # holds the temp adjusted volumes and dates respectively

    for i in range(len(closes) - 1):
        box_diff = int((closes[i + 1] - prev_close_box) / box_size)
        if box_diff == 0:
            if volumes is not None:
                volume_cache += volumes[i]
            continue

        boxes.append(box_diff)
        if volumes is not None:
            temp_volumes.append(volumes[i] + volume_cache)
            volume_cache = 0
        temp_dates.append(dates[i])
        prev_close_box += box_diff * box_size

    # combine adjacent similarly signed differences
    boxes, indexes = combine_adjacent(boxes)
    new_volumes, new_dates = coalesce_volume_dates(temp_volumes, temp_dates,
                                                   indexes)

    #subtract 1 from the abs of each diff except the first to account for the first box using the last box in the opposite direction
    first_elem = boxes[0]
    boxes = [
        boxes[i] - int((boxes[i] / abs(boxes[i])))
        for i in range(1, len(boxes))
    ]
    boxes.insert(0, first_elem)

    # adjust volume and dates to make sure volume is combined into non 0 box indexes and only use dates from non 0 box indexes
    temp_volumes, temp_dates = [], []
    for i in range(len(boxes)):
        if boxes[i] == 0:
            volume_cache += new_volumes[i]
        else:
            temp_volumes.append(new_volumes[i] + volume_cache)
            volume_cache = 0
            temp_dates.append(new_dates[i])

    #remove 0s from boxes
    boxes = list(filter(lambda diff: diff != 0, boxes))

    # combine adjacent similarly signed differences again after 0s removed
    boxes, indexes = combine_adjacent(boxes)
    new_volumes, new_dates = coalesce_volume_dates(temp_volumes, temp_dates,
                                                   indexes)

    curr_price = closes[0]
    box_values = []  # y values for the boxes
    circle_patches = [
    ]  # list of circle patches to be used to create the cirCollection
    line_seg = []  # line segments that make up the Xs

    for index, difference in enumerate(boxes):
        diff = abs(difference)

        sign = (difference / abs(difference))  # -1 or 1
        start_iteration = 0 if sign > 0 else 1

        x = [index] * (diff)
        y = [
            curr_price + (i * box_size * sign)
            for i in range(start_iteration, diff + start_iteration)
        ]

        curr_price += (box_size * sign * (diff))
        box_values.append(sum(y) / len(y))

        for i in range(len(x)):  # x and y have the same length
            height = box_size * 0.85
            width = 0.6
            if height < 0.5:
                width = height

            padding = (box_size * 0.075)
            if sign == 1:  # X
                line_seg.append([(x[i] - width / 2, y[i] + padding),
                                 (x[i] + width / 2, y[i] + height + padding)
                                 ])  # create / part of the X
                line_seg.append([(x[i] - width / 2, y[i] + height + padding),
                                 (x[i] + width / 2, y[i] + padding)
                                 ])  # create \ part of the X
            else:  # O
                circle_patches.append(
                    Ellipse((x[i], y[i] - (height / 2) - padding), width,
                            height))

    useAA = 0,  # use tuple here
    lw = 0.5

    cirCollection = PatchCollection(circle_patches)
    cirCollection.set_facecolor([tfc] * len(circle_patches))
    cirCollection.set_edgecolor([dc] * len(circle_patches))

    xCollection = LineCollection(line_seg,
                                 colors=[uc] * len(line_seg),
                                 linewidths=lw,
                                 antialiaseds=useAA)
    return [cirCollection,
            xCollection], new_dates, new_volumes, box_values, box_size
コード例 #11
0
def _construct_renko_collections(dates,
                                 highs,
                                 lows,
                                 volumes,
                                 config_renko_params,
                                 closes,
                                 marketcolors=None):
    """Represent the price change with bricks

    NOTE: this code assumes if any value open, low, high, close is
    missing they all are missing

    Algorithm Explanation
    ---------------------
    In the first part of the algorithm, we populate the cdiff array
    along with adjusting the dates and volumes arrays into the new_dates and
    new_volumes arrays. A single date includes a range from no bricks to many 
    bricks, if a date has no bricks it shall not be included in new_dates, 
    and if it has n bricks then it will be included n times. Volumes use a 
    volume cache to save volume amounts for dates that do not have any bricks
    before adding the cache to the next date that has at least one brick.
    We populate the cdiff array with each close values difference from the 
    previously created brick divided by the brick size.

    In the second part of the algorithm, we iterate through the values in cdiff
    and add 1s or -1s to the bricks array depending on whether the value is 
    positive or negative. Every time there is a trend change (ex. previous brick is
    an upbrick, current brick is a down brick) we draw one less brick to account
    for the price having to move the previous bricks amount before creating a 
    brick in the opposite direction.

    In the final part of the algorithm, we enumerate through the bricks array and
    assign up-colors or down-colors to the associated index in the color array and
    populate the verts list with each bricks vertice to be used to create the matplotlib
    PolyCollection.

    Useful sources:
    https://avilpage.com/2018/01/how-to-plot-renko-charts-with-python.html
    https://school.stockcharts.com/doku.php?id=chart_analysis:renko
    
    Parameters
    ----------
    dates : sequence
        sequence of dates
    highs : sequence
        sequence of high values
    lows : sequence
        sequence of low values
    config_renko_params : kwargs table (dictionary)
        brick_size : size of each brick
        atr_length : length of time used for calculating atr
    closes : sequence
        sequence of closing values
    marketcolors : dict of colors: up, down, edge, wick, alpha

    Returns
    -------
    ret : list
        rectCollection
    """
    renko_params = _process_kwargs(config_renko_params, _valid_renko_kwargs())
    if marketcolors is None:
        marketcolors = _get_mpfstyle('classic')['marketcolors']
        #print('default market colors:',marketcolors)

    brick_size = renko_params['brick_size']
    atr_length = renko_params['atr_length']

    if brick_size == 'atr':
        if atr_length == 'total':
            brick_size = _calculate_atr(len(closes) - 1, highs, lows, closes)
        else:
            brick_size = _calculate_atr(atr_length, highs, lows, closes)
    else:  # is an integer or float
        upper_limit = (max(closes) - min(closes)) / 2
        lower_limit = 0.01 * _calculate_atr(
            len(closes) - 1, highs, lows, closes)
        if brick_size > upper_limit:
            raise ValueError(
                "Specified brick_size may not be larger than (50% of the close price range of the dataset) which has value: "
                + str(upper_limit))
        elif brick_size < lower_limit:
            raise ValueError(
                "Specified brick_size may not be smaller than (0.01* the Average True Value of the dataset) which has value: "
                + str(lower_limit))

    alpha = marketcolors['alpha']

    uc = mcolors.to_rgba(marketcolors['candle']['up'], alpha)
    dc = mcolors.to_rgba(marketcolors['candle']['down'], alpha)
    euc = mcolors.to_rgba(marketcolors['edge']['up'], 1.0)
    edc = mcolors.to_rgba(marketcolors['edge']['down'], 1.0)

    cdiff = [
    ]  # holds the differences between each close and the previously created brick / the brick size
    prev_close_brick = closes[0]
    volume_cache = 0  # holds the volumes for the dates that were skipped
    new_dates = []  # holds the dates corresponding with the index
    new_volumes = [
    ]  # holds the volumes corresponding with the index.  If more than one index for the same day then they all have the same volume.

    for i in range(len(closes) - 1):
        brick_diff = int((closes[i + 1] - prev_close_brick) / brick_size)
        if brick_diff == 0:
            if volumes is not None:
                volume_cache += volumes[i]
            continue

        cdiff.extend([int(brick_diff / abs(brick_diff))] * abs(brick_diff))
        if volumes is not None:
            new_volumes.extend([volumes[i] + volume_cache] * abs(brick_diff))
            volume_cache = 0
        new_dates.extend([dates[i]] * abs(brick_diff))
        prev_close_brick += brick_diff * brick_size

    bricks = []  # holds bricks, -1 for down bricks, 1 for up bricks
    curr_price = closes[0]

    last_diff_sign = 0  # direction the bricks were last going in -1 -> down, 1 -> up
    dates_volumes_index = 0  # keeps track of the index of the current date/volume
    for diff in cdiff:

        curr_diff_sign = diff / abs(diff)
        if last_diff_sign != 0 and curr_diff_sign != last_diff_sign:
            last_diff_sign = curr_diff_sign
            new_dates.pop(dates_volumes_index)
            if volumes is not None:
                if dates_volumes_index == len(new_volumes) - 1:
                    new_volumes[dates_volumes_index -
                                1] += new_volumes[dates_volumes_index]
                else:
                    new_volumes[dates_volumes_index +
                                1] += new_volumes[dates_volumes_index]
                new_volumes.pop(dates_volumes_index)
            continue
        last_diff_sign = curr_diff_sign

        if diff > 0:
            bricks.extend([1] * abs(diff))
        else:
            bricks.extend([-1] * abs(diff))
        dates_volumes_index += 1

    verts = []  # holds the brick vertices
    colors = []  # holds the facecolors for each brick
    edge_colors = []  # holds the edgecolors for each brick
    brick_values = []  # holds the brick values for each brick
    for index, number in enumerate(bricks):
        if number == 1:  # up brick
            colors.append(uc)
            edge_colors.append(euc)
        else:  # down brick
            colors.append(dc)
            edge_colors.append(edc)

        curr_price += (brick_size * number)
        brick_values.append(curr_price)

        x, y = index, curr_price

        verts.append(
            ((x, y), (x, y + brick_size), (x + 1, y + brick_size), (x + 1, y)))

    useAA = 0,  # use tuple here
    lw = None
    rectCollection = PolyCollection(verts,
                                    facecolors=colors,
                                    antialiaseds=useAA,
                                    edgecolors=edge_colors,
                                    linewidths=lw)
    return [
        rectCollection,
    ], new_dates, new_volumes, brick_values, brick_size
コード例 #12
0
def _construct_tline_collections(tlines, dtix, dates, opens, highs, lows,
                                 closes):
    """construct trend line collections

    Parameters
    ----------
    tlines : sequence
        sequences of pairs of date[time]s

        date[time] may be (a) pandas.to_datetime parseable string,
                          (b) pandas Timestamp, or
                          (c) python datetime.datetime or datetime.date

    tlines may also be a dict, containing
    the following keys:

        'tlines'     : the same as defined above: sequence of pairs of date[time]s
        'colors'     : colors for the above tlines
        'linestyle'  : line types for the above tlines
        'linewidths' : line types for the above tlines

    dtix:  date index for the x-axis, used for converting the dates when
           x-values are 'evenly spaced integers' (as when skipping non-trading days)

    Returns
    -------
    ret : list
        lines collections
    """
    if tlines is None:
        return None

    if isinstance(tlines, dict):
        tconfig = _process_kwargs(tlines, _valid_lines_kwargs())
        tlines = tconfig['tlines']
    else:
        tconfig = _process_kwargs({}, _valid_lines_kwargs())

    tline_use = tconfig['tline_use']
    tline_method = tconfig['tline_method']

    #print('tconfig=',tconfig)
    #print('tlines=',tlines)

    # reconstruct the data frame:
    df = pd.DataFrame(
        {
            'open': opens,
            'high': highs,
            'low': lows,
            'close': closes
        },
        index=pd.DatetimeIndex(mdates.num2date(dates)))

    # possible `tvalue`s : close,open,high,low,oc_avg,hl_avg,ohlc_avg,hilo
    #          'hilo' means high on the up trend, low on the down trend.
    # possible `tmethod`s: point-to-point, leastsquares

    def _tline_point_to_point(dfslice, tline_use):
        p1 = dfslice.iloc[0]
        p2 = dfslice.iloc[-1]
        x1 = p1.name
        y1 = p1[tline_use].mean()
        x2 = p2.name
        y2 = p2[tline_use].mean()
        return ((x1, y1), (x2, y2))

    def _tline_lsq(dfslice, tline_use):
        '''
        This closed-form linear least squares algorithm was taken from:
        https://mmas.github.io/least-squares-fitting-numpy-scipy
        '''
        s = dfslice[tline_use].mean(axis=1)
        xs = mdates.date2num(s.index.to_pydatetime())
        ys = s.values
        a = np.vstack([xs, np.ones(len(xs))]).T
        m, b = np.dot(np.linalg.inv(np.dot(a.T, a)), np.dot(a.T, ys))
        x1, x2 = xs[0], xs[-1]
        y1 = m * x1 + b
        y2 = m * x2 + b
        x1, x2 = mdates.num2date(x1), mdates.num2date(x2)
        return ((x1, y1), (x2, y2))

    if isinstance(tline_use, str):
        tline_use = [
            tline_use,
        ]
    tline_use = [u.lower() for u in tline_use]

    alines = []
    for d1, d2 in tlines:
        dfslice = df.loc[d1:d2]
        if len(dfslice) < 2:
            dfdr = '\ndf date range: [' + str(df.index[0]) + ' , ' + str(
                df.index[-1]) + ']'
            raise ValueError('\ntlines date pair (' + str(d1) + ',' + str(d2) +
                             ') too close, or wrong order, or out of range!' +
                             dfdr)
        if tline_method == 'least squares' or tline_method == 'least-squares':
            p1, p2 = _tline_lsq(dfslice, tline_use)
        elif tline_method == 'point-to-point':
            p1, p2 = _tline_point_to_point(dfslice, tline_use)
        else:
            raise ValueError('\nUnrecognized value for `tline_method` = "' +
                             str(tline_method) + '"')

        alines.append((p1, p2))

    del tconfig['alines']
    alines = dict(alines=alines, **tconfig)
    alines['tlines'] = None

    return _construct_aline_collections(alines, dtix)
コード例 #13
0
def _construct_vline_collections(vlines, dtix, miny, maxy):
    """Construct vertical lines collection
    Parameters
    ----------
    vlines : sequence
        sequence of dates or datetimes at which to draw vertical lines
        dates/datetimes may be (a) pandas.to_datetime parseable string,
                               (b) pandas Timestamp
                               (c) python datetime.datetime or datetime.date

    vlines may also be a dict, containing
    the following keys:

        'vlines'     : the same as defined above: sequence of dates/datetimes
        'colors'     : colors for the above vlines
        'linestyle'  : line types for the above vlines
        'linewidths' : line types for the above vlines

    dtix:  date index for the x-axis, used for converting the dates when
           x-values are 'evenly spaced integers' (as when skipping non-trading days)

    miny : minimum y-value for the vertical line

    maxy : maximum y-value for the vertical line

    Returns
    -------
    ret : list
        lines collections
    """

    if vlines is None:
        return None

    #print('_construct_vline_collections() called:',
    #      '\nvlines=',vlines,
    #      '\ndtix=',dtix)
    #print('miny,maxy=',miny,maxy)

    if isinstance(vlines, dict):
        vconfig = _process_kwargs(vlines, _valid_lines_kwargs())
        vlines = vconfig['vlines']
    else:
        vconfig = _process_kwargs({}, _valid_lines_kwargs())

    #print('vconfig=',vconfig)
    #print('vlines=',vlines)

    if not isinstance(vlines, (list, tuple)):
        vlines = [
            vlines,
        ]

    lines = []
    for val in vlines:
        lines.append([(val, miny), (val, maxy)])

    lines = _convert_segment_dates(lines, dtix)

    lw = vconfig['linewidths']
    co = vconfig['colors']
    ls = vconfig['linestyle']
    al = vconfig['alpha']
    lcollection = LineCollection(lines,
                                 colors=co,
                                 linewidths=lw,
                                 linestyles=ls,
                                 antialiaseds=(0, ),
                                 alpha=al)
    return lcollection
コード例 #14
0
ファイル: _styles.py プロジェクト: matplotlib/mplfinance
def make_marketcolors(**kwargs):
    '''
    Create a 'marketcolors' dict that is structured as expected
    by mplfinance._styles code:
        up     = color for close >= open
        down   = color for close  < open
        edge   = color for edge of candlestick; if "inherit"
                 then edge color will be same as up or down.
        wick   = color for wick of candlestick; if "inherit"
                 then wick color will be same as up or down.
        alpha  = opacity, 0.0 to 1.0, of candlestick face.
        ohlc   = color of ohlc bars when all the same color;
                 if ohlc == "inherit" then use up/down colors.
        volume = color of volume bars when all the same color;
                 if volume == "inherit" then use up/down colors.
    '''

    config = _process_kwargs(kwargs, _valid_make_marketcolors_kwargs())

    if config['base_mpf_style'] is not None:
        style = _get_mpfstyle(config['base_mpf_style'])
    else:
        style = _get_mpfstyle('default')

    marketcolors = style['marketcolors']

    up   = config['up']
    down = config['down']
    if up is not None and down is not None:
        marketcolors.update(candle=dict(up=up,down=down))
    elif up is not None:
        candle = marketcolors['candle']
        candle.update(up=up)
        marketcolors.update(candle=candle)
    elif down is not None:
        candle = marketcolors['candle']
        candle.update(down=down)
        marketcolors.update(down=down)

    def _check_and_set_mktcolor(candle,**kwarg):
        if len(kwarg) != 1:
            raise ValueError('Expect only ONE kwarg')
        key,value = kwarg.popitem()
        if isinstance(value,(dict)):
            colors = value
        elif isinstance(value,str) and value == 'inherit'[0:len(value)]:
            colors = candle
        else:
            colors = dict(up=value, down=value)
        for updown in ['up','down']:
            if not _mpf_is_color_like(colors[updown]):
                err = f'NOT is_color_like() for {key}[\'{updown}\'] = {colors[updown]}'
                raise ValueError(err)
        return colors

    candle = marketcolors['candle']

    for kw in ['edge','volume','ohlc','wick']:
        # `inherit=True` takes precedence:
        if config[kw] is not None or config['inherit'] == True:
            if config['inherit'] == True:
                kwa = {kw:'i'}
            else:
                kwa = {kw:config[kw]}
            c   = _check_and_set_mktcolor(candle,**kwa)
            marketcolors.update([(kw,c)])

    if config['hollow'] is not None:
        marketcolors.update({'hollow':config['hollow']})

    if config['alpha'] is not None:
        marketcolors.update({'alpha':config['alpha']})

    if config['vcdopcod'] is not None:
        marketcolors.update({'vcdopcod':config['vcdopcod']})

    return marketcolors
コード例 #15
0
def plot(data, **kwargs):
    """
    Given a Pandas DataFrame containing columns Open,High,Low,Close and optionally Volume
    with a DatetimeIndex, 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, renko, 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.')

    if config['volume']:
        if config['volume'] not in ['B', 'C']: config['volume'] = 'B'

    ha, hb, hc = _determine_relative_panel_heights(config['addplot'],
                                                   config['volume'],
                                                   config['panel_ratio'])

    axA1, axA2, axB1, axB2, axC1, axC2, actual_order = _create_panel_axes(
        fig, ha, hb, hc, config['panel_order'])

    internalAxes = dict(A=(axA1, axA2), B=(axB1, axB2), C=(axC1, axC2))

    volumeAxes = internalAxes[
        config['volume']][0] if config['volume'] else None

    fmtstring = _determine_format_string(dates, config['datetime_format'])

    ptype = config['type']

    if config['show_nontrading']:
        formatter = mdates.DateFormatter(fmtstring)
        xdates = dates
    else:
        formatter = IntegerIndexDateTimeFormatter(dates, fmtstring)
        xdates = np.arange(len(dates))
    axA1.xaxis.set_major_formatter(formatter)

    collections = None
    if ptype == 'line':
        axA1.plot(xdates, closes, color=config['linecolor'])
    else:
        collections = _construct_mpf_collections(ptype, dates, xdates, opens,
                                                 highs, lows, closes, volumes,
                                                 config, style)

    if ptype in VALID_PMOVE_TYPES:
        collections, new_dates, volumes, brick_values, size = collections
        formatter = IntegerIndexDateTimeFormatter(new_dates, fmtstring)
        xdates = np.arange(len(new_dates))
        axA1.xaxis.set_major_formatter(formatter)

    if collections is not None:
        for collection in collections:
            axA1.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 = pd.Series(closes).rolling(mav).mean().values
            if mavc:
                axA1.plot(xdates, mavprices, color=next(mavc))
            else:
                axA1.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
    if len(xdates) == 1:  # kludge special case
        minx = minx - 0.75
        maxx = maxx + 0.75
    if ptype not in VALID_PMOVE_TYPES:
        _lows = lows
        _highs = highs
    else:
        _lows = brick_values
        _highs = [brick + size for brick in brick_values]

    miny = np.nanmin(_lows)
    maxy = np.nanmax(_highs)
    #if len(xdates) > 1:
    #   stdy = (stat.stdev(_lows) + stat.stdev(_highs)) / 2.0
    #else:  # kludge special case
    #   stdy = 0.02 * math.fabs(maxy - miny)
    # print('minx,miny,maxx,maxy,stdy=',minx,miny,maxx,maxy,stdy)

    if config['set_ylim'] is not None:
        axA1.set_ylim(config['set_ylim'][0], config['set_ylim'][1])
    else:
        corners = (minx, miny), (maxx, maxy)
        axA1.update_datalim(corners)

    if config['return_calculated_values'] is not None:
        retdict = config['return_calculated_values']
        if ptype in VALID_PMOVE_TYPES:
            prekey = ptype
            retdict[prekey + '_bricks'] = brick_values
            retdict[prekey + '_dates'] = mdates.num2date(new_dates)
            retdict[prekey + '_size'] = size
            if config['volume']:
                retdict[prekey + '_volumes'] = volumes
        if mavgs is not None:
            for i in range(0, len(mavgs)):
                retdict['mav' + str(mavgs[i])] = mavprices
        retdict['minx'] = minx
        retdict['maxx'] = maxx
        retdict['miny'] = miny
        retdict['maxy'] = maxy

    # Note: these are NOT mutually exclusive, so the order of this
    #       if/elif is important: VALID_PMOVE_TYPES must be first.
    if ptype in VALID_PMOVE_TYPES:
        dtix = pd.DatetimeIndex([dt for dt in mdates.num2date(new_dates)])
    elif not config['show_nontrading']:
        dtix = data.index
    else:
        dtix = None

    line_collections = []
    line_collections.append(
        _construct_aline_collections(config['alines'], dtix))
    line_collections.append(
        _construct_hline_collections(config['hlines'], minx, maxx))
    line_collections.append(
        _construct_vline_collections(config['vlines'], dtix, miny, maxy))
    tlines = config['tlines']
    if isinstance(tlines, (list, tuple)) and all(
        [isinstance(item, dict) for item in tlines]):
        pass
    else:
        tlines = [
            tlines,
        ]
    for tline_item in tlines:
        line_collections.append(
            _construct_tline_collections(tline_item, dtix, dates, opens, highs,
                                         lows, closes))

    for collection in line_collections:
        if collection is not None:
            axA1.add_collection(collection)

    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
        volumeAxes.bar(xdates, volumes, width=width, color=vcolors)
        miny = 0.3 * np.nanmin(volumes)
        maxy = 1.1 * np.nanmax(volumes)
        volumeAxes.set_ylim(miny, maxy)

    xrotation = config['xrotation']
    _adjust_ticklabels_per_bottom_panel(axA1, axB1, axC1, actual_order, hb, hc,
                                        formatter, xrotation)

    used_axA2 = False
    used_axB2 = False
    used_axC2 = 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(np.nanmin(lows)), 1e-7), 10) - 0.5
        hi = math.log(max(math.fabs(np.nanmax(highs)), 1e-7), 10) + 0.5

        # May 2020: Main panel is now called 'A', and Lower is called 'B'
        omrange = {'A': {'lo': lo, 'hi': hi}, 'B': None, 'C': None}
        if config['volume']:
            lo = math.log(max(math.fabs(np.nanmin(volumes)), 1e-7), 10) - 0.5
            hi = math.log(max(math.fabs(np.nanmax(volumes)), 1e-7), 10) + 0.5
            omrange.update(B={'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(np.nanmax(yd)), 1e-7), 10)
                ymlo = math.log(max(math.fabs(np.nanmin(yd)), 1e-7), 10)
                secondary_y = False
                if apdict['secondary_y'] == 'auto':
                    if apdict['panel'] == 'lower' or apdict['panel'] == 'B':
                        # 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['B'] is None:
                            omrange.update(B={'lo': ymlo, 'hi': ymhi})
                        elif ymlo < omrange['B']['lo'] or ymhi > omrange['B'][
                                'hi']:
                            secondary_y = True
                    elif apdict['panel'] == 'C':
                        if omrange['C'] is None:
                            omrange.update(B={'lo': ymlo, 'hi': ymhi})
                        elif ymlo < omrange['C']['lo'] or ymhi > omrange['C'][
                                'hi']:
                            secondary_y = True
                    elif ymlo < omrange['A']['lo'] or ymhi > omrange['A']['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' or apdict['panel'] == 'B':
                    ax = axB2 if secondary_y else axB1
                elif apdict['panel'] == 'C':
                    ax = axC2 if secondary_y else axC1
                else:
                    ax = axA2 if secondary_y else axA1

                if ax == axA2:
                    used_axA2 = True
                if ax == axB2:
                    used_axB2 = True
                if ax == axC2:
                    used_axC2 = True

                aptype = apdict['type']
                if aptype == 'scatter':
                    size = apdict['markersize']
                    mark = apdict['marker']
                    color = apdict['color']
                    # -------------------------------------------------------- #
                    # This fixes Issue#77, but breaks other stuff:
                    # ax.set_ylim(ymin=(miny - 0.4*stdy),ymax=(maxy + 0.4*stdy))
                    # -------------------------------------------------------- #
                    ax.scatter(xdates, ydata, s=size, marker=mark, color=color)
                elif aptype == 'bar':
                    width = apdict['width']
                    bottom = apdict['bottom']
                    color = apdict['color']
                    alpha = apdict['alpha']
                    ax.bar(xdates,
                           ydata,
                           width=width,
                           bottom=bottom,
                           color=color,
                           alpha=alpha)
                elif aptype == 'line':
                    ls = apdict['linestyle']
                    color = apdict['color']
                    ax.plot(xdates, ydata, linestyle=ls, color=color)
                #elif aptype == 'ohlc' or aptype == 'candle':
                # This won't work as is, because here we are looping through one column at a time
                # and mpf_collections needs ohlc columns:
                #    collections =_construct_mpf_collections(aptype,dates,xdates,opens,highs,lows,closes,volumes,config,style)
                #    if len(collections) == 1: collections = [collections]
                #    for collection in collections:
                #        ax.add_collection(collection)
                else:
                    raise ValueError('addplot type "' + str(aptype) +
                                     '" NOT yet supported.')

    if config['set_ylim_panelB'] is not None:
        miny = config['set_ylim_panelB'][0]
        maxy = config['set_ylim_panelB'][1]
        axB1.set_ylim(miny, maxy)

    if config['set_ylim_panelC'] is not None:
        miny = config['set_ylim_panelC'][0]
        maxy = config['set_ylim_panelC'][1]
        axC1.set_ylim(miny, maxy)

    if config['yscale'] is not None:
        yscale = config['yscale']
        panel = 'A'
        kwargs = None
        if isinstance(yscale, dict):
            if 'panel' in yscale: panel = yscale['panel']
            if 'kwargs' in yscale: kwargs = yscale['kwargs']
            yscale = yscale['yscale']
        ax = internalAxes[panel][0]
        if kwargs is not None:
            ax.set_yscale(yscale, **kwargs)
        else:
            ax.set_yscale(yscale)

    # put the twinx() on the "other" side:
    if style['y_on_right']:
        axA1.yaxis.set_label_position('right')
        axA1.yaxis.tick_right()
        axA2.yaxis.set_label_position('left')
        axA2.yaxis.tick_left()
        if axB1 and axB2:
            axB1.yaxis.set_label_position('right')
            axB1.yaxis.tick_right()
            if axB2 != axB1:
                axB2.yaxis.set_label_position('left')
                axB2.yaxis.tick_left()
    else:
        axA1.yaxis.set_label_position('left')
        axA1.yaxis.tick_left()
        axA2.yaxis.set_label_position('right')
        axA2.yaxis.tick_right()
        if axB1 and axB2:
            axB1.yaxis.set_label_position('left')
            axB1.yaxis.tick_left()
            if axB2 != axB1:
                axB2.yaxis.set_label_position('right')
                axB2.yaxis.tick_right()

    # 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()

    axA1.autoscale_view()  # Is this really necessary??

    axA1.set_ylabel(config['ylabel'])

    if config['volume']:
        volumeAxes.figure.canvas.draw()  # This is needed to calculate offset
        offset = volumeAxes.yaxis.get_major_formatter().get_offset()
        volumeAxes.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
        volumeAxes.set_ylabel(vol_label)

    if config['title'] is not None:
        fig.suptitle(config['title'], size='x-large', weight='semibold')

    if not used_axA2 and axA2 is not None:
        axA2.get_yaxis().set_visible(False)

    if not used_axB2 and axB2 is not None:
        axB2.get_yaxis().set_visible(False)

    if not used_axC2 and axC2 is not None:
        axC2.get_yaxis().set_visible(False)

    axlist = [axA1, axA2]
    if axB1: axlist.append(axB1)
    if axB2: axlist.append(axB2)
    if axC1: axlist.append(axC1)
    if axC2: axlist.append(axC2)

    if config['axesoffdark']: fig.patch.set_facecolor('black')
    if config['axesoff']: fig.patch.set_visible(False)
    if config['axesoffdark'] or config['axesoff']:
        for ax in axlist:
            ax.set_xlim(xdates[0], xdates[-1])
            ax.set_axis_off()

    if config['savefig'] is not None:
        save = config['savefig']
        if isinstance(save, dict):
            plt.savefig(**save)
        else:
            plt.savefig(save)
        if config['closefig']:
            plt.close(fig)
    elif not config['returnfig']:
        plt.show(block=config['block']
                 )  # https://stackoverflow.com/a/13361748/1639359
        if config['block']:
            plt.close(fig)

    if config['returnfig']:
        return (fig, axlist)
コード例 #16
0
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'])
コード例 #17
0
def plot( data, **kwargs ):
    """
    Given a Pandas DataFrame containing columns Open,High,Low,Close and optionally Volume
    with a DatetimeIndex, 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, renko, etc.
    Also provide ability to plot trading signals, and/or addtional user-defined data.
    """

    config = _process_kwargs(kwargs, _valid_plot_kwargs())

    # translate alias types:
    config['type'] = _get_valid_plot_types(config['type'])
    
    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)

    external_axes_mode = _check_for_external_axes(config)

    if external_axes_mode:
        if config['figscale'] is not None:
            warnings.warn('\n\n ================================================================= '+
                          '\n\n   WARNING: `figscale` has NO effect in External Axes Mode.'+
                          '\n\n ================================================================ ',
                          category=UserWarning)
        if config['figratio'] is not None:
            warnings.warn('\n\n ================================================================= '+
                          '\n\n   WARNING: `figratio` has NO effect in External Axes Mode.'+
                          '\n\n ================================================================ ',
                          category=UserWarning)
        if config['figsize'] is not None:
            warnings.warn('\n\n ================================================================= '+
                          '\n\n   WARNING: `figsize` has NO effect in External Axes Mode.'+
                          '\n\n ================================================================ ',
                          category=UserWarning)
    else:
        if config['figscale'] is None: config['figscale'] = 1.0
        if config['figratio'] is None: config['figratio'] = DEFAULT_FIGRATIO

    style = config['style']

    if external_axes_mode and hasattr(config['ax'],'mpfstyle') and style is None:
        style = config['ax'].mpfstyle
    elif style is None:
        style = 'default'

    if isinstance(style,str):
        style = _styles._get_mpfstyle(style)

    config['style'] = style

    if isinstance(style,dict):
        if not external_axes_mode: _styles._apply_mpfstyle(style)
    else:
        raise TypeError('style should be a `dict`; why is it not?')

    # ----------------------------------------------------------------------
    # TODO:  Add some warnings, or raise an exception, if external_axes_mode
    #        and user is trying to figscale, figratio, or figsize.
    # ----------------------------------------------------------------------

    if not external_axes_mode:
        fig = plt.figure()
        _adjust_figsize(fig,config)
    else:
        fig = None

    if config['volume'] and volumes is None:
        raise ValueError('Request for volume, but NO volume data.')

    if external_axes_mode:
        panels     = None
        if config['volume']:
            volumeAxes = config['volume']
            volumeAxes.set_axisbelow(config['saxbelow'])
    else:
        panels = _build_panels(fig, config)
        volumeAxes = panels.at[config['volume_panel'],'axes'][0] if config['volume'] is True else None

    fmtstring = _determine_format_string( dates, config['datetime_format'] )

    ptype = config['type'] 

    if config['show_nontrading']:
        formatter = mdates.DateFormatter(fmtstring)
        xdates = dates
    else:
        formatter = IntegerIndexDateTimeFormatter(dates, fmtstring)
        xdates = np.arange(len(dates))

    if external_axes_mode:
        axA1 = config['ax']
        axA1.set_axisbelow(config['saxbelow'])
    else:
        axA1 = panels.at[config['main_panel'],'axes'][0]

    # Will have to handle widths config separately for PMOVE types ??
    config['_width_config'] = _determine_width_config(xdates, config)


    rwc = config['return_width_config']
    if isinstance(rwc,dict) and len(rwc)==0:
        config['return_width_config'].update(config['_width_config'])
 

    collections = None
    if ptype == 'line':
        lw = config['_width_config']['line_width']
        axA1.plot(xdates, closes, color=config['linecolor'], linewidth=lw)
    else:
        collections =_construct_mpf_collections(ptype,dates,xdates,opens,highs,lows,closes,volumes,config,style)

    if ptype in VALID_PMOVE_TYPES:
        collections, new_dates, volumes, brick_values, size = collections
        formatter = IntegerIndexDateTimeFormatter(new_dates, fmtstring)
        xdates = np.arange(len(new_dates))

    if collections is not None:
        for collection in collections:
            axA1.add_collection(collection)

    if ptype in VALID_PMOVE_TYPES:
        mavprices = _plot_mav(axA1,config,xdates,brick_values)
    else:
        mavprices = _plot_mav(axA1,config,xdates,closes)

    avg_dist_between_points = (xdates[-1] - xdates[0]) / float(len(xdates))
    if not config['tight_layout']:
        minx = xdates[0]  - avg_dist_between_points
        maxx = xdates[-1] + avg_dist_between_points
    else:
        minx = xdates[0]  - (0.45 * avg_dist_between_points)
        maxx = xdates[-1] + (0.45 * avg_dist_between_points)

    if len(xdates) == 1:  # kludge special case
        minx = minx - 0.75
        maxx = maxx + 0.75
    if ptype not in VALID_PMOVE_TYPES:
        _lows  = lows
        _highs = highs
    else:
        _lows  = brick_values
        _highs = [brick+size for brick in brick_values]

    miny = np.nanmin(_lows)
    maxy = np.nanmax(_highs)

    if config['ylim'] is not None:
        axA1.set_ylim(config['ylim'][0], config['ylim'][1])
    elif config['tight_layout']:
        ydelta = 0.01 * (maxy-miny)
        axA1.set_ylim(miny-ydelta,maxy+ydelta)

    if config['xlim'] is not None:
        axA1.set_xlim(config['xlim'][0], config['xlim'][1])
    elif config['tight_layout']:
        axA1.set_xlim(minx,maxx)

    if (config['ylim'] is None and
        config['xlim'] is None and
        not config['tight_layout']):
        corners = (minx, miny), (maxx, maxy)
        axA1.update_datalim(corners)

    if config['return_calculated_values'] is not None:
        retdict = config['return_calculated_values']
        if ptype in VALID_PMOVE_TYPES:
            prekey = ptype
            retdict[prekey+'_bricks'] = brick_values
            retdict[prekey+'_dates'] = mdates.num2date(new_dates)
            retdict[prekey+'_size'] = size
            if config['volume']:
                retdict[prekey+'_volumes'] = volumes
        if config['mav'] is not None:
            mav = config['mav']
            if len(mav) != len(mavprices):
                warnings.warn('len(mav)='+str(len(mav))+' BUT len(mavprices)='+str(len(mavprices)))
            else:
                for jj in range(0,len(mav)):     
                    retdict['mav' + str(mav[jj])] = mavprices[jj]
        retdict['minx'] = minx
        retdict['maxx'] = maxx
        retdict['miny'] = miny
        retdict['maxy'] = maxy

    # Note: these are NOT mutually exclusive, so the order of this
    #       if/elif is important: VALID_PMOVE_TYPES must be first.
    if ptype in VALID_PMOVE_TYPES:
        dtix = pd.DatetimeIndex([dt for dt in mdates.num2date(new_dates)])
    elif not config['show_nontrading']:
        dtix = data.index
    else:
        dtix = None

    line_collections = []
    line_collections.append(_construct_aline_collections(config['alines'], dtix))
    line_collections.append(_construct_hline_collections(config['hlines'], minx, maxx))
    line_collections.append(_construct_vline_collections(config['vlines'], dtix, miny, maxy))
    tlines = config['tlines']
    if isinstance(tlines,(list,tuple)) and all([isinstance(item,dict) for item in tlines]):
        pass
    else:
        tlines = [tlines,]
    for tline_item in tlines:
        line_collections.append(_construct_tline_collections(tline_item, dtix, dates, opens, highs, lows, closes))
     
    for collection in line_collections:
        if collection is not None:
            axA1.add_collection(collection)

    datalen = len(xdates)
    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)

        w  = config['_width_config']['volume_width']
        lw = config['_width_config']['volume_linewidth']

        adjc =  _adjust_color_brightness(vcolors,0.90)
        volumeAxes.bar(xdates,volumes,width=w,linewidth=lw,color=vcolors,ec=adjc)
        vymin = 0.3 * np.nanmin(volumes)
        vymax = 1.1 * np.nanmax(volumes)
        volumeAxes.set_ylim(vymin,vymax)

    xrotation = config['xrotation']
    if not external_axes_mode:
        _set_ticks_on_bottom_panel_only(panels,formatter,rotation=xrotation)
    else:
        axA1.tick_params(axis='x',rotation=xrotation)
        axA1.xaxis.set_major_formatter(formatter)

    addplot = config['addplot']
    if addplot is not None and ptype not in VALID_PMOVE_TYPES:
        # NOTE: If in external_axes_mode, then all code relating
        #       to panels and secondary_y becomes irrrelevant.
        #       If the user wants something on a secondary_y then user should
        #       determine that externally, and pass in the appropriate axes.

        if not external_axes_mode:
            # Calculate the Order of Magnitude Range ('mag')
            # If addplot['secondary_y'] == 'auto', then: If the addplot['data']
            # is out of the Order of Magnitude Range, then use secondary_y.

            lo = math.log(max(math.fabs(np.nanmin(lows)),1e-7),10) - 0.5
            hi = math.log(max(math.fabs(np.nanmax(highs)),1e-7),10) + 0.5

            panels['mag'] = [None]*len(panels)  # create 'mag'nitude column

            panels.at[config['main_panel'],'mag'] = {'lo':lo,'hi':hi} # update main panel magnitude range

            if config['volume']:
                lo = math.log(max(math.fabs(np.nanmin(volumes)),1e-7),10) - 0.5
                hi = math.log(max(math.fabs(np.nanmax(volumes)),1e-7),10) + 0.5
                panels.at[config['volume_panel'],'mag'] = {'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:

            panid = apdict['panel'] 
            if not external_axes_mode:
                if   panid == 'main' : panid = 0  # for backwards compatibility
                elif panid == 'lower': panid = 1  # for backwards compatibility
                if apdict['y_on_right'] is not None:
                    panels.at[panid,'y_on_right'] = apdict['y_on_right']

            aptype = apdict['type']
            if aptype == 'ohlc' or aptype == 'candle':
                ax = _addplot_collections(panid,panels,apdict,xdates,config)
                _addplot_apply_supplements(ax,apdict)
            else:         
                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:
                    ydata = apdata.loc[:,column] if havedf else column
                    ax = _addplot_columns(panid,panels,ydata,apdict,xdates,config)
                    _addplot_apply_supplements(ax,apdict)

    # fill_between is NOT supported for external_axes_mode
    # (caller can easily call ax.fill_between() themselves).
    if config['fill_between'] is not None and not external_axes_mode:
        fb    = config['fill_between']
        panid = config['main_panel']
        if isinstance(fb,dict):
            if 'x' in fb:
                raise ValueError('fill_between dict may not contain `x`')
            if 'panel' in fb:
                panid = fb['panel']
                del fb['panel']
        else:
            fb = dict(y1=fb)
        fb['x'] = xdates
        ax = panels.at[panid,'axes'][0]
        ax.fill_between(**fb)
            
    # put the primary axis on one side,
    # and the twinx() on the "other" side:
    if not external_axes_mode:
        for panid,row in panels.iterrows():
            ax = row['axes']
            y_on_right = style['y_on_right'] if row['y_on_right'] is None else row['y_on_right']
            _set_ylabels_side(ax[0],ax[1],y_on_right)
    else:
        y_on_right = style['y_on_right']
        _set_ylabels_side(axA1,None,y_on_right)

    # 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()

    axA1.autoscale_view()  # Is this really necessary??
                           # It appears to me, based on experience coding types 'ohlc' and 'candle'
                           # for `addplot`, that this IS necessary when the only thing done to the
                           # the axes is .add_collection().  (However, if ax.plot() .scatter() or
                           # .bar() was called, then possibly this is not necessary; not entirely
                           # sure, but it definitely was necessary to get 'ohlc' and 'candle' 
                           # working in `addplot`).

    axA1.set_ylabel(config['ylabel'])

    if config['volume']:
        if external_axes_mode:
            volumeAxes.tick_params(axis='x',rotation=xrotation)
            volumeAxes.xaxis.set_major_formatter(formatter)
        vxp = config['volume_exponent']
        if vxp == 'legacy':
            volumeAxes.figure.canvas.draw()  # This is needed to calculate offset
            offset = volumeAxes.yaxis.get_major_formatter().get_offset()
            if len(offset) > 0:
                offset = (' x '+offset)
        elif isinstance(vxp,int) and vxp > 0:
            volumeAxes.ticklabel_format(useOffset=False,scilimits=(vxp,vxp),axis='y')
            offset = '  $10^{'+str(vxp)+'}$'
        elif isinstance(vxp,int) and vxp == 0:
            volumeAxes.ticklabel_format(useOffset=False,style='plain',axis='y')
            offset = ''
        else:
            offset = ''
            scilims = plt.rcParams['axes.formatter.limits']
            if scilims[0] < scilims[1]:
                for power in (5,4,3,2,1):
                    xp = scilims[1]*power
                    if vymax >= 10.**xp:
                        volumeAxes.ticklabel_format(useOffset=False,scilimits=(xp,xp),axis='y')
                        offset = '  $10^{'+str(xp)+'}$'
                        break
            elif scilims[0] == scilims[1] and scilims[1] != 0:
                volumeAxes.ticklabel_format(useOffset=False,scilimits=scilims,axis='y')
                offset = ' $10^'+str(scilims[1])+'$'
        volumeAxes.yaxis.offsetText.set_visible(False)
        if config['ylabel_lower'] is None:
            vol_label = 'Volume'+offset
        else:
            if len(offset) > 0:
                offset = '\n'+offset
            vol_label = config['ylabel_lower'] + offset
        volumeAxes.set_ylabel(vol_label)
    
    if config['title'] is not None:
        if config['tight_layout']:
            # IMPORTANT: 0.89 is based on the top of the top panel
            #            being at 0.18+0.7 = 0.88.  See _panels.py
            # If the value changes there, then it needs to change here.
            title_kwargs = dict(size='x-large',weight='semibold', va='bottom', y=0.89)
        else:
            title_kwargs = dict(size='x-large',weight='semibold', va='center')
        if isinstance(config['title'],dict):
            title_dict = config['title']
            if 'title' not in title_dict:
                raise ValueError('Must have "title" entry in title dict')
            else:
                title = title_dict['title']
                del title_dict['title']
            title_kwargs.update(title_dict)  # allows override default values set by mplfinance above
        else:
            title = config['title']      # config['title'] is a string
        fig.suptitle(title,**title_kwargs)
    
    
    if config['axtitle'] is not None:
        axA1.set_title(config['axtitle'])

    if not external_axes_mode:
        for panid,row in panels.iterrows():
            if not row['used2nd']:
                row['axes'][1].set_visible(False)

    if external_axes_mode:
        return None

    # Should we create a new kwarg to return a flattened axes list
    # versus a list of tuples of primary and secondary axes?
    # For now, for backwards compatibility, we flatten axes list:
    axlist = [ax for axes in panels['axes'] for ax in axes]

    if config['axisoff']:
        for ax in axlist:
            ax.set_axis_off()

    if config['savefig'] is not None:
        save = config['savefig']
        if isinstance(save,dict):
            if config['tight_layout'] and 'bbox_inches' not in save:
                plt.savefig(**save,bbox_inches='tight')
            else:
                plt.savefig(**save)
        else:
            if config['tight_layout']:
                plt.savefig(save,bbox_inches='tight')
            else:
                plt.savefig(save)
        if config['closefig']: # True or 'auto'
            plt.close(fig)
    elif not config['returnfig']:
        plt.show(block=config['block']) # https://stackoverflow.com/a/13361748/1639359 
        if config['closefig'] == True or (config['block'] and config['closefig']):
            plt.close(fig)
    
    if config['returnfig']:
        if config['closefig'] == True: plt.close(fig)
        return (fig, axlist)
コード例 #18
0
ファイル: plotting.py プロジェクト: zhuxinkai/mplfinance
def plot(data, **kwargs):
    """
    Given a Pandas DataFrame containing columns Open,High,Low,Close and optionally Volume
    with a DatetimeIndex, 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, renko, 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)

    if config['figsize'] is None:
        w, h = config['figratio']
        r = float(w) / float(h)
        if r < 0.20 or r > 5.0:
            raise ValueError(
                '"figratio" (aspect ratio)  must be between 0.20 and 5.0 (but is '
                + str(r) + ')')
        default_scale = DEFAULT_FIGRATIO[1] / h
        h *= default_scale
        w *= default_scale
        base = (w, h)
        figscale = config['figscale']
        fsize = [d * figscale for d in base]
    else:
        fsize = config['figsize']

    fig = plt.figure()
    fig.set_size_inches(fsize)

    if config['volume'] and volumes is None:
        raise ValueError('Request for volume, but NO volume data.')

    panels = _build_panels(fig, config)

    volumeAxes = panels.at[config['volume_panel'],
                           'axes'][0] if config['volume'] is True else None

    fmtstring = _determine_format_string(dates, config['datetime_format'])

    ptype = config['type']

    if config['show_nontrading']:
        formatter = mdates.DateFormatter(fmtstring)
        xdates = dates
    else:
        formatter = IntegerIndexDateTimeFormatter(dates, fmtstring)
        xdates = np.arange(len(dates))

    axA1 = panels.at[config['main_panel'], 'axes'][0]

    # Will have to handle widths config separately for PMOVE types ??
    config['_width_config'] = _determine_width_config(xdates, config)

    rwc = config['return_width_config']
    if isinstance(rwc, dict) and len(rwc) == 0:
        config['return_width_config'].update(config['_width_config'])

    collections = None
    if ptype == 'line':
        lw = config['_width_config']['line_width']
        axA1.plot(xdates, closes, color=config['linecolor'], linewidth=lw)
    else:
        collections = _construct_mpf_collections(ptype, dates, xdates, opens,
                                                 highs, lows, closes, volumes,
                                                 config, style)

    if ptype in VALID_PMOVE_TYPES:
        collections, new_dates, volumes, brick_values, size = collections
        formatter = IntegerIndexDateTimeFormatter(new_dates, fmtstring)
        xdates = np.arange(len(new_dates))

    if collections is not None:
        for collection in collections:
            axA1.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

        # Get rcParams['lines.linewidth'] and scale it
        # according to the deinsity of data??

        for mav in mavgs:
            if ptype in VALID_PMOVE_TYPES:
                mavprices = pd.Series(brick_values).rolling(mav).mean().values
            else:
                mavprices = pd.Series(closes).rolling(mav).mean().values

            lw = config['_width_config']['line_width']
            if mavc:
                axA1.plot(xdates, mavprices, linewidth=lw, color=next(mavc))
            else:
                axA1.plot(xdates, mavprices, linewidth=lw)

    avg_dist_between_points = (xdates[-1] - xdates[0]) / float(len(xdates))
    if not config['tight_layout']:
        #print('plot: xdates[-1]=',xdates[-1])
        #print('plot: xdates[ 0]=',xdates[ 0])
        #print('plot: len(xdates)=',len(xdates))
        #print('plot: avg_dist_between_points =',avg_dist_between_points)
        minx = xdates[0] - avg_dist_between_points
        maxx = xdates[-1] + avg_dist_between_points
    else:
        minx = xdates[0] - (0.45 * avg_dist_between_points)
        maxx = xdates[-1] + (0.45 * avg_dist_between_points)

    if len(xdates) == 1:  # kludge special case
        minx = minx - 0.75
        maxx = maxx + 0.75
    if ptype not in VALID_PMOVE_TYPES:
        _lows = lows
        _highs = highs
    else:
        _lows = brick_values
        _highs = [brick + size for brick in brick_values]

    miny = np.nanmin(_lows)
    maxy = np.nanmax(_highs)
    #if len(xdates) > 1:
    #   stdy = (stat.stdev(_lows) + stat.stdev(_highs)) / 2.0
    #else:  # kludge special case
    #   stdy = 0.02 * math.fabs(maxy - miny)
    # print('minx,miny,maxx,maxy,stdy=',minx,miny,maxx,maxy,stdy)

    if config['set_ylim'] is not None:
        axA1.set_ylim(config['set_ylim'][0], config['set_ylim'][1])
    elif config['tight_layout']:
        axA1.set_xlim(minx, maxx)
        ydelta = 0.01 * (maxy - miny)
        axA1.set_ylim(miny - ydelta, maxy + ydelta)
    else:
        corners = (minx, miny), (maxx, maxy)
        axA1.update_datalim(corners)

    if config['return_calculated_values'] is not None:
        retdict = config['return_calculated_values']
        if ptype in VALID_PMOVE_TYPES:
            prekey = ptype
            retdict[prekey + '_bricks'] = brick_values
            retdict[prekey + '_dates'] = mdates.num2date(new_dates)
            retdict[prekey + '_size'] = size
            if config['volume']:
                retdict[prekey + '_volumes'] = volumes
        if mavgs is not None:
            for i in range(0, len(mavgs)):
                retdict['mav' + str(mavgs[i])] = mavprices
        retdict['minx'] = minx
        retdict['maxx'] = maxx
        retdict['miny'] = miny
        retdict['maxy'] = maxy

    # Note: these are NOT mutually exclusive, so the order of this
    #       if/elif is important: VALID_PMOVE_TYPES must be first.
    if ptype in VALID_PMOVE_TYPES:
        dtix = pd.DatetimeIndex([dt for dt in mdates.num2date(new_dates)])
    elif not config['show_nontrading']:
        dtix = data.index
    else:
        dtix = None

    line_collections = []
    line_collections.append(
        _construct_aline_collections(config['alines'], dtix))
    line_collections.append(
        _construct_hline_collections(config['hlines'], minx, maxx))
    line_collections.append(
        _construct_vline_collections(config['vlines'], dtix, miny, maxy))
    tlines = config['tlines']
    if isinstance(tlines, (list, tuple)) and all(
        [isinstance(item, dict) for item in tlines]):
        pass
    else:
        tlines = [
            tlines,
        ]
    for tline_item in tlines:
        line_collections.append(
            _construct_tline_collections(tline_item, dtix, dates, opens, highs,
                                         lows, closes))

    for collection in line_collections:
        if collection is not None:
            axA1.add_collection(collection)

    datalen = len(xdates)
    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)

        w = config['_width_config']['volume_width']
        lw = config['_width_config']['volume_linewidth']

        adjc = _adjust_color_brightness(vcolors, 0.90)
        volumeAxes.bar(xdates,
                       volumes,
                       width=w,
                       linewidth=lw,
                       color=vcolors,
                       ec=adjc)
        miny = 0.3 * np.nanmin(volumes)
        maxy = 1.1 * np.nanmax(volumes)
        volumeAxes.set_ylim(miny, maxy)

    xrotation = config['xrotation']
    _set_ticks_on_bottom_panel_only(panels, formatter, rotation=xrotation)

    addplot = config['addplot']
    if addplot is not None and ptype not in VALID_PMOVE_TYPES:
        # Calculate the Order of Magnitude Range ('mag')
        # 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(np.nanmin(lows)), 1e-7), 10) - 0.5
        hi = math.log(max(math.fabs(np.nanmax(highs)), 1e-7), 10) + 0.5

        panels['mag'] = [None] * len(panels)  # create 'mag' column

        panels.at[config['main_panel'], 'mag'] = {
            'lo': lo,
            'hi': hi
        }  # update main panel magnitude range

        if config['volume']:
            lo = math.log(max(math.fabs(np.nanmin(volumes)), 1e-7), 10) - 0.5
            hi = math.log(max(math.fabs(np.nanmax(volumes)), 1e-7), 10) + 0.5
            panels.at[config['volume_panel'], 'mag'] = {'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(np.nanmax(yd)), 1e-7), 10)
                ymlo = math.log(max(math.fabs(np.nanmin(yd)), 1e-7), 10)
                secondary_y = False
                panid = apdict['panel']
                if panid == 'main': panid = 0  # for backwards compatibility
                elif panid == 'lower': panid = 1  # for backwards compatibility
                if apdict['secondary_y'] == 'auto':
                    # If mag(nitude) for this panel is not yet set, then set it
                    # here, as this is the first ydata to be plotted on this panel:
                    # i.e. consider this to be the 'primary' axis for this panel.
                    p = panid, 'mag'
                    if panels.at[p] is None:
                        panels.at[p] = {'lo': ymlo, 'hi': ymhi}
                    elif ymlo < panels.at[p]['lo'] or ymhi > panels.at[p]['hi']:
                        secondary_y = True
                    #if secondary_y:
                    #    print('auto says USE secondary_y ... for panel',panid)
                    #else:
                    #    print('auto says do NOT use secondary_y ... for panel',panid)
                else:
                    secondary_y = apdict['secondary_y']
                    #print("apdict['secondary_y'] says secondary_y is",secondary_y)

                if secondary_y:
                    ax = panels.at[panid, 'axes'][1]
                    panels.at[panid, 'used2nd'] = True
                else:
                    ax = panels.at[panid, 'axes'][0]

                if (apdict["ylabel"] is not None):
                    ax.set_ylabel(apdict["ylabel"])

                aptype = apdict['type']
                if aptype == 'scatter':
                    size = apdict['markersize']
                    mark = apdict['marker']
                    color = apdict['color']
                    if isinstance(mark, (list, tuple, np.ndarray)):
                        _mscatter(xdates,
                                  ydata,
                                  ax=ax,
                                  m=mark,
                                  s=size,
                                  color=color)
                    else:
                        ax.scatter(xdates,
                                   ydata,
                                   s=size,
                                   marker=mark,
                                   color=color)
                elif aptype == 'bar':
                    width = apdict['width']
                    bottom = apdict['bottom']
                    color = apdict['color']
                    alpha = apdict['alpha']
                    ax.bar(xdates,
                           ydata,
                           width=width,
                           bottom=bottom,
                           color=color,
                           alpha=alpha)
                elif aptype == 'line':
                    ls = apdict['linestyle']
                    color = apdict['color']
                    ax.plot(xdates, ydata, linestyle=ls, color=color)
                #elif aptype == 'ohlc' or aptype == 'candle':
                # This won't work as is, because here we are looping through one column at a time
                # and mpf_collections needs ohlc columns:
                #    collections =_construct_mpf_collections(aptype,dates,xdates,opens,highs,lows,closes,volumes,config,style)
                #    if len(collections) == 1: collections = [collections]
                #    for collection in collections:
                #        ax.add_collection(collection)
                else:
                    raise ValueError('addplot type "' + str(aptype) +
                                     '" NOT yet supported.')

    if config['fill_between'] is not None:
        fb = config['fill_between']
        panid = config['main_panel']
        if isinstance(fb, dict):
            if 'x' in fb:
                raise ValueError('fill_between dict may not contain `x`')
            if 'panel' in fb:
                panid = fb['panel']
                del fb['panel']
        else:
            fb = dict(y1=fb)
        fb['x'] = xdates
        ax = panels.at[panid, 'axes'][0]
        ax.fill_between(**fb)

    if config['set_ylim_panelB'] is not None:
        miny = config['set_ylim_panelB'][0]
        maxy = config['set_ylim_panelB'][1]
        panels.at[1, 'axes'][0].set_ylim(miny, maxy)

    # put the twinx() on the "other" side:
    if style['y_on_right']:
        for ax in panels['axes'].values:
            ax[0].yaxis.set_label_position('right')
            ax[0].yaxis.tick_right()
            ax[1].yaxis.set_label_position('left')
            ax[1].yaxis.tick_left()
    else:
        for ax in panels['axes'].values:
            ax[0].yaxis.set_label_position('left')
            ax[0].yaxis.tick_left()
            ax[1].yaxis.set_label_position('right')
            ax[1].yaxis.tick_right()

    # 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()

    axA1.autoscale_view()  # Is this really necessary??

    axA1.set_ylabel(config['ylabel'])

    if config['volume']:
        volumeAxes.figure.canvas.draw()  # This is needed to calculate offset
        offset = volumeAxes.yaxis.get_major_formatter().get_offset()
        volumeAxes.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
        volumeAxes.set_ylabel(vol_label)

    if config['title'] is not None:
        if config['tight_layout']:
            # IMPORTANT: 0.89 is based on the top of the top panel
            #            being at 0.18+0.7 = 0.88.  See _panels.py
            # If the value changes there, then it needs to change here.
            fig.suptitle(config['title'],
                         size='x-large',
                         weight='semibold',
                         va='bottom',
                         y=0.89)
        else:
            fig.suptitle(config['title'],
                         size='x-large',
                         weight='semibold',
                         va='center')

    for panid, row in panels.iterrows():
        if not row['used2nd']:
            row['axes'][1].set_visible(False)

    # Should we create a new kwarg to return a flattened axes list
    # versus a list of tuples of primary and secondary axes?
    # For now, for backwards compatibility, we flatten axes list:
    axlist = [ax for axes in panels['axes'] for ax in axes]

    if config['axisoff']:
        for ax in axlist:
            ax.set_xlim(xdates[0], xdates[-1])
            ax.set_axis_off()

    if config['savefig'] is not None:
        save = config['savefig']
        if isinstance(save, dict):
            # Expand to fill chart if axisoff
            if config['axisoff'] and 'bbox_inches' not in save:
                plt.savefig(**save, bbox_inches='tight')
            else:
                plt.savefig(**save)
        else:
            if config['axisoff']:
                plt.savefig(save, bbox_inches='tight')
            else:
                plt.savefig(save)
        if config['closefig']:  # True or 'auto'
            plt.close(fig)
    elif not config['returnfig']:
        plt.show(block=config['block']
                 )  # https://stackoverflow.com/a/13361748/1639359
        if config['closefig'] == True or (config['block']
                                          and config['closefig']):
            plt.close(fig)

    if config['returnfig']:
        if config['closefig'] == True: plt.close(fig)
        return (fig, axlist)
コード例 #19
0
ファイル: plotting.py プロジェクト: strikar21/mplfinance
def plot( data, **kwargs ):
    """
    Given a Pandas DataFrame containing columns Open,High,Low,Close and optionally Volume
    with a DatetimeIndex, 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, renko, 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 = config['style'] = _styles._get_mpfstyle(style)

    if isinstance(style,dict):
        _styles._apply_mpfstyle(style)
    else:
       raise TypeError('style should be a `dict`; why is it not?')
    
    if config['figsize'] is None:
        w,h = config['figratio']
        r = float(w)/float(h)
        if r < 0.20 or r > 5.0:
            raise ValueError('"figratio" (aspect ratio)  must be between 0.20 and 5.0 (but is '+str(r)+')')
        default_scale = DEFAULT_FIGRATIO[1]/h
        h *= default_scale
        w *= default_scale
        base      = (w,h)
        figscale  = config['figscale']
        fsize     = [d*figscale for d in base]
    else:
        fsize = config['figsize']
    
    fig = plt.figure()
    fig.set_size_inches(fsize)

    if config['volume'] and volumes is None:
        raise ValueError('Request for volume, but NO volume data.')

    panels = _build_panels(fig, config)

    volumeAxes = panels.at[config['volume_panel'],'axes'][0] if config['volume'] is True else None

    fmtstring = _determine_format_string( dates, config['datetime_format'] )

    ptype = config['type'] 

    if config['show_nontrading']:
        formatter = mdates.DateFormatter(fmtstring)
        xdates = dates
    else:
        formatter = IntegerIndexDateTimeFormatter(dates, fmtstring)
        xdates = np.arange(len(dates))

    axA1 = panels.at[config['main_panel'],'axes'][0]

    # Will have to handle widths config separately for PMOVE types ??
    config['_width_config'] = _determine_width_config(xdates, config)


    rwc = config['return_width_config']
    if isinstance(rwc,dict) and len(rwc)==0:
        config['return_width_config'].update(config['_width_config'])
 

    collections = None
    if ptype == 'line':
        lw = config['_width_config']['line_width']
        axA1.plot(xdates, closes, color=config['linecolor'], linewidth=lw)
    else:
        collections =_construct_mpf_collections(ptype,dates,xdates,opens,highs,lows,closes,volumes,config,style)

    if ptype in VALID_PMOVE_TYPES:
        collections, new_dates, volumes, brick_values, size = collections
        formatter = IntegerIndexDateTimeFormatter(new_dates, fmtstring)
        xdates = np.arange(len(new_dates))

    if collections is not None:
        for collection in collections:
            axA1.add_collection(collection)

    mav = config['mav']
    mavwidth = config['_width_config']['line_width']
    mavcolors = style['mavcolors']
    if ptype in VALID_PMOVE_TYPES:
        mavprices = _plot_mav(axA1, mav, xdates, brick_values, mavwidth, mavcolors)
        else:
        mavprices = _plot_mav(axA1, mav, xdates, closes, mavwidth, mavcolors)