From 58a0e0b893c5b1229bee723ce695384d5b137096 Mon Sep 17 00:00:00 2001 From: Daniel Goldfarb Date: Wed, 24 Aug 2022 19:15:44 -0400 Subject: [PATCH 01/22] fix linestyle validator --- src/mplfinance/plotting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mplfinance/plotting.py b/src/mplfinance/plotting.py index b3d4f341..0b446d3a 100644 --- a/src/mplfinance/plotting.py +++ b/src/mplfinance/plotting.py @@ -1165,7 +1165,7 @@ def _auto_secondary_y( panels, panid, ylo, yhi ): def _valid_addplot_kwargs(): - valid_linestyles = ('-','solid','--','dashed','-.','dashdot','.','dotted',None,' ','') + valid_linestyles = ('-','solid','--','dashed','-.','dashdot',':','dotted',None,' ','') valid_types = ('line','scatter','bar', 'ohlc', 'candle','step') valid_stepwheres = ('pre','post','mid') valid_edgecolors = ('face', 'none', None) From f06987df4a2db23b2d77abc6f0aaf5f59177b797 Mon Sep 17 00:00:00 2001 From: Animesh Sahu Date: Sat, 1 Oct 2022 20:19:55 +0530 Subject: [PATCH 02/22] Change maximum panels to 32 in docs & examples, in regards to completion of #377 --- examples/panels.ipynb | 2 +- markdown/subplots.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/panels.ipynb b/examples/panels.ipynb index 75c9164c..3212577f 100644 --- a/examples/panels.ipynb +++ b/examples/panels.ipynb @@ -17,7 +17,7 @@ "![sample_subplot](data/sample_subplot.png)\n", "\n", "### Features and Basic Terminology\n", - "* Panels are identified by their \"Panel ID\", an integer ranging from 0 to 9.\n", + "* Panels are identified by their \"Panel ID\", an integer ranging from 0 to 31.\n", "* Panel ID's are always numbered from top to bottom, thus:\n", " - Panel 0 is always the uppermost panel, Panel 1 is just below that, and so on.\n", "* The \"*main panel*\" is the panel where candlesticks/ohlc data are plotted.\n", diff --git a/markdown/subplots.md b/markdown/subplots.md index 0a7fa5b8..0a43f04c 100644 --- a/markdown/subplots.md +++ b/markdown/subplots.md @@ -17,10 +17,10 @@ * The Panels Method attains its simplicity, in part, by having certain limitations.
These limitiations are: - Subplots are always stacked vertically. - All subplots share the same x-axis. - - There is a maximum of 10 subplots. + - There is a maximum of 32 subplots. * The Panels Method is adequate to plot: - ohlc, candlesticks, etc. - - with volume, and + - with volume, and - with one or more studies/indicators, such as: - MACD, DMI, RSI, Bollinger, Accumulation/Distribution Oscillator, Commodity Channel Index, Etc. * [**See here for a tutorial and details on implementing the mplfinance Panels Method for subplots.**](https://github.com/matplotlib/mplfinance/blob/master/examples/panels.ipynb) From 59b42e8efddaa919e9782e97092dc99dab1df2f7 Mon Sep 17 00:00:00 2001 From: nijek Date: Sun, 9 Oct 2022 09:47:58 -0300 Subject: [PATCH 03/22] mpf now accepts alpha parameter as list for [hvat]lines so it is possible to set a different alpha for each line. --- src/mplfinance/_utils.py | 195 ++++++++++++++++++++------------------- 1 file changed, 99 insertions(+), 96 deletions(-) diff --git a/src/mplfinance/_utils.py b/src/mplfinance/_utils.py index 447a52ea..5d555ffa 100644 --- a/src/mplfinance/_utils.py +++ b/src/mplfinance/_utils.py @@ -9,7 +9,7 @@ from itertools import cycle -from matplotlib import colors as mcolors +from matplotlib import colors as mcolors, pyplot as plt from matplotlib.patches import Ellipse from matplotlib.collections import LineCollection, PolyCollection, PatchCollection @@ -83,7 +83,7 @@ def _check_and_convert_xlim_configuration(data, config): xlim = [ _date_to_mdate(dt) for dt in xlim] else: xlim = [ _date_to_iloc_extrapolate(data.index.to_series(),dt) for dt in xlim] - + return xlim @@ -109,7 +109,7 @@ def _construct_mpf_collections(ptype,dates,xdates,opens,highs,lows,closes,volume dates, highs, lows, volumes, config['pnf_params'], closes, marketcolors=style['marketcolors']) else: raise TypeError('Unknown ptype="',str(ptype),'"') - + return collections @@ -140,7 +140,7 @@ def combine_adjacent(arr): Returns ------- output: new summed array - indexes: indexes indicating the first + indexes: indexes indicating the first element summed for each group in arr """ output, indexes = [], [] @@ -153,7 +153,7 @@ def combine_adjacent(arr): output.append(sum(arr[:index])) indexes.append(curr_i) curr_i += index - + for _ in range(index): arr.pop(0) return output, indexes @@ -190,7 +190,7 @@ def _updown_colors(upcolor,downcolor,opens,closes,use_prev_close=False): if not use_prev_close: return [ cmap[opn < cls] for opn,cls in zip(opens,closes) ] else: - first = cmap[opens[0] < closes[0]] + first = cmap[opens[0] < closes[0]] _list = [ cmap[pre < cls] for cls,pre in zip(closes[1:], closes) ] return [first] + _list @@ -208,8 +208,8 @@ def _make_updown_color_list(key,marketcolors,opens,closes,overrides=None): ups[ix] = mco[key][ 'up' ] downs[ix] = mco[key]['down'] return [ups[ix] if opens[ix] < closes[ix] else downs[ix] for ix in range(length)] - - + + def _updownhollow_colors(upcolor,downcolor,hollowcolor,opens,closes): if upcolor == downcolor: return upcolor @@ -221,7 +221,7 @@ def _updownhollow_colors(upcolor,downcolor,hollowcolor,opens,closes): def _date_to_iloc(dtseries,date): - '''Convert a `date` to a location, given a date series w/a datetime index. + '''Convert a `date` to a location, given a date series w/a datetime index. If `date` does not exactly match a date in the series then interpolate between two dates. If `date` is outside the range of dates in the series, then raise an exception . @@ -258,7 +258,7 @@ def _date_to_iloc_linear(dtseries,date,trace=False): i1 = 0.0 i2 = len(dtseries) - 1.0 if trace: print('i1,i2=',i1,i2) - + slope = (i2 - i1) / (d2 - d1) yitrcpt1 = i1 - (slope*d1) if trace: print('slope,yitrcpt=',slope,yitrcpt1) @@ -268,7 +268,7 @@ def _date_to_iloc_linear(dtseries,date,trace=False): print('WARNING: yintercepts NOT equal!!!(',yitrcpt1,yitrcpt2,')') yitrcpt = (yitrcpt1 + yitrcpt2) / 2.0 else: - yitrcpt = yitrcpt1 + yitrcpt = yitrcpt1 return (slope * _date_to_mdate(date)) + yitrcpt def _date_to_iloc_5_7ths(dtseries,date,direction,trace=False): @@ -288,14 +288,14 @@ def _date_to_iloc_5_7ths(dtseries,date,direction,trace=False): return loc_5_7ths def _date_to_iloc_extrapolate(dtseries,date): - '''Convert a `date` to a location, given a date series w/a datetime index. + '''Convert a `date` to a location, given a date series w/a datetime index. If `date` does not exactly match a date in the series then interpolate between two dates. If `date` is outside the range of dates in the series, then extrapolate: Extrapolation results in increased error as the distance of the extrapolation increases. We have two methods to extrapolate: (1) Determine a linear equation based on the data provided in `dtseries`, and use that equation to calculate the location for the date. - (2) Multiply by 5/7 the number of days between the edge date of dtseries and the + (2) Multiply by 5/7 the number of days between the edge date of dtseries and the date for which we are requesting a location. THIS ASSUMES DAILY data AND a 5 DAY TRADING WEEK. Empirical observation (scratch_pad/date_to_iloc_extrapolation.ipynb) shows that @@ -348,7 +348,7 @@ def _date_to_mdate(date): def _convert_segment_dates(segments,dtindex): ''' - Convert line segment dates to matplotlib dates + Convert line segment dates to matplotlib dates Inputted segment dates may be: pandas-parseable date-time string, pandas timestamp, or a python datetime or date, or (if dtindex is not None) integer index A "segment" is a "sequence of lines", @@ -363,7 +363,7 @@ def _convert_segment_dates(segments,dtindex): new_line = [] for dt,value in line: if dtindex is not None: - date = _date_to_iloc(dtseries,dt) + date = _date_to_iloc(dtseries,dt) else: date = _date_to_mdate(dt) if date is None: @@ -374,14 +374,14 @@ def _convert_segment_dates(segments,dtindex): def _valid_renko_kwargs(): ''' - Construct and return the "valid renko kwargs table" for the mplfinance.plot(type='renko') - function. A valid kwargs table is a `dict` of `dict`s. The keys of the outer dict are - the valid key-words for the function. The value for each key is a dict containing 3 + Construct and return the "valid renko kwargs table" for the mplfinance.plot(type='renko') + function. A valid kwargs table is a `dict` of `dict`s. The keys of the outer dict are + the valid key-words for the function. The value for each key is a dict containing 3 specific keys: "Default", "Description" and "Validator" with the following values: "Default" - The default value for the kwarg if none is specified. "Description" - The description for the kwarg. "Validator" - A function that takes the caller specified value for the kwarg, - and validates that it is the correct type, and (for kwargs with + and validates that it is the correct type, and (for kwargs with a limited set of allowed values) may also validate that the kwarg value is one of the allowed values. ''' @@ -404,14 +404,14 @@ def _valid_renko_kwargs(): def _valid_pnf_kwargs(): ''' - Construct and return the "valid pnf kwargs table" for the mplfinance.plot(type='pnf') - function. A valid kwargs table is a `dict` of `dict`s. The keys of the outer dict are - the valid key-words for the function. The value for each key is a dict containing 3 + Construct and return the "valid pnf kwargs table" for the mplfinance.plot(type='pnf') + function. A valid kwargs table is a `dict` of `dict`s. The keys of the outer dict are + the valid key-words for the function. The value for each key is a dict containing 3 specific keys: "Default", "Description" and "Validator" with the following values: "Default" - The default value for the kwarg if none is specified. "Description" - The description for the kwarg. "Validator" - A function that takes the caller specified value for the kwarg, - and validates that it is the correct type, and (for kwargs with + and validates that it is the correct type, and (for kwargs with a limited set of allowed values) may also validate that the kwarg value is one of the allowed values. ''' @@ -438,16 +438,17 @@ def _valid_pnf_kwargs(): def _valid_lines_kwargs(): + ''' - Construct and return the "valid lines (hlines,vlines,alines,tlines) kwargs table" + Construct and return the "valid lines (hlines,vlines,alines,tlines) kwargs table" for the mplfinance.plot() `[h|v|a|t]lines=` kwarg functions. - A valid kwargs table is a `dict` of `dict`s. The keys of the outer dict are - the valid key-words for the function. The value for each key is a dict containing 3 + A valid kwargs table is a `dict` of `dict`s. The keys of the outer dict are + the valid key-words for the function. The value for each key is a dict containing 3 specific keys: "Default", "Description" and "Validator" with the following values: "Default" - The default value for the kwarg if none is specified. "Description" - The description for the kwarg. "Validator" - A function that takes the caller specified value for the kwarg, - and validates that it is the correct type, and (for kwargs with + and validates that it is the correct type, and (for kwargs with a limited set of allowed values) may also validate that the kwarg value is one of the allowed values. ''' @@ -496,7 +497,7 @@ def _valid_lines_kwargs(): 'Description' : 'line style of [hvat]lines (or sequence of line styles, if each line to have a different linestyle)', 'Validator' : lambda value: value is None or value in valid_linestyles or all([v in valid_linestyles for v in value]) }, - + 'linewidths': { 'Default' : None, 'Description' : 'line width of [hvat]lines (or sequence of line widths, if each line to have a different width)', 'Validator' : lambda value: value is None @@ -504,9 +505,11 @@ def _valid_lines_kwargs(): or all([isinstance(v,(float,int)) for v in value]) }, 'alpha' : { 'Default' : 1.0, - 'Description' : 'Opacity of [hvat]lines. float from 0.0 to 1.0 '+ - ' (1.0 means fully opaque; 0.0 means transparent.', - 'Validator' : lambda value: isinstance(value,(float,int)) }, + 'Description' : 'Opacity of [hvat]lines (or sequence of opacities,' + + 'if each line is to have a different opacity)' + + 'float from 0.0 to 1.0 '+' (1.0 means fully opaque; 0.0 means transparent.', + 'Validator' : lambda value: isinstance(value,(float,int)) + or all([isinstance(v, (float, int)) for v in value]) }, 'tline_use' : { 'Default' : 'close', @@ -549,7 +552,7 @@ def _construct_ohlc_collections(dates, opens, highs, lows, closes, marketcolors= Returns ------- - ret : list + ret : list a list or tuple of matplotlib collections to be added to the axes """ @@ -629,7 +632,7 @@ def _construct_candlestick_collections(dates, opens, highs, lows, closes, market ret : list (lineCollection, barCollection) """ - + _check_input(opens, highs, lows, closes) if marketcolors is None: @@ -649,10 +652,10 @@ def _construct_candlestick_collections(dates, opens, highs, lows, closes, market rangeSegLow = [((date, low), (date, min(open,close))) for date, low, open, close in zip(dates, lows, opens, closes)] - + rangeSegHigh = [((date, high), (date, max(open,close))) for date, high, open, close in zip(dates, highs, opens, closes)] - + rangeSegments = rangeSegLow + rangeSegHigh alpha = marketcolors['alpha'] @@ -685,7 +688,7 @@ def _construct_candlestick_collections(dates, opens, highs, lows, closes, market def _construct_hollow_candlestick_collections(dates, opens, highs, lows, closes, marketcolors=None, config=None): """Represent today's open to close as a "bar" line (candle body) and high low range as a vertical line (candle wick) - + If config['type']=='hollow_and_filled' (hollow and filled candles) then candle edge and wick color depend on PREVIOUS close to today's close (up or down), and the center of the candle body (hollow or filled) depends on the today's open to close (up or down). @@ -712,7 +715,7 @@ def _construct_hollow_candlestick_collections(dates, opens, highs, lows, closes, ret : list (lineCollection, barCollection) """ - + _check_input(opens, highs, lows, closes) if marketcolors is None: @@ -732,23 +735,23 @@ def _construct_hollow_candlestick_collections(dates, opens, highs, lows, closes, rangeSegLow = [((date, low), (date, min(open,close))) for date, low, open, close in zip(dates, lows, opens, closes)] - + rangeSegHigh = [((date, high), (date, max(open,close))) for date, high, open, close in zip(dates, highs, opens, closes)] - + rangeSegments = rangeSegLow + rangeSegHigh alpha = marketcolors['alpha'] uc = mcolors.to_rgba(marketcolors['candle'][ 'up' ], alpha) dc = mcolors.to_rgba(marketcolors['candle']['down'], alpha) - + hc = mcolors.to_rgba(marketcolors['hollow']) if 'hollow' in marketcolors else (0,0,0,0) - + colors = _updownhollow_colors(uc, dc, hc, opens, closes) # for candle body. edgecolor = _updown_colors(uc, dc, opens, closes, use_prev_close=True) - + wickcolor = _updown_colors(uc, dc, opens, closes, use_prev_close=True) # For hollow candles, we scale the candle linewidth up a little: @@ -778,19 +781,19 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param --------------------- 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 + 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 + 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 + 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 + 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 @@ -801,7 +804,7 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param 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 @@ -825,10 +828,10 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param renko_params = _process_kwargs(config_renko_params, _valid_renko_kwargs()) if marketcolors is None: marketcolors = _get_mpfstyle('classic')['marketcolors'] - + brick_size = renko_params['brick_size'] atr_length = renko_params['atr_length'] - + if brick_size == 'atr': if atr_length == 'total': @@ -849,7 +852,7 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param 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 @@ -876,7 +879,7 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param 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 @@ -889,7 +892,7 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param new_volumes.pop(dates_volumes_index) continue last_diff_sign = curr_diff_sign - + if diff > 0: bricks.extend([1]*abs(diff)) else: @@ -911,7 +914,7 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param curr_price += (brick_size * number) brick_values.append(curr_price) - + x, y = index, curr_price verts.append(( @@ -943,41 +946,41 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf --------------------- 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 + 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 + 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 + 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. During this same step we also combine like signed elements - and associated volume/date data ignoring any zero values that are created by - subtracting 1 from the box value. Next we recreate the box array utilizing a - rolling_change and volume_cache to store and sum the changes that don't break + 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. During this same step we also combine like signed elements + and associated volume/date data ignoring any zero values that are created by + subtracting 1 from the box value. Next we recreate the box array utilizing a + rolling_change and volume_cache to store and sum the changes that don't break the reversal threshold. 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 + 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 + 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 + 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 @@ -1001,7 +1004,7 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf pointnfig_params = _process_kwargs(config_pointnfig_params, _valid_pnf_kwargs()) if marketcolors is None: marketcolors = _get_mpfstyle('classic')['marketcolors'] - + box_size = pointnfig_params['box_size'] atr_length = pointnfig_params['atr_length'] reversal = pointnfig_params['reversal'] @@ -1021,7 +1024,7 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf if reversal < 1 or reversal > 9: raise ValueError("Specified reversal must be an integer in the range [1,9]") - + alpha = marketcolors['alpha'] uc = mcolors.to_rgba(marketcolors['ohlc'][ 'up' ], alpha) @@ -1032,7 +1035,7 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf 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: @@ -1050,7 +1053,7 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf # combine adjacent similarly signed differences boxes, indexes = combine_adjacent(boxes) new_volumes, new_dates = coalesce_volume_dates(temp_volumes, temp_dates, indexes) - + adjusted_boxes = [boxes[0]] temp_volumes, temp_dates = [new_volumes[0]], [new_dates[0]] volume_cache = 0 @@ -1086,7 +1089,7 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf boxes = [adjusted_boxes[0]] new_volumes = [temp_volumes[0]] new_dates = [temp_dates[0]] - + rolling_change = 0 volume_cache = 0 biggest_difference = 0 # only used for the last column @@ -1094,11 +1097,11 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf #Clean data to account for reversal size (added to allow overriding the default reversal of 1) for i in range(1, len(adjusted_boxes)): - # Add to rolling_change and volume_cache which stores the box and volume values + # Add to rolling_change and volume_cache which stores the box and volume values rolling_change += adjusted_boxes[i] volume_cache += temp_volumes[i] - # if rolling_change is the same sign as the previous box and the abs value is bigger than the + # if rolling_change is the same sign as the previous box and the abs value is bigger than the # abs value of biggest_difference then we should replace biggest_difference with rolling_change if rolling_change*boxes[-1] > 0 and abs(rolling_change) > abs(biggest_difference): biggest_difference = rolling_change @@ -1116,14 +1119,14 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf boxes.append(rolling_change) new_volumes.append(volume_cache) new_dates.append(temp_dates[i]) - + # reset rolling_change and volume_cache once we've used them rolling_change = 0 volume_cache = 0 - + # reset biggest_difference as we start from the beginning every time there is a reversal biggest_difference = 0 - + # Adjust the last box column if the left over rolling_change is the same sign as the column boxes[-1] += biggest_difference new_volumes[-1] += volume_cache @@ -1138,33 +1141,33 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf 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( 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 + 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, @@ -1182,7 +1185,7 @@ def _construct_aline_collections(alines, dtix=None): ---------- alines : sequence sequences of segments, which are sequences of lines, - which are sequences of two or more points ( date[time], price ) or (x,y) + 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 @@ -1269,7 +1272,7 @@ def _construct_hline_collections(hlines,minx,maxx): #print('hconfig=',hconfig) #print('hlines=',hlines) - + lines = [] if not isinstance(hlines,(list,tuple)): hlines = [hlines,] # may be a single price value @@ -1332,7 +1335,7 @@ def _construct_vline_collections(vlines,dtix,miny,maxy): #print('vconfig=',vconfig) #print('vlines=',vlines) - + if not isinstance(vlines,(list,tuple)): vlines = [vlines,] @@ -1416,7 +1419,7 @@ def _tline_lsq(dfslice,tline_use): https://mmas.github.io/least-squares-fitting-numpy-scipy ''' si = dfslice[tline_use].mean(axis=1) - s = si.dropna() + s = si.dropna() if len(s) < 2: err = 'NOT enough data for Least Squares' if (len(si) > 2): @@ -1453,7 +1456,7 @@ def _tline_lsq(dfslice,tline_use): alines.append((p1,p2)) del tconfig['alines'] - alines = dict(alines=alines,**tconfig) + alines = dict(alines=alines,**tconfig) alines['tlines'] = None return _construct_aline_collections(alines, dtix) @@ -1471,7 +1474,7 @@ class IntegerIndexDateTimeFormatter(Formatter): you would otherwise plot on that axis. Construct this formatter by providing the arrange of datetimes (as matplotlib floats). When the formatter receives an integer in the range, it will look up the - datetime and format it. + datetime and format it. """ def __init__(self, dates, fmt='%b %d, %H:%M'): @@ -1485,7 +1488,7 @@ def __call__(self, x, pos=0): # not sure what 'pos' is for: see # https://matplotlib.org/gallery/ticks_and_spines/date_index_formatter.html ix = int(np.round(x)) - + if ix >= self.len or ix < 0: date = None dateformat = '' From c09265f2f375e2d584c565ab09557e7bec2c0dd5 Mon Sep 17 00:00:00 2001 From: nijek Date: Sun, 9 Oct 2022 10:25:39 -0300 Subject: [PATCH 04/22] mpf now accepts alpha parameter as list for [hvat]lines so it is possible to set a different alpha for each line. I also imported matplotlib.pyplot as plt to solve a unresolved reference on function _mscatter(x,y,ax=None, m=None, **kw) --- src/mplfinance/_utils.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/mplfinance/_utils.py b/src/mplfinance/_utils.py index 5d555ffa..a4f80dfc 100644 --- a/src/mplfinance/_utils.py +++ b/src/mplfinance/_utils.py @@ -438,7 +438,6 @@ def _valid_pnf_kwargs(): def _valid_lines_kwargs(): - ''' Construct and return the "valid lines (hlines,vlines,alines,tlines) kwargs table" for the mplfinance.plot() `[h|v|a|t]lines=` kwarg functions. @@ -504,12 +503,12 @@ def _valid_lines_kwargs(): or isinstance(value,(float,int)) or all([isinstance(v,(float,int)) for v in value]) }, - 'alpha' : { 'Default' : 1.0, - 'Description' : 'Opacity of [hvat]lines (or sequence of opacities,' - + 'if each line is to have a different opacity)' - + 'float from 0.0 to 1.0 '+' (1.0 means fully opaque; 0.0 means transparent.', - 'Validator' : lambda value: isinstance(value,(float,int)) - or all([isinstance(v, (float, int)) for v in value]) }, + 'alpha': {'Default': 1.0, + 'Description': 'Opacity of [hvat]lines (or sequence of opacities,' + + 'if each line is to have a different opacity)' + + 'float from 0.0 to 1.0 ' + ' (1.0 means fully opaque; 0.0 means transparent.', + 'Validator': lambda value: isinstance(value, (float, int)) + or all([isinstance(v, (float, int)) for v in value])}, 'tline_use' : { 'Default' : 'close', From 75b41f65a06fa6f87e44eb57166c04fb0bd4d80b Mon Sep 17 00:00:00 2001 From: Daniel Goldfarb Date: Thu, 13 Oct 2022 17:11:02 -0400 Subject: [PATCH 05/22] Update _version.py --- src/mplfinance/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mplfinance/_version.py b/src/mplfinance/_version.py index 9bca395f..44439d7d 100644 --- a/src/mplfinance/_version.py +++ b/src/mplfinance/_version.py @@ -1,4 +1,4 @@ -version_info = (0, 12, 9, 'beta', 1) +version_info = (0, 12, 9, 'beta', 2) _specifier_ = {'alpha': 'a','beta': 'b','candidate': 'rc','final': ''} From 47e4c0c7a8acdb91f4a1bca5f43f12ffc54c45b9 Mon Sep 17 00:00:00 2001 From: andrewrgarcia Date: Wed, 19 Oct 2022 14:40:19 -0400 Subject: [PATCH 06/22] start dev: exponential mav for Issue #506 --- src/mplfinance/plotting.py | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/mplfinance/plotting.py b/src/mplfinance/plotting.py index b3d4f341..6f6884d6 100644 --- a/src/mplfinance/plotting.py +++ b/src/mplfinance/plotting.py @@ -1147,6 +1147,45 @@ def _plot_mav(ax,config,xdates,prices,apmav=None,apwidth=None): mavp_list.append(mavprices) return mavp_list + +def _plot_ema(ax,config,xdates,prices,apmav=None,apwidth=None): + '''ema: exponential moving average''' + style = config['style'] + if apmav is not None: + mavgs = apmav + else: + mavgs = config['ema'] + mavp_list = [] + if mavgs is not None: + shift = None + if isinstance(mavgs,dict): + shift = mavgs['shift'] + mavgs = mavgs['period'] + 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 idx,mav in enumerate(mavgs): + # mean = pd.Series(prices).rolling(mav).mean() + mean = pd.Series(prices).ewm(span=mav,adjust=False).mean() + if shift is not None: + mean = mean.shift(periods=shift[idx]) + mavprices = mean.values + lw = config['_width_config']['line_width'] + if mavc: + ax.plot(xdates, mavprices, linewidth=lw, color=next(mavc)) + else: + ax.plot(xdates, mavprices, linewidth=lw) + mavp_list.append(mavprices) + return mavp_list + + def _auto_secondary_y( panels, panid, ylo, yhi ): # 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: From 0cba1ce4b52a152e3838ce58063d7bc76f91d49b Mon Sep 17 00:00:00 2001 From: andrewrgarcia Date: Thu, 20 Oct 2022 16:05:13 -0400 Subject: [PATCH 07/22] add ema label in _valid_plot_kwargs(); _mav_validator also because ema has the same data-types as mav --- src/mplfinance/plotting.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mplfinance/plotting.py b/src/mplfinance/plotting.py index 6f6884d6..c8bcb406 100644 --- a/src/mplfinance/plotting.py +++ b/src/mplfinance/plotting.py @@ -119,6 +119,10 @@ def _valid_plot_kwargs(): 'mav' : { 'Default' : None, 'Description' : 'Moving Average window size(s); (int or tuple of ints)', 'Validator' : _mav_validator }, + + 'ema' : { 'Default' : None, + 'Description' : 'Exponential Moving Average window size(s); (int or tuple of ints)', + 'Validator' : _mav_validator }, 'renko_params' : { 'Default' : dict(), 'Description' : 'dict of renko parameters; call `mpf.kwarg_help("renko_params")`', From 95f0e332daa3e7260dbfc31775ff77a4242eefdf Mon Sep 17 00:00:00 2001 From: andrewrgarcia Date: Fri, 21 Oct 2022 14:45:16 -0400 Subject: [PATCH 08/22] made suggested changes and tested new ema option against baseline --- src/mplfinance/plotting.py | 1348 +++++++++++++++++--------------- tests/test_ema.py | 85 ++ tests/test_images/test_ema.png | Bin 0 -> 57100 bytes 3 files changed, 794 insertions(+), 639 deletions(-) create mode 100644 tests/test_ema.py create mode 100644 tests/test_images/test_ema.png diff --git a/src/mplfinance/plotting.py b/src/mplfinance/plotting.py index c8bcb406..a04e9c65 100644 --- a/src/mplfinance/plotting.py +++ b/src/mplfinance/plotting.py @@ -1,10 +1,10 @@ -import matplotlib.dates as mdates +import matplotlib.dates as mdates import matplotlib.pyplot as plt import matplotlib.colors as mcolors -import matplotlib.axes as mpl_axes +import matplotlib.axes as mpl_axes import matplotlib.figure as mpl_fig import pandas as pd -import numpy as np +import numpy as np import copy import io import os @@ -14,7 +14,7 @@ from itertools import cycle #from pandas.plotting import register_matplotlib_converters -#register_matplotlib_converters() +# register_matplotlib_converters() from mplfinance._utils import _construct_aline_collections from mplfinance._utils import _construct_hline_collections @@ -51,7 +51,8 @@ VALID_PMOVE_TYPES = ['renko', 'pnf'] -DEFAULT_FIGRATIO = (8.00,5.75) +DEFAULT_FIGRATIO = (8.00, 5.75) + def with_rc_context(func): ''' @@ -64,24 +65,26 @@ def decorator(*args, **kwargs): return func(*args, **kwargs) return decorator + def _warn_no_xgaps_deprecated(value): - warnings.warn('\n\n ================================================================= '+ - '\n\n WARNING: `no_xgaps` is /deprecated/:'+ - '\n Default value is now `no_xgaps=True`'+ - '\n However, to set `no_xgaps=False` and silence this warning,'+ - '\n use instead: `show_nontrading=True`.'+ + warnings.warn('\n\n ================================================================= ' + + '\n\n WARNING: `no_xgaps` is /deprecated/:' + + '\n Default value is now `no_xgaps=True`' + + '\n However, to set `no_xgaps=False` and silence this warning,' + + '\n use instead: `show_nontrading=True`.' + '\n\n ================================================================ ', category=DeprecationWarning) - return isinstance(value,bool) + return isinstance(value, bool) + def _warn_set_ylim_deprecated(value): - warnings.warn('\n\n ================================================================= '+ - '\n\n WARNING: `set_ylim=(ymin,ymax)` kwarg '+ - '\n has been replaced with: '+ - '\n `ylim=(ymin,ymax)`.'+ + warnings.warn('\n\n ================================================================= ' + + '\n\n WARNING: `set_ylim=(ymin,ymax)` kwarg ' + + '\n has been replaced with: ' + + '\n `ylim=(ymin,ymax)`.' + '\n\n ================================================================ ', category=DeprecationWarning) - return isinstance(value,bool) + return isinstance(value, bool) def _valid_plot_kwargs(): @@ -98,297 +101,299 @@ def _valid_plot_kwargs(): ''' vkwargs = { - 'columns' : { 'Default' : None, # use default names: ('Open', 'High', 'Low', 'Close', 'Volume') - 'Description' : ('Column names to be used when plotting the data.'+ - ' Default: ("Open", "High", "Low", "Close", "Volume")'), - 'Validator' : lambda value: isinstance(value, (tuple, list)) - and len(value) == 5 - and all(isinstance(c, str) for c in value) }, - 'type' : { 'Default' : 'ohlc', - 'Description' : 'Plot type: '+str(_get_valid_plot_types()), - 'Validator' : lambda value: value in _get_valid_plot_types() }, - - 'style' : { 'Default' : None, - 'Description' : 'plot style; see `mpf.available_styles()`', - 'Validator' : _styles._valid_mpf_style }, - - 'volume' : { 'Default' : False, - 'Description' : 'Plot volume: True, False, or set to Axes object on which to plot.', - 'Validator' : lambda value: isinstance(value,bool) or isinstance(value,mpl_axes.Axes) }, - - 'mav' : { 'Default' : None, - 'Description' : 'Moving Average window size(s); (int or tuple of ints)', - 'Validator' : _mav_validator }, - - 'ema' : { 'Default' : None, - 'Description' : 'Exponential Moving Average window size(s); (int or tuple of ints)', - 'Validator' : _mav_validator }, - - 'renko_params' : { 'Default' : dict(), - 'Description' : 'dict of renko parameters; call `mpf.kwarg_help("renko_params")`', - 'Validator' : lambda value: isinstance(value,dict) }, - - 'pnf_params' : { 'Default' : dict(), - 'Description' : 'dict of point-and-figure parameters; call `mpf.kwarg_help("pnf_params")`', - 'Validator' : lambda value: isinstance(value,dict) }, - - 'study' : { 'Default' : None, - 'Description' : 'kwarg not implemented', - 'Validator' : lambda value: _kwarg_not_implemented(value) }, - - 'marketcolor_overrides' : { 'Default' : None, - 'Description' : 'sequence of color objects to override market colors.'+ - 'sequence must be same length as ohlc(v) DataFrame. Each'+ - 'color object may be a color, marketcolor object, or None.', - 'Validator' : _mco_validator }, - - 'mco_faceonly' : { 'Default' : False, # If True: Override only the face of the candle - 'Description' : 'True/False marketcolor_overrides only apply to face of candle.', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'no_xgaps' : { 'Default' : True, # None means follow default logic below: - 'Description' : 'deprecated', - 'Validator' : lambda value: _warn_no_xgaps_deprecated(value) }, - - 'show_nontrading' : { 'Default' : False, - 'Description' : 'True/False show spaces for non-trading days/periods', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'figscale' : { 'Default' : None, # scale base figure size up or down. - 'Description' : 'Scale figure size up (if > 1) or down (if < 1)', - 'Validator' : lambda value: isinstance(value,float) or isinstance(value,int) }, - - 'figratio' : { 'Default' : None, # aspect ratio; scaled to 8.0 height - 'Description' : 'Aspect ratio of the figure. Default: (8.00,5.75)', - 'Validator' : lambda value: isinstance(value,(tuple,list)) - and len(value) == 2 - and isinstance(value[0],(float,int)) - and isinstance(value[1],(float,int)) }, - - 'figsize' : { 'Default' : None, # figure size; overrides figratio and figscale - 'Description' : ('Figure size: overrides both figscale and figratio,'+ - ' else defaults to figratio*figscale'), - 'Validator' : lambda value: isinstance(value,(tuple,list)) - and len(value) == 2 - and isinstance(value[0],(float,int)) - and isinstance(value[1],(float,int)) }, - - 'fontscale' : { 'Default' : None, # scale all fonts up or down - 'Description' : 'Scale font sizes up (if > 1) or down (if < 1)', - 'Validator' : lambda value: isinstance(value,float) or isinstance(value,int) }, - - 'linecolor' : { 'Default' : None, # line color in line plot - 'Description' : 'Line color for `type=line`', - 'Validator' : lambda value: mcolors.is_color_like(value) }, - - 'title' : { 'Default' : None, # Figure Title - 'Description' : 'Figure Title (see also `axtitle`)', - 'Validator' : lambda value: isinstance(value,(str,dict)) }, - - 'axtitle' : { 'Default' : None, # Axes Title (subplot title) - 'Description' : 'Axes Title (subplot title)', - 'Validator' : lambda value: isinstance(value,(str,dict)) }, - - 'ylabel' : { 'Default' : 'Price', # y-axis label - 'Description' : 'label for y-axis of main plot', - 'Validator' : lambda value: isinstance(value,str) }, - - 'ylabel_lower' : { 'Default' : None, # y-axis label default logic below - 'Description' : 'label for y-axis of volume', - 'Validator' : lambda value: isinstance(value,str) }, - - 'addplot' : { 'Default' : None, - 'Description' : 'addplot object or sequence of addplot objects (from `mpf.make_addplot()`)', - 'Validator' : lambda value: isinstance(value,dict) or (isinstance(value,list) and all([isinstance(d,dict) for d in value])) }, - - 'savefig' : { 'Default' : None, - 'Description' : 'file name, or BytesIO, or dict with key `fname` plus other keys allowed as '+ - ' kwargs to matplotlib `Figure.savefig()`', - 'Validator' : lambda value: isinstance(value,dict) or isinstance(value,str) or isinstance(value, io.BytesIO) or isinstance(value, os.PathLike) }, - - 'block' : { 'Default' : None, - 'Description' : 'True/False wait for figure to be closed before returning', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'returnfig' : { 'Default' : False, - 'Description' : 'return Figure and list of Axes objects created by mplfinance;'+ - ' user must display plot when ready, usually by calling `mpf.show()`', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'return_calculated_values' : { 'Default' : None, - 'Description' : 'set to a variable containing an empty dict; `mpf.plot()` will fill'+ - ' the dict with various mplfinance calculated values', - 'Validator' : lambda value: isinstance(value, dict) and len(value) == 0}, - - 'set_ylim' : { 'Default' : None, - 'Description' : 'deprecated', - 'Validator' : lambda value: _warn_set_ylim_deprecated(value) }, - - 'ylim' : { 'Default' : None, - 'Description' : 'Limits for y-axis as tuple (min,max), i.e. (bottom,top)', - 'Validator' : lambda value: isinstance(value, (list,tuple)) and len(value) == 2 - and all([isinstance(v,(int,float)) for v in value])}, - - 'xlim' : { 'Default' : None, - 'Description' : 'Limits for x-axis as tuple (min,max), i.e. (left,right)', - 'Validator' : lambda value: _xlim_validator(value) }, - - 'set_ylim_panelB' : { 'Default' : None, - 'Description' : 'deprecated', - 'Validator' : lambda value: _warn_set_ylim_deprecated(value) }, - - 'hlines' : { 'Default' : None, - 'Description' : 'Draw one or more HORIZONTAL LINES across entire plot, by'+ - ' specifying a price, or sequence of prices. May also be a dict'+ - ' with key `hlines` specifying a price or sequence of prices, plus'+ - ' one or more of the following keys: `colors`, `linestyle`,'+ - ' `linewidths`, `alpha`.', - 'Validator' : lambda value: _hlines_validator(value) }, - - 'vlines' : { 'Default' : None, - 'Description' : 'Draw one or more VERTICAL LINES across entire plot, by'+ - ' specifying a date[time], or sequence of date[time]. May also'+ - ' be a dict with key `vlines` specifying a date[time] or sequence'+ - ' of date[time], plus one or more of the following keys:'+ - ' `colors`, `linestyle`, `linewidths`, `alpha`.', - 'Validator' : lambda value: _vlines_validator(value) }, - - 'alines' : { 'Default' : None, - 'Description' : 'Draw one or more ARBITRARY LINES anywhere on the plot, by'+ - ' specifying a sequence of two or more date/price pairs, or by'+ - ' specifying a sequence of sequences of two or more date/price pairs.'+ - ' May also be a dict with key `alines` (as date/price pairs described above),'+ - ' plus one or more of the following keys:'+ - ' `colors`, `linestyle`, `linewidths`, `alpha`.', - 'Validator' : lambda value: _alines_validator(value) }, - - 'tlines' : { 'Default' : None, - 'Description' : 'Draw one or more TREND LINES by specifying one or more pairs of date[times]'+ - ' between which each trend line should be drawn. May also be a dict with key'+ - ' `tlines` as just described, plus one or more of the following keys:'+ - ' `colors`, `linestyle`, `linewidths`, `alpha`, `tline_use`,`tline_method`.', - 'Validator' : lambda value: _tlines_validator(value) }, - - 'panel_ratios' : { 'Default' : None, - 'Description' : 'sequence of numbers indicating relative sizes of panels; sequence len'+ - ' must be same as number of panels, or len 2 where first entry is for'+ - ' main panel, and second entry is for all other panels', - 'Validator' : lambda value: isinstance(value,(tuple,list)) and len(value) <= 32 and - all([isinstance(v,(int,float)) for v in value]) }, - - 'main_panel' : { 'Default' : 0, - 'Description' : 'integer - which panel is the main panel for `.plot()`', - 'Validator' : lambda value: _valid_panel_id(value) }, - - 'volume_panel' : { 'Default' : 1, - 'Description' : 'integer - which panel is the volume panel', - 'Validator' : lambda value: _valid_panel_id(value) }, - - 'num_panels' : { 'Default' : None, - 'Description' : 'total number of panels', - 'Validator' : lambda value: isinstance(value,int) and value in range(1,32+1) }, - - 'datetime_format' : { 'Default' : None, - 'Description' : 'x-axis tick format as valid `strftime()` format string', - 'Validator' : lambda value: isinstance(value,str) }, - - 'xrotation' : { 'Default' : 45, - 'Description' : 'Angle (degrees) for x-axis tick labels; 90=vertical', - 'Validator' : lambda value: isinstance(value,(int,float)) }, - - 'axisoff' : { 'Default' : False, - 'Description' : '`axisoff=True` means do NOT display any axis.', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'closefig' : { 'Default' : 'auto', - 'Description' : 'True|False close the Figure before returning', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'fill_between' : { 'Default' : None, - 'Description' : 'fill between specification as y-value, or sequence of'+ - ' y-values, or dict containing key "y1" plus any additional'+ - ' kwargs for `fill_between()`', - 'Validator' : _fill_between_validator }, - - 'tight_layout' : { 'Default' : False, - 'Description' : 'True|False implement tight layout (minimal padding around Figure)'+ - ' (see also `scale_padding` kwarg)', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'scale_padding' : { 'Default' : 1.0, # Issue#193 - 'Description' : 'Increase, > 1.0, or decrease, < 1.0, padding around figure.'+ - ' May also be a dict containing one or more of the following keys:'+ - ' "top", "bottom", "left", "right", to individual scale padding'+ - ' on each side of Figure.', - 'Validator' : lambda value: _scale_padding_validator(value) }, - - 'width_adjuster_version' : { 'Default' : 'v1', - 'Description' : 'specify version of object width adjustment algorithm: "v0" or "v1"'+ - ' (See also "widths" tutorial in mplfinance examples folder).', - 'Validator' : lambda value: value in ('v0', 'v1') }, - - 'scale_width_adjustment' : { 'Default' : None, - 'Description' : 'scale width of plot objects wider, > 1.0, or narrower, < 1.0'+ - ' may also be a dict to scale individual widths.'+ - ' (See also "widths" tutorial in mplfinance examples folder).', - 'Validator' : lambda value: isinstance(value,dict) and len(value) > 0 }, - - 'update_width_config' : { 'Default' : None, - 'Description' : 'dict - update individual items in width configuration.'+ - ' (See also "widths" tutorial in mplfinance examples folder).', - 'Validator' : lambda value: isinstance(value,dict) and len(value) > 0 }, - - 'return_width_config' : { 'Default' : None, - 'Description' : 'empty dict variable to be filled with width configuration settings.', - 'Validator' : lambda value: isinstance(value,dict) and len(value)==0 }, - - 'saxbelow' : { 'Default' : True, # Issue#115 Comment#639446764 - 'Description' : 'set the volume Axes below (behind) all other Axes objects', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'ax' : { 'Default' : None, - 'Description' : 'Matplotlib Axes object on which to plot', - 'Validator' : lambda value: isinstance(value,mpl_axes.Axes) }, - - 'volume_exponent' : { 'Default' : None, - 'Description' : 'integer exponent on the volume axis'+ - ' (or set to "legacy" for old mplfinance style)', - 'Validator' : lambda value: isinstance(value,int) or value == 'legacy'}, - - 'tz_localize' : { 'Default' : True, - 'Description' : 'True|False localize the times in the DatetimeIndex', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'yscale' : { 'Default' : None, - 'Description' : 'y-axis scale: "linear", "log", "symlog", or "logit"', - 'Validator' : lambda value: _yscale_validator(value) }, - - 'volume_yscale' : { 'Default' : None, - 'Description' : 'Volume y-axis scale: "linear", "log", "symlog", or "logit"', - 'Validator' : lambda value: _yscale_validator(value) }, - - 'volume_ylim' : { 'Default' : None, - 'Description' : 'Volume y-axis limits as tuple (min,max), i.e. (bottom,top)', - 'Validator' : lambda value: isinstance(value, (list,tuple)) and len(value) == 2 - and all([isinstance(v,(int,float)) for v in value])}, - - 'volume_alpha' : { 'Default' : 1, # alpha of Volume bars - 'Description' : 'opacity for Volume bar: 0.0 (transparent) to 1.0 (opaque)', - 'Validator' : lambda value: isinstance(value,(int,float)) or - all([isinstance(v,(int,float)) for v in value]) }, - - 'warn_too_much_data' : { 'Default' : 599, - 'Description' : 'Tolerance for data amount in plot. Default=599 rows.'+ - ' Values greater than \'warn_too_much_data\' will trigger a warning.', - 'Validator' : lambda value: isinstance(value,int) }, + 'columns': {'Default': None, # use default names: ('Open', 'High', 'Low', 'Close', 'Volume') + 'Description': ('Column names to be used when plotting the data.' + + ' Default: ("Open", "High", "Low", "Close", "Volume")'), + 'Validator': lambda value: isinstance(value, (tuple, list)) + and len(value) == 5 + and all(isinstance(c, str) for c in value)}, + 'type': {'Default': 'ohlc', + 'Description': 'Plot type: '+str(_get_valid_plot_types()), + 'Validator': lambda value: value in _get_valid_plot_types()}, + + 'style': {'Default': None, + 'Description': 'plot style; see `mpf.available_styles()`', + 'Validator': _styles._valid_mpf_style}, + + 'volume': {'Default': False, + 'Description': 'Plot volume: True, False, or set to Axes object on which to plot.', + 'Validator': lambda value: isinstance(value, bool) or isinstance(value, mpl_axes.Axes)}, + + 'mav': {'Default': None, + 'Description': 'Moving Average window size(s); (int or tuple of ints)', + 'Validator': _mav_validator}, + + 'ema': {'Default': None, + 'Description': 'Exponential Moving Average window size(s); (int or tuple of ints)', + 'Validator': _mav_validator}, + + 'renko_params': {'Default': dict(), + 'Description': 'dict of renko parameters; call `mpf.kwarg_help("renko_params")`', + 'Validator': lambda value: isinstance(value, dict)}, + + 'pnf_params': {'Default': dict(), + 'Description': 'dict of point-and-figure parameters; call `mpf.kwarg_help("pnf_params")`', + 'Validator': lambda value: isinstance(value, dict)}, + + 'study': {'Default': None, + 'Description': 'kwarg not implemented', + 'Validator': lambda value: _kwarg_not_implemented(value)}, + + 'marketcolor_overrides': {'Default': None, + 'Description': 'sequence of color objects to override market colors.' + + 'sequence must be same length as ohlc(v) DataFrame. Each' + + 'color object may be a color, marketcolor object, or None.', + 'Validator': _mco_validator}, + + 'mco_faceonly': {'Default': False, # If True: Override only the face of the candle + 'Description': 'True/False marketcolor_overrides only apply to face of candle.', + 'Validator': lambda value: isinstance(value, bool)}, + + 'no_xgaps': {'Default': True, # None means follow default logic below: + 'Description': 'deprecated', + 'Validator': lambda value: _warn_no_xgaps_deprecated(value)}, + + 'show_nontrading': {'Default': False, + 'Description': 'True/False show spaces for non-trading days/periods', + 'Validator': lambda value: isinstance(value, bool)}, + + 'figscale': {'Default': None, # scale base figure size up or down. + 'Description': 'Scale figure size up (if > 1) or down (if < 1)', + 'Validator': lambda value: isinstance(value, float) or isinstance(value, int)}, + + 'figratio': {'Default': None, # aspect ratio; scaled to 8.0 height + 'Description': 'Aspect ratio of the figure. Default: (8.00,5.75)', + 'Validator': lambda value: isinstance(value, (tuple, list)) + and len(value) == 2 + and isinstance(value[0], (float, int)) + and isinstance(value[1], (float, int))}, + + 'figsize': {'Default': None, # figure size; overrides figratio and figscale + 'Description': ('Figure size: overrides both figscale and figratio,' + + ' else defaults to figratio*figscale'), + 'Validator': lambda value: isinstance(value, (tuple, list)) + and len(value) == 2 + and isinstance(value[0], (float, int)) + and isinstance(value[1], (float, int))}, + + 'fontscale': {'Default': None, # scale all fonts up or down + 'Description': 'Scale font sizes up (if > 1) or down (if < 1)', + 'Validator': lambda value: isinstance(value, float) or isinstance(value, int)}, + + 'linecolor': {'Default': None, # line color in line plot + 'Description': 'Line color for `type=line`', + 'Validator': lambda value: mcolors.is_color_like(value)}, + + 'title': {'Default': None, # Figure Title + 'Description': 'Figure Title (see also `axtitle`)', + 'Validator': lambda value: isinstance(value, (str, dict))}, + + 'axtitle': {'Default': None, # Axes Title (subplot title) + 'Description': 'Axes Title (subplot title)', + 'Validator': lambda value: isinstance(value, (str, dict))}, + + 'ylabel': {'Default': 'Price', # y-axis label + 'Description': 'label for y-axis of main plot', + 'Validator': lambda value: isinstance(value, str)}, + + 'ylabel_lower': {'Default': None, # y-axis label default logic below + 'Description': 'label for y-axis of volume', + 'Validator': lambda value: isinstance(value, str)}, + + 'addplot': {'Default': None, + 'Description': 'addplot object or sequence of addplot objects (from `mpf.make_addplot()`)', + 'Validator': lambda value: isinstance(value, dict) or (isinstance(value, list) and all([isinstance(d, dict) for d in value]))}, + + 'savefig': {'Default': None, + 'Description': 'file name, or BytesIO, or dict with key `fname` plus other keys allowed as ' + + ' kwargs to matplotlib `Figure.savefig()`', + 'Validator': lambda value: isinstance(value, dict) or isinstance(value, str) or isinstance(value, io.BytesIO) or isinstance(value, os.PathLike)}, + + 'block': {'Default': None, + 'Description': 'True/False wait for figure to be closed before returning', + 'Validator': lambda value: isinstance(value, bool)}, + + 'returnfig': {'Default': False, + 'Description': 'return Figure and list of Axes objects created by mplfinance;' + + ' user must display plot when ready, usually by calling `mpf.show()`', + 'Validator': lambda value: isinstance(value, bool)}, + + 'return_calculated_values': {'Default': None, + 'Description': 'set to a variable containing an empty dict; `mpf.plot()` will fill' + + ' the dict with various mplfinance calculated values', + 'Validator': lambda value: isinstance(value, dict) and len(value) == 0}, + + 'set_ylim': {'Default': None, + 'Description': 'deprecated', + 'Validator': lambda value: _warn_set_ylim_deprecated(value)}, + + 'ylim': {'Default': None, + 'Description': 'Limits for y-axis as tuple (min,max), i.e. (bottom,top)', + 'Validator': lambda value: isinstance(value, (list, tuple)) and len(value) == 2 + and all([isinstance(v, (int, float)) for v in value])}, + + 'xlim': {'Default': None, + 'Description': 'Limits for x-axis as tuple (min,max), i.e. (left,right)', + 'Validator': lambda value: _xlim_validator(value)}, + + 'set_ylim_panelB': {'Default': None, + 'Description': 'deprecated', + 'Validator': lambda value: _warn_set_ylim_deprecated(value)}, + + 'hlines': {'Default': None, + 'Description': 'Draw one or more HORIZONTAL LINES across entire plot, by' + + ' specifying a price, or sequence of prices. May also be a dict' + + ' with key `hlines` specifying a price or sequence of prices, plus' + + ' one or more of the following keys: `colors`, `linestyle`,' + + ' `linewidths`, `alpha`.', + 'Validator': lambda value: _hlines_validator(value)}, + + 'vlines': {'Default': None, + 'Description': 'Draw one or more VERTICAL LINES across entire plot, by' + + ' specifying a date[time], or sequence of date[time]. May also' + + ' be a dict with key `vlines` specifying a date[time] or sequence' + + ' of date[time], plus one or more of the following keys:' + + ' `colors`, `linestyle`, `linewidths`, `alpha`.', + 'Validator': lambda value: _vlines_validator(value)}, + + 'alines': {'Default': None, + 'Description': 'Draw one or more ARBITRARY LINES anywhere on the plot, by' + + ' specifying a sequence of two or more date/price pairs, or by' + + ' specifying a sequence of sequences of two or more date/price pairs.' + + ' May also be a dict with key `alines` (as date/price pairs described above),' + + ' plus one or more of the following keys:' + + ' `colors`, `linestyle`, `linewidths`, `alpha`.', + 'Validator': lambda value: _alines_validator(value)}, + + 'tlines': {'Default': None, + 'Description': 'Draw one or more TREND LINES by specifying one or more pairs of date[times]' + + ' between which each trend line should be drawn. May also be a dict with key' + + ' `tlines` as just described, plus one or more of the following keys:' + + ' `colors`, `linestyle`, `linewidths`, `alpha`, `tline_use`,`tline_method`.', + 'Validator': lambda value: _tlines_validator(value)}, + + 'panel_ratios': {'Default': None, + 'Description': 'sequence of numbers indicating relative sizes of panels; sequence len' + + ' must be same as number of panels, or len 2 where first entry is for' + + ' main panel, and second entry is for all other panels', + 'Validator': lambda value: isinstance(value, (tuple, list)) and len(value) <= 32 and + all([isinstance(v, (int, float)) for v in value])}, + + 'main_panel': {'Default': 0, + 'Description': 'integer - which panel is the main panel for `.plot()`', + 'Validator': lambda value: _valid_panel_id(value)}, + + 'volume_panel': {'Default': 1, + 'Description': 'integer - which panel is the volume panel', + 'Validator': lambda value: _valid_panel_id(value)}, + + 'num_panels': {'Default': None, + 'Description': 'total number of panels', + 'Validator': lambda value: isinstance(value, int) and value in range(1, 32+1)}, + + 'datetime_format': {'Default': None, + 'Description': 'x-axis tick format as valid `strftime()` format string', + 'Validator': lambda value: isinstance(value, str)}, + + 'xrotation': {'Default': 45, + 'Description': 'Angle (degrees) for x-axis tick labels; 90=vertical', + 'Validator': lambda value: isinstance(value, (int, float))}, + + 'axisoff': {'Default': False, + 'Description': '`axisoff=True` means do NOT display any axis.', + 'Validator': lambda value: isinstance(value, bool)}, + + 'closefig': {'Default': 'auto', + 'Description': 'True|False close the Figure before returning', + 'Validator': lambda value: isinstance(value, bool)}, + + 'fill_between': {'Default': None, + 'Description': 'fill between specification as y-value, or sequence of' + + ' y-values, or dict containing key "y1" plus any additional' + + ' kwargs for `fill_between()`', + 'Validator': _fill_between_validator}, + + 'tight_layout': {'Default': False, + 'Description': 'True|False implement tight layout (minimal padding around Figure)' + + ' (see also `scale_padding` kwarg)', + 'Validator': lambda value: isinstance(value, bool)}, + + 'scale_padding': {'Default': 1.0, # Issue#193 + 'Description': 'Increase, > 1.0, or decrease, < 1.0, padding around figure.' + + ' May also be a dict containing one or more of the following keys:' + + ' "top", "bottom", "left", "right", to individual scale padding' + + ' on each side of Figure.', + 'Validator': lambda value: _scale_padding_validator(value)}, + + 'width_adjuster_version': {'Default': 'v1', + 'Description': 'specify version of object width adjustment algorithm: "v0" or "v1"' + + ' (See also "widths" tutorial in mplfinance examples folder).', + 'Validator': lambda value: value in ('v0', 'v1')}, + + 'scale_width_adjustment': {'Default': None, + 'Description': 'scale width of plot objects wider, > 1.0, or narrower, < 1.0' + + ' may also be a dict to scale individual widths.' + + ' (See also "widths" tutorial in mplfinance examples folder).', + 'Validator': lambda value: isinstance(value, dict) and len(value) > 0}, + + 'update_width_config': {'Default': None, + 'Description': 'dict - update individual items in width configuration.' + + ' (See also "widths" tutorial in mplfinance examples folder).', + 'Validator': lambda value: isinstance(value, dict) and len(value) > 0}, + + 'return_width_config': {'Default': None, + 'Description': 'empty dict variable to be filled with width configuration settings.', + 'Validator': lambda value: isinstance(value, dict) and len(value) == 0}, + + 'saxbelow': {'Default': True, # Issue#115 Comment#639446764 + 'Description': 'set the volume Axes below (behind) all other Axes objects', + 'Validator': lambda value: isinstance(value, bool)}, + + 'ax': {'Default': None, + 'Description': 'Matplotlib Axes object on which to plot', + 'Validator': lambda value: isinstance(value, mpl_axes.Axes)}, + + 'volume_exponent': {'Default': None, + 'Description': 'integer exponent on the volume axis' + + ' (or set to "legacy" for old mplfinance style)', + 'Validator': lambda value: isinstance(value, int) or value == 'legacy'}, + + 'tz_localize': {'Default': True, + 'Description': 'True|False localize the times in the DatetimeIndex', + 'Validator': lambda value: isinstance(value, bool)}, + + 'yscale': {'Default': None, + 'Description': 'y-axis scale: "linear", "log", "symlog", or "logit"', + 'Validator': lambda value: _yscale_validator(value)}, + + 'volume_yscale': {'Default': None, + 'Description': 'Volume y-axis scale: "linear", "log", "symlog", or "logit"', + 'Validator': lambda value: _yscale_validator(value)}, + + 'volume_ylim': {'Default': None, + 'Description': 'Volume y-axis limits as tuple (min,max), i.e. (bottom,top)', + 'Validator': lambda value: isinstance(value, (list, tuple)) and len(value) == 2 + and all([isinstance(v, (int, float)) for v in value])}, + + 'volume_alpha': {'Default': 1, # alpha of Volume bars + 'Description': 'opacity for Volume bar: 0.0 (transparent) to 1.0 (opaque)', + 'Validator': lambda value: isinstance(value, (int, float)) or + all([isinstance(v, (int, float)) for v in value])}, + + 'warn_too_much_data': {'Default': 599, + 'Description': 'Tolerance for data amount in plot. Default=599 rows.' + + ' Values greater than \'warn_too_much_data\' will trigger a warning.', + 'Validator': lambda value: isinstance(value, int)}, } _validate_vkwargs_dict(vkwargs) return vkwargs -###@with_rc_context -def plot( data, **kwargs ): +# @with_rc_context + + +def plot(data, **kwargs): """ Given a Pandas DataFrame containing columns Open,High,Low,Close and optionally Volume with a DatetimeIndex, plot the data. @@ -402,61 +407,66 @@ def plot( data, **kwargs ): # translate alias types: config['type'] = _get_valid_plot_types(config['type']) - - dates,opens,highs,lows,closes,volumes = _check_and_prepare_data(data, config) + + dates, opens, highs, lows, closes, volumes = _check_and_prepare_data( + data, config) config['xlim'] = _check_and_convert_xlim_configuration(data, config) if config['type'] in VALID_PMOVE_TYPES and config['addplot'] is not None: - err = "`addplot` is not supported for `type='" + config['type'] +"'`" + err = "`addplot` is not supported for `type='" + config['type'] + "'`" raise ValueError(err) if config['marketcolor_overrides'] is not None: if len(config['marketcolor_overrides']) != len(dates): - raise ValueError('`marketcolor_overrides` must be same length as dataframe.') + raise ValueError( + '`marketcolor_overrides` must be same length as dataframe.') 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.'+ + 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.'+ + 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.'+ + 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 + 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: + 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): + 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) + 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?') if not external_axes_mode: fig = plt.figure() - _adjust_figsize(fig,config) + _adjust_figsize(fig, config) else: fig = None @@ -467,29 +477,31 @@ def plot( data, **kwargs ): if external_axes_mode: panels = None - axA1 = config['ax'] + axA1 = config['ax'] axA1.set_axisbelow(config['saxbelow']) if config['volume']: volumeAxes = config['volume'] volumeAxes.set_axisbelow(config['saxbelow']) else: panels = _build_panels(fig, config) - axA1 = panels.at[config['main_panel'],'axes'][0] + axA1 = panels.at[config['main_panel'], 'axes'][0] if config['volume']: if config['volume_panel'] == config['main_panel']: # ohlc and volume on same panel: move volume to secondary axes: - volumeAxes = panels.at[config['volume_panel'],'axes'][1] - volumeAxes.set_zorder(axA1.get_zorder()-0.1) # Make sure ohlc is above volume - axA1.patch.set_visible(False) # Let volume show through - panels.at[config['main_panel'],'used2nd'] = True + volumeAxes = panels.at[config['volume_panel'], 'axes'][1] + # Make sure ohlc is above volume + volumeAxes.set_zorder(axA1.get_zorder()-0.1) + # Let volume show through + axA1.patch.set_visible(False) + panels.at[config['main_panel'], 'used2nd'] = True else: - volumeAxes = panels.at[config['volume_panel'],'axes'][0] + volumeAxes = panels.at[config['volume_panel'], 'axes'][0] else: volumeAxes = None fmtstring = _determine_format_string(dates, config['datetime_format']) - ptype = config['type'] + ptype = config['type'] if config['show_nontrading']: formatter = mdates.DateFormatter(fmtstring) @@ -502,7 +514,7 @@ def plot( data, **kwargs ): config['_width_config'] = _determine_width_config(xdates, config) rwc = config['return_width_config'] - if isinstance(rwc,dict) and len(rwc)==0: + if isinstance(rwc, dict) and len(rwc) == 0: config['return_width_config'].update(config['_width_config']) collections = None @@ -510,19 +522,20 @@ def plot( data, **kwargs ): 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) + collections = _construct_mpf_collections( + ptype, dates, xdates, opens, highs, lows, closes, volumes, config, style) if ptype in VALID_PMOVE_TYPES: collections, calculated_values = collections - volumes = calculated_values['volumes'] - pmove_dates = calculated_values['dates'] - pmove_values = calculated_values['values'] - if all([isinstance(v,(list,tuple)) for v in pmove_values]): + volumes = calculated_values['volumes'] + pmove_dates = calculated_values['dates'] + pmove_values = calculated_values['values'] + if all([isinstance(v, (list, tuple)) for v in pmove_values]): pmove_avgvals = [sum(v)/len(v) for v in pmove_values] else: pmove_avgvals = pmove_values - pmove_size = calculated_values['size'] - pmove_counts = calculated_values['counts'] if 'counts' in calculated_values else None + pmove_size = calculated_values['size'] + pmove_counts = calculated_values['counts'] if 'counts' in calculated_values else None formatter = IntegerIndexDateTimeFormatter(pmove_dates, fmtstring) xdates = np.arange(len(pmove_dates)) @@ -531,26 +544,29 @@ def plot( data, **kwargs ): axA1.add_collection(collection) if ptype in VALID_PMOVE_TYPES: - mavprices = _plot_mav(axA1,config,xdates,pmove_avgvals) + mavprices = _plot_mav(axA1, config, xdates, pmove_avgvals) + emaprices = _plot_ema(axA1, config, xdates, pmove_avgvals) + else: - mavprices = _plot_mav(axA1,config,xdates,closes) + mavprices = _plot_mav(axA1, config, xdates, closes) + emaprices = _plot_ema(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 + minx = xdates[0] - avg_dist_between_points maxx = xdates[-1] + avg_dist_between_points else: - minx = xdates[0] - (0.45 * avg_dist_between_points) + 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 + _lows = lows _highs = highs else: - _lows = pmove_avgvals + _lows = pmove_avgvals _highs = [value+pmove_size for value in pmove_avgvals] miny = np.nanmin(_lows) @@ -562,43 +578,54 @@ def plot( data, **kwargs ): ydelta = 0.01 * (maxy-miny) if miny > 0.0: # don't let it go negative: - setminy = max(0.9*miny,miny-ydelta) + setminy = max(0.9*miny, miny-ydelta) else: setminy = miny-ydelta - axA1.set_ylim(setminy,maxy+ydelta) + axA1.set_ylim(setminy, 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) + axA1.set_xlim(minx, maxx) if (config['ylim'] is None and config['xlim'] is None and - not config['tight_layout']): + 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 == 'renko': - retdict['renko_bricks' ] = pmove_values - retdict['renko_dates' ] = mdates.num2date(pmove_dates) - retdict['renko_size' ] = pmove_size + retdict['renko_bricks'] = pmove_values + retdict['renko_dates'] = mdates.num2date(pmove_dates) + retdict['renko_size'] = pmove_size retdict['renko_volumes'] = volumes if config['volume'] else None elif ptype == 'pnf': - retdict['pnf_dates' ] = mdates.num2date(pmove_dates) - retdict['pnf_counts' ] = pmove_counts - retdict['pnf_values' ] = pmove_values - retdict['pnf_avgvals' ] = pmove_avgvals - retdict['pnf_size' ] = pmove_size - retdict['pnf_volumes' ] = volumes if config['volume'] else None + retdict['pnf_dates'] = mdates.num2date(pmove_dates) + retdict['pnf_counts'] = pmove_counts + retdict['pnf_values'] = pmove_values + retdict['pnf_avgvals'] = pmove_avgvals + retdict['pnf_size'] = pmove_size + retdict['pnf_volumes'] = volumes if config['volume'] else None 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))) + warnings.warn('len(mav)='+str(len(mav)) + + ' BUT len(mavprices)='+str(len(mavprices))) else: - for jj in range(0,len(mav)): + for jj in range(0, len(mav)): retdict['mav' + str(mav[jj])] = mavprices[jj] + + if config['ema'] is not None: + ema = config['ema'] + if len(ema) != len(emaprices): + warnings.warn('len(ema)='+str(len(ema)) + + ' BUT len(emaprices)='+str(len(emaprices))) + else: + for jj in range(0, len(ema)): + retdict['ema' + str(ema[jj])] = emaprices[jj] + retdict['minx'] = minx retdict['maxx'] = maxx retdict['miny'] = miny @@ -614,58 +641,63 @@ def plot( data, **kwargs ): 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)) + 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]): + if isinstance(tlines, (list, tuple)) and all([isinstance(item, dict) for item in tlines]): pass else: - tlines = [tlines,] + tlines = [tlines, ] for tline_item in tlines: - line_collections.append(_construct_tline_collections(tline_item, dtix, dates, opens, highs, lows, closes)) - + 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() + vup, vdown = style['marketcolors']['volume'].values() #-- print('vup,vdown=',vup,vdown) - vcolors = _updown_colors(vup, vdown, opens, closes, use_prev_close=style['marketcolors']['vcdopcod']) + 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'] + w = config['_width_config']['volume_width'] lw = config['_width_config']['volume_linewidth'] - adjc = _adjust_color_brightness(vcolors,0.90) + adjc = _adjust_color_brightness(vcolors, 0.90) valp = config['volume_alpha'] - volumeAxes.bar(xdates,volumes,width=w,linewidth=lw,color=vcolors,ec=adjc,alpha=valp) + volumeAxes.bar(xdates, volumes, width=w, linewidth=lw, + color=vcolors, ec=adjc, alpha=valp) if config['volume_ylim'] is not None: vymin = config['volume_ylim'][0] vymax = config['volume_ylim'][1] else: vymin = 0.3 * np.nanmin(volumes) vymax = 1.1 * np.nanmax(volumes) - volumeAxes.set_ylim(vymin,vymax) + volumeAxes.set_ylim(vymin, vymax) xrotation = config['xrotation'] if not external_axes_mode: - _set_ticks_on_bottom_panel_only(panels,formatter,rotation=xrotation) + _set_ticks_on_bottom_panel_only(panels, formatter, rotation=xrotation) else: - axA1.tick_params(axis='x',rotation=xrotation) + axA1.tick_params(axis='x', rotation=xrotation) axA1.xaxis.set_major_formatter(formatter) ysd = config['yscale'] - if isinstance(ysd,dict): + if isinstance(ysd, dict): yscale = ysd['yscale'] - del ysd['yscale'] - axA1.set_yscale(yscale,**ysd) - elif isinstance(ysd,str): + del ysd['yscale'] + axA1.set_yscale(yscale, **ysd) + elif isinstance(ysd, str): axA1.set_yscale(ysd) - addplot = config['addplot'] if addplot is not None and ptype not in VALID_PMOVE_TYPES: @@ -679,59 +711,67 @@ def plot( data, **kwargs ): # 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 + 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 + 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} + 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 + 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))) + raise TypeError( + 'addplot must be `dict`, or `list of dict`, NOT '+str(type(addplot))) for apdict in addplot: - panid = apdict['panel'] + 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 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'] + 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,xdates) - else: + ax = _addplot_collections( + panid, panels, apdict, xdates, config) + _addplot_apply_supplements(ax, apdict, xdates) + else: apdata = apdict['data'] - if isinstance(apdata,list) and not isinstance(apdata[0],(float,int)): + 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): + if isinstance(apdata, pd.DataFrame): havedf = True else: havedf = False # must be a single series or array - apdata = [apdata,] # make it iterable + 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,xdates) + ydata = apdata.loc[:, column] if havedf else column + ax = _addplot_columns( + panid, panels, ydata, apdict, xdates, config) + _addplot_apply_supplements(ax, apdict, xdates) # 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: fblist = copy.deepcopy(config['fill_between']) if _num_or_seq_of_num(fblist): - fblist = [dict(y1=fblist),] - elif isinstance(fblist,dict): - fblist = [fblist,] + fblist = [dict(y1=fblist), ] + elif isinstance(fblist, dict): + fblist = [fblist, ] if not _list_of_dict(fblist): raise TypeError('Bad type for `fill_between` specifier.') for fb in fblist: @@ -741,62 +781,61 @@ def plot( data, **kwargs ): if 'panel' in fb: panid = fb['panel'] del fb['panel'] - fb['x'] = xdates # add 'x' to caller's fb dict - ax = panels.at[panid,'axes'][0] + fb['x'] = xdates # add 'x' to caller's fb dict + 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(): + 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) + _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) + _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: 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']: + # if config['autofmt_xdate']: #print('CALLING fig.autofmt_xdate()') - #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`). + # 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.tick_params(axis='x', rotation=xrotation) volumeAxes.xaxis.set_major_formatter(formatter) vscale = 'linear' ysd = config['volume_yscale'] - if isinstance(ysd,dict): + if isinstance(ysd, dict): yscale = ysd['yscale'] - del ysd['yscale'] - volumeAxes.set_yscale(yscale,**ysd) + del ysd['yscale'] + volumeAxes.set_yscale(yscale, **ysd) vscale = yscale - elif isinstance(ysd,str): + elif isinstance(ysd, str): volumeAxes.set_yscale(ysd) vscale = ysd offset = '' @@ -807,24 +846,28 @@ def plot( data, **kwargs ): 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') + 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') + 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): + 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') + 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') + volumeAxes.ticklabel_format( + useOffset=False, scilimits=scilims, axis='y') offset = ' $10^'+str(scilims[1])+'$' volumeAxes.yaxis.offsetText.set_visible(False) @@ -835,7 +878,7 @@ def plot( data, **kwargs ): 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: `y=0.89` is based on the top of the top panel @@ -844,24 +887,24 @@ def plot( data, **kwargs ): title_kwargs = dict(va='bottom', y=0.89) else: title_kwargs = dict(va='center') - if isinstance(config['title'],dict): + 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 + # allows override default values set by mplfinance above + title_kwargs.update(title_dict) else: title = config['title'] # config['title'] is a string - fig.suptitle(title,**title_kwargs) - - + 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(): + for panid, row in panels.iterrows(): if not row['used2nd']: row['axes'][1].set_visible(False) @@ -879,25 +922,27 @@ def plot( data, **kwargs ): if config['savefig'] is not None: save = config['savefig'] - if isinstance(save,dict): + if isinstance(save, dict): if config['tight_layout'] and 'bbox_inches' not in save: - fig.savefig(**save,bbox_inches='tight') + fig.savefig(**save, bbox_inches='tight') else: fig.savefig(**save) else: if config['tight_layout']: - fig.savefig(save,bbox_inches='tight') + fig.savefig(save, bbox_inches='tight') else: fig.savefig(save) - if config['closefig']: # True or 'auto' + if config['closefig']: # True or 'auto' plt.close(fig) elif not config['returnfig']: - plt.show(block=config['block']) # https://stackoverflow.com/a/13361748/1639359 + # https://stackoverflow.com/a/13361748/1639359 + plt.show(block=config['block']) if config['closefig'] == True or (config['block'] and config['closefig']): plt.close(fig) - + if config['returnfig']: - if config['closefig'] == True: plt.close(fig) + if config['closefig'] == True: + plt.close(fig) return (fig, axlist) # rcp = copy.deepcopy(plt.rcParams) @@ -906,30 +951,33 @@ def plot( data, **kwargs ): # print('rcpdfhead(3)=',rcpdf.head(3)) # return # rcpdf -def _adjust_figsize(fig,config): + +def _adjust_figsize(fig, config): if fig is None: return if config['figsize'] is None: - w,h = config['figratio'] + 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)+')') + 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] + base = (w, h) + figscale = config['figscale'] + fsize = [d*figscale for d in base] else: fsize = config['figsize'] fig.set_size_inches(fsize) + def _adjust_fontsize(config): if config['fontscale'] is None: return - if not isinstance(plt.rcParams['font.size'],(float,int)): - warnings.warn('\n\n ================================================================= '+ - '\n\n WARNING: Unable to scale fonts: plt.rcParams["font.size"] is NOT a float!'+ + if not isinstance(plt.rcParams['font.size'], (float, int)): + warnings.warn('\n\n ================================================================= ' + + '\n\n WARNING: Unable to scale fonts: plt.rcParams["font.size"] is NOT a float!' + '\n\n ================================================================ ', category=UserWarning) return @@ -949,18 +997,19 @@ def _adjust_fontsize(config): # None: 1.0, # } # -------------------------------------------- - fontstuff = ['axes.labelsize','axes.titlesize', 'figure.titlesize','legend.fontsize', - 'legend.title_fontsize','xtick.labelsize','ytick.labelsize'] + fontstuff = ['axes.labelsize', 'axes.titlesize', 'figure.titlesize', 'legend.fontsize', + 'legend.title_fontsize', 'xtick.labelsize', 'ytick.labelsize'] for item in fontstuff: - if isinstance(plt.rcParams[item],(float,int)): + if isinstance(plt.rcParams[item], (float, int)): plt.rcParams[item] *= config['fontscale'] -def _addplot_collections(panid,panels,apdict,xdates,config): + +def _addplot_collections(panid, panels, apdict, xdates, config): apdata = apdict['data'] aptype = apdict['type'] external_axes_mode = apdict['ax'] is not None - + #--------------------------------------------------------------# # Note: _auto_secondary_y() sets the 'magnitude' column in the # `panels` dataframe, which is needed for automatically @@ -973,134 +1022,152 @@ def _addplot_collections(panid,panels,apdict,xdates,config): # if any have secondary_y='auto', but since that is the # default value, we will just assume we have at least one. - valid_apc_types = ['ohlc','candle'] + valid_apc_types = ['ohlc', 'candle'] if aptype not in valid_apc_types: - raise TypeError('Invalid aptype='+str(aptype)+'. Must be one of '+str(valid_apc_types)) - if not isinstance(apdata,pd.DataFrame): - raise TypeError('addplot type "'+aptype+'" MUST be accompanied by addplot data of type `pd.DataFrame`') - d,o,h,l,c,v = _check_and_prepare_data(apdata,config) - + raise TypeError('Invalid aptype='+str(aptype) + + '. Must be one of '+str(valid_apc_types)) + if not isinstance(apdata, pd.DataFrame): + raise TypeError('addplot type "'+aptype + + '" MUST be accompanied by addplot data of type `pd.DataFrame`') + d, o, h, l, c, v = _check_and_prepare_data(apdata, config) + mc = apdict['marketcolors'] if _is_marketcolor_object(mc): apstyle = config['style'].copy() apstyle['marketcolors'] = mc else: apstyle = config['style'] - - collections = _construct_mpf_collections(aptype,d,xdates,o,h,l,c,v,config,apstyle) + + collections = _construct_mpf_collections( + aptype, d, xdates, o, h, l, c, v, config, apstyle) if not external_axes_mode: - lo = math.log(max(math.fabs(np.nanmin(l)),1e-7),10) - 0.5 - hi = math.log(max(math.fabs(np.nanmax(h)),1e-7),10) + 0.5 - secondary_y = _auto_secondary_y( panels, panid, lo, hi ) + lo = math.log(max(math.fabs(np.nanmin(l)), 1e-7), 10) - 0.5 + hi = math.log(max(math.fabs(np.nanmax(h)), 1e-7), 10) + 0.5 + secondary_y = _auto_secondary_y(panels, panid, lo, hi) if 'auto' != apdict['secondary_y']: - secondary_y = apdict['secondary_y'] + secondary_y = apdict['secondary_y'] if secondary_y: - ax = panels.at[panid,'axes'][1] - panels.at[panid,'used2nd'] = True - else: - ax = panels.at[panid,'axes'][0] + ax = panels.at[panid, 'axes'][1] + panels.at[panid, 'used2nd'] = True + else: + ax = panels.at[panid, 'axes'][0] else: ax = apdict['ax'] for coll in collections: ax.add_collection(coll) if apdict['mav'] is not None: - apmavprices = _plot_mav(ax,config,xdates,c,apdict['mav']) + apmavprices = _plot_mav(ax, config, xdates, c, apdict['mav']) + # if apdict['ema'] is not None: + # apemaprices = _plot_ema(ax, config, xdates, c, apdict['ema']) + ax.autoscale_view() return ax -def _addplot_columns(panid,panels,ydata,apdict,xdates,config): + +def _addplot_columns(panid, panels, ydata, apdict, xdates, config): external_axes_mode = apdict['ax'] is not None if not external_axes_mode: secondary_y = False if apdict['secondary_y'] == 'auto': 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 = _auto_secondary_y( panels, panid, ymlo, ymhi ) + 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 = _auto_secondary_y(panels, panid, ymlo, ymhi) 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] + ax = panels.at[panid, 'axes'][1] + panels.at[panid, 'used2nd'] = True + else: + ax = panels.at[panid, 'axes'][0] else: ax = apdict['ax'] aptype = apdict['type'] if aptype == 'scatter': - size = apdict['markersize'] - mark = apdict['marker'] + size = apdict['markersize'] + mark = apdict['marker'] color = apdict['color'] alpha = apdict['alpha'] - edgecolors = apdict['edgecolors'] + edgecolors = apdict['edgecolors'] linewidths = apdict['linewidths'] - if isinstance(mark,(list,tuple,np.ndarray)): - _mscatter(xdates, ydata, ax=ax, m=mark, s=size, color=color, alpha=alpha, edgecolors=edgecolors, linewidths=linewidths) + if isinstance(mark, (list, tuple, np.ndarray)): + _mscatter(xdates, ydata, ax=ax, m=mark, s=size, color=color, + alpha=alpha, edgecolors=edgecolors, linewidths=linewidths) else: - ax.scatter(xdates, ydata, s=size, marker=mark, color=color, alpha=alpha, edgecolors=edgecolors, linewidths=linewidths) + ax.scatter(xdates, ydata, s=size, marker=mark, color=color, + alpha=alpha, edgecolors=edgecolors, linewidths=linewidths) elif aptype == 'bar': - width = 0.8 if apdict['width'] is None else apdict['width'] + width = 0.8 if apdict['width'] is None else apdict['width'] bottom = apdict['bottom'] - color = apdict['color'] - alpha = apdict['alpha'] - ax.bar(xdates,ydata,width=width,bottom=bottom,color=color,alpha=alpha) + 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'] - width = apdict['width'] if apdict['width'] is not None else 1.6*config['_width_config']['line_width'] - alpha = apdict['alpha'] - ax.plot(xdates,ydata,linestyle=ls,color=color,linewidth=width,alpha=alpha) + ls = apdict['linestyle'] + color = apdict['color'] + width = apdict['width'] if apdict['width'] is not None else 1.6 * \ + config['_width_config']['line_width'] + alpha = apdict['alpha'] + ax.plot(xdates, ydata, linestyle=ls, color=color, + linewidth=width, alpha=alpha) elif aptype == 'step': stepwhere = apdict['stepwhere'] ls = apdict['linestyle'] - color = apdict['color'] - width = apdict['width'] if apdict['width'] is not None else 1.6*config['_width_config']['line_width'] - alpha = apdict['alpha'] - ax.step(xdates,ydata,where = stepwhere,linestyle=ls,color=color,linewidth=width,alpha=alpha) + color = apdict['color'] + width = apdict['width'] if apdict['width'] is not None else 1.6 * \ + config['_width_config']['line_width'] + alpha = apdict['alpha'] + ax.step(xdates, ydata, where=stepwhere, linestyle=ls, + color=color, linewidth=width, alpha=alpha) else: raise ValueError('addplot type "'+str(aptype)+'" NOT yet supported.') if apdict['mav'] is not None: - apmavprices = _plot_mav(ax,config,xdates,ydata,apdict['mav']) - + apmavprices = _plot_mav(ax, config, xdates, ydata, apdict['mav']) + # if apdict['ema'] is not None: + # apemaprices = _plot_ema(ax, config, xdates, ydata, apdict['ema']) return ax -def _addplot_apply_supplements(ax,apdict,xdates): + +def _addplot_apply_supplements(ax, apdict, xdates): if (apdict['ylabel'] is not None): ax.set_ylabel(apdict['ylabel']) if apdict['ylim'] is not None: - ax.set_ylim(apdict['ylim'][0],apdict['ylim'][1]) + ax.set_ylim(apdict['ylim'][0], apdict['ylim'][1]) if apdict['title'] is not None: ax.set_title(apdict['title']) ysd = apdict['yscale'] - if isinstance(ysd,dict): + if isinstance(ysd, dict): yscale = ysd['yscale'] - del ysd['yscale'] - ax.set_yscale(yscale,**ysd) - elif isinstance(ysd,str): + del ysd['yscale'] + ax.set_yscale(yscale, **ysd) + elif isinstance(ysd, str): ax.set_yscale(ysd) # added by Wen if "fill_between" in apdict and apdict['fill_between'] is not None: # deep copy because mplfinance code sometimes modifies the fill_between dict fblist = copy.deepcopy(apdict['fill_between']) - if isinstance(fblist,dict): - fblist = [fblist,] + if isinstance(fblist, dict): + fblist = [fblist, ] if _list_of_dict(fblist): for fb in fblist: if 'x' in fb: raise ValueError('fill_between dict may not contain `x`') - fb['x'] = xdates # add 'x' to caller's fb dict + fb['x'] = xdates # add 'x' to caller's fb dict ax.fill_between(**fb) else: - raise ValueError('Invalid addplot fill_between: must be `dict` or `list of dict`') + raise ValueError( + 'Invalid addplot fill_between: must be `dict` or `list of dict`') + -def _set_ylabels_side(ax_pri,ax_sec,primary_on_right): +def _set_ylabels_side(ax_pri, ax_sec, primary_on_right): # put the primary axis on one side, # and the twinx() on the "other" side: if primary_on_right == True: @@ -1116,7 +1183,8 @@ def _set_ylabels_side(ax_pri,ax_sec,primary_on_right): ax_sec.yaxis.set_label_position('right') ax_sec.yaxis.tick_right() -def _plot_mav(ax,config,xdates,prices,apmav=None,apwidth=None): + +def _plot_mav(ax, config, xdates, prices, apmav=None, apwidth=None): style = config['style'] if apmav is not None: mavgs = apmav @@ -1125,20 +1193,20 @@ def _plot_mav(ax,config,xdates,prices,apmav=None,apwidth=None): mavp_list = [] if mavgs is not None: shift = None - if isinstance(mavgs,dict): + if isinstance(mavgs, dict): shift = mavgs['shift'] mavgs = mavgs['period'] - if isinstance(mavgs,int): + 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 idx,mav in enumerate(mavgs): + for idx, mav in enumerate(mavgs): mean = pd.Series(prices).rolling(mav).mean() if shift is not None: mean = mean.shift(periods=shift[idx]) @@ -1152,7 +1220,7 @@ def _plot_mav(ax,config,xdates,prices,apmav=None,apwidth=None): return mavp_list -def _plot_ema(ax,config,xdates,prices,apmav=None,apwidth=None): +def _plot_ema(ax, config, xdates, prices, apmav=None, apwidth=None): '''ema: exponential moving average''' style = config['style'] if apmav is not None: @@ -1162,160 +1230,162 @@ def _plot_ema(ax,config,xdates,prices,apmav=None,apwidth=None): mavp_list = [] if mavgs is not None: shift = None - if isinstance(mavgs,dict): + if isinstance(mavgs, dict): shift = mavgs['shift'] mavgs = mavgs['period'] - if isinstance(mavgs,int): + 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 idx,mav in enumerate(mavgs): + for idx, mav in enumerate(mavgs): # mean = pd.Series(prices).rolling(mav).mean() - mean = pd.Series(prices).ewm(span=mav,adjust=False).mean() + mean = pd.Series(prices).ewm(span=mav, adjust=False).mean() if shift is not None: mean = mean.shift(periods=shift[idx]) - mavprices = mean.values + emaprices = mean.values lw = config['_width_config']['line_width'] if mavc: - ax.plot(xdates, mavprices, linewidth=lw, color=next(mavc)) + ax.plot(xdates, emaprices, linewidth=lw, color=next(mavc)) else: - ax.plot(xdates, mavprices, linewidth=lw) - mavp_list.append(mavprices) + ax.plot(xdates, emaprices, linewidth=lw) + mavp_list.append(emaprices) return mavp_list -def _auto_secondary_y( panels, panid, ylo, yhi ): +def _auto_secondary_y(panels, panid, ylo, yhi): # 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. secondary_y = False - p = panid,'mag' + p = panid, 'mag' if panels.at[p] is None: - panels.at[p] = {'lo':ylo,'hi':yhi} + panels.at[p] = {'lo': ylo, 'hi': yhi} elif ylo < panels.at[p]['lo'] or yhi > panels.at[p]['hi']: secondary_y = True - #if secondary_y: + # if secondary_y: # print('auto says USE secondary_y ... for panel',panid) - #else: + # else: # print('auto says do NOT use secondary_y ... for panel',panid) return secondary_y + def _valid_addplot_kwargs(): - valid_linestyles = ('-','solid','--','dashed','-.','dashdot','.','dotted',None,' ','') - valid_types = ('line','scatter','bar', 'ohlc', 'candle','step') - valid_stepwheres = ('pre','post','mid') + valid_linestyles = ('-', 'solid', '--', 'dashed', '-.', + 'dashdot', '.', 'dotted', None, ' ', '') + valid_types = ('line', 'scatter', 'bar', 'ohlc', 'candle', 'step') + valid_stepwheres = ('pre', 'post', 'mid') valid_edgecolors = ('face', 'none', None) vkwargs = { - 'scatter' : { 'Default' : False, - 'Description' : "Deprecated. (Use kwarg `type='scatter' instead.", - 'Validator' : lambda value: isinstance(value,bool) }, - - 'type' : { 'Default' : 'line', - 'Description' : 'addplot type: "line","scatter","bar", "ohlc", "candle","step"', - 'Validator' : lambda value: value in valid_types }, - - 'mav' : { 'Default' : None, - 'Description' : 'Moving Average window size(s); (int or tuple of ints)', - 'Validator' : _mav_validator }, - - 'panel' : { 'Default' : 0, - 'Description' : 'Panel (int 0-31) to use for this addplot', - 'Validator' : lambda value: _valid_panel_id(value) }, - - 'marker' : { 'Default' : 'o', - 'Description' : "marker for `type='scatter'` plot", - 'Validator' : lambda value: _bypass_kwarg_validation(value) }, - - 'markersize' : { 'Default' : 18, - 'Description' : 'size of marker for `type="scatter"`; default=18', - 'Validator' : lambda value: isinstance(value,(int,float)) }, - - 'color' : { 'Default' : None, - 'Description' : 'color (or sequence of colors) of line(s), scatter marker(s), or bar(s).', - 'Validator' : lambda value: mcolors.is_color_like(value) or - (isinstance(value,(list,tuple,np.ndarray)) and all([mcolors.is_color_like(v) for v in value])) }, - - 'linestyle' : { 'Default' : None, - 'Description' : 'line style for `type=line` ('+str(valid_linestyles)+')', - 'Validator' : lambda value: value in valid_linestyles }, - - 'linewidths' : { 'Default': None, - 'Description' : 'edge widths of scatter markers', - 'Validator' : lambda value: isinstance(value,(int,float)) }, - - 'edgecolors' : { 'Default': None, - 'Description' : 'edgecolors of scatter markers', - 'Validator': lambda value: mcolors.is_color_like(value) or value in valid_edgecolors}, - - 'width' : { 'Default' : None, # width of `bar` or `line` - 'Description' : 'width of bar or line for `type="bar"` or `type="line"', - 'Validator' : lambda value: isinstance(value,(int,float)) or - all([isinstance(v,(int,float)) for v in value]) }, - - 'bottom' : { 'Default' : 0, # bottom for `type=bar` plots - 'Description' : 'bottom value for `type=bar` bars. Default=0', - 'Validator' : lambda value: isinstance(value,(int,float)) or - all([isinstance(v,(int,float)) for v in value]) }, - 'alpha' : { 'Default' : 1, # alpha of `bar`, `line`, or `scatter` - 'Description' : 'opacity for 0.0 (transparent) to 1.0 (opaque)', - 'Validator' : lambda value: isinstance(value,(int,float)) or - all([isinstance(v,(int,float)) for v in value]) }, - - 'secondary_y' : { 'Default' : 'auto', - 'Description' : "True|False|'auto' place the additional plot data on a"+ - " secondary y-axis. 'auto' compares the magnitude or the"+ - " addplot data, to data already on the axis, and if it appears"+ - " they are of different magnitudes, then it uses a secondary y-axis."+ - " True or False always override 'auto'.", - 'Validator' : lambda value: isinstance(value,bool) or value == 'auto' }, - - 'y_on_right' : { 'Default' : None, - 'Description' : 'True|False put y-axis tick labels on the right, for this addplot'+ - ' regardless of what the mplfinance style says to to.', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'ylabel' : { 'Default' : None, - 'Description' : 'label for y-axis (for this addplot)', - 'Validator' : lambda value: isinstance(value,str) }, - - 'ylim' : {'Default' : None, - 'Description' : 'Limits for addplot y-axis as tuple (min,max), i.e. (bottom,top)', - 'Validator' : lambda value: isinstance(value, (list,tuple)) and len(value) == 2 - and all([isinstance(v,(int,float)) for v in value])}, - - 'title' : { 'Default' : None, - 'Description' : 'Axes Title (subplot title) for this addplot.', - 'Validator' : lambda value: isinstance(value,str) }, - - 'ax' : { 'Default' : None, - 'Description' : 'Matplotlib Axes object on which to plot this addplot', - 'Validator' : lambda value: isinstance(value,mpl_axes.Axes) }, - - 'yscale' : { 'Default' : None, - 'Description' : 'addplot y-axis scale: "linear", "log", "symlog", or "logit"', - 'Validator' : lambda value: _yscale_validator(value) }, - - 'stepwhere' : { 'Default' : 'pre', - 'Description' : "'pre','post', or 'mid': where to place step relative"+ - " to data for `type='step'`", - 'Validator' : lambda value : value in valid_stepwheres }, - - 'marketcolors': { 'Default' : None, # use 'style' for default, instead. - 'Description' : "marketcolors for this addplot (instead of the mplfinance"+ - " style\'s marketcolors). For addplot `type='ohlc'`"+ - " and type='candle'", - 'Validator' : lambda value: _is_marketcolor_object(value) }, - 'fill_between': { 'Default' : None, # added by Wen - 'Description' : " fill region", - 'Validator' : _fill_between_validator }, + 'scatter': {'Default': False, + 'Description': "Deprecated. (Use kwarg `type='scatter' instead.", + 'Validator': lambda value: isinstance(value, bool)}, + + 'type': {'Default': 'line', + 'Description': 'addplot type: "line","scatter","bar", "ohlc", "candle","step"', + 'Validator': lambda value: value in valid_types}, + + 'mav': {'Default': None, + 'Description': 'Moving Average window size(s); (int or tuple of ints)', + 'Validator': _mav_validator}, + + 'panel': {'Default': 0, + 'Description': 'Panel (int 0-31) to use for this addplot', + 'Validator': lambda value: _valid_panel_id(value)}, + + 'marker': {'Default': 'o', + 'Description': "marker for `type='scatter'` plot", + 'Validator': lambda value: _bypass_kwarg_validation(value)}, + + 'markersize': {'Default': 18, + 'Description': 'size of marker for `type="scatter"`; default=18', + 'Validator': lambda value: isinstance(value, (int, float))}, + + 'color': {'Default': None, + 'Description': 'color (or sequence of colors) of line(s), scatter marker(s), or bar(s).', + 'Validator': lambda value: mcolors.is_color_like(value) or + (isinstance(value, (list, tuple, np.ndarray)) and all([mcolors.is_color_like(v) for v in value]))}, + + 'linestyle': {'Default': None, + 'Description': 'line style for `type=line` ('+str(valid_linestyles)+')', + 'Validator': lambda value: value in valid_linestyles}, + + 'linewidths': {'Default': None, + 'Description': 'edge widths of scatter markers', + 'Validator': lambda value: isinstance(value, (int, float))}, + + 'edgecolors': {'Default': None, + 'Description': 'edgecolors of scatter markers', + 'Validator': lambda value: mcolors.is_color_like(value) or value in valid_edgecolors}, + + 'width': {'Default': None, # width of `bar` or `line` + 'Description': 'width of bar or line for `type="bar"` or `type="line"', + 'Validator': lambda value: isinstance(value, (int, float)) or + all([isinstance(v, (int, float)) for v in value])}, + + 'bottom': {'Default': 0, # bottom for `type=bar` plots + 'Description': 'bottom value for `type=bar` bars. Default=0', + 'Validator': lambda value: isinstance(value, (int, float)) or + all([isinstance(v, (int, float)) for v in value])}, + 'alpha': {'Default': 1, # alpha of `bar`, `line`, or `scatter` + 'Description': 'opacity for 0.0 (transparent) to 1.0 (opaque)', + 'Validator': lambda value: isinstance(value, (int, float)) or + all([isinstance(v, (int, float)) for v in value])}, + + 'secondary_y': {'Default': 'auto', + 'Description': "True|False|'auto' place the additional plot data on a" + + " secondary y-axis. 'auto' compares the magnitude or the" + + " addplot data, to data already on the axis, and if it appears" + + " they are of different magnitudes, then it uses a secondary y-axis." + + " True or False always override 'auto'.", + 'Validator': lambda value: isinstance(value, bool) or value == 'auto'}, + + 'y_on_right': {'Default': None, + 'Description': 'True|False put y-axis tick labels on the right, for this addplot' + + ' regardless of what the mplfinance style says to to.', + 'Validator': lambda value: isinstance(value, bool)}, + + 'ylabel': {'Default': None, + 'Description': 'label for y-axis (for this addplot)', + 'Validator': lambda value: isinstance(value, str)}, + + 'ylim': {'Default': None, + 'Description': 'Limits for addplot y-axis as tuple (min,max), i.e. (bottom,top)', + 'Validator': lambda value: isinstance(value, (list, tuple)) and len(value) == 2 + and all([isinstance(v, (int, float)) for v in value])}, + + 'title': {'Default': None, + 'Description': 'Axes Title (subplot title) for this addplot.', + 'Validator': lambda value: isinstance(value, str)}, + + 'ax': {'Default': None, + 'Description': 'Matplotlib Axes object on which to plot this addplot', + 'Validator': lambda value: isinstance(value, mpl_axes.Axes)}, + + 'yscale': {'Default': None, + 'Description': 'addplot y-axis scale: "linear", "log", "symlog", or "logit"', + 'Validator': lambda value: _yscale_validator(value)}, + + 'stepwhere': {'Default': 'pre', + 'Description': "'pre','post', or 'mid': where to place step relative" + + " to data for `type='step'`", + 'Validator': lambda value: value in valid_stepwheres}, + + 'marketcolors': {'Default': None, # use 'style' for default, instead. + 'Description': "marketcolors for this addplot (instead of the mplfinance" + + " style\'s marketcolors). For addplot `type='ohlc'`" + + " and type='candle'", + 'Validator': lambda value: _is_marketcolor_object(value)}, + 'fill_between': {'Default': None, # added by Wen + 'Description': " fill region", + 'Validator': _fill_between_validator}, } @@ -1340,4 +1410,4 @@ def make_addplot(data, **kwargs): if config['scatter'] == True and config['type'] == 'line': config['type'] = 'scatter' - return dict( data=data, **config) + return dict(data=data, **config) diff --git a/tests/test_ema.py b/tests/test_ema.py new file mode 100644 index 00000000..7fa3b519 --- /dev/null +++ b/tests/test_ema.py @@ -0,0 +1,85 @@ + +import mplfinance as mpf +import requests # for making http requests to binance +import json # for parsing what binance sends back to us +import pandas as pd # for storing and manipulating the data we get back +import numpy as np # numerical python, i usually need this somewhere +# and so i import by habit nowadays + +import matplotlib.pyplot as plt # for charts and such +import datetime as dt # for dealing with times + +INTERVAL = '1d' + + +def get_bars(quote, interval=INTERVAL): + + root_url = 'https://api.binance.com/api/v1/klines' + url = root_url + '?symbol=' + quote + '&interval=' + interval + data = json.loads(requests.get(url).text) + df = pd.DataFrame(data) + df.columns = ['open_time', + 'o', 'h', 'l', 'c', 'v', + 'close_time', 'qav', 'num_trades', + 'taker_base_vol', 'taker_quote_vol', 'ignore'] + df.index = [dt.datetime.fromtimestamp(x/1000.0) for x in df.close_time] + + return df + + +def coinpair(quote, interval='1d', base='USDT'): + '''returns ohlc data of the quote cryptocurrency with + the base currency (i.e. 'market'); base for alts must be either USDT or BTC''' + + btcusd = 1 if quote == 'BTC' else \ + get_bars('BTCUSDT', interval=interval)['c'].astype('float') \ + if base == 'USDT' else 1 + + base0 = 'USDT' if quote == 'BTC' else 'BTC' + + df = get_bars(quote + base0, interval=interval) + + df['close'] = df['c'].astype('float')*btcusd + df['open'] = df['o'].astype('float')*btcusd + df['high'] = df['h'].astype('float')*btcusd + df['low'] = df['l'].astype('float')*btcusd + + df.drop(['o', 'h', 'l', 'c'], axis=1, inplace=True) + print(quote, base, 'on {} candles'.format(interval)) + + return df + + +def test_ema(): + + coin = 'BTC' + market = 'USDT' + candles = '1M' + + df = coinpair(coin, interval=candles, base=market) + + # mpf.plot(df,type='candle',figratio=(5,2),figscale=0.5,\ + # title=coin+market+" ({} candles)".format(candles),\ + # yscale='log' + # ) + # + + ema25 = df['close'].ewm(span=25.0, adjust=False).mean() + mav25 = df['close'].rolling(window=25).mean() + + # mpf.plot(df, type='ohlc', mav=25) + + ap = [ + mpf.make_addplot(df, panel=1, type='ohlc', color='c', + ylabel='mpf mav', mav=25, secondary_y=False), + mpf.make_addplot(ema25, panel=2, type='line', width=2, color='c', + ylabel='calculated', secondary_y=False), + mpf.make_addplot(mav25, panel=2, type='line', width=2, color='blue', + ylabel='calculated', secondary_y=False) + + ] + mpf.plot(df, ylabel="mpf ema", type='ohlc', + ema=25, addplot=ap, panel_ratios=(1, 1)) + + +test_ema() diff --git a/tests/test_images/test_ema.png b/tests/test_images/test_ema.png new file mode 100644 index 0000000000000000000000000000000000000000..0cc180b2537f8cd9b4e34ba152c41e74bb343bc3 GIT binary patch literal 57100 zcmeFZbySso*DZ{#2m%5E0-`k19TF0PN=bK1!=}4bkPfA!QMx-tKw4Tsx|HtjJnOoj z=Z$g3`mdp`l^vA+JkWLYb!UCXf9yReJ?1BYP)3TSGKU zJ$q{lD|-u5{fCZ*wsxjgD0U_`CN_qLCieE$e9X-M^>-#KTVv*EvfuzTw1;Rf#Do={ z6ILf&+!gn}TwDyrO6|Y*`TXd)YQ{%2^5<{wX*3nczwJr*Zg&HmL%pKv^M4yjTN=3h5TR4#T@#DjL#t2@$$=)`D z2eAo9UA)P?v9VoGl6QEM4Pr7($&nvHy&b1(V(?R7gs$<=8)-#U@b9$f|N9jgp8xAd zZ{Qv*f>fI0PFNqtqOhr$%FmIv#9~QGp z`7cw^#i7FF`rJpCr1T_VFEsn%RUmu9{rP|UHLcxb{qe`I|Jz?KGg;sh5U@KeD<1E! zNhS+={P^+XAvgEX&fzB=&-TNunWceT0*{k@!_hKR5cIZe7`%6KRjXtw*~P%QpRy^)=r{o2S#UQKN& zzBS0-|9R={tRf|O`9_~xxU|g7#z)(83_5iQfr04M(s5!gF1#)-E@_#W{lx|nldda_ z(F|%LHa4t8L_|qwv$oaGWo0w7viv`3y9Pwjt4v$-Aa4{E{VdJrwVVHtnAp8KQmR&J zgn2OO#vCTFPq;Evco3QG;ocR)nx|Ik3m@8Cui0SpIzKg<;zptc@g@8UZLc$jj?PZC zZ_cb*HIC^Tl_>t5E_V2ecTUdZl9CdWiLabH^WCZDLj}7_{eGFro^b+h`$c+9*U<(G zb!4@*AHW#wu8n<)jKrJoNtDc0&i~QTarrY`=s>OtyX7bcs&-GAE3ACNxmX&e9UHdfs)g3OzAE6|YK@ynEY^$MGN%DptRYkzHwRXRf0`?`yz5GQBUyLXq-Z{HRWTA`nD ztZ8p=H=Q1YnerF9Dm9#wm-l+A&V!PQs@UWgx$hm)mlx+}E{A{pcbG49-l+PaRAv1FreB=$^ zn52{xB@fSVsQ_$9+ld+{8n+3f-i_QLOX{OzJfXqomjlikpB3E6lu1m<$q5|N@k)g2 zEq310ze6V2r64QNQhPAj8E;j03-;UV#0BwZ&*T&pufFMD%Hy_p_Uzdu>-jFCmoHz! zeVCL5o5t;-dRZ{YDJbaZ=uFE5eC6zR-jD7bo1H1AJDyZN=%&LS!;l_d_Z>9+>oFF- zJc^fhC|D`46{qmsCm&V;+Gx3X+jzB|K%Wmm{7RE`BY>|LX;?M>{_^-^hIzD#W~ zu>W{(b)?_?NSb>>%*2G2MkWC*t5PC6EzL(cU;Pp`DQO7YQl-?|48dbuT-=#k+6z_? z;1XnLSXo&^DrheLydWocXMg6S_uHLk2P-M<*3Or??1{Ji>&O*Y*3duB9jP?t7O*-l zUf3L8EPJ#%RvJIcL^q3{xcgY=3D4QMs7rTxN=e+$KcD&53bOL9XDJNjzQ>>>k~FCR94kLAkjrR#S{ zehlVoWM^etA@vU^P#BkZ}-FNnD@o>^d6n; zukA$IRBOViS)4a@KhFn?#CNyOcTbZ24k6|a4-XfC9ryg@%f`RfMTVXC3fbbhEE`dk z5pi*&vugx6)p!JPlaF9^7Cx1KvgkfFIicO#&vEZ?lx=>r?MC+dB4=3>9&l7ZZ3=Ar^?Fr zs;jH>bn24`blg7%;L_YBBis1oK?%`KI`^4xewfgCOyUzK?d=^t`^E1ihMlzZ^fM3y zjkagokrBhESJtf@d-3w6{!(8$9;5`LF2W3{SdpOuEwz%@x41OA3rfb;zMXD&+OLnt zwnxygJFcpRk_q*|p-jdq@{KjvjeB0N4i_`17QK10@hzgVLPv-bNWB6?OeZ>6F=~jzsO{NT)#Fy^w{4nfa zXBS|xB&JJ#;yOGG;hy}lJ=aOBB>{gq+)2I|HzBH-=z(Bd>sqqRZuXm2Jmmg>2@#}m z(`p8yick{XY2Gr&^+URT4!XyWQ@(vmppXbXEcPWQx8=+#Bq*TL@_nv;z4Lv3W1~oG zFi}%`#9nfs#uG!4LhahPQgtLne`>MSGvAtSN#ZSB#=Up1^$HgMcgTwI#mfKuwaxa* ztkdf7TRlSllhxAI@#=`-VO5N#KM;`gS_1LymwyMy3Lg3V{Q1gDMF}$U>^la6iaVry zV$RMy5Ho2gDVs~jjY@xI%915{ox3=$4mYs(^3SxX2SUKoB!NqV<3ar7FD>))^SkmP zIT`DzzCQV*M~~bg_uXmuFUl7NI6K_hDfIc0UU z{Pw5LIfv*J_;|Xu(SlhuNtkN#oNI;W$=K9g*sZ2U#9uon$gz47pU^QdP(FI}Hc7zE6*Gwdrl|O_q;LN5?&?Scymwr4|DW+} z4%f^5{P_*!%ZTLU-fV?T{?gXuPL^QtLK|9ga+OR;(wofFR?)XL$UJs&6f&f~eEs^h zfi1&SpHQ5A@Fg<9+p0_!0!OnEib_gH|2n92qpjY`&o82cz6}62Vp#MpL$;)XoHI25 zKo6&y0S*T{gBns25xbFYsT#@%9S7%QP{x+w<$DRnD`8%z6d9)5Jx?|cYaWx6>&qcY z_$pD>El%Wrmbf-v5>J;)e+)9LPRjrNTM?PZ2cCy|+AeeWU+tGf;Gz<3ef5fT8!ij| zeg#+9k<~_D1Xt;Cy4A*iy4eI*D);KuQ(<9YZ$o^*ZF%X{jiTFoblPL;wJ5uYeZGPH zgaK4)#R9|8aU}Ja@6|gn85f6P7gbq7Qs&DykmY_0mK8cWItzw3WJjARkAq(YT2Hj% zB*dC!dq|1#pV#t#8nqi7))U?f514CI9;>kAg`gNjz^wcO#^B?7 zDJiL1Lx9&4UFEnqIP~U=A0{xl?GNm_DL<$&SxmTX)Drr>HI4fQkPLb4MYCgwBIuMs z)Sxu&QS_7Go16r31!p6}YcJC?Gb6&o8zF}Cch;S5OSXoQ$-<6Y9IIsR&yb#hsnqVJ zXJRq}NV>Z+6b8$O4K>aKd*`oG>zwV!*x2?rm#!d~v;MG!c{wr5VXh-8n2_}yVABtA zaW9RG=AR{o(2dI$6nD>Z| z?$#RwY5tNRW3`&(_uNe-E4Cctdf>Lo+St@Y#HxQ44I&~OH6E57i|%(cIF&Q32|7kb z!}am%?h-Os3t3b$$4e{K^=We@n!{gL3ToWzLJ+V8HyOif;5-w;&EHx2pV+hsRXD;a zD|E&IA29}S7)-(&zPBL1;~E z%|nMQu5{G%Lv(MrAI>ERR>M^`6uklmB(nTlKE^Hz3F$m=`>uP-EszIlqpjfVuuWut zzKgqUP-!)ll&?{F`9nm6C_p@Z;oA>sY5gI3jwl#GnueJ4S=jsAU@}abu<)h#@82gS z??u_vOhD?^w0ZJOEKg>LIP$4(brRznSX5z0I}3KlyQaM*G)d0y0I#)%l6E!+;6e_b zH_SgdJD4(tgXU>ehC;yawS~$2+vF$Ucd2&($_yBNG=Qm@Q0(kQtCgGK8yXtUE-c{Q zzptKYoFnRY=V|xfmz86fuZ-OQ`@r10RnU;%&02oKaDz7CMUX*t(cV@Y+0GA&FlWZx zT8`eWnKl5|2w;VJ!1Jh6N4xKCw*74B5qVz!={em0!HLh4%5|M~5T=eEerH z8?SIXmTzmw$;qK5@Y?wT7I9@x@<7FoUOET7_YZOv*faIl`#tXd#j*CS<1@XJqFdMFxy1jsBtQR4gDT>iyStBT)UcKS8qLq z<#DFL%$3T=|AuL(e#<-|H z_YVO?TDWb%QaiH|vUl&@n|Ek1g$z!1zIg*lEp;9zOs2I^c+E9o3C$)tO=cgE5B~b~ z3vF+oHYzgmXO0rREN^DNnpNy*8?z!cHFYcQx1Oma(>R{Qp6fKW`B7e*P;%GBk~gxL z2__ufknQt1`nmmk{ky5t!wRDND5qzJoMtVHdhf$ zvTMMYiN)9_wL*H&+U_{^M-pKshZybjC>@3MolzzAMGsVzD+hOHV#si{o$+IOdbKhW zxx{vXFcRKID1fbNCXyQF3CtmYBIWSvsztBm`RNhb>5$hM!OlXj@t^N+VTUc?AXLY!68B`qHolQpH0WIi$5cj|`7@mzrYPjnNK|dcAtSzrAv_ z*@VT4;r(R2Y9@Rz{=1OQK>j8S)7i_?@x+d{HePgg8E zLQ%l}yk_Rw@YP|ZH8o7I1E5C(6w#dkQ;-3rplB-1x0z|(8n^4+YPdo6AwIq{oJw-I z#PD{1rtHCQ)8*gU7Q;nvAu0lFSF86FV6HnPhdP~7P_T~9G1K4QFyV>Sy;E!6_~_Zw z+jc{f^$9NZ1-@sG2-*PbY}8+P3ZCsMRG=mTJLk583;7&ZV=}vXz0U3W`upSE$6!Dh zw7&jk|7)tMO6Yw9%LvfsumxxJZ`cXjoy%StviZo8Q1epTE1uIerz#eM5BEVmhQN1rM z6>~tTwDZP2zzUx~KRn<5RKE!brpT!K{{HHScmEP48(TQkMz(FFPMe)N=Y6=I1GtI)%wWl?6|rjA6R81=RM4IL+{24!>5R2l80UcRo_Bn{GTIBTm7HGug8e*Ugp&qbBjPCSDBFB%AEd2s-?D13T+?oJgL zMEq{!cktD#fLUp})ZcOuBO}O}n2>Ux`-5K)e}9YX4o3qaC2ohbI8SZJO?4fq?-53s z$W*ECR%Lfa87psQFTM6tQ{Ujp#l}#_6(ez+4t~g0cXWN7qD_s*;ojQK)=3P3mun%| z>#QPGW5MZY1XFQy?&i^@VYWGMUP{%a8;!)z-{Qk*3=6sGX(9F@>(YMo--9Afpv7`> zb6w7l7B0o`*pLI8@ols5wr4>s%u`5D%)mFxq(5F#8!NjbZ;5X%w25C82)5QQa?z4J zsO2uXw8*j+V3cQx8L#)}A)X)3%lrA(u_)W@KN_V8UNMGv@^*6(qc+;PT@ht=H`ouw zh!UlUNxQKv_{JBxJ+U<6xvi-A`NwxoAdtvtY9^4GQ zX|m@HN`1n*?>4ejy|qShn4$6+2G4UpPBkfSOx7Ol_RAg}uM{FLR*Wm3I-fZ@?q5sJ zMKqF-Y&Pk^YT2#xhJMAwL{*NDy41t!{Vg|l?AF{DLigwP*h%V;mM{L(5Ozp&^gxH|;0Xg< z;DVLcNj#Z3)NkGG9Mb2XY)d9+=)TYGsS=0~nTYKeP>8Un22nxH`Al3~UP}v+0$`A~ zTp6H915IEHvY>9IM!_<${#V?(ZIx?eUAxa81cX4Mz+g!!%&}2p%>E?Dlr(p@697qj zPftc!S((i6<*cl%?`w`g0xtWF2KYa>qhq1};g~#2+#%4ZU&PvH(!wZ?_vn5E6{FAG zWcJ0r)kQ19S4Gk(3|Cqu4;cV3GzOFb3_7bpD~8YjFaho@mT{*|3^UcsO}463>2q+Rk!~mkxMJ#!7{`mCf3+ihF*&s zIam1s8(2XIZ(?D=xB|I~j_ylZTJuho9w7;@ZCXz`uERXsXeVUa?JV8+rCGcFfFQE- z0to|y&p6$$DB?^geuR~mRg9WG;TY^3g^4Su-B-u-1cvlwB5UrEPHC{$t5$9!UYT+2 zunYfJzKdNV)g7u#0~Qls2d0#;aXwPpcjZkh^tVrS%Wu~n7n=F#ZYofOH;8kP27jdy ze=)PaHnBF)q~zuW9P7BXsFRnnbPtrl+vR{*5w&J}XD1*eWG-3*r3c8Q3^j584Rifz z__LsUTix7K*d!!fjFsb!fDm6if4-TQT2&R()g@J7H6`=x*);*z-9NCpzWl>Q!#ag8 z6{9BjfYFrs7tS2_IGE7hQ>(V6Dy%zxJmqyEU}Iy06x34}=Lc7B=m=!81wH>m$31b{ z%`oTHb+_(`afh$E&DCF!E5s=h3Vci}oT_uw*en3*&?LKyK>bQ1o0P{cm^%oI0_vQ? z+Wq^W79_NG$VzOQ1b4sJro%2uzqd${vl0)KZdRK%-Wg- zt{48Al9J-Ql>W&-G!%i1pq2r(d>=|iv>0~dI|xArYSoN{&@m3sJu}5^4=DKg6L2WR z(Ks8d00Sbz)}<@gKL9s?oEK7(-9k@oNg^%z^bNNzTidR?=SIh5f&^`l9KV5gKN3JHdsOSUWZS_i2_cxav{+ZS!&tp?xEV3Sm>V&+ul%Uzs03`>& zmdvl|(sxi@Mk*|c)zs9w;<>|8QplAHwY%ZX>b*gYOV&Ij2y11G5Vb{njklRUL`S!# zy@=p93?+TiiYv?CFxwtc>)H>5N*9N8Nq0mZyQ~|d?Dw8-aY}Atxn|<%lMWoyd(mzS zLfbzmNO-LOyqja%6C!V7QLS-g=h!CB?kdXXR3(#VLX|z7u8?rmx)a%vzZikR`-@fsq!7ecc?XK1kSv+cL zL`O$QKEA$#OR>v8zg$5BowaI$)A&z9eeLM}AWBxOxg$p^LZvI!9Tk1~XOeEl*j|r0!G#+C(Gf)-%Vl{#J)P5w7KF8x$e_T$ZIVHIzQjG> z{H!cmw4MZBQ3&5aQ-(xmllnQH?_)GQWMk_yM-?9vig$Mtm<3wx+=Wi71ic_16xMgVevphQ9eK*Is7IPR>ET<3>k^PZk<t-x3LEf?fHk`~KWXpk0$DPQ7**9^JciVFVS>lBPUAK= zumleu`eBiwiY!F0?0U%kcMWe~?H0yWGp9dQ>SN=DIzo_@o*k@DAPNnCr|rRdhTFlq zbl>`2Cr);(g)Ql4(m#()RXJoe`vX|w%ij%Ft8cH6-X{S)?$lnBVC+xHPO`$u&glY@ z`s;)1$|nybJXXve%NXk>nOXsNw4!qnS5(CE!1>Q5RQ;JFFvoqmS%eU!3@r^nN$5%z zN(M%W-F%P%Mvss0{^!XD>EYpb)yvJUJ+-&zkjX5aWGfHT$pRiWn8rD=UZ9@?!bWp* zGsmd!sW(l4f7Z8G3d})pIdUCB&f(Nue%f&}LRpdnBx@BZPc04xCg%e?%%n=^jkSNh zC^YZ^~o31K~$~&nm3eNN=qP^zF%KLth zTBi5-l2_K8gm%kLP8UF5teHG$yExxqa-G~YNItV}LhqE9# zpo$Xo2tVOhAZCI3i<9~tKcUmNQ>XLXNprAfD5t;*^9_y!?|6OF%1CcSzSGH2`ya+SbG}OcV z+1ugu+=}k`ktMW2)P9-O-e`AHHd;a2gmIVIxzNF*J4u-8-odo3MES*fs-8L(i$gVU z>tl0=Pz6raLkhiCPNExOrGA84e?85LPcG2a3hEE0F3zUlLN_&ui;Kt7j}iS3VlilG zo9OE0pjAGXk+~BUCA3SpHd-D8L;&0C=iOx4z}d^Gs{R1_iyyI4KqSF9!$&FfEAW1yZBSc00{~FfD&7#!#tu&;TzLa3 ze`^SFn_aJf1gLnxfC|9nzlJORw%aF}WGMp%2cRrIMn$y%u{2!k#{FMp%X#1!19_j~ zOKI-juUKw(NGDOvo01gD{d{FlG2hd@XP7G190qCZb4toPpm#t99OW2s#d^U1W3txm zQWYS9Wqv`!4!j4hi;tq2w1DcTHfRlg#J~{T;C+J~a98iCrejSaSX*K zaiWGBE-n>Rp`^Wm>&4e@-?KhFwBd7F?@D8{adimH4y-5dfn_ybg>H zwQSP(>$Q70*-^D9_Rtmm71F8x7~B4C{R2I8>q{)n{_`s6>fJAevkXfi2<`MdLE;J*CL?axI*ZEE|F{44{SgfE2AMjFEW#d_Lsf3(nzpt&})7lf= z!ZFWgnc&|Llv*EIA-=0hiWO(w)VBQAcO8_<%s@jOVd1+nJ9 zQu92Pjv$zj81~>YnXnMEO>Tv6vB9^rYhC4(l&*a@2oo68>#oPDIyiQ6e1a|^?_ zF!2>LV)z)6N;Bj+V7E(8PQ6!XFZRd4 z3pk_uO95?yh0|i?h5g+mA*K~uQqEjemt8k5#pF$lH&_k2SvcKm;uvz`Kk@T*#FjIQ zs2D{IbcT=FK1HV$3$N8}*Dg(8*}>C^ORq}g>|Fg0EY zA$sPg+I*qjVgCT~A@+TeW+>W@Er>&|wbK**_I%XV^|+3;a~P^uG$Eeds$lJe`HyKGk^fNXMlN3GI11mwJ!qLTJ!#l%0q(?`^QoyR} z%L#tr$@m^;i>6A5DedP(w%ntxHKdU1VOY4Sq}?0T+6qMhPzJv$9^Um9ZGW<`DvGlu z@SbrisP*~@7Z;V7sGsU`zoyQ+ZEs0Ty%1A|G#!hZ<{d*PwwHAkPE+MT9sIgq&nv^3 zT$4zW8knkR^E+?)rV@!Dv*v(VP@+Z%p{J3U>HQlxLJGrolWQVHsh2HFLM1VCF>4+7 z80%w-pG&7tqVd??L=02*qu4CW=Uw$dm~ZyZD0Jhfecty$d{8~~(pdodq~C}$)5gws zIWB%F1CQHqVSG7E-l3e_ACDrkz~@)@%~VWBjQ0#UlCOrtbJZ235^|bC&wu@nnEWao z@zM-cn15#Fc@n>+7+LeVB+#}WbN^vh&VFPehjdO)bYWw2bj(w+55#VtR*qU3Qc1YK zhijmEZ1si9$lA@z)5W-9 ziCn&kl<$_zd?K=}sr1#*HST`6zx<`@ukZTaow&(LD=eo8*2wyT5r5z}j=8@7SzE;< z{Nqs*ShpERzF8$55mW1+Bl~ zx-Y4CT>FhT`sf=i(@Oa>ln3BZ2f5@2P5$>##egveSEjQat>w$ z3$;IbsXd<^IZ+sY-(2WQ^ky(hiLk-(eBi8B<*15E7T0P2ymY+@O-#D;vm^RX;BY>b z>6l$J!1fL?dFM~X3%<3_^RC0*4Io%zB^U6>-#<2mW>Rdz+2in-__JE+5^~V zwn)PX`HnwD#-ZvC+wN#O5`U4CE0bJ)%1Mp-j2@woDXXwnKz-92Y!w!SBu{K(Is(x} zL`3{+xZW<8zJ*V8m~o4LSPNf9eVzE@h-*=4(DeQDe5BjLQh$sgzWvMRly`}7uHxto z`!_r8ACwC9s@;2m{g+Viwhd`?H1E{o;_O!;5K{;X!}L)Y_Ok=0c!n0NF#N~Sn!WKN`~%eZEg-S`at zuOH8++{4sDTr1MrIqU~jJZ9WFq`4)srYQ#!Sj;^K#|!5vl@e0sZ-3k05bc`jM^iO> zP*_^T#%_=YiWQ;b~ih9qp*jQ~eclXX+ib#?8q# z;bqgiAH``cUPPGeGUWcmhCH##t%dg|<$K;AY$fT@F&vrhFP}!6m_HheN^FiWTM}dY z2xD{VOrouq)@sdp_~dCV&0WnyB@D5Sebf zm*DM;cJKOl&kDv28sF2Yyo=ezt<9OB0r#EoCU51>(nqWqWAA(8R{9Fg=jZ|)?xjy1J@w9I#jqA@4tILT zcd>P`kaKXfFgYQnmE71%=1PJ#l&NE5wt*_B7JM%wepdL=|GB6=Vez%%Q9ZFrHTLt2 z%MrU%!EJ+lp-ZwtO*T0^;-FYOc6{)oEjq@!Ve-o>4EvC6JdO~+>xtKvdK1OKN^OLicaQwQ$BLC-a zAgl0gpO?Ks)j#MhY?W;cap{Tm`cS)U8G2C~Gwe^xMtw#mQoMEAcoZ{-&ycXgu{9jf zn4z81`Ez(SX+f3&eUEIO&uex3D8gZ|08BJ-VU5ww*@+mJ~ac)a$&A6k)lA zbsa#v$ZBemfM0I*p#DOjp`n4mvjppP;aDECj!+i!O-|*4I_tq#pF!%nkQ?4E1xC)O zH>n%+RzenCgjC=^Kb(n-j%IUO*Yr{;yyIb`;U^LWY~tslIb-H;o0m;ZO{L;EZXveO zVuLp4=>VD-eit@iYR~gOBi=Wh#QXCS;4Q_wcaP2F=flD|;7|TVmYgcEB9yJW@@b=wtmDda@HWUvepKcY=6yI z_#IXC_qvx#s@fu)rlq+V?e|gxn>21_|B{O)oL-%2&*JDKVz&X>c6a?uRCeG$rTvlI z(`thn!HHr&fB&1wzdKnz+tsLUW2rApr*{kgPu&F$ZS*?bconC#U?_so0*4XM0%gWW zV8kyPTT9ExaE-!3N8cU~^Y?#9-pH*Y$sHVL^ZI{mJ+Q0qFL2}wUNi<9uOaw@UK<*I z1SuT63ohV+iUs{`YisMgAPKq4M{o-x|8{OlMl8^iWbof`Onmhu1Hy3_ZXBk|pHt^c_X0?%467sy@ES^B_p4pgux)Ii{dO=_$EAMIlf zgRD?EB94vTBmpqGkUVi*-FytC;E%4Z*Wce<+FktRt#dj}@C}MKhvib+xtHR>gu-DD z1i)EO#m*kl(eZMMLmTEfP77?&z`{^V#XNago``y5xAaIXuUVR9h1^C3kp^g;Ad79OC{hEZ~4aDMP*&WO7_wNlA$7x;#&L)@D z;x^TK&)Q5dfI~&A*B!?Rw6-6ZAU=WTiTk7u8PTbmSw(wLHl`f=G&Rc8Ih81q>dXm<1e!jg5``oud_&lE6WL`3Z5Tfk$lB zv1VZlyrcbf9&X^RKz|DCNg|&E__Cclu3o!lv(wG()?qPKN7~Op#9{JbXkS4=0ilV& zrLt36$A+qmPe@3hM%BV{J`@E9Kg=7L(^z1qN0eH@VNhr=ncfM2ad5DS*5lA3O@p4xHuo_hicuN zN`@@=PwQMKjKT|Lw~&U7VP?(QW$WeNbU8UW&Op+_^$3mu_G)l^|b_cnF ziW=xo@NjeN-^n0zyVDyYL)=&=Q1fCvTm+#kx68fzdj%qgG#@|Cy@h!vr+*#z%r|@v zwd$%{9@^^a2Q{qN&^GgRKF*v6JoB|-em*`QVq$)P`D%q<84S1(su3Zqt_SODbasEfUjlp83mF+ynLtKBuMmHC zF($oy%@qkSoW*5IWwWFqW=us<2@79CHAKebir>0 zqF5AkKlPS?%|~8NP8b>j2zji*BF3v75xD+eY@wgk&(Fe;6ick81Z9aJiol)#BJsLe z4{h0qg~jCRneYVoFG0*JGam{~OG7rKMuo-J<5JUp>Q%=lkVd5^J#jut+I(3eMOG&| zWx03H0Vr*I{L-Zl-!gq}9Q4K#d=3&BBnc`IoqL5Y5`e{oJzoVo8lu0gD>79{CT( z?GS%7lDxoEC(zv12Hpy=f6wg==^Qw5{<{xAKx5(dty_AVe;VvTjDo(0O=!0S#Kr2R zg7hiQrl@jRC@Coc_Rv8(Z3K@ODWC(b_5p%t6uRLPVxYq}4lWPmg1~t@*?l`;;rLzo z5Du(#qy-PeOcwV;YcFMRJuuA6Gnd+xYlceXfl!9bCZX@&TdJ?@wic0(={Mg*)M=qX z8RWd+&9V@dmQ{ZF5*=_T;thwqa8mQ~pB@A-TdX+MpOC^|ct}F8ce;bKrMac$SE+HY zQ6AWmpo0PkU03kB-1iutvt@-~GCeahQf8{aKJcF{2$w)Y_xYEdFgq`Amw{V40yIm2 z1iZGh{$%FR%y4d-edX`7({5Y6Tu?&66krYBYFDyCQl2TWWY6bCBWE%MzFQp{5}_T; zWOM7^`Vjcxr-I7?dE->`N7rcf0=xj@zj3lhG{la(#Kb{*gfQ9Md8)<#dkk+!AvCJ_=>+nv) z>Y{;qFE8+WK(LsBz8OuY2?sEBgBt5HA8UNrZRP0=hDkn2I6xYeob_#_7o+}c&FU5w z7WiA>d(aDY;}2lYg(wy6zx}d8pnfKX(30y5Lx&(LnvMvGV52=y#=NK#Om83D2uotG zb_MeX6%9=(!~|aqqIca!2B3AHt{MyN_<)ZGTAZfA=onF-3(Xw1!ssad>TEJJ#OxBp zT(@|11}D;6=t+bS8xH!hg51 zC=7|<;S~GtH2HI^%6rFadO!yfv}CB(x^jZzn!xAK!ESz9jnSkFfxt5>(f|3MEM`Su ze>Lv|5f5^tKb=CxD=n=FU7~&tR*Y04bFgSJ3a{z)?qIo0g82cc@yoGu*MA88tGUGm z(_iQUJWu+Z_ir*|7Hjq*vkJ=q+A>)58m}T)%G$a}wjwGisRtG}EKgkM1-j|PR2?WyLqX*ckl8eJr&e|ZsB7JkbnU0I?sNT0Cl7?Gq`h3AHIY*=V#i& zpwkl(8324%s*wKog7H{%hW0jklX&19u2Ggzc>6?5Q8|33<~T|XU5)+jueNLLBxOn*4aL!X$w3H z;H3b>ON<8iTmTAR=yFD?5U@hWJaJ-zcu7uAfBVh94GT~~Re@o_hKEEdf#)LxrXpyy z=-toOOmve=h?8{&dP2a#_B*oK+RiII+Fl|pR{GQkAyN#~|gcQAv1 zA>n4tncqa;|KwnR8uKq5E*@SpSZKPy+i+>P&f`gjDHP$K`7FV{&j=-o;>E@4gqvoh z;rYeZkU*Dp4AGs-fe-z|jpDK`*6*srp`j4`va`p z?cLp$MHZ5cgK#4wHBQXn_;jpUH7-|&en4_+>Wm%(eFFpVcwIL``D;C?TbX79$t)cj z1rriTPESwATU{>MZ%mSARckKU^Icj_U}j^x>)r5&56)??xTh6;g=6G4jhF#);{D2c z&L!%8Mzv!EsT(J`_zInGg^4|TR{fqasO9pT_SZQ(Jf$O2VDR}@lZR0$(Hn@W4EH2! zYi$UkvlEqEwvOxb6Z|K14<~i((0gK_!|y5w}pikVF9?9GXJ8 zR&1ozP$}gd97?@eAE2Yde3=M;`qzwkR9Vf;BAnst+CKXs8YlaHTuCieoLgvX&qUv` z_WH_1oP(K(n~2C|G}xXah68&d>aNKe`J{FkwQy2s6M(u)7y453T`!sd?UZ_)I1c8i zDOeS%>gqmxS}XAKUeNnLe{@xNa|hI+edFxYV_0Id=-LFyvPw#M%Jk6v23^dVpRcBF z^$EOT{6foFpTK*K%0A7oJ8gnAbAl8~8tWQ*QqG{~I^Q@%M8`BzsuxUF$1&I3jL-I6 zD=Pq%p% znh;Q{!M4H+-K>di^Yim;0N~K3r>76W=dsL)OG+yJ^eGxRzo3Z$0!%<~us!{ipU}BG zgnk4>lAT8K3ZykC6QI9oz{E`vKkQ<=tcADi`RIg=JrpehYsz30U>^Rye=o5t7P(@^ zydlH-NwaQ@_AP@RtmQ&dA!Rp%^-h$Ix!^*%-|wF%n)G`n=5e<*ii?Yp_CQ2qAGhsf zgj%(}K|2XBEB8?qU=x0DDxt>ej^>F0>ofsp_tL_GfnGW9Av-%RY+?1O6==}F!^2~- zecjEh2sJM;m&KnSA1Ml+zOEc=@95Bnr1-QuSR_$K0%Y4okwApY&4)2pz-HbCZy1I zqiY}$$mm8AuibUzc@WUME)75n@c@7oNGe${v9C`)$zxv)>D>l_`%Rp75m+DCAPoV` z4H>bZrl7b2Y{<)(fh(HOgE0%}kP;+(koXaZ0v?DKNMfse7eF>be;Ne^h4)7a3ERb= zkD#1r1!!^W_HFaQ9cmwJbtfvHpuxCDE_#Kk8VXAI^fE8A3XbG+>AnP(@+_qu+IUZo zY<(yFINx|Lq5j*@;DK;(K)_!>uuhE5%NrZL@aT#dPV;+wTHVKmuC+&CttI zJhldi7a@keI<77ADO+N@L2m;sumr}vNeCGNkg`$92!Jv?)6&OpN4sfyB-w&DC})>p>O zKjktpUH#`92f>msx~j=NVUO!~r@BzgeZpa=80vR%GJ*#WdUP(1hr~M<_go)%o!Y=_ zOS}_0+C!o9g)_fVy145f+78#o&@%zo3l_Q(gjG54_|O=eckk-T}`MH=6aOlnomsQMI&=L0ywSi*hdWi2?u zcz2l2o<77QOJTvzSX{Da;(1TYIVFluRuFQB>WR)1Ng;d1yA?jzBK};;U-*ua(Cg$n zkHi@>4>hf#^mOa_4DlacCF4uYjGva|#;{kwux}zrO)=jj>61$*{pWkmN)kk!={b-6 zvR;Ro{k0qLotfTJdOCo`%X5c>d~Q9!UU}Cil?J0WIMr-{(gM~t_NVM(H9zCukI!?} zM^e>!Tz>IC#pNqdI;{>xD^R)|v+WnR_fTSdCre_5el*&L5xzm{Ij(4~pcLh;Sk$l9 z${ks=r+4kMb%eftkv{nyA^Sdahn5}JVR#Hp>esJv&?0*44REJOa_vi(tkq$JkD@LFV7|(OLce_QtzN!-S@Yr?Oyx_zvm*%1 zDdFU@5=7i{boeUnS z@VFYpC=PoPO}BI;2A*C*MFxdjZ@Dn$d+Zd0aI6DKdU?`QiSiHW>P3I;EjQjJjYU30 zDNE(|1v%KD;jRS@H7SFohs^7gRmby)3V8*lz+(fzQFlf~M*q)jYuM#HzClX^!%?YN z719>i^l8kYp&9q*bg^n*r}NyF>u{WqmmmW2d0lsD-odCJx7mKq zJwZjrgs-~FU+(y~{h-x)Si2R`OC#q8=LS#-864sryK25Ns(S7oaQnG55X z5>-}NES~xMF`OFytGu*DQq5TnJc9)4z&?1CialTK zV|pKKMo!Pw=b`7;{<*FPdK1)^g_(2t*8AS-4fl-cZZN46cLs+x1O|Kwj5TQ)qIbYu z`vk!T@sT2E?Qk(Q41$KZl9D|iDFjUnVy<9>Kl$a&p`yuf-=C zAH97!o|o@(7e+H}Wfu-(2Kf1bib5XA1AL9O=Vy0lR|I^95ZCSyob%Mn1Ht3t zQknyUyN)zVI}KW8I^GGt*BDyKqZD-(S=!f$n14=8a-!fjhTlj8T=8lqWWdM??V|8p3g_wyk(-Z3Q#XFwPC6z%x_GBF>HX6t_pVs@ z-RE!eHks5Rst$tdN)GQm#6~})As714h6ICJ8o@yA1YXn@=(<^NQAdYR7$%de^lgQ| zX<=A%Ok_*1>~7ajW%W0T{z0Zp7O_&)V3$+w;AU zlCqeILlRn8o&BhD+8^wdMC0Prlq=$NZ~1dqTCKG{8@-FJvV{vcgGU=6-5`i*c?J?F z>I5+rL-O$t2+#wNqn+~wg4F(>JoWC*0UDQfm+*HvahEn#X4>4fD6y7BSUadhe~EKA zF`_`3h>VIt_>c8(J2Fz#xPW?KAtO(zT7+lyfYtxa*bLq?JxIfoStW35m~u){vWoOp z*~UdlHN5s!VQ+P^8phugH;u~}pALUKByx8)K6A8ENpI@c9OkqBgBF~(+D@8o7Z`belANO8;VPX5tc#1iOHz`{@11f|6h7owWuwR!0(Ql7Wn=ih&G|ZyDG#Es_uNG2whIsM-(sTQ55gjNH@aEOQ24M zN|!VXNA%o~A5S)Xo#{oB0z_8{dHW`6Y6GHBQ<}xX#|?r1Rm6E1UB7|dE~t$ue(0Jx;8^U(d-BjK`zwmMBh{Tlp>|)}ii3KdrFB2&-xc&azvRKZ znH#E0>9ysZP%^@3u>ROMa%-NdnlWU?y^tmnMJEYePc)&y1y2x$R$N$En3|5x7$V4Y z+iL)XB@)gAnV^%z!R7$C%g)K^LS>CInMB0DU}Jd{99%&pB{Vi(2M0xbQQNwd#jBt| zlt7Vy{SMP;D~263MqmsAuiDhUpzb7y5YU+bwwgzmHt|-4d`K7jbvAd-I``?;ao0=| z?cUxO>>RHsnst9zj#A4wV<~MXwEbAl4{4?s<}k3x8RC$>I?VKa&@FNx8w9*bkXr!3 z^T~eWL+uh1i$HVoBr57WI4x+aZ8i#$czAe#5)E&&&U5;%r>eFaK*Y2Tw8R$OZ|>p9 zvG#z)L(|FO_yLwP$Xp8mVn?*e^ZFD#zS3%T^Odntit*EE?MgD}!j8yn+NFH>n!3?o zag)?1-!z~M^m57sB@Pnr5RCrpw0G4{+&p{sY_ugv$8yu7pNpNW0cGffksV^*zFqO= zO%W0>f7vrUaPCOcL8<5Qd!L=(lb>5XnXvbme#|Ae z-oT~&^WD>Zu5S_pJHKqck=2L7NlGQ{o1@n9#2rOn(jR6|`TeIBF0X38c{$AT!*##S z=q&G^T|q{F3bWKlY6C~#Gx&XVbbLh^7%7!u}Y9;yRJWX`NklLsXabK(~_o%~0X`MRsmy1%nYfBDMbSKzstw)j?h zQe53UOG|rPLCq}zy(bugL39zgvkO6-6#kw}-A>B_{LH%f1mZ^c$Q|=Hit2)AO zdOOcFpVhJIrv8Lwx`S#$%CP}+&swv5GCj+DCC9l$vl08Y8nWEgc&XPBZft8h*S}C! z`+FwRU+Au@@z5MEVbc4qaORlC;>@7v#(+1EC6pT~Z5HqLc` zXTZbl5t+Bgm#lul|LD0xupCZhlo6!@ZhtH9F=n|N%AH!kL5XA%&*gVK%VUQ;C%UbU zZbLgg+L?9Dk^2c$2!HbgAntenAy;Y)-ULhEj)Mp3QFLJ4$D?CqOhS__=jf=^iLe)T zC>7F$2_NK;4f^p6`JT8GXXyLSG{(h9`?bHRwojGg%Iedwh-|a3C$sqAkZaxd@_dI1 z$+B-op{;H_`^80*2Kpd^Hk90A6c ze(Dz$1NRFA8U^n(UpotOB@1bRL@X7JEZ8W}109d)vDAEZ5W^3IAHDH-Y8k6SEQopF zMr6k-ECH-o#n}RmMo))YOgncP&wIOv>hw4D`eoVNBCEK|`#E{iHO}i~uut4RR1_%o z?96GSKEXzG=f9CGfa(jyBvEa^ku|$my1IB73q6)`K!E6q!wrWqva-O_PQw!j5)0@)HWcL8LC8J4XhU<{bb_bAU?_ z=phf!aaYzgDkyM=$HMe)M%!cIgfak^uL7kLkroKv=^Ba>A75YHn~`Wc(n3-%NU5cL zQy1hc{yL|BePP#L`E-p34z>Am-hI_$PcF4BVI_DVzZ2;tB`6kxNzF+Wu#16vWg*`ciz4vGr1KGm|p1x^$lQ^2ddmiyPE8*9}xHDcS#(#Sgzs`-wxwcKW5eAHSV&nWyUX|^L0HhT+f!(NANkuhyM*H$sr zaP@6cWp3JbN^~}q`}dsBo=yHbNZ^mNe$wc~!o?f~!2J@-C0)bERIU}GznMOv7Q)cD zXrdM~^=b0%?Q=z!CaRf+H=qh2r=ow6l42q}i&A6zfuy}OxL++&%}^H?oD7~EJT`uV z{&_L)8+!HxiX-ZqYIg7{QXj{U(feI*4`h;|w*6)1n)AW=qM&V`0{=Ghps$twYO5tW z;=FIp*&CW8+w-5|&^1aubvbGNjk!fA!?;_{sGhIP(ero#C%nfNgm_}e2$>vTxU z<3z11NudVfPWQj2e+_@Rng2qK$)@H5e~|<7H%#iOY9T>t`?t3-U5GBeAGe`5oLJOVP$79o!(Uoi|4ZsHlHSo;(01~&=OzMadJHVNx~D2d{7iCtLSDI zHOVm=tzh=$kb_Y!MQ1l@$>{8v=wjk-izwX|_FKQq)bmA0y)NMrcz4D8Q@-B;23;#pH2$(dKncfn#)kh#?%|Ah!s{abOd8sDYTmbzVv^6jvu zW1DpTw%p{rebR01?TM&?Z%tiMiuY4e9;d3$&4q7RyAs{Fxq*);EMnU@B%g#_ifNZS zEPFjPcDL;Q$1#GXm5?}jre{$RA@<%taEXzERRl?t-l-dyS%f|4}k%$}6{P11LsZ2!O% zPj|MHDdKZ?sZw^RM*Xq;+Lu`!GsCx(IPSMw8Yi2T%K2{L z-eIWduV}j0!Hf2r)j>5i+e=%weM?@hk~I|J5Lzg_dof!5gYB^APcB)FU5wp{o1NB% zrTQIMM`VpFYuZ-bdRT45TQ0O0j+I28;ddvi-|yJ%CAFNO!OXnzqZm(sM5hU_tS(cYtY7OThU(morIF~x z9&LvQFHNm^(~h}PO51C>T$CK4+00hs`^dPF$!=y`qwO;3*~@;viQYUev+$m*RqO4P z1@rCjFq%`xWcqAo}?khEqwIZ;JfQ zVrfFmRPTHqpFJWM*LduYb6(LFuc?7`whr`vI20{IKp5F*?^DDKAJ9Ye*{QZ1#wK-N##=9@+3t1%VY20yhjP_SIuY5GHF|P7r^U!@666BWD zC(o1@*59G~6c9Pv<43mK;oGQ2I)9dX<6BBS?VNDCDAhrUpSp=Mjw*EHuFkPZ`i?33OeNeicCBF;ru8U);L*rIADVD(l(R#>#eJxjxTC?oLlL9MVqw(kKB&!D6;+HAbCNamapAF;@f1DuAIh@je zOQJiWBj%`6Arlan2R?$z8BiWXgln1g2{lDAaQ}K_R%SG$f$wn%XR8#13*FrH$IWR3 zkL%)$^xQjMr9Ro83MvC>R$9fiQ^VITu!}$5>BrCvS8lt0>yw?QfHG_OEm+)hU7XU$ zp{T45YuQGl{(18-NA6HwoaRF|>2WswHFMLuqrR@XeSQd%c<^nN>EiJ-)VyN5u=FTR zzb>hH?;P6RF-Th{?@AY0Jm-3A$N~?Y5nq(Ji}Yq*twjv7gON>*0Jy9! z3$(=LJ#DF{r3!LS%Y=Z!WMIlGv4A#!5FtUyx?u~VNC?~I#mJdiIPxfBJ9NNr@J{RGEOR zW!~@=KKLrfdVeG7N!@6kt}^raJx|O%(_ zkIxm0Unmww>Up=|^paHMr^(;HDjVPIkM>C;(w-X^=lvr`O0R4s;OseYv;cWJVn1{; z{96m)TD;9t=HO0O#9VGU8O?n_O)X0+E&b2m&sz54di{OyZKQu{`f;}uL@*#cI2NR} z)z~4-f@rlwSCTlT#P&Rw z4OEv%P7FPy-+*v2LSgga={`cAU2OLEP&YE=9W_8T2O(TTPaAF!ULUIW#3T#nC{g8c_WyrS+faDTW_-x_ zPmal%##vXKQLQ7SnTX9KKxrs(iI87_*68hli#>!NtKVgu3+ImL_E5p5lR#4v!2%G^ z5UIn2EDh)p0d?!?#Z2iSR+or~Af7D(LX$u?GJ#bIYUdSvXq_oT=jjZU9${MULQs06 z#Qgb4>EnOTBW-E^{-)4==Mg#q0f8&XHeOp>BOzGFb7;O%Itc(jR3?!55{bpGw?18- z2k?Kyb-@(3fV`cZFtn7sPy!;fl8KFtsvIJV&YbHE$fYAYG?)B@}>Q<%1ZB-KY&PNb#0x+k5v61wtN67eLJAXFtXT~W?)OJH^ zn*#DEq0)8a-u}M;Q!gP{o0~&!h28A@d}_;)HYlD+SYWUqk|o=}MaM}?={*H#3Tx58 zQCkjMENg-u-w2oS3p+5Ri8#p+qj&?DBZB?$Hqc`aBbRS9Ex&(5xsh<2%WKfU($YL6 z0tD$4j{HOdaBi4&MQGc@hoQ=9x&8C|0`AlqRbI_H+Rgnhsf6p~vexeDB`e8pl`|Gc z&K4*3^(Q43z1jWAM=;ShS}5%B&f@Yr8l<}T#&?UIXN;p@+xZEDHCemsE5CV z8bSpL?n;sgG7_%tX^(F@aYA{pV|vE)O{-OE+u*c-v+tft)}x2~_XZ$f^b?I~Zm9w} zC9O@56ig~mjELHQq5V&}18D;}IhMM`&{ut)uY`g2r!VQbOZ{E`pkF`M9RuX%fqdfu z#((P_-dvSxmAK(ld&BA9hj2Zo?T^Jb>Z6Q##2uUODIR@+pt-Tu054#|TjO-uE`8G^ z@&^gU5W$Lh{F%}}{)^KjCmdo|dmzcKKUcuzaB$S2<)8dkj|g!{GC}@VK&czwFO0j1 zf`S4Yj$fY@#wa|(1Wp@aY-iDrn(cjSeR^QVT=viYWF` zk^QqOMBodwREQ3!YHVci$|zflz}i5}qJSC|={QhU(B6$mZ-B&^NXP+(K!#-Ys}Njq zp0KN8@rQa0LZI~BTtG*fCmbNY5(cE!@e85cr!dXJKOkU(*_|G|1G}_GN$N+OkLIaG zdUcf8$y<6I5@Fe2LH~vj?N8AdL`wpoL2eO#C0NcZ}@aXa54JR?6 zqeB#}`-EHseUK}?H%M(}`k-UEU&W@T-!ync~bBedf0 z#iZRw(EX5&=rZgcQxCE>5Ge{!jsaXeK2<=djS+c_K%jYy;@D8C*9e!vF9@R^=wji3q=jCNhzei*^M?o`#s`#1O07%$pJ0-Xe~=$S`HKY8mYnMs3Dp{mF+&2x^2mBf z?<2z5iL7|&=Ff)<`?j{WvP#+iJ-Mnwk=OCtS#||$jF;VgmN4`byWnChVnPq=TIQ%R)Sry0>)9rbVg})=< z``=e+y{wB71juP-CV)hljSUR`ei0llkk5+{5l+P4u)NHfsQD6au`RJKCXLXOTyNwi zfgk&-^@*$8Bxh)Ryf#RB?LE>~)?HHr{{CL2_`1woWP&lv4M9Ld1g+fE_DevP$WH^cwgChQ(+5^hw|_`Y^S|E z;ISeIN|?)JOq2NeH~JB@+Q>wbQBxywjl2c|z9`5(Ch<{G} zg(o?TmsM1V7vRoB0(MVs>CI|qH8niJ@Etk-=MluD0k9_$)q($|q_qDM-uCb>Fo~V6 zM-7SrY%ovvxaEa zZq0kUzUD^!=_uQekliwmThVs-ec&$*^Lvb=|CZaPF(#GmV2laV0{B@7FM3V*^v)|Y z!*<@C;`;Ym3C4$a`NM={3@63%@-nGaeDjaWLtZ!nx10}uUlH~qJSGh}F(~FwA?pV7 zTaN5`a<9;Syj{B;myHOC|)`!kNqt-O*xeA3MO;pc!W8=hmc{cBzd==6 zZ6HM8v#4mXFB1tgxG;ox8ZRd*RnP!rgJ2Oxan~6Epx1G1QzL3VTw*4i286x;6&OR# zdks7TalPOW{L=V52=aDFeFbm*{`;|c@v-L?7psSCh`~35?ww*TbGt~`J!=evX7Snc zzAl3_!`5Jrjj7PG5!0S9mxu7Qd^*-aq=ymZE|DOHU5K(AmB#yqhCQ$_5}kq6t>45H z1Hxzf{yk|2BI+Q94#ufR)uw=5kr*}st{O2pRgQ1}v(_cPmEUW(87l9=KBs}SmPm+o zAecD!#4r_sPVglXO&ane39}A~;E2`{I811}!I?uhpCVVQk6IjSk`V-TRy{wzbs_mI zet$0+F+2j9`!~P$(-HGe2trr;Qd@1UAMfeA1s}fk6|nsQlMa`N96MM!vqaf8fXPKXtNQTM}=8%KX@-+}g#~ zAGr$u=57D!o-Zmo-cZC6P9j32vAr`uMFQnLG@Te@FoY)-LuBrt;Iwa3(>ONujEMG! zHWdQ)EX|-)I(bXwm#wsmV7IXOAm_i46o1f6Yll&Arc5;)vLTJx zqGfl;T5`2E(a@m!1PH+XFh-M4M%m2cgguRdX|;Q@%m0E1q^@*zGMe&Pr0JKvtq$Un z_Hcjl!4ek`jSDiubbHLm8;QX?kThdWg#-9?!!>(*dkzU_Awup;%!F7By%!j`2m2uU zPSlyuRavF>j(_>Gfzba#7eMyEr6ChQLxv>@HpI!{110Rt!%mq`@Q$XG$66lrI_aACt+H24MpY^mr+&{ z6&51xn;@j;pH4HpBc}(k$&hn3AKici-JRcXlW!MC85x(l9L+xE7mm zQ9sGgygzGiN>cGt*;E4U8r%6+%7GJ{eGVhb#^EC0N6{J4r$Q{zH`#XgG4{}Yp2@Sw zPf6jlRn0D|R5<6zB9gQpS;c^8GVb^p?$mm;pMCCzgkb2 zOlbPP^K;<*-S2mLU}g3lFAqJ}XJyx=xeorHivSjEgeTRh?7wKl&Nn?C7l{lZyJZ%7 z5sw)6bF~bXh_7A5R^NR@O|8yRbh6FUBzlh)lqGp!J)?MVqmy=z-j*zEO#0& zl8uf}h{hz1wAOv$-{I-`wKp+hwn0F*NOYoCF46uvzb>CDUy&Hzk*Qg2x2WuNrny1x zMwDCKKa|7b_tFp!dmBq)i*g#fz)vM$>8@rtQctsgWxqvKvQ>qhL>mn!t*)t3lvK|O+Z+AY?z~7ynH}@s>38l4`}BR~YHs`UJHHn* z_zZ1W)PhCm{i28JFC^K@s(L+tLNnM@nBjDrjCJdAjmW|-_Om5ro4j}J|Mc0Y^TWrF z7B&M&nXN>WDgivgm2Y`vKb|X%h^=b$dD@M0){2{+Kh@qgotBbFp1!nr-K#A4w#OdI zU)v|frh+a?8jdgA@Le=_*ZdQB=nQ=a*8(^D3t@+6-b3fqXcg_6cE{%>DNy++YjJ&K zt=DW1VVRfn4e2gbD`#U-f18|{z3_39jDyqz;f?mGC!WQT-ClMx^C>oE#PzA^g%_OY z#E6ykNXU4T^J5LcSp+TDhKcOe*jK7JBJ1J$(=WI_oeAKSEdV9*MfL#6eU ztS&dE7j2sS?ISQ#I?%vXcT+2S_>_N8OwT9YUc~HVIT(gS2U)U8y$P9%%Vkd0vTtsA zDb{7S&oDL7C!F77^d;#?{r??i%6A$S9 z^YxlB5-w49Qs#3guPwaoW$CCK0yER{{_O`EtD>_HS8B~{GM}t)zp-yb ze?8e&wAojF@>cj3<_|uxmjt3OtZD1jh}3J|)M~V=(cHQlyezjihnt15gd@}Mm5ny{ z_dN@Kx#Rb5%J;H4BIqC6FrP;;6e1F!m_>AEJF!knuDtrtu$78FbE$P-hlsx1(D7)# zvlFcwr4x-uA}>U%I*%*q_x}##k!C64Nzi`tFi&u|@J-<*(eOYMd`3lun z0xKT`E%Kws-pvUIs>PJ`I>%;5k%x?}mM-)2xzp|nzjIcY(l)2ry-qP9e(WemUPN|s z>GS720DX^WsQvzPK?B|%97f?#G zOx#Oz>(7RQn&VyrcW)+^8(vRZJiybf@T;I#W~{J*j!M9-zP=L|t)cU6)wT#8S-E%k zC}x| zWE}ZWSoJJZE$EIye2q`NEdE+A-sA*`afBX;1G6Pz_1_cKu$z z_aN4|c3+4S;VQg8H9$mQqx!9>J=0H%yR!F|rs3Fe-@A;( z9h2i{ZhyTrR_f$_B2@clj){Rg3xiU+(fMrhvYRD&H_JDUl&GpD#(p@J85HCr$k1fb z`8O#Qh#64foxpHcI<<&i$a6L|r$# z(&p9|^>?MYTY`TC@O&?wy8muQPik*xo$gl7Ui-3W^OfVq6D>sem*)jI!-_1R^DKl(BpI{boL;YyxoeS5JwTqFxq*m6(TE? z7zPJcr2@Rra?ilpmqbGLJ{#&CB7!4|pfkR%bKj*sf{C-?^{iD`iN@gVj9beCVXMnO zsb(a__T+r1@)K~GC`sK%6Ctb;5%Y5T9w*CO{AXdEbGa8v)cMGUAC&rvFte zmNJoXRkbxsNyVCQzPBlF+PVe?4teQ21=yrz_CIBUMp#oKE z?4P|Ho%g7`E5ClXwpb9Qrz%-vkk)gr;7J{G&(M(dc`^~CZns|bK}6F_)jbpv264ttpDugx zo8=jo$xqC^`;~pF;=<>(OBH{%PA}JGt5ka9(IEOLl+$%6QC(nhoJ^t)%M@-O>U)`rKWa_`UB=z+%9I}V&b$VSr$xa>C)yQ?7)w#d6xBfsHwqww)5DrHpOEo zLz=rkL|^DXB>pZXCuzB1#-ngnz?Cw|S5kEA+J zPS~YycODZQ*zraY%`x=RC7_Z1rKoQ0dX7n;=n>H-r?Q#xV=X>5*1g{)XMy>92cqa<7&69BuGRzRImJi>qME`pUbtyNVU^G|rZenqj%c)w$D+J!VS4|#4Hdj1na z6#9ubCf-l@s97)1t+Q@4$cbh!aT+k^A7Rl~_%N-cbxO~_{bg6CS}O2HSdV-E(w(*( zI3Lj1j+{2%x1JU9Sbr?}a*c-aSrNb4OE%Xjb*`EW`Kq$K?Hqr%?k45cC?&a`)qd=A zUCxBaV|jJPZIg=UJW`L!`I~@X&?<7cai3=e*6!S^N>~`)uqLGt1#GTywM9-;qE4FU z?WP$@{TT+Dk^WdAnOhNSS*ptP+mA2L)tJ9^|CzQj!tI{?INbijCfbtMd#IZQfaOI9 zr@oN-M5hf_aU7!?cJTB2A>aTLT0ao^_y7V)00TzAlp(E!oSl}E_zn;nuy;0GM3_Ag zrlpxaMPO`%4*|pSc1TI}t0CQE9t}n&Fau;i0p;O^C4dN*vRc&qKj{ey76b{1M_jD1g{Eq6Y$bI zv)pX3*zm6t+4q#*WEju}j7li-6G6kG&drAH5ihSnD#OL|=V?g<6U*bV%!`ba(UjEm ztB6b>m{>0_611P>trTT0bVaIh|JDM`e>n_ECIQ7k>J9^bEh4TBD(;v_^ML$TI`fTX zwQz4nL&O!*DKQ?a<5WjJvwz%@z+u0&y{4#h?U4HwEb{AXwkDelLP!-stfPw z;x#wf^r>04)0LQq=s~ZhrkQnhBgB<#4AQGYB=8~Q$IE`v;a&t68uWib?+!*B3;+La zWGGsEiQOdhmagG7*9zqZ9ihl3Ds~gvtL_H;R);r^blkb&b%Eb#rYUEb#dGP_m$hKy zSv3x~ByYbYRbe5$P`fSNiT5%caY!O7efy0z=DM5CsA^~sBO<|3Pd@!e zNn#6-=V;+AToOT^t`Ey#D+5cY5{Cyg3v>rwyvW1l&AfZSxyh zB`K<@A?OPa>}4%2SIu$o?o<$#?-3NFi%k1MKljpgM#fAevaT^B{D{TBjS|V5?m$VC zkTvsWY;%2UpmOoDJ1~CX@PjT-EdRH>^c#=j0dcp8E;*OhhQpl3G{e>DlC#!d#wuHq zSVUeg{w%lR_eG98~~P4$LwwE>nKb|8Ly(g zZVx=sqKA)@7^WAWoy{btjGAX0b_eJWfH>jJD}kO9&OfA^<;&HsL*QJE@y72!x)Bi^ z0r6X$tqzC&c6|ys6A6iC(}#+2piTQ?uqc5RaQd3|<0$En{|DSCisy_c5S(3&~K z@}z8)WW~Ll^c1z!y!PgHwbX&UZnHE?Ps`~{eT_c1jG~_~IGy1txJ2X!X!$q1)8BviM>DP=BON!>t&?8-AkEWv1lnVU_m&u`|{olU;7t;$=Qc!TS$VTi zCr}|o02|6!`1jsYH@1h{j%~Okd~FsAvqs66_*Q04&S_Z9-e?KJz;!;9waD$~LDCLU zQFaM^VS9Mfs$WPUt?nn#UeKAV(u+GP&C8totJe1h36<`rXHmXB*tQT88D>0e`p|a1 zf?C7cW-w+YQcKXr;5qa5_U;{P1r!qNuf4~{j8Eq0=YDNgyg&Ge-}hpDk>wS)$mer> zwUIA%4%UW8N7Lb>B1$maV%&urN|j}J>_HsdMG^bM*W)MdSR(urxklE*rW`mYL?q~W zyDF?u!7(v`@QRX=wBDE!(j-hl*i>tps|CQ87Fr*QOTr14b}O~0qi6Jcz7nQ2th zbvV>g)#Y&D^gL1)b|2wF2Wbc5DU!NM-#pk{Y#^|2~B857adH2|prwU`{&(-wx_J#$g!C`t0 zPPHo-obp0tAB156sAmYjW95Oj7<)PlTj|l97Z6?8?T862sHWIUduK5vnaIl`=pX!A z6JeuT+%FHZPb*TcMnwlE{_3jCbBdM}swfo}4fNJyI1qLH{zQvW(+x!lfxw+5(gp0- zq&vzR8gw2XVIO;9C+_+d!JkLX+iB6wu;jToI~$-r#Og&3g3&~32zB&3g>xM@n!oEk zzL(I}QOET}(!Ii#(~AFIQ*k7@$Dg_jHg7g;aCyCQ`Ci7JBWLbcICGB)XFE=O-VGV$ zjP=jdHnRsj(%jHiVe|3yevIpWchT;=npal%zV}C-wvs))^R6T2?bne}dOrI5Y~928 zuP)l<$nsSV8dfhGy`~iYVsx0@Q?c-Y#iwV7$z^>IQbROddsgIwxaoXl$h<4Oz(hYg zCqwC7k;(1M_woIwDyj)5iZk^ZeAT}Nc)w14yZuJ>aR2nQ7th<=t(<9IC%MhnRfq3q zJWHrHSlvo|*dN1k{<3_C0%4DdiP^J)h0qrrFY<>d*$(OmJ@Fwf8dsDC+t7HF!!Fa9 z0|}p1W_7v4ql|#Z&7oF`Q zlT|`$FP6P(PR?38QTEjUmtCaER-wK%@Z{=oB#t1_d! zaj#s*o5($Zq0d_>>jkLdhN3?>XJn95mW&t-n=Vf4Kj2!S3CEF_P~gbh@+^t=l~lI& z8_6_RZi6uw6&1xsPJ+6ZNUjtO+}gh1NQj-bX8nsv*0y=9%X!)^drLPM?!L#I(CZ#s zFm*3nzlV3`QR}E%MVpKJiE|IXvVN?)O4P>_7NALKF<7q_6+W_UeW8YY@<*0JT3VWb zKujJ#!eakHTGr>X*=|+|SFeN=nJ2equ5P;9`MZ4jDMOxhc6HbprRdjY9uiUGV zypa8G3yT0TwJKauL2HocuWsv?>6RQr0p|CIysh=41E z@PPe+-R;}d3@(y069K{TEqtdEs?LQl-Ed8txNXMxXpl1Oa(`2GG4Dz+d)LK3p$S{# zPV);EEjGpLk#on}oiGll$aMcunnV1e_6N7AH&Sw+DB&(F;l@9F@t;JHj5itn@rzRO zv!hsVhq&mR@c*Po_^4JSThE*|RkwzbQ^ho2&o3b&$IfW6V+q zZm=ou6WX+tBe_?FNMUPCBg))VApzc6)u$3Sw3^YU@X`DFa?)%TWoF$#{DVf6nc1#s zf~3bVD?t@Q<@|OuUzV@DCe&d6ewX~L-JS2b%f8WN+6tXKFwUDdUQy?AvVok5oR(?B zIa(nJk2+2o!!0*>bND;E7{77SX6EA`WMbr}63CB=-O<|2yj^;pGMSDdw}R9*5>|7v z>FXv}Ns=VhY&_HTbB$zLhhv{gc$~Y<*Y%|A*wvG2AL3Nb#kh+|L|$HucXr#}l#)ho zdDYRh^$LSFN4c$6vG^m;^|qdn3WHU*XF1iGG_x|B zo~vgY$+p;YR*^()9@%*Loqf1)My^R}Ehd!Ob<2Y64iW_u--eoUlHcRVXVf}%<0@r> z8>oJa+~OlK1$yi3x|4I3mOq%i;p(}%>sjneQPiA2Cy#5|pOk&?$yx1E6-Mhew%vJj zUZVZMy!>aWTH!yQa=z>C=jAFs^?40$A-{9l*++GpQK!%A(H8C*Zi>y*7ijOcRYVEO z7oNQ6W1;7Fpp>3rK}=$B(K)$eh%DpXX@r0}b8-K0{rakE=y0r2=UviQ|5!fq4@Rn^ zr!-gJ*YlDM?g}j*%i8&cFSK0qz}=kWQRCGfxdo$Q@3XX2!PGZ1>XeU&DRa1TeO~N- z>=ez);KG%ybhpM~x#w}1le^xx=K<6-T@u z2yNk2OR65b%975U`Y3*X4S6ZIrYjfsT+#Kp^8(vkNrgf7&U(y;cZllI^Bo@LV;qg5 zwhLyz8AUx;OCSDpWXCY+$H>{pXCphlh;}lM^X)F9{^fq|Xv(0i$`XE+nQL|EFEK{Z zQMEZu6OxgHL2Xz`z4KX4=l5K+Ha2{sqD-8loSaob#E^t*TaI*sB@wh{I}V1?qDKU*>uY6@?)=qE~{PbtTuLhq9+(Rxi$qh?0(M0 zsi3#Cw2SsTHsC4W6WR}i{rF`<_55f{S${5Q=^SdOrVsr*Yj@o0Fi9hef@$W*C$`?a&&%mMAJlKj;HbD5&Gh>?F)O| zE9R@ncMlw&B{>}OxMQ!ky8q-oGFvvqp*^AHr}=J2UfG}#e8s;B@;IAUyKZ0GdH9$ELmGz>u#)6BOJnuTHgvidBTHK2a|WDTpAvH z8XNbR!tj{ng)Mu?1860Lc^HlvPdW~+kF5`roC9{xhMwgqvltW%Ex*j?8+L`};yntz ztNV1Snf&$VmBL57%0C<|I34uSgS=h(Al0Kr>AG94MorQlp4qxL2IG#h`~0k|?FrCV zCZqRCI7{o4MG>-FMfGuKY<4SU$SxJt(9UvgK3~5p8}N+sv4$FA#b4iKpU1w3-M{I( z9+Qegx2M$d#viPTjJ!Fgm9$UrS&UI<1y?BY9XoB5u)1?dwBVj1pWGqxby*z*JF z+wN!!ldIT26}oh8P5GRZ$~k7)ntSITr%!X2xhd?*TxUD(gdZ>v5@7n+brq9`&pzOe znJGvJ$v#U(e3?DNe~dLLix2W%FGwuoF|N+EzvKiA7BtZ_yG@?rUc`^@b;^7n8crk# zV?3G<(n5A(exlP%Zv}+VbRi192wr`rE%En#*fXf@^FDqgBqWGjkM}QOvS4q1Ky=>l%9u9C`_?UCmfkx9p+R6rUl<<{pAcOLmboLq zSZPVgTDd88OX$l_34{!zYy7h1{y#6_sJzSg$zSCa1*x;AC(>eElzwSxsnTPos+n1= zR+_8j)GJ%gORBqkwRBbEOm+&F?houobM7_qX+cEVELUOq{Fq$wagu%B0qT&+bNrT9 zii`#RqvjwD7Bap@9;u=9-&06i?<<%0@hrM!(vID` zH^LuLxdb8dsiYQOQPIv(X9flaSj)&rqjo1m^KNnxKXZVZk4gT9goMP$1D_(Xt(v?* z8%Rw{TQ&5~uNnP@;mUus-1bjK_=A2H1a$*R& z7qoWQAg4wqnFwUf)J{%L|5QLY1eT#>J|(KXNf-)12YmW8g>H*!q{qd3@ylA zB&#j2tn55+fEJ%`vCz>7#T?Ndiap;slsj|z7br4vy027 zRF04LaPmCi+UcIxcD<9%W_=4Z3aHZq_fl;%%mU839J%h9h z0iSnj%9g+CD;p?A9$r2A1~PFO#P>9#0m5QJ3A52gl7IEpSqbY4jQX1~*QUUB^(TW#2rN1ed0W+365B_#q{u4RU;_U23@j!z?GNmT;B*pW4eb;`p?2v zxRi+0VdA(?o-;HtHy?+u3@5@JIl_<5?)nPK6lme=_E3&j$$6{B#BTRcMivj&-UA0@ z(Fz?l`N$5LqSha|ZCkc@fyucFqzO_8FIhfDs|)@8{lwe?j3&->`)Q?9eDhQ9@LS;; z)k{=VR61oIT%;W#3LBH&y?aM~`YtUoWj?hWt*lJ@60gWleH1SE0kZU z?8g0Q0J;Dvd3gbvZk)B)HCj_O7_6+Uu$T%UODc6-OsC!978?soMN7+8$Q`R8$)gDz z`Hu^7myyGoIpI`ZR@N!p6F@ld-=oOL`+%MxVb-TUJ+U|?$=j0r5zjce4;sS7vS3;uqpb!zd z1AGADXN>$<7041ppcK5AW}A{Fu&kVbTk*tqXyTmk(16|yy#b9a1!~e zLf0EO-Tx3IdC^Ai61{~^J~r+sr1D8 zNY~XaNQX9g|9y zPxlr(F`y5xhWh(bu9+HsNHtZR4sS#vv9X1%y99HK+K_JFzRh~=5f(ID=(~8#u0dKR z>bWKXhh%fMu_CNgSFtERsxzYAwCOgE%Q=RP^9h>d+x|`Qa+~c_e^e4LA;j~VMp*4lVjTwcHV-P9qT~F zzyJ^A!xhkJW^$0BeHBpWmdI(F6{zgNF_sfTW+NaSeOcM35fKV_n|Q=lHDkR$o6!77 zA(1acK@KM&2}Xxjo#8Sjykjde;0Od((tQ~w!N;a+tG`}c+G~oP9%B(&CNTKx-i7B< zN+3q|46MUc`IX2fWYm;j-y;xg4<*wAwu~nilD6h4!4d--b@cVXahDk97=FI^(NUum zjSTL#4E^U2UoOdyd9a}Hfp6m>N{rL6EkBKkxj;B;07@#GnWfZaxt8iVW3)YgbxqA< znYdVdy%}DPkGyO9FEd)YXkK#(3VvY6AQ||XpaGC%w3TN224Vs^jLg1e4=;)?h<1I3 zm6OmXftn_VC>tv54Or8Y&+7IUI_5@GANaQxV2YKKi_7rZwVm(3O-vxXVbTzrFad?( z$bY~GV}PY$86?OL+}SYV@Zf>NAb{ItWo3nJ&b%}IrJM06U{Lx-fOGgB%9Asozy>EL z1F(j?!_JTKUz@))MBm4zy!;XtMw4pJ3``cCEtpRRL>zZDoPK@{Os&3w!hl| zWKjv>`d3{&Ogx9!b&p#0USStF-f$ADZHB>iTnsA{r8P+em-&^JN@C60MN5X+R$A<= ztrHHoJxB_VD<68ozRHNL_B0|Mkkj`NOS*l^k4jY|ZM}9?iH95+Tqq7g6l_$R>2&x^v)^$S^tM*@lFK z@a+&^YiMX7qHJejUy8-^K@36wH*o*{=HEZRTOXcYJqnFMf%6YO1gQI3;w`{mGb^hC%@$FQ!Xo=Io>MUPMV~^L6&dNwnKMYPy*krdC<>Y=6)}!*~kk1f@M5dA2JEONYSaF8)+@b#Z_NdWPI? zm_`o6C4hzi??(&QT4#w%j+|~3XDzW@VI|4T&1GTo7)P2PL5dMpOq_N^TU>>%Lf%=0 zmiqrG?#=(XZnwApw_Ht1p-7TSbEqUGX(TF2GzkqTNfM&aT!u_(mJ}gMrBaBb%o-Fa zQW+AOuhOiE`+4rYzu)^$xc6iK@Y$aXZ?D&Ru63;AIM!O76paQBpQk>}}Q5}6IS>o-7=6k}mN z&HUA~vW8A3Y0edX>x$l#(l*4DgxwJlkE4{Xl*AV6riB`IRNvnTn3;EMN5bmW3&H;+ zr0HALOpB`mNx+&eI&TmEM>FrXx501QpSrfzUgKS>bI^+0@dmEkvaMA9o$0jq5hQoZ$Jyr7X z`{HvIL+%&W=LgnP$$ok_Wpd5^A_Luq)eVM+2%5WhOZSxVUua;aob)>V`g!=JhOh%X z0cJlpP7#F)<(N{W!7qc*ZUNTG9g5wl6GSn`out`(=dNAtfWmgn7Fi_!WQ@J%<Y36;3C;9VxZ!ji+d|yr=PcZH{-oRv7UKZ?i-~_RAOekT&seU5UMl_YKY24!eyz)D2OA#hK)#mWy?fckV(J(H z+jvsrwJtBYw2^o%O4_HRRgw{>GOk^VL;Di#(;W8t7cbf+SO&qw{9#DKkoHnqiVbf0 z*fE-EnytT5I@ypp#`S3bG{*NQP7*k9DT`KnhFDSXgaWm8&xP?SZJME(w;yjWn{aUl+ZhLTVS~dF6$rYu)!& z)=lVJv@IG>1_7t%!_(%01Q_I~6gtR75yOlW@yiSE>5o0);%&XjVdAT%CI+GOk&==U zMkz{vt&-Way5>=Lo>#a3J67@Dk()}j-ba-4&yF54^+>=gp8;z;L4J>}d996YuA{Y- zjWW}|#0I)vCXz;6-tO@hvW`n7ZxsB9JIAQw5bIeT(-cfDVrf}!S z7v|8;D8Kg!qY{SqlsRj2b>vcdpYL_HF*c|vjrs77s~pd%dAPmxOTUBv;^rv$a((ph zf&+r0BD{y~nf?BVc4p0)-@KwTcyPYrJy5W?oz@IOd5F<)DifWzBxWQfSUhm~85IBp z9#P~i58ywBw?Brd@+%P(|9mGv{8tdli$wNRH*i9N*jCf)xNt; z0$6h7Pg*85GZz)HMww-?CYZjWf8Iz~x-VfvsHlCQl?2O4b|7XIa+f;;I3cXX;sS~0 zvzE!>#3V5k7uJEX;vMFB>^?AakTBmQI|iIsbgqzzY0QEM#I2HpBT4ve*#A+++oa=L z%&-iOP~*DMnGRjsWmc|6iZ<$=+>>9fSIi{QO5!I z1<81JLDDX&oqA54dT@crW>TY+elqIa@{f7Lzxh!4xu@nbl_1wXkOr~&lcuGoE%`@k{xPrjvP9$*$K{v-{JF0&I92u@1s$0@j> zFUNpsWFpN1z{xiLtvNk9HnJT@a0zUO6QvVavTHabu^O}GjbYjRS0!nNl|OjPeO4qU z6+w-Vu0t6I#5{7bzE8NJB>hK>kl}=OS2Ng=ot-^l#;6S=c4>|3F7n*P(JIyh7hkZ= zDt4X5%dEt0Ku85STT3ajw& z@CrTKqWVE8R-v%B^(;fyWAdHcX>CU?E1M@&kIWqwJ$q*{|WTqiOHWT^1;m{%cAG*OQVzBttKri$W&BN4q|EL%*dDhYCo1GxnK=lP+!H|!+KD2t(~ zgq*=q%56?eNXV_MRDz$$qdTMi!-u`ljXG;Ty-<#MewAO0t}}Lht*Qv0rC$WOh0DoI zb>!X#kgWRo9!_iAR6CxS8;D(Pz=R3CMQWphaIQQb#4Q>)V1Se;%X#ttq4j)x?4SYw zmQHmyvNH>}ubDluFT&z9%60}sJV#F8PgO@wfb^Y?zlS5BQ61X)=Zm?}S+=*eDqZam zt-Y)BA)`%#E}$Jk%)Ato2Rxr2j9<>Zd$L65e4d6o&_yC28I zIl&lE_X15QIw%#mAfe<(Q2-0&igHl=IS8zgHvZci-!xwn9Cm(w?iT}L?4k>s5D`x^ z6v$jWRGynZiT9k9Z{RB*;iZI;3FKSIpVB8AhcppZ;Ct%%YsCxa|MW*@r2)(io2pXO zu%%DmFtE#jM#mtWuPcC!^UhT`Rb3kJ^y>O>G$1=TXQu1X_@F?D4uI2jpx#$nqFr58 zy8YLip|;t+ilPR=7c8oO+f0x4JX}#?mz`ax!ABj)yG6Z$K5n6W)VeTc3-1i>I!;$2 zl9k}L__KFqWo6y|ww((`_p&G>cZY{}r=8;bkj3ZJt3H25ELJXUT2pK|y^U#X1vPWt;eBH|LEwSm}@n5f`*3a zuvO0+8~qr0!Ay7tCEh8hr>qqKn@m#18gzVwA$?|RoLD_rA&5v8Lbz9-G^sBkl_Imp z_pFSJ9j|lN^7ny}l zpT7E@Is7hpWo0Kv*=5n(eP%QDAsWo!al_N?hRi65nbHq#bsFcMch9pB=)&{gr-~po zC7qv!36^4%h1I1MQi4K(l561JK#j~md=Ux>G4TZ*&R~Bj=-c*Lo7!5p{(&t;=nOA> znb2EM>DTj)YZYzK4v2bKUhcQ0=fsHqjKPSLctMEa&(^wR$ zakq7e)yni>>{|0MaR6zOfb8bwRS1$0=4cR*oPCP&x~HkRLULKKz>l9}vvQeQb3yL5 z=I2tP3>v%cX-_smL4p?1PH>Zy{I!~G-=F)j#<2X>#EDmcC_i zQN@L&QGC)NE^TzmiEmPtC%t^i|CI#- zmR!)nf`T#xC;#*ltl07TQ2oR;#-}GwB75lXUB$ISct>ot5RPE0_1s_a5Reb+` zH_~^Zcr|2Tfw8$!Qu7XU6oX2*Huh9LL#}MT(*?O7{BBI|p{lB?+lzwEa`Gf{k$Utd zOt7PMiHJjk$H|jdAv1^&@Q3YBGj7E~IsmtD}M0wqvhh|o$s1y};hIsE+G zB*w1_EBpEzKaF2YLv;9&BlyItv<(}L{HRi}q1d5tKvK&!zVuPU79$Xu$XnF01KGw( z%$myc!3wY+D+QlH%(7LAh*)$i*#53o=a()E-CEboWDikycxGg;fZH>iiJOd-6SM|D zZ%?+HJgt}?8&DbWAEdL)b(|0 zFa^6PS#nq8aCXWv+#T#9)lH`+Er{bC2{bB1cB~MBn&&slIV$@4`o?i-1acyC!b9z1 z<0o4>?8F;^yx-RPb2TQ8C1z8g@1K@;Xg}5Xt zy2LZ_mPtmS1Ex;~VfXL!ENJHIbG$#acu_Jl08i*qIS}HU0s$=GeuZqbq(rv(%m#07 zy={NLErvvxHZmfgFXD|&XCJKlmzQ(IuO~Z;#mtb$-#^xc`+Rv>Pc21xSBM`#UPZ;( z)vHz4$d5r!&lxN#7M6kjzM91@b*Q%n7&Ey^R0l`|fyv41GOsRe3ki0*t0l|PUa@TD z%-bysJZUiCY%R`Mfxlxu5J7JLjOraR+oscPg*GEBaY&(VG>dXo=|Vb5Q6#bjilAJ- ze&P5W9FXcb8>0_?y%_MRsE3-`UNV$ST0XR~+pGJ1U8*ig5$Oa0!uFb7gnzcKN+S%w z&Ex>S-FeHTXe+-fNdyhJcwtuIM@piNQbWfd;2ziWr_$Hf5&xN6Ly@eA2?$X|d;&Mt z;ZbbOv+8*H`j^!E2^u%02mieZr+sEYk^&!_*?s}og!-$iw`26|ba52<1#H8ud*M90 zcf1~ixnERNAC>X0f2tSChOUFP7FxAf6{D2ZZpZ%r`>Z@?4(Eq}A*K-s0Us%J*Ab0k zN2Cn4*R+}Uv|bAPgRQ@eV2J=dWI^cHO^RWp6{y7X!Y69LlgN^LTDl z6?7s~r*_f>{g3E^MpbV|VTrBK=Ble*&JHO=_~uWaa)GI0Yh6gj1OdNz*GBM-FlKi+Tm=8#ll4#BcljJK zm}#+l_RuwioHh3mFLz_&>==E&&K4g=pX%RxRzEJGM;ce zyi`at)Bax5(&7zuABWHp`F``S|Ljs9VjgYA=X&uh=E>*J-8a~24Ex&B`G@d>*x3z0 z1MVVm+pxiqARyiy>}@WE4V;P*f1zl3YJi%VK4?4|XkcVB2n*V-Ge$}Rd>gwX) zh=xak8#U{YZ@Cn-jwK>>{HN>-u-1}wysITWF4St8&w~Mj;S4!@_5AH}UFZ9ZpZE3n zmatdQ?PjC#n zsu@(NudbWaU0rCBbSmb!s|pNG!O9Djwv8+PB$248&oY{}fYU5M0uSgC%jpORWpQ^W zDkpVHuU2_}t6Z&4dDVRi_0cX>qf=d;C_r)K0WwFkth>H*)yy@o3KWXIg?Svg|4=Gg z)4+DYfk0u##aeIhQ z9n+=b4psIYmU*EUWF{;EZ!#bB0d!Cv>m;{p*RGObZ{FovughH6>}aCZyGhYOv7P|= zx3uBTX4l=b6XF;jQ-)ysS}4fFbaW2xiEdQw{Y3%V#e~u!7L~`6T2cWK!E!<^VsQMq$7lE0e5DGaZ^KUhtD|Vs(2cuSJUWwH8 z@rv$Mn2IM5v(xbtC$#b22{$nhH^54Bn`4-XX=99M^!WABC$GR?QX~i}f=QKDW_`7Y z?ey37r+trlXzP$o%|4Geu3UL)MKLyK$@9og;e)5Pjrrv~C%t-v_Qi|E-b=Ba?Zr_w z3wo=xX8o44v*yfEO#k+wCaQMpkDBr7%a_czwKWqZ1lrpn>^X?K54qfDs3KE2v-KI! zE3CALg$3&JDo5e*Pn`n4LDS4ARh@5T)l!nSPF6Yov-!^4w$whgNE?Qw*35QM|CZX5+Hks z2I>})WpnN3k4t8Fyyl1t17N6%?rd-0?%lcxy%VT#2~uUOQ<;Q_WWsqC%tU>barWOo ze>5*Rr($#yCcFRH3k69EG<7&EK!1ZKVZ*_m{JJLV>WrRF z?e7@Bf~pl%5Tx_ytNNn~IKzTZ?$xW8fJh{aT~KYe4_IJG;;67cf|I?n=|5kj(5!#!oz`($8ANceocbB?e8_6zH-2gFAsP4saZ0 zv=#Ps7^8Z({d#kH{9xBeVNp7}i}Kp=Ip z`p__ALzFY`A?0>K7T1H%Y*+)q(uL8<9XLL3>uydF_A=H7wp4eDNP{At3hi^6dsja3 z<8?#m`<^{|3~Y>5dG`3JOJ*9}j>ZcP$*%YrMu!d@5P&Y8Yj;d_6Xv{geJmNe9FN{8 zeykYR3iLBtpZ!fqT$gUDdQ+(pPjNRaA>;hN*vak{23hgz#V4|hO3@PR-&1;*WqXhD zL;YK;8-#V}wh&ZN%6q2MDj?VZCPP~StDG3Ld?+$fT+Bre?$ahPu*2@1bdQWMdqHt@ znu>mMgWHmW_i_6OfOCv^aA_UPmw14`U=RLTn*891jYi3n%L?~nrkKn4?@}6RJJV=z zz))hMxbbkeJ<{dn6?^vl)1W!X^xDj@{oKpB7A`KDy6c`Es`Y)RMRYqjYpyBd-@ma*<}23|KHw&c|nMOwAh)n=RMdac?nX*=-GKLf5W4^HhgY@vGM za|N%|1?ytft!=#)Dt}rimm0kGRLO!Y#|c+F&s!HPP70opGNN6=h%;7GXE+8#j<}@S zuc!OGg6Ue_bEj$h4Ql+;+8m*g7#{y$pPnPV@6IQMXxQwAw?W^DCNW-c&az#gGp zIFFl|T3>Iz{Z|jrJ_jQB*fAC1W#6%>?Q=#(#?%pq)!DBk$@ubdhso*&5~&bG#eDc( zFJC&=zBk;g6kAYTS^1xC0`dg4g7DC~H{DVEaD8@PQ&Ur6wihUvTeFAgE)lL}{^MPz zcrz)fv#zc#4826{UlVDupI8{3W&GPo+BRhFYu;d_FbS~4l_{d8ifO?kBTOUnY;3wn zY8DShu5fkT zi@owWH@Yf)U7sDr#adE3($aKk>X8H)eJ-dA9_6QeWqRyXiez9le2Q>$Hee1CAX-I5 zB|4+Fyj+2(TucbsfAr`c)|c3wrSwM(gMpazznuiV9^ViHV8$J9F>|Ng4bqTI1!V`(Jx! zH}>nWNA=dctF6^M2~kS_`sMqmqCzlMR*WnM>!By=7KvmL7^MvoaYopvp{R8rPG8%I(9BdB&$sJ4}j%|Ik;?zWMS&xDJ< zfnF8W)uEgIc>C_mP|t*q!Q)q=y;FyF$FIvdxOmZg?taNmop1erxKCRYF#2$aM_1h?{ zmlPS&CZFGR)^DV@FP>}2u&E#U{sF-d##gs{(~P+)S#52oOl#f-%jm>_f1-D zL^+S^7=tFP%Zq<4kKrndjO@+6i6A}@2F%Q789Pl(yP;>$^+BDw8x&F7wbOEgb%_qB zG-r~Mc5=%hP{nNn7{s%ehdhp5SP;OR2v!0(ydLdqv`710YI?fIZy7J0QU>Px@mus6 zNdNZ)qjOlbE)A1bziLWSaIlQ%K!AuWhWNHvv?u^~uVAWTw*4A@J~>&+pf=)cg>&Yl zGXNPZ*V9Z*yWoL4Q}*^<*Nh1<2B!l8I*eljnv$w&34guDh!MVT)2^-Wen|$u<*`(s3{*OFP6xV)7Zcx0i&$N?pT@r6n}t6!mLs zF==UOS;2B1F42q0^|->GV;Ss_)%J?3>Ca0NP8Mbf{`SbHyp2}*LCu?yF{QP|A$$GW zcF&svo>o>a65?jgL7l$NX~(kermBm+p%eBxm10@pD!tYX&)2g zOyKFBJGy#)+T`i!c5!*l+AW+aJtkK(Nrvts*&YR5%1%76nm=DUX@QKSx~iM3?2Of` zhXPygFD-3tyK8_>>WxAry!2#3(T-76RCGNmO-bE%qxYGirn7SznhH?X#6HAVGM8@r z`Q}y=TOO*7#3_27)?feB0dtJ-dj%P_OS6`BaB#TLJagvEsYo)Szm8ZXChjv(|7fh? zoBcdI!4Ps`oYt=|rsxw67(tOky>4-?2Cj+b8m_ekPYs?)MgLH*af}sY5=`{w^S?VJ zS!uo;*KO0*zEM$8Q&KJluJ@DMu*qRF2<9krWpQ*uLVvaY<8$r^W;Ke(ff^cj=MA_% z7KZEhic=a#*po)@-Pfm!Uk8^^UJ5msB(T8gQ`odu6}LA0PBWcd^XA=M7jfLsA~eU1 zJ3`9VPO!X<>|cdCUnORc;>OEdn>F`DL`1|D5bevRrb5njmhZQ-QXg7>R#aAA=4Yq# z$`Tih=(?vb86EI`)gMc9WwG4t zm1AoAp87i$m!WT<|Jt!CT+{+_hewWd44t%1>-FdFfa8pmIi`VsrZu_sW4yFK>nFrp z#M7ia&P5qCl{t8DkWt+D{1iq(3TVi+l|n$?-N z_F~-cjoEDkTO~!s!glT2RpGY$wrvzkd8j>t7cN-vC-KK`^=tRXMC*G6!-J$;yQW`M zb1Kz`SJ+;%$RW9OUmQYuV3`^=h^V3`7`kOGyGLLezR+Ism8 z2Bs&dOH1%EZu%~oaYXnNeCMr6Q!ZY(eI+w9at@>0lNrfab8~x)c4EID!Xc}qtb7Zz z$bEp%(M9K0P5IL-ZG)S5Q{})I8LcAaEcb>EQ51+-TYg@yZM%u*j9y`TG=z%H$r?Rn z#xeIs?8;$j(1sxgZ+rjfpjmL#%XdtA9~Bj4htPz?#LMDy(mLIzF?#s|bB|2pcsubs z4&Gi_9T^CjcKz?@8EWx~iCZ6mMdmTNmg6U?cq8?UtgL)Wl!}T9a*uyXiZ-YFzot@P z8!C#*qr}~5XUENR>Cdi zCeht0Jb4K3uidt#@piFW^j*I78=E1z)W3TdLJsYW%ID_pzB4c|aM?ghU^9mB{y0v= zHZK&A6{}YHU#;~GiP)Mdc@81ML9JKuetT@Z{=|t6^je&`aKRYX8qHVCYE|yjhfNjA z5MqVAD|Z}_wYtqcElWm?Nv~ri;0J?;_;lXt{%kxL{nCRQhqIR*vPy#VMBQ7Xvt`QE z!bSb^uBqO1`}TIB;5e|zUb=kwg~Db2F2cW%sYKH`c5KndG;>fphpzC1h9rH|p1hFU zCsZ=*hUq6vBWu@cT(HgLHVeyrJs%$2Sp?<_#wc+3SqNS^;S<6-f=e_P-jkcVRsViW z7QRe&A%#-9=M@zE!}dCnet<2zS@(zCz-{R%uhRJc_O1vgEY1k=Bzy@atsCDw)YI2j z$NwQKE2}(lqh?7-NyhkPh=X9gdJN=4-@bi2+O=BmXjD{uLPC2n+5uul?DQ}UEOVG1 z^mA3%y7}|x^UrV&)V`HYc6OO1IH}_mzM~;JRX1$V;TErOaS7$3{`qmUtq|62g}r_GtB$9Q<3$4_dRa5J_5Onf z{WCw&EElA2v2xl02=ilOJ5wZn`}PftsOR0>U(x#SKZ-V6{p2DJSzBB0+rNJ&0$(0vD_=!Rm(!Xx z(}YfPFG|cj<}lRbKcvVL0|u>aY=r4hEZ_v9nX6Vlef$^BTP(l9+SHD#BnoR9wV=yi zI9?ZPT>mumYs;Za(v3bGUR_*zx)KESiqQJ*+gqeRI$r+oXO*hoyY6}6hD`dZ1r)|E zR@>8 zF}dqp{G8~4&*2j#YFiIK;pLLFoYt;2B(70Yi6LRAWUL?RKWtbhp`L-8&?$GbFiKf3 zIGV~ctM2hjnHCb zY;2GBMEs>tp}-f3fqv2S5r;j(e>$Q;;sOYy+S*ZUa>r?`3jaNN?3f3=mRU;o?%g|c z_Ux|A%n8IL@88CFRiw?$&0TeFj*J}9ll7+7R#sMSn!l?kO2Xq_92ka=t=q zU0Z7)H80};#Pm7!{W(y(g6yF5lg2=J=ul4xmlbr_2-h)^Pp)!4H~T?V)gBmhW&Kb! zdHWLm>8j7N_uu{PYi{)U-KU{8Kjv#`X=Q-iANwsBq#(NbDcpx?XzWHp0#b?odrGF> zuS#P~*g8b|7Hy^=2<$9<^nzazv3JvJGjhN?nXdb1o{9+dy4RN);Q5<3w{;C0HLyL4 zfos*1HBSH*jvhVgwmSa}Q&)v;q&o75b@GwL{z@mOJVZp;!593Gi$;^@CW?*%*b$*4 zafF`37YE88 zz7FnmnC9m05<0V~#5)R?SnW+IFOyzZsaq|R{H!!PI0%Y@OP?bE;NL!5mM)TeVHPOo zuuWw^=(;;ijZ0d7$Y;hp&>HSFDND}#`+)-h0xo=G!*fub9^Q z0FgEg47zTec(XUTU3KynWqcyLhNbRko$~NOUux2U1u0sMwsY0uS7(MRlpnC%L>Kvr z*EXX&p>9azO1@~GitNVMRUa-ZN-x_m!KYx)n~m!ZOis9QLk%a?5OOk$;1o}JFC-xKPH*B8E2~0nH=Bg z&5TD?yTvNVQ@t!wW{8zvJuEK66x@eaQd&AM Date: Sat, 22 Oct 2022 00:34:29 +0530 Subject: [PATCH 09/22] Added Date field on x-axis --- build/lib/mplfinance/__init__.py | 7 + build/lib/mplfinance/_arg_validators.py | 445 +++++ build/lib/mplfinance/_helpers.py | 121 ++ build/lib/mplfinance/_kwarg_help.py | 154 ++ build/lib/mplfinance/_mpf_warnings.py | 16 + build/lib/mplfinance/_mplrcputils.py | 87 + build/lib/mplfinance/_mplwraps.py | 121 ++ build/lib/mplfinance/_panels.py | 232 +++ build/lib/mplfinance/_styledata/__init__.py | 45 + build/lib/mplfinance/_styledata/binance.py | 29 + build/lib/mplfinance/_styledata/blueskies.py | 23 + build/lib/mplfinance/_styledata/brasil.py | 25 + build/lib/mplfinance/_styledata/charles.py | 30 + build/lib/mplfinance/_styledata/checkers.py | 26 + build/lib/mplfinance/_styledata/classic.py | 28 + build/lib/mplfinance/_styledata/default.py | 29 + build/lib/mplfinance/_styledata/ibd.py | 38 + build/lib/mplfinance/_styledata/kenan.py | 33 + build/lib/mplfinance/_styledata/mike.py | 36 + .../lib/mplfinance/_styledata/nightclouds.py | 23 + build/lib/mplfinance/_styledata/sas.py | 4 + .../mplfinance/_styledata/starsandstripes.py | 24 + build/lib/mplfinance/_styledata/yahoo.py | 23 + build/lib/mplfinance/_styles.py | 382 +++++ build/lib/mplfinance/_utils.py | 1515 +++++++++++++++++ build/lib/mplfinance/_version.py | 6 + build/lib/mplfinance/_widths.py | 197 +++ build/lib/mplfinance/original_flavor.py | 885 ++++++++++ build/lib/mplfinance/plotting.py | 1307 ++++++++++++++ dist/mplfinance-0.12.9b2-py3.10.egg | Bin 0 -> 148059 bytes src/mplfinance/plotting.py | 7 + 31 files changed, 5898 insertions(+) create mode 100644 build/lib/mplfinance/__init__.py create mode 100644 build/lib/mplfinance/_arg_validators.py create mode 100644 build/lib/mplfinance/_helpers.py create mode 100644 build/lib/mplfinance/_kwarg_help.py create mode 100644 build/lib/mplfinance/_mpf_warnings.py create mode 100644 build/lib/mplfinance/_mplrcputils.py create mode 100644 build/lib/mplfinance/_mplwraps.py create mode 100644 build/lib/mplfinance/_panels.py create mode 100644 build/lib/mplfinance/_styledata/__init__.py create mode 100644 build/lib/mplfinance/_styledata/binance.py create mode 100644 build/lib/mplfinance/_styledata/blueskies.py create mode 100644 build/lib/mplfinance/_styledata/brasil.py create mode 100644 build/lib/mplfinance/_styledata/charles.py create mode 100644 build/lib/mplfinance/_styledata/checkers.py create mode 100644 build/lib/mplfinance/_styledata/classic.py create mode 100644 build/lib/mplfinance/_styledata/default.py create mode 100644 build/lib/mplfinance/_styledata/ibd.py create mode 100644 build/lib/mplfinance/_styledata/kenan.py create mode 100644 build/lib/mplfinance/_styledata/mike.py create mode 100644 build/lib/mplfinance/_styledata/nightclouds.py create mode 100644 build/lib/mplfinance/_styledata/sas.py create mode 100644 build/lib/mplfinance/_styledata/starsandstripes.py create mode 100644 build/lib/mplfinance/_styledata/yahoo.py create mode 100644 build/lib/mplfinance/_styles.py create mode 100644 build/lib/mplfinance/_utils.py create mode 100644 build/lib/mplfinance/_version.py create mode 100644 build/lib/mplfinance/_widths.py create mode 100644 build/lib/mplfinance/original_flavor.py create mode 100644 build/lib/mplfinance/plotting.py create mode 100644 dist/mplfinance-0.12.9b2-py3.10.egg diff --git a/build/lib/mplfinance/__init__.py b/build/lib/mplfinance/__init__.py new file mode 100644 index 00000000..f1e1865c --- /dev/null +++ b/build/lib/mplfinance/__init__.py @@ -0,0 +1,7 @@ +import mplfinance._mpf_warnings +from mplfinance.plotting import plot, make_addplot +from mplfinance._styles import make_mpf_style, make_marketcolors +from mplfinance._styles import available_styles, write_style_file +from mplfinance._version import __version__ +from mplfinance._mplwraps import figure, show +from mplfinance._kwarg_help import kwarg_help diff --git a/build/lib/mplfinance/_arg_validators.py b/build/lib/mplfinance/_arg_validators.py new file mode 100644 index 00000000..d7398232 --- /dev/null +++ b/build/lib/mplfinance/_arg_validators.py @@ -0,0 +1,445 @@ +import matplotlib.dates as mdates +import pandas as pd +import numpy as np +import datetime +from mplfinance._helpers import _list_of_dict, _mpf_is_color_like +from mplfinance._helpers import _num_or_seq_of_num +import matplotlib as mpl +import warnings + +def _check_and_prepare_data(data, config): + ''' + Check and Prepare the data input: + For now, data must be a Pandas DataFrame with a DatetimeIndex + and columns named 'Open', 'High', 'Low', 'Close', and optionally 'Volume' + + Later (if there is demand for it) we may accept all of the following data formats: + 1. Pandas DataFrame with DatetimeIndex (as described above) + 2. Pandas Series with DatetimeIndex: + Values are close prices, and Series generates a line plot + 3. Tuple of Lists, or List of Lists: + The inner Lists are each columns, in the order: DateTime, Open, High, Low, Close, Volume + 4. Tuple of Tuples or List of Tuples: + The inner tuples are each row, containing values in the order: DateTime, Open, High, Low, Close, Volume + + Return a Tuple of Lists: datetimes, opens, highs, lows, closes, volumes + ''' + if not isinstance(data, pd.core.frame.DataFrame): + raise TypeError('Expect data as DataFrame') + + if not isinstance(data.index,pd.core.indexes.datetimes.DatetimeIndex): + raise TypeError('Expect data.index as DatetimeIndex') + + if (len(data.index) > config['warn_too_much_data'] and + (config['type']=='candle' or config['type']=='ohlc' or config['type']=='hollow_and_filled') + ): + warnings.warn('\n\n ================================================================= '+ + '\n\n WARNING: YOU ARE PLOTTING SO MUCH DATA THAT IT MAY NOT BE'+ + '\n POSSIBLE TO SEE DETAILS (Candles, Ohlc-Bars, Etc.)'+ + '\n For more information see:'+ + '\n - https://github.com/matplotlib/mplfinance/wiki/Plotting-Too-Much-Data'+ + '\n '+ + '\n TO SILENCE THIS WARNING, set `type=\'line\'` in `mpf.plot()`'+ + '\n OR set kwarg `warn_too_much_data=N` where N is an integer '+ + '\n LARGER than the number of data points you want to plot.'+ + '\n\n ================================================================ ', + category=UserWarning) + + # We will not be fully case-insensitive (since Pandas columns as NOT case-insensitive) + # but because so many people have requested it, for the default column names we will + # try both Capitalized and lower case: + columns = config['columns'] + if columns is None: + columns = ('Open', 'High', 'Low', 'Close', 'Volume') + if all([c.lower() in data for c in columns[0:4]]): + columns = ('open', 'high', 'low', 'close', 'volume') + + o, h, l, c, v = columns + cols = [o, h, l, c] + + if config['tz_localize']: + dates = mdates.date2num(data.index.tz_localize(None).to_pydatetime()) + else: # Just in case someone was depending on this bug (Issue 236) + dates = mdates.date2num(data.index.to_pydatetime()) + opens = data[o].values + highs = data[h].values + lows = data[l].values + closes = data[c].values + if v in data.columns: + volumes = data[v].values + cols.append(v) + else: + volumes = None + + for col in cols: + if not all( isinstance(v,(float,int)) for v in data[col] ): + raise ValueError('Data for column "'+str(col)+'" must be ALL float or int.') + + return dates, opens, highs, lows, closes, volumes + +def _get_valid_plot_types(plottype=None): + + _alias_types = { + 'candlestick' : 'candle', + 'ohlc_bars' : 'ohlc', + 'hollow_candle' : 'hollow_and_filled', + 'hollow' : 'hollow_and_filled', + 'hnf' : 'hollow_and_filled', + } + + _valid_types = ['candle','ohlc', 'line','renko','pnf','hollow_and_filled'] + + _valid_types_all = _valid_types.copy() + _valid_types_all.extend(_alias_types.keys()) + + if plottype is None: + return _valid_types_all + elif plottype in _alias_types: + return _alias_types[plottype] + return plottype + + +def _mav_validator(mav_value): + ''' + Value for mav (moving average) keyword may be: + scalar int greater than 1, or tuple of ints, or list of ints (each greater than 1) + or a dict of `period` and `shift` each of which may be: + scalar int, or tuple of ints, or list of ints: each `period` int must be greater than 1 + ''' + def _valid_mav(value, is_period=True): + if not isinstance(value,(tuple,list,int)): + return False + if isinstance(value,int): + return (value >= 2 or not is_period) + # Must be a tuple or list here: + for num in value: + if not isinstance(num,int) or (is_period and num < 2): + return False + return True + + if not isinstance(mav_value,(tuple,list,int,dict)): + return False + + if not isinstance(mav_value,dict): + return _valid_mav(mav_value) + + else: #isinstance(mav_value,dict) + if 'period' not in mav_value: return False + + period = mav_value['period'] + if not _valid_mav(period): return False + + if 'shift' not in mav_value: return True + + shift = mav_value['shift'] + if not _valid_mav(shift, False): return False + if isinstance(period,int) and isinstance(shift,int): return True + if isinstance(period,(tuple,list)) and isinstance(shift,(tuple,list)): + if len(period) != len(shift): return False + return True + return False + + +def _hlines_validator(value): + if isinstance(value,dict): + if 'hlines' in value: + value = value['hlines'] + else: + return False + return ( isinstance(value,(float,int)) or (isinstance(value,(list,tuple)) and + all([isinstance(v,(float,int)) for v in value])) ) + +def _is_datelike(value): + if isinstance(value, (pd.Timestamp,datetime.datetime,datetime.date)): + return True + if isinstance(value,str): + try: + dt = pd.to_datetime(value) + return True + except: + return False + return False + +def _xlim_validator(value): + return (isinstance(value, (list,tuple)) and len(value) == 2 + and (all([isinstance(v,(int,float)) for v in value]) + or all([_is_datelike(v) for v in value]))) + +def _vlines_validator(value): + '''Validate `vlines` kwarg value: must be "datelike" or sequence of "datelike" + ''' + if isinstance(value,dict): + if 'vlines' in value: + value = value['vlines'] + else: + return False + if _is_datelike(value): return True + if not isinstance(value,(list,tuple)): return False + if not all([_is_datelike(v) for v in value]): return False + return True + +def _alines_validator(value, returnStandardizedValue=False): + ''' + Value for segments to be passed into LineCollection constructor must be: + - a sequence of `lines`, where + - a `lines` is a sequence of 2 or more vertices, where + - a vertex is a `pair`, aka a sequence of two values, an x and a y point. + + From matplotlib.collections: + `segments` are: + A sequence of (line0, line1, line2), where: + + linen = (x0, y0), (x1, y1), ... (xm, ym) + + or the equivalent numpy array with two columns. Each line can be a different length. + + The above is from the matplotlib LineCollection documentation. + It basically says that the "segments" passed into the LineCollection constructor + must be a Sequence of Sequences of 2 or more xy Pairs. However here in `mplfinance` + we want to allow that (seq of seq of xy pairs) _as well as_ just a sequence of pairs. + Therefore here in the validator we will allow both: + (a) seq of at least 2 date,float pairs (this is a 'line' as defined above) + (b) seq of seqs of at least 2 date,float pairs (this is a 'seqment' as defined above) + ''' + if isinstance(value,dict): + if 'alines' in value: + value = value['alines'] + else: + return False + + if not isinstance(value,(list,tuple)): + return False if not returnStandardizedValue else None + + if not all([isinstance(line,(list,tuple)) and len(line) > 1 for line in value]): + return False if not returnStandardizedValue else None + + # now, were the above really `lines`, or were they simply `vertices` + if all( [ isinstance(point,(list,tuple)) and len(point)==2 and + _is_datelike(point[0]) and isinstance(point[1],(float,int)) + for line in value for point in line ] ): + # they were lines: + return True if not returnStandardizedValue else value + + # here, if valid, we have a sequence of vertices (points) + if all( [ isinstance(point,(list,tuple)) and len(point)==2 and + _is_datelike(point[0]) and isinstance(point[1],(float,int)) + for point in value ] ): + return True if not returnStandardizedValue else [value,] + + return False if not returnStandardizedValue else None + +def _tlines_validator(value): + ''' + Validate `tlines` kwarg value: must be sequence of "datelike" pairs. + ''' + def _tlines_subvalidator(value): + if isinstance(value,dict): + if 'tlines' in value: + value = value['tlines'] + else: + return False + if not isinstance(value,(list,tuple)): + return False + if not all([isinstance(pair,(list,tuple)) and len(pair) == 2 and + _is_datelike(pair[0]) and _is_datelike(pair[1]) for pair in value]): + return False + return True + + if isinstance(value,(list,tuple)) and all([isinstance(v,dict) for v in value]): + for v in value: + if not _tlines_subvalidator(v): + return False + return True + else: + return _tlines_subvalidator(value) + +def _bypass_kwarg_validation(value): + ''' For some kwargs, we either don't know enough, or + the validation is too complex to make it worth while, + so we bypass kwarg validation. If the kwarg is + invalid, then eventually an exception will be + raised at the time the kwarg value is actually used. + ''' + return True + +def _kwarg_not_implemented(value): + ''' If you want to list a kwarg in a valid_kwargs dict for a given + function, but you have not yet, or don't yet want to, implement + the kwarg; or you simply want to (temporarily) disable the kwarg, + then use this function as the kwarg validator + ''' + raise NotImplementedError('kwarg NOT implemented.') + +def _validate_vkwargs_dict(vkwargs): + """ + Check that we didn't make a typo in any of the things + that should be the same for all vkwargs dict items: + + {kwarg : {'Default': ... , 'Description': ... , 'Validator': ...} } + """ + for key, value in vkwargs.items(): + # has been changed to 3 to support descriptions + if len(value) != 3: + raise ValueError('Items != 3 in valid kwarg table, for kwarg "'+key+'"') + if 'Default' not in value: + raise ValueError('Missing "Default" value for kwarg "'+key+'"') + if 'Description' not in value: + raise ValueError('Missing "Description" value for kwarg "'+key+'"') + if 'Validator' not in value: + raise ValueError('Missing "Validator" function for kwarg "'+key+'"') + +def _process_kwargs(kwargs, vkwargs): + ''' + Given a "valid kwargs table" and some kwargs, verify that each key-word + is valid per the kwargs table, and that the value of the kwarg is the + correct type. Fill a configuration dictionary with the default value + for each kwarg, and then substitute in any values that were provided + as kwargs and return the configuration dictionary. + ''' + # initialize configuration from valid_kwargs_table: + config = {} + for key, value in vkwargs.items(): + config[key] = value['Default'] + + # now validate kwargs, and for any valid kwargs + # replace the appropriate value in config: + for key in kwargs.keys(): + if key not in vkwargs: + raise KeyError('Unrecognized kwarg="'+str(key)+'"') + else: + value = kwargs[key] + try: + valid = vkwargs[key]['Validator'](value) + except Exception as ex: + ex.extra_info = 'kwarg "'+key+'" validator raised exception to value: "'+str(value)+'"' + raise + if not valid: + import inspect + v = inspect.getsource(vkwargs[key]['Validator']).strip() + raise TypeError('kwarg "'+key+'" validator returned False for value: "'+str(value)+'"\n '+v) + + # --------------------------------------------------------------- + # At this point in the loop, if we have not raised an exception, + # then kwarg is valid as far as we can tell, therefore, + # go ahead and replace the appropriate value in config: + + config[key] = value + + return config + +def _valid_panel_id(panid): + return panid in ['main','lower'] or (isinstance(panid,int) and panid >= 0 and panid < 32) + +def _scale_padding_validator(value): + if isinstance(value,(int,float)): + return True + elif isinstance(value,dict): + valid_keys=('left','right','top','bottom') + for key in value: + if key not in valid_keys: + raise ValueError('Invalid key "'+str(key)+'" found in `scale_padding` dict.') + if not isinstance(value[key],(int,float)): + raise ValueError('`scale_padding` dict contains non-number at key "'+str(key)+'"') + return True + else: + raise ValueError('`scale_padding` kwarg must be a number, or dict of (left,right,top,bottom) numbers.') + return False + +def _yscale_validator(value): + if isinstance(value,str) and value in ("linear", "log", "symlog", "logit"): + return True + + if not isinstance(value,dict): + return False + + # At this point, value is a dict: + if not 'yscale' in value: + return False + + yscale = value['yscale'] + if not (isinstance(yscale,str) and yscale in ("linear", "log", "symlog", "logit")): + return False + + return True + + +def _is_marketcolor_object(obj): + if not isinstance(obj,dict): return False + market_colors_keys = ('candle','edge','wick','ohlc') + return all([k in obj for k in market_colors_keys]) + + +def _mco_validator(value): # marketcolor overrides validator + if isinstance(value,dict): # not yet supported, but maybe we will have other + if 'colors' not in value: # kwargs related to mktcolor overrides (ex: `mco_faceonly`) + raise ValueError('`marketcolor_overrides` as dict must contain `colors` key.') + colors = value['colors'] + else: + colors = value + if not isinstance(colors,(list,tuple,np.ndarray)): + return False + return all([(c is None or + _mpf_is_color_like(c) or + _is_marketcolor_object(c) ) for c in colors]) + +def _check_for_external_axes(config): + ''' + Check that all `fig` and `ax` kwargs are either ALL None, + or ALL are valid instances of Figures/Axes: + + An external Axes object can be passed in three places: + - mpf.plot() `ax=` kwarg + - mpf.plot() `volume=` kwarg + - mpf.make_addplot() `ax=` kwarg + ALL three places MUST be an Axes object, OR + ALL three places MUST be None. But it may not be mixed. + ''' + ap_axlist = [] + 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 apd in addplot: + ap_axlist.append(apd['ax']) + + if len(ap_axlist) > 0: + if config['ax'] is None: + if not all([ax is None for ax in ap_axlist]): + raise ValueError('make_addplot() `ax` kwarg NOT all None, while plot() `ax` kwarg IS None') + else: # config['ax'] is NOT None: + if not isinstance(config['ax'],mpl.axes.Axes): + raise ValueError('plot() ax kwarg must be of type `matplotlib.axis.Axes`') + if not all([isinstance(ax,mpl.axes.Axes) for ax in ap_axlist]): + raise ValueError('make_addplot() `ax` kwargs must all be of type `matplotlib.axis.Axes`') + + # At this point, if we have not raised an exception, then plot(ax=) and make_addplot(ax=) + # are in sync: either they are all None, or they are all of type `matplotlib.axes.Axes`. + # Therefore we only need plot(ax=), i.e. config['ax'], as we check `volume`: ### and `fig`: + + if config['ax'] is None: + if isinstance(config['volume'],mpl.axes.Axes): + raise ValueError('`volume` set to external Axes requires all other Axes be external.') + #if config['fig'] is not None: + # raise ValueError('`fig` kwarg must be None if `ax` kwarg is None.') + else: + if not isinstance(config['volume'],mpl.axes.Axes) and config['volume'] != False: + raise ValueError('`volume` must be of type `matplotlib.axis.Axes`') + #if not isinstance(config['fig'],mpl.figure.Figure): + # raise ValueError('`fig` kwarg must be of type `matplotlib.figure.Figure`') + + external_axes_mode = True if isinstance(config['ax'],mpl.axes.Axes) else False + return external_axes_mode + +def _valid_fb_dict(value): + return (isinstance(value,dict) and + 'y1' in value and + _num_or_seq_of_num(value['y1'])) + +def _fill_between_validator(value): + if _num_or_seq_of_num(value): return True + if _valid_fb_dict(value): return True + if _list_of_dict(value): + return all([_valid_fb_dict(v) for v in value]) + return False diff --git a/build/lib/mplfinance/_helpers.py b/build/lib/mplfinance/_helpers.py new file mode 100644 index 00000000..c10ddf92 --- /dev/null +++ b/build/lib/mplfinance/_helpers.py @@ -0,0 +1,121 @@ +""" +Some helper functions for mplfinance. +NOTE: This is the lowest level in mplfinance: + This file should have NO dependencies on + any other mplfinance files. +""" + +import datetime +import matplotlib.dates as mdates +import matplotlib.colors as mcolors +import numpy as np + +def _adjust_color_brightness(color,amount=0.5): + + def _adjcb(c1, amount): + import matplotlib.colors as mc + import colorsys + # mc.is_color_like(value) + try: + c = mc.cnames[c1] + except: + c = c1 + c = colorsys.rgb_to_hls(*mc.to_rgb(c)) + return colorsys.hls_to_rgb(c[0], max(0, min(1, amount * c[1])), c[2]) + + if not isinstance(color,(list,tuple)): + return _adjcb(color,amount) + + cout = [] + cadj = {} + for c1 in color: + if c1 in cadj: + cout.append(cadj[c1]) + else: + newc = _adjcb(c1,amount) + cadj[c1] = newc + cout.append(cadj[c1]) + return cout + + +def _determine_format_string( dates, datetime_format=None ): + """ + Determine the datetime format string based on the averge number + of days between data points, or if the user passed in kwarg + datetime_format, use that as an override. + """ + avg_days_between_points = (dates[-1] - dates[0]) / float(len(dates)) + + if datetime_format is not None: + return datetime_format + + # 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' + return fmtstring + + +def _list_of_dict(x): + ''' + Return True if x is a list of dict's + ''' + return isinstance(x,list) and all([isinstance(item,dict) for item in x]) + +def _num_or_seq_of_num(value): + return ( isinstance(value,(int,float,np.integer,np.floating)) or + (isinstance(value,(list,tuple,np.ndarray)) and + all([isinstance(v,(int,float,np.integer,np.floating)) for v in value])) + ) + +def roundTime(dt=None, roundTo=60): + """Round a datetime object to any time lapse in seconds + dt : datetime.datetime object, default now. + roundTo : Closest number of seconds to round to, default 1 minute. + Author: Thierry Husson 2012 - Use it as you want but don't blame me. + """ + if dt is None : dt = datetime.datetime.now() + seconds = (dt.replace(tzinfo=None) - dt.min).seconds + rounding = (seconds+roundTo/2) // roundTo * roundTo + return dt + datetime.timedelta(0,rounding-seconds,-dt.microsecond) + + +def _is_uint8_rgb_or_rgba(tup): + """ Deterine if rgb or rgba is in (0-255) format: + Matplotlib expects rgb (and rgba) tuples to contain + three (or four) floats between 0.0 and 1.0 + + Some people express rgb as tuples of three integers + between 0 and 255. + (In rgba, alpha is still a float from 0.0 to 1.0) + """ + if isinstance(tup,str): return False + if not np.iterable(tup): return False + L = len(tup) + if L < 3 or L > 4: return False + if L == 4 and (tup[3] < 0 or tup[3] > 1): return False + return not any([not isinstance(v,(int,np.unsignedinteger)) or v<0 or v>255 for v in tup[0:3]]) + +def _mpf_is_color_like(c): + """Determine if an object is a color. + + Identical to `matplotlib.colors.is_color_like()` + BUT ALSO considers int (0-255) rgb and rgba colors. + """ + if mcolors.is_color_like(c): return True + return _is_uint8_rgb_or_rgba(c) + +def _mpf_to_rgba(c, alpha=None): + cnew = c + if _is_uint8_rgb_or_rgba(c) and any(e>1 for e in c[:3]): + cnew = tuple([e/255. for e in c[:3]]) + if len(c) == 4: cnew += c[3:] + return mcolors.to_rgba(cnew, alpha) diff --git a/build/lib/mplfinance/_kwarg_help.py b/build/lib/mplfinance/_kwarg_help.py new file mode 100644 index 00000000..c4ea52b5 --- /dev/null +++ b/build/lib/mplfinance/_kwarg_help.py @@ -0,0 +1,154 @@ +import mplfinance as mpf +import pandas as pd +import textwrap + +def df_wrapcols(df,wrap_columns=None): + + if wrap_columns is None: return df + if not isinstance(wrap_columns,dict): + raise TypeError('wrap_columns must be a dict of column_names and wrap_lengths') + + for col in wrap_columns: + if col not in df.columns: + raise ValueError('column "'+str(col)+'" not found in df.columns') + + index = [] + column_data = {} + for col in df.columns: + column_data[col] = [] + + for ix in df.index: + row = df.loc[ix,] + + row_data = {} + for col in row.index: + cstr = str(row[col]) + if col in wrap_columns: + wlen = wrap_columns[col] + tw = textwrap.wrap(cstr,wlen) if not cstr.isspace() else [' '] + else: + tw = [cstr] + row_data[col] = tw + + cmax = max(row_data,key=lambda k: len(row_data[k])) + rlen = len(row_data[cmax]) + for r in range(rlen): + for col in row.index: + extension = [' ']*(rlen - len(row_data[col])) + row_data[col].extend(extension) + column_data[col].append(row_data[col][r]) + ixstr = str(ix)+'.'+str(r) if r > 0 else str(ix) + index.append(ixstr) + + return pd.DataFrame(column_data,index=index) + +def make_left_formatter(maxwidth): + wm3 = maxwidth-3 + w = maxwidth + def left_formatter(value): + if not isinstance(value,str): + return f'{value:<}' + elif value[0:maxwidth] == '-'*maxwidth: + return f'{value:<{w}.{w}s}' + elif len(value) > maxwidth: + return f'{value:<{wm3}.{wm3}s}...' + else: + return f'{value:<{w}.{w}s}' + return left_formatter + + +def kwarg_help( func_name=None, kwarg_names=None, sort=False ): + + func_kwarg_map = { + 'plot' : mpf.plotting._valid_plot_kwargs, + 'make_addplot' : mpf.plotting._valid_addplot_kwargs, + 'make_marketcolors' : mpf._styles._valid_make_marketcolors_kwargs, + 'make_mpf_style' : mpf._styles._valid_make_mpf_style_kwargs, + 'renko_params' : mpf._utils._valid_renko_kwargs, + 'pnf_params' : mpf._utils._valid_pnf_kwargs, + 'lines' : mpf._utils._valid_lines_kwargs, + 'scale_width_adjustment': mpf._widths._valid_scale_width_kwargs, + 'update_width_config': mpf._widths._valid_update_width_kwargs, + } + + func_kwarg_aliases = { + 'addplot' : mpf.plotting._valid_addplot_kwargs, + 'marketcolors' : mpf._styles._valid_make_marketcolors_kwargs, + 'mpf_style' : mpf._styles._valid_make_mpf_style_kwargs, + 'style' : mpf._styles._valid_make_mpf_style_kwargs, + 'renko' : mpf._utils._valid_renko_kwargs, + 'pnf' : mpf._utils._valid_pnf_kwargs, + 'hlines' : mpf._utils._valid_lines_kwargs, + 'alines' : mpf._utils._valid_lines_kwargs, + 'tlines' : mpf._utils._valid_lines_kwargs, + 'vlines' : mpf._utils._valid_lines_kwargs, + } + + if func_name is None: + print('\nUsage: `kwarg_help(func_name)` or `kwarg_help(func_name,kwarg_names)`') + print(' kwarg_help is available for the following func_names:') + s = str(list(func_kwarg_map.keys())) + text = textwrap.wrap(s,68) + for t in text: + print(' ',t) + print() + return + + fkmap = {**func_kwarg_map, **func_kwarg_aliases} + + if func_name not in fkmap: + raise ValueError('Function name "'+func_name+'" NOT a valid function name') + + if kwarg_names is not None and isinstance(kwarg_names,str): + kwarg_names = [ kwarg_names, ] + + if ( kwarg_names is not None + and (not isinstance(kwarg_names,(list,tuple)) + or not all([isinstance(k,str) for k in kwarg_names]) + ) + ): + raise ValueError('kwarg_names must be a sequence (list,tuple) of strings') + + vks = fkmap[func_name]() + + df = (pd.DataFrame(vks).T).drop('Validator',axis=1) + df.index.name = 'Kwarg' + if sort: df.sort_index(inplace=True) + df.reset_index(inplace=True) + + if kwarg_names is not None: + for k in kwarg_names: + if k not in df['Kwarg'].values: + print(' Warning: "'+k+'" is not a valid `kwarg_name` for `func_name` "'+func_name,'"') + df = df[ df['Kwarg'].isin(kwarg_names) ] + if len(df) < 1: + raise ValueError(' None of specified `kwarg_names` are valid for `func_name` "'+func_name,'"') + + df['Default'] = ["'"+d+"'" if isinstance(d,str) else str(d) for d in df['Default']] + + klen = df['Kwarg'].str.len().max()+1 + dlen = df['Default'].str.len().max()+1 + + wraplen = max( 40, 80-(klen+dlen) ) + df = df_wrapcols(df,wrap_columns={'Description':wraplen}) + + dividers = [] + for col in df.columns: + dividers.append('-'*int(df[col].str.len().max())) + dfd = pd.DataFrame(dividers).T + dfd.columns = df.columns + dfd.index = pd.Index(['---']) + + df = dfd.append(df) + + formatters = { 'Kwarg' : make_left_formatter( klen ), + 'Default' : make_left_formatter( dlen ), + 'Description' : make_left_formatter( wraplen ), + } + + print('\n ','-'*78) + print(' Kwargs for func_name "'+func_name+'":') + + s = df.to_string(formatters=formatters,index=False,justify='left') + + print('\n ',s.replace('\n','\n ')) diff --git a/build/lib/mplfinance/_mpf_warnings.py b/build/lib/mplfinance/_mpf_warnings.py new file mode 100644 index 00000000..dd687d02 --- /dev/null +++ b/build/lib/mplfinance/_mpf_warnings.py @@ -0,0 +1,16 @@ +import sys as __sys +if not __sys.warnoptions: + import os as __os + import warnings as __warnings + __warnings.filterwarnings("default",category=DeprecationWarning,module='mplfinance') # Change the filter in this process + __os.environ["PYTHONWARNINGS"] = "default::DeprecationWarning:mplfinance" # Also affect subprocesses + +if __sys.version_info <= (3, 6): + __warnings.filterwarnings("default",category=ImportWarning,module='mplfinance') # Change the filter in this process + __os.environ["PYTHONWARNINGS"] = "default::ImportWarning:mplfinance" # Also affect subprocesses + __warnings.warn('\n\n ================================================================= '+ + '\n\n WARNING: `mplfinance` is NOT supported for Python versions '+ + '\n less than 3.6' + '\n\n ================================================================= ', + category=ImportWarning) + diff --git a/build/lib/mplfinance/_mplrcputils.py b/build/lib/mplfinance/_mplrcputils.py new file mode 100644 index 00000000..ec39a05c --- /dev/null +++ b/build/lib/mplfinance/_mplrcputils.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +""" +rcparams utilities +""" + +import pandas as pd +import matplotlib.pyplot as plt +import sys + +__author__ = "Daniel Goldfarb" +__version__ = "0.1.0" +__license__ = "MIT" + +def rcParams_to_df(rcp,name=None): + keys = [] + vals = [] + for item in rcp: + keys.append(item) + vals.append(rcp[item]) + df = pd.DataFrame(vals,index=pd.Index(keys,name='rcParamsKey')) + if name is not None: + df.columns = [name] + else: + df.columns = ['Value'] + return df + +def compare_styles(s1,s2): + with plt.rc_context(): + plt.style.use('default') + plt.style.use(s1) + df1 = rcParams_to_df(plt.rcParams,name=s1) + + with plt.rc_context(): + plt.style.use('default') + plt.style.use(s2) + df2 = rcParams_to_df(plt.rcParams,name=s2) + + df = pd.concat([df1,df2],axis=1) + dif = df[df[s1] != df[s2]].dropna(how='all') + return (dif,df,df1,df2) + +def main(): + """ Main entry point of the app """ + def usage(): + print('\n Usage: rcparams \n') + print(' Available commands: ') + print(' rcparams find ') + print(' rcparams compare ') + print('') + exit(1) + commands = ('find','compare') + + if len(sys.argv) < 3 : + print('\n Too few arguments!') + usage() + + command = sys.argv[1] + if command not in commands: + print('\n Unrecognized command \"'+command+'\"') + usage() + + if command == 'find': + findstr = sys.argv[2] + df = rcParams_to_df(plt.rcParams) + if findstr == '--all': + for key in df.index: + print(key+':',df.loc[key,'Value']) + else: + print(df[df.index.str.contains(findstr)]) + + elif command == 'compare': + if len(sys.argv) < 4 : + print('\n Need two styles to compare!') + usage() + style1 = sys.argv[2] + style2 = sys.argv[3] + dif,df,df1,df2 = compare_styles(style1,style2) + print('\n==== dif ====\n',dif) + + else: + print('\n Unrecognized command \"'+command+'\"') + usage() + + +if __name__ == "__main__": + """ This is executed when run from the command line """ + main() diff --git a/build/lib/mplfinance/_mplwraps.py b/build/lib/mplfinance/_mplwraps.py new file mode 100644 index 00000000..bad47fbb --- /dev/null +++ b/build/lib/mplfinance/_mplwraps.py @@ -0,0 +1,121 @@ +import matplotlib.pyplot as plt +import matplotlib.figure as mplfigure +import matplotlib.axes as mpl_axes +from mplfinance import _styles +import numpy as np + +""" + This file contains: + + (1) A wrapper of method `matplotlib.pyplot.figure()` that creates a + `mplfinance.Mpf_Figure` which is derived from `matplotlib.figure.Figure` + The wrapper function is the same as `matplotlib.pyplot.figure()` except + that it additionally accepts kwarg `style=` to set the mplfinance style. + + (2) Class `mplfinance.Mpf_Figure` derived from `matplotlib.figure.Figure` + which has the following overrides: + - Attribute `mpfstyle` indicating the mplfinance style used at Figure creation. + - Methods (listed below) which are identical to the same method in class + `matplotlib.figure.Figure` except that the `mplfinance.Mpf_Figure` versions: + - accept kwarg `style=` to set the mplfinance style of Subplot Axes, or + - if `style=` is not specified, then the attribute + `mplfinance.Mpf_Figure.mpfstyle` is used for the Subplot Axes style. + - Figure.add_subplot() + - Figure.add_axes() + - Figure.subplot() (this is analogous to pyplot.subplot() which calls Figure.add_subplot()) + - Figure.subplots() + + (3) A wrapper to matplot.pyplot.show(), because it happens often enough, when using mplfinance, + that sometimes one has to import matplotlib.pyplot *ONLY* for the purpose of calling .show() +""" + +show = plt.show # Not a true wrapper, rather an assignment. + +def _check_for_and_apply_style(kwargs): + + if 'style' in kwargs: + style = kwargs['style'] + del kwargs['style'] + else: + style = 'default' + + if not _styles._valid_mpf_style(style): + raise TypeError('Invalid mplfinance style') + + if isinstance(style,str): + 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?') + + return style + + +def figure(*args,**kwargs): + + style = _check_for_and_apply_style(kwargs) + + f = plt.figure(FigureClass=Mpf_Figure,*args,**kwargs) + f.mpfstyle = style + return f + + +class Mpf_Figure(mplfigure.Figure): + + def add_subplot(self,*args,**kwargs): + + if 'style' in kwargs or not hasattr(self,'mpfstyle'): + style = _check_for_and_apply_style(kwargs) + else: + style = _check_for_and_apply_style(dict(style=self.mpfstyle)) + + ax = mplfigure.Figure.add_subplot(self,*args,**kwargs) + ax.mpfstyle = style + return ax + + def add_axes(self,*args,**kwargs): + + if 'style' in kwargs or not hasattr(self,'mpfstyle'): + style = _check_for_and_apply_style(kwargs) + else: + style = _check_for_and_apply_style(dict(style=self.mpfstyle)) + + ax = mplfigure.Figure.add_axes(self,*args,**kwargs) + ax.mpfstyle = style + return ax + + def subplot(self,*args,**kwargs): + + plt.figure(self.number) # make it the current Figure + + if 'style' in kwargs or not hasattr(self,'mpfstyle'): + style = _check_for_and_apply_style(kwargs) + else: + style = _check_for_and_apply_style(dict(style=self.mpfstyle)) + + ax = plt.subplot(*args,**kwargs) + ax.mpfstyle = style + return ax + + + def subplots(self,*args,**kwargs): + + if 'style' in kwargs or not hasattr(self,'mpfstyle'): + style = _check_for_and_apply_style(kwargs) + self.mpfstyle = style + else: + style = _check_for_and_apply_style(dict(style=self.mpfstyle)) + + axlist = mplfigure.Figure.subplots(self,*args,**kwargs) + + if isinstance(axlist,mpl_axes.Axes): + axlist.mpfstyle = style + elif isinstance(axlist,np.ndarray): + for ax in axlist.flatten(): + ax.mpfstyle = style + else: + raise TypeError('Unexpected type ('+str(type(axlist))+') '+ + 'returned from "matplotlib.figure.Figure.subplots()"') + return axlist diff --git a/build/lib/mplfinance/_panels.py b/build/lib/mplfinance/_panels.py new file mode 100644 index 00000000..7d8524a2 --- /dev/null +++ b/build/lib/mplfinance/_panels.py @@ -0,0 +1,232 @@ +from mplfinance._helpers import _list_of_dict +from mplfinance._arg_validators import _valid_panel_id +import pandas as pd + +def _build_panels( figure, config ): + """ + Create and return a DataFrame containing panel information + and Axes objects for each panel, etc. + + We allow up to 32 panels, identified by their panel id (panid) + which is an integer 0 through 31. + + Parameters + ---------- + figure : pyplot.Figure + figure on which to create the Axes for the panels + + config : dict + config dict from `mplfinance.plot()` + + Config + ------ + The following items are used from `config`: + + num_panels : integer (0-31) or None + number of panels to create + + addplot : dict or None + value for the `addplot=` kwarg passed into `mplfinance.plot()` + + volume_panel : integer (0-31) or None + panel id (0-number_of_panels) + + main_panel : integer (0-31) or None + panel id (0-number_of_panels) + + panel_ratios : sequence or None + sequence of relative sizes for the panels; + + NOTE: If len(panel_ratios) == number of panels (regardless + of whether number of panels was specified or inferred), + then panel ratios are the relative sizes of each panel, + in panel id order, 0 through N (where N = number of panels). + + If len(panel_ratios) != number of panels, then len(panel_ratios) + must equal 2, and panel_ratios[0] is the relative size for the 'main' + panel, and panel_ratios[1] is the relative size for all other panels. + + If the number of panels == 1, the panel_ratios is ignored. + + +Returns + ---------- + panels : pandas.DataFrame + dataframe indexed by panel id (panid) and having the following columns: + axes : tuple of matplotlib.Axes (primary and secondary) for each column. + used secondary : bool indicating whether or not the seconday Axes is in use. + relative size : height of panel as proportion of sum of all relative sizes + + """ + + num_panels = config['num_panels'] + addplot = config['addplot'] + volume = config['volume'] + volume_panel = config['volume_panel'] + num_panels = config['num_panels'] + main_panel = config['main_panel'] + panel_ratios = config['panel_ratios'] + + if not _valid_panel_id(main_panel): + raise ValueError('main_panel id must be integer 0 to 31, but is '+str(main_panel)) + + if num_panels is None: # then infer the number of panels: + pset = {0} # start with a set including only panel zero + 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))) + + backwards_panel_compatibility = {'main':0,'lower':1,'A':0,'B':1,'C':2} + + for apdict in addplot: + panel = apdict['panel'] + if panel in backwards_panel_compatibility: + panel = backwards_panel_compatibility[panel] + if not _valid_panel_id(panel): + raise ValueError('addplot panel must be integer 0 to 31, but is "'+str(panel)+'"') + pset.add(panel) + + if volume is True: + if not _valid_panel_id(volume_panel): + raise ValueError('volume_panel must be integer 0 to 31, but is "'+str(volume_panel)+'"') + pset.add(volume_panel) + + pset.add(main_panel) + + pset = sorted(pset) + missing = [m for m in range(len(pset)) if m not in pset] + if len(missing) != 0: + raise ValueError('inferred panel list is missing panels: '+str(missing)) + + else: + if not isinstance(num_panels,int) or num_panels < 1 or num_panels > 32: + raise ValueError('num_panels must be integer 1 to 32, but is "'+str(volume_panel)+'"') + pset = range(0,num_panels) + + _nones = [None]*len(pset) + panels = pd.DataFrame(dict(axes=_nones, + relsize=_nones, + lift=_nones, + height=_nones, + used2nd=[False]*len(pset), + title=_nones, + y_on_right=_nones), + index=pset) + panels.index.name = 'panid' + + # Now determine the height for each panel: + # ( figure, num_panels='infer', addplot=None, volume_panel=None, main_panel=0, panel_ratios=None ): + + if panel_ratios is not None: + if not isinstance(panel_ratios,(list,tuple)): + raise TypeError('panel_ratios must be a list or tuple') + if len(panel_ratios) != len(panels) and not (len(panel_ratios)==2 and len(panels) > 2): + err = 'len(panel_ratios) must be 2, or must be same as number of panels' + err += '\nlen(panel_ratios)='+str(len(panel_ratios))+' num panels='+str(len(panels)) + raise ValueError(err) + if len(panel_ratios) == 2 and len(panels) > 2: + pratios = [panel_ratios[1]]*len(panels) + pratios[main_panel] = panel_ratios[0] + else: + pratios = panel_ratios + else: + pratios = [2]*len(panels) + pratios[main_panel] = 5 + + panels['relsize'] = pratios + #print('len(panels)=',len(panels)) + #print('len(pratios)=',len(pratios)) + + #print('pratios=') + #print(pratios) + + #print('panels=') + #print(panels) + + # TODO: Throughout this section, right_pad is intentionally *less* + # than left_pad. This assumes that the y-axis labels are on + # the left, which is true for many mpf_styles, but *not* all. + # Ideally need to determine which side has the axis labels. + # And keep in mind, if secondary_y is in effect, then both + # sides can have axis labels. + + left_pad = 0.18 + right_pad = 0.10 + top_pad = 0.12 + bot_pad = 0.18 + + scale_left = scale_right = scale_top = scale_bot = 1.0 + + scale_padding = config['scale_padding'] + if isinstance(scale_padding,dict): + if 'left' in scale_padding: scale_left = scale_padding['left'] + if 'right' in scale_padding: scale_right = scale_padding['right'] + if 'top' in scale_padding: scale_top = scale_padding['top'] + if 'bottom' in scale_padding: scale_bot = scale_padding['bottom'] + else: # isinstance(scale_padding,(int,float): + scale_left = scale_right = scale_top = scale_bot = scale_padding + + if config['tight_layout']: + right_pad *= 0.4 + top_pad *= 0.4 + scale_left *= 0.6 + scale_right *= 0.6 + scale_top *= 0.6 + scale_bot *= 0.6 + + left_pad *= scale_left + right_pad *= scale_right + top_pad *= scale_top + bot_pad *= scale_bot + + plot_height = 1.0 - (bot_pad + top_pad ) + plot_width = 1.0 - (left_pad + right_pad) + + # print('scale_padding=',scale_padding) + # print('left_pad =',left_pad) + # print('right_pad=',right_pad) + # print('top_pad =',top_pad) + # print('bot_pad =',bot_pad) + # print('plot_height =',plot_height) + # print('plot_width =',plot_width) + + psum = sum(pratios) + for panid,size in enumerate(pratios): + panels.at[panid,'height'] = plot_height * size / psum + + # Now create the Axes: + + for panid,row in panels.iterrows(): + height = row.height + lift = panels['height'].loc[panid+1:].sum() + panels.at[panid,'lift'] = lift + if panid == 0: + # rect = [left, bottom, width, height] + ax0 = figure.add_axes( [left_pad, bot_pad+lift, plot_width, height] ) + else: + ax0 = figure.add_axes( [left_pad, bot_pad+lift, plot_width, height], sharex=panels.at[0,'axes'][0] ) + ax1 = ax0.twinx() + ax1.grid(False) + if config['saxbelow']: # issue#115 issuecomment-639446764 + ax0.set_axisbelow(True) # so grid does not show through plot data on any panel. + elif panid == volume_panel: + ax0.set_axisbelow(True) # so grid does not show through volume bars. + panels.at[panid,'axes'] = (ax0,ax1) + + return panels + + +def _set_ticks_on_bottom_panel_only(panels,formatter,rotation=45): + + bot = panels.index.values[-1] + ax = panels.at[bot,'axes'][0] + ax.tick_params(axis='x',rotation=rotation) + ax.xaxis.set_major_formatter(formatter) + + if len(panels) == 1: return + + for panid in panels.index.values[::-1][1:]: + panels.at[panid,'axes'][0].tick_params(axis='x',labelbottom=False) + diff --git a/build/lib/mplfinance/_styledata/__init__.py b/build/lib/mplfinance/_styledata/__init__.py new file mode 100644 index 00000000..0dcbf598 --- /dev/null +++ b/build/lib/mplfinance/_styledata/__init__.py @@ -0,0 +1,45 @@ +''' +__init__ for mplfinance._styledata module +''' + +from mplfinance._styledata import default +from mplfinance._styledata import nightclouds +from mplfinance._styledata import classic +from mplfinance._styledata import mike +from mplfinance._styledata import charles +from mplfinance._styledata import blueskies +from mplfinance._styledata import starsandstripes +from mplfinance._styledata import sas +from mplfinance._styledata import brasil +from mplfinance._styledata import yahoo +from mplfinance._styledata import checkers +from mplfinance._styledata import binance +from mplfinance._styledata import kenan +from mplfinance._styledata import ibd + +_style_names = [n for n in dir() if not n.startswith('_')] + +_styles = {} +for name in _style_names: + cmd = f'_styles.update({name} = {name}.style)' + eval(cmd) + +def _validate_style(style): + keys = ['base_mpl_style','marketcolors','mavcolors','y_on_right', + 'gridcolor','gridstyle','facecolor','rc' ] + for key in keys: + if key not in style.keys(): + err = f'Key "{key}" not found in style:\n\n {style}' + raise ValueError(err) + + mktckeys = ['candle','edge','wick','ohlc','volume','alpha'] + for key in mktckeys: + if key not in style['marketcolors'].keys(): + err = f'Key "{key}" not found in marketcolors for style:\n\n {style}' + raise ValueError(err) + +#print('type(_styles)=',type(_styles)) +#print('_styles=',_styles) +for s in _styles.keys(): + _validate_style(_styles[s]) + diff --git a/build/lib/mplfinance/_styledata/binance.py b/build/lib/mplfinance/_styledata/binance.py new file mode 100644 index 00000000..880096a8 --- /dev/null +++ b/build/lib/mplfinance/_styledata/binance.py @@ -0,0 +1,29 @@ +style = dict(style_name = 'binance', + base_mpl_style= 'seaborn-darkgrid', + marketcolors = {'candle' : {'up':'#70a800', 'down':'#ea0070'}, + 'edge' : {'up':'#70a800', 'down':'#ea0070'}, + 'wick' : {'up':'#70a800', 'down':'#ea0070'}, + 'ohlc' : {'up':'#70a800', 'down':'#ea0070'}, + 'volume' : {'up':'#70a800', 'down':'#ea0070'}, + 'vcedge' : {'up':'#70a800', 'down':'#ea0070'}, + 'vcdopcod': False, + 'alpha' : 0.9, + }, + mavcolors = ['#ffc201','#ff10ff','#cd0468','#1f77b4', + '#ff7f0e','#2ca02c','#40e0d0'], + y_on_right = False, + gridcolor = '#d0d0d0', + gridstyle = '--', + facecolor = '#ffffff', + rc = [ ('axes.edgecolor' , '#e6e6e6' ), + ('axes.linewidth' , 1.5 ), + ('axes.labelsize' , 'medium' ), + ('axes.labelweight', 'semibold'), + ('lines.linewidth' , 2.0 ), + ('font.weight' , 'medium' ), + ('font.size' , 12.0 ), + ('figure.titlesize', 'x-large' ), + ('figure.titleweight','semibold'), + ], + base_mpf_style= 'binance' + ) diff --git a/build/lib/mplfinance/_styledata/blueskies.py b/build/lib/mplfinance/_styledata/blueskies.py new file mode 100644 index 00000000..26985778 --- /dev/null +++ b/build/lib/mplfinance/_styledata/blueskies.py @@ -0,0 +1,23 @@ +style = dict(style_name = 'blueskies', + base_mpl_style='fast', + marketcolors = {'candle' : {'up':'w', 'down':'#0095ff'}, + 'edge' : {'up':'k', 'down':'#0095ff'}, + 'wick' : {'up':'k', 'down':'#0095ff'}, + 'ohlc' : {'up':'#0095ff', 'down':'#0095ff'}, + 'volume' : {'up':'w', 'down':'#0095ff'}, + 'vcdopcod': False, + 'alpha' : 1.0, + }, + mavcolors = None, + y_on_right = False, + facecolor = '#dbf1ff', + gridcolor = None, + gridstyle = None, + rc = [('patch.linewidth' , 1.0 ), + ('patch.force_edgecolor', True ), + ('lines.linewidth' , 1.0 ), + ('figure.titlesize' , 'x-large' ), + ('figure.titleweight' , 'semibold'), + ], + base_mpf_style='blueskies', + ) diff --git a/build/lib/mplfinance/_styledata/brasil.py b/build/lib/mplfinance/_styledata/brasil.py new file mode 100644 index 00000000..f3e73e75 --- /dev/null +++ b/build/lib/mplfinance/_styledata/brasil.py @@ -0,0 +1,25 @@ +style = {'base_mpl_style': 'fast', + 'marketcolors': {'candle': {'up': '#fedf00', 'down': '#002776'}, + 'edge' : {'up': '#fedf00', 'down': '#002776'}, + 'wick' : {'up': '#fedf00', 'down': '#002776'}, + 'ohlc' : {'up': '#fedf00', 'down': '#002776'}, + 'volume': {'up': '#fedf00', 'down': '#002776'}, + 'vcedge': {'up': '#1f77b4', 'down': '#1f77b4'}, + 'vcdopcod': False, + 'alpha': 0.9}, + 'mavcolors' : None, + 'facecolor' : None, + 'gridcolor' : None, + 'gridstyle' : None, + 'y_on_right': True, + 'rc': {'axes.grid' : True, + 'axes.grid.axis': 'y', + 'grid.color' : '#fedf00', + 'grid.linestyle': '--', + 'axes.facecolor': '#009b3a', + 'axes.edgecolor': '#002776', + 'figure.titlesize' : 'x-large', + 'figure.titleweight': 'semibold', + }, + 'base_mpf_style': 'brasil' + } diff --git a/build/lib/mplfinance/_styledata/charles.py b/build/lib/mplfinance/_styledata/charles.py new file mode 100644 index 00000000..0347a605 --- /dev/null +++ b/build/lib/mplfinance/_styledata/charles.py @@ -0,0 +1,30 @@ +style = dict(style_name = 'charles', + base_mpl_style= 'fast', + marketcolors = {'candle' : {'up':'#006340', 'down':'#a02128'}, + 'edge' : {'up':'#006340', 'down':'#a02128'}, + 'wick' : {'up':'#006340', 'down':'#a02128'}, + 'ohlc' : {'up':'#006340', 'down':'#a02128'}, + 'volume' : {'up':'#007a00', 'down':'#d50d18'}, + 'vcdopcod': True, # Volume Color Depends On Price Change On Day + 'alpha' : 1.0, + }, + mavcolors = ['#ef5714','#ef5714','#9f4878','#9f4878'], + y_on_right = True, + gridcolor = '#a0a0a0', + gridstyle = '--', + facecolor = 'w', + rc = [ ('axes.edgecolor' , 'white' ), + ('axes.linewidth' , 1.5 ), + ('axes.labelsize' , 'large' ), + ('axes.labelweight', 'semibold'), + ('axes.grid' , True ), + ('axes.grid.axis' , 'y' ), + ('grid.linewidth' , 0.4 ), + ('lines.linewidth' , 2.0 ), + ('font.weight' , 'medium' ), + ('font.size' , 10.0 ), + ('figure.titlesize', 'x-large' ), + ('figure.titleweight','semibold'), + ], + base_mpf_style= 'charles' + ) diff --git a/build/lib/mplfinance/_styledata/checkers.py b/build/lib/mplfinance/_styledata/checkers.py new file mode 100644 index 00000000..f599758d --- /dev/null +++ b/build/lib/mplfinance/_styledata/checkers.py @@ -0,0 +1,26 @@ +style = {'base_mpl_style': 'ggplot', + 'marketcolors' : {'candle': {'up': '#000000', 'down': '#ff0000'}, + 'edge' : {'up': '#000000', 'down': '#ff0000'}, + 'wick' : {'up': '#606060', 'down': '#606060'}, + 'ohlc' : {'up': '#000000', 'down': '#ff0000'}, + 'volume': {'up': '#6f6f6f', 'down': '#ff4040'}, + 'vcedge': {'up': '#1f77b4', 'down': '#1f77b4'}, + 'vcdopcod' : False, + 'alpha' : 0.9}, + 'mavcolors' : None, + 'facecolor' : 'w', + 'gridcolor' : '#c0c0c0', + 'gridstyle' : '-', + 'y_on_right' : True, + 'rc' : {'axes.grid.axis': 'both', + 'axes.grid' : True, + 'axes.edgecolor': '#c0c0c0', + 'axes.labelcolor': 'k', + 'ytick.color' : 'k', + 'xtick.color' : 'k', + 'lines.markeredgecolor': 'k', + 'patch.force_edgecolor': True, + 'figure.titlesize' : 'x-large', + 'figure.titleweight' : 'semibold', + }, + 'base_mpf_style': 'checkers'} diff --git a/build/lib/mplfinance/_styledata/classic.py b/build/lib/mplfinance/_styledata/classic.py new file mode 100644 index 00000000..0a2fe461 --- /dev/null +++ b/build/lib/mplfinance/_styledata/classic.py @@ -0,0 +1,28 @@ +style = dict(style_name = 'classic', + base_mpl_style= 'fast', + marketcolors = {'candle' : {'up':'w', 'down':'k'}, + 'edge' : {'up':'k', 'down':'k'}, + 'wick' : {'up':'k', 'down':'k'}, + 'ohlc' : {'up':'k', 'down':'k'}, + 'volume' : {'up':'#181818', 'down':'#181818'}, + 'vcedge' : {'up':'#181818', 'down':'#181818'}, + 'vcdopcod': False, # Volume Color is Per Price Change On Day + 'alpha' : 0.9, + }, + mavcolors = ['#1a1a1a','#262626','#333333','#404040'], + y_on_right = True, + gridcolor = '#cccccc', + gridstyle = '--', + facecolor = 'w', + rc = [ ('axes.edgecolor' , 'black' ), + ('axes.linewidth' , 1.5 ), + ('axes.labelsize' , 'large' ), + ('axes.labelweight', 'semibold'), + ('lines.linewidth' , 2.0 ), + ('font.weight' , 'medium' ), + ('font.size' , 12.0 ), + ('figure.titlesize', 'x-large' ), + ('figure.titleweight','semibold'), + ], + base_mpf_style= 'classic' + ) diff --git a/build/lib/mplfinance/_styledata/default.py b/build/lib/mplfinance/_styledata/default.py new file mode 100644 index 00000000..7e2690ad --- /dev/null +++ b/build/lib/mplfinance/_styledata/default.py @@ -0,0 +1,29 @@ +style = dict(style_name = 'default', + base_mpl_style= 'seaborn-darkgrid', + marketcolors = {'candle' : {'up':'w', 'down':'k'}, + 'edge' : {'up':'k', 'down':'k'}, + 'wick' : {'up':'k', 'down':'k'}, + 'ohlc' : {'up':'k', 'down':'k'}, + 'volume' : {'up':'#1f77b4', 'down':'#1f77b4'}, + 'vcedge' : {'up':'#1f77b4', 'down':'#1f77b4'}, + 'vcdopcod': False, # Volume Color is Per Price Change On Day + 'alpha' : 0.9, + }, + mavcolors = ['#40e0d0','#ff00ff','#ffd700','#1f77b4', + '#ff7f0e','#2ca02c','#e377c2'], + y_on_right = False, + gridcolor = None, + gridstyle = None, + facecolor = '#DCE3EF', + rc = [ ('axes.edgecolor' , 'black' ), + ('axes.linewidth' , 1.5 ), + ('axes.labelsize' , 'large' ), + ('axes.labelweight', 'semibold'), + ('lines.linewidth' , 2.0 ), + ('font.weight' , 'medium' ), + ('font.size' , 12.0 ), + ('figure.titlesize', 'x-large' ), + ('figure.titleweight','semibold'), + ], + base_mpf_style= 'default' + ) diff --git a/build/lib/mplfinance/_styledata/ibd.py b/build/lib/mplfinance/_styledata/ibd.py new file mode 100644 index 00000000..c233f885 --- /dev/null +++ b/build/lib/mplfinance/_styledata/ibd.py @@ -0,0 +1,38 @@ +style = dict(style_name = 'ibd', + base_mpl_style= 'fast', + marketcolors = {'candle' : {'up':'#2A3FE5', 'down':'#DB39AD'}, + 'edge' : {'up':'#2A3FE5', 'down':'#DB39AD'}, + 'wick' : {'up':'#2A3FE5', 'down':'#DB39AD'}, + 'ohlc' : {'up':'#2A3FE5', 'down':'#DB39AD'}, + 'volume' : {'up':'#2A3FE5', 'down':'#DB39AD'}, + 'vcedge' : {'up':'#2A3FE5', 'down':'#DB39AD'}, + 'vcdopcod': True, # Volume Color is Per Price Change On Day + 'alpha' : 1.0, + }, + mavcolors = ['green','red','black','blue'], + y_on_right = True, + gridcolor = None, + gridstyle = None, + facecolor = None, + rc = [ ('axes.titlesize', 8), + ('axes.labelsize', 8) , + ('lines.linewidth', 3), + ('lines.markersize', 4), + ('ytick.left', False), + ('ytick.right', True), + ('ytick.labelleft', False), + ('ytick.labelright', True), + ('xtick.labelsize', 6), + ('ytick.labelsize', 7), + ('axes.linewidth', 0.8), + ('grid.alpha', 0.2), + ('axes.grid' , True ), + ('axes.grid.axis' , 'y' ), + ('grid.color' , '#b0b0b0' ), + ('grid.linestyle' , 'solid' ), + ('grid.linewidth' , 0.8 ), + ('figure.titlesize', 'x-large' ), + ('figure.titleweight','semibold'), + ], + base_mpf_style= 'ibd' + ) diff --git a/build/lib/mplfinance/_styledata/kenan.py b/build/lib/mplfinance/_styledata/kenan.py new file mode 100644 index 00000000..98ed0620 --- /dev/null +++ b/build/lib/mplfinance/_styledata/kenan.py @@ -0,0 +1,33 @@ +style = { 'style_name': 'kenan', + 'base_mpl_style': 'seaborn-darkgrid', + 'marketcolors': { 'candle': {'up': 'k', 'down': 'r'}, + 'edge': {'up': 'k', 'down': 'r'}, + 'wick': {'up': 'k', 'down': 'r'}, + 'ohlc': {'up': 'k', 'down': 'k'}, + 'volume': {'up': '#1f77b4', 'down': '#1f77b4'}, + 'vcedge': {'up': '#1f77b4', 'down': '#1f77b4'}, + 'vcdopcod': False, + 'alpha': 0.9, + 'hollow': 'w'}, + 'mavcolors': [ '#40e0d0', + '#ff00ff', + '#ffd700', + '#1f77b4', + '#ff7f0e', + '#2ca02c', + '#e377c2'], + 'y_on_right': False, + 'gridcolor': None, + 'gridstyle': None, + 'facecolor': '#DCE3EF', + 'rc': [ ('axes.edgecolor', 'black'), + ('axes.linewidth', 1.5), + ('axes.labelsize', 'large'), + ('axes.labelweight', 'semibold'), + ('lines.linewidth', 2.0), + ('font.weight', 'medium'), + ('font.size', 12.0), + ('figure.titlesize', 'x-large' ), + ('figure.titleweight','semibold'), + ], + 'base_mpf_style': 'default'} diff --git a/build/lib/mplfinance/_styledata/mike.py b/build/lib/mplfinance/_styledata/mike.py new file mode 100644 index 00000000..ef026e24 --- /dev/null +++ b/build/lib/mplfinance/_styledata/mike.py @@ -0,0 +1,36 @@ +style = dict(style_name = 'mike', + base_mpl_style= 'dark_background', + marketcolors = {'candle' : {'up':'#000000', 'down':'#0080ff'}, + 'edge' : {'up':'#ffffff', 'down':'#0080ff'}, + 'wick' : {'up':'#ffffff', 'down':'#ffffff'}, + 'ohlc' : {'up':'#ffffff', 'down':'#ffffff'}, + 'volume' : {'up':'#7189aa', 'down':'#7189aa'}, + 'vcdopcod': False, # Volume Color Depends On Price Change On Day + 'alpha' : 1.0, + }, + mavcolors = ['#ec009c','#78ff8f','#fcf120'], + y_on_right = True, + gridcolor = None, + gridstyle = None, + facecolor = None, + rc = [ ('axes.edgecolor' , 'white' ), + ('axes.linewidth' , 1.5 ), + ('axes.labelsize' , 'large' ), + ('axes.labelweight', 'semibold'), + ('axes.grid' , True ), + ('axes.grid.axis' , 'both' ), + ('axes.grid.which' , 'major' ), + ('grid.alpha' , 0.9 ), + ('grid.color' , '#b0b0b0' ), + ('grid.linestyle' , '--' ), + ('grid.linewidth' , 0.8 ), + ('figure.facecolor', '#0a0a0a' ), + ('patch.linewidth' , 1.0 ), + ('lines.linewidth' , 1.0 ), + ('font.weight' , 'medium' ), + ('font.size' , 10.0 ), + ('figure.titlesize', 'x-large' ), + ('figure.titleweight','semibold'), + ], + base_mpf_style= 'mike' + ) diff --git a/build/lib/mplfinance/_styledata/nightclouds.py b/build/lib/mplfinance/_styledata/nightclouds.py new file mode 100644 index 00000000..2507d12e --- /dev/null +++ b/build/lib/mplfinance/_styledata/nightclouds.py @@ -0,0 +1,23 @@ +style = dict(style_name = 'nightclouds', + base_mpl_style='dark_background', + marketcolors = {'candle' : {'up':'w', 'down':'#0095ff'}, + 'edge' : {'up':'w', 'down':'#0095ff'}, + 'wick' : {'up':'w', 'down':'w'}, + 'ohlc' : {'up':'w', 'down':'w'}, + 'volume' : {'up':'w', 'down':'#0095ff'}, + 'vcdopcod': False, + 'alpha' : 1.0, + }, + mavcolors = ['#40e0d0','#ff00ff','#ffd700','#1f77b4', + '#ff7f0e','#2ca02c','#e377c2'], + y_on_right = False, + facecolor = '#0b0b0b', + gridcolor = '#999999', + gridstyle = '--', + rc = [('patch.linewidth' , 1.0 ), + ('lines.linewidth' , 1.0 ), + ('figure.titlesize', 'x-large' ), + ('figure.titleweight','semibold'), + ], + base_mpf_style='nightclouds', + ) diff --git a/build/lib/mplfinance/_styledata/sas.py b/build/lib/mplfinance/_styledata/sas.py new file mode 100644 index 00000000..e88e310a --- /dev/null +++ b/build/lib/mplfinance/_styledata/sas.py @@ -0,0 +1,4 @@ +# style sas is just an abbreviation for starsandstripes: + +from mplfinance._styledata import starsandstripes +style = starsandstripes.style diff --git a/build/lib/mplfinance/_styledata/starsandstripes.py b/build/lib/mplfinance/_styledata/starsandstripes.py new file mode 100644 index 00000000..09120cd6 --- /dev/null +++ b/build/lib/mplfinance/_styledata/starsandstripes.py @@ -0,0 +1,24 @@ +style = {'base_mpl_style': 'fast', + 'marketcolors': {'candle': {'up': '#082865', 'down': '#ae0019'}, + 'edge' : {'up': '#082865', 'down': '#ae0019'}, + 'wick' : {'up': '#082865', 'down': '#ae0019'}, + 'ohlc' : {'up': '#082865', 'down': '#ae0019'}, + 'volume': {'up': '#082865', 'down': '#ae0019'}, + 'vcedge': {'up': '#1f77b4', 'down': '#1f77b4'}, + 'vcdopcod': False, + 'alpha': 0.9}, + 'mavcolors': None, + 'facecolor': None, + 'gridcolor': None, + 'gridstyle': None, + 'y_on_right': True, + 'rc': {'axes.edgecolor': '#082865', + 'axes.grid' : True, + 'axes.grid.axis': 'y', + 'grid.color' : '#082865', + 'grid.linestyle': '--', + 'figure.titlesize' :'x-large', + 'figure.titleweight':'semibold', + }, + 'base_mpf_style': 'starsandstripes' + } diff --git a/build/lib/mplfinance/_styledata/yahoo.py b/build/lib/mplfinance/_styledata/yahoo.py new file mode 100644 index 00000000..85f1c576 --- /dev/null +++ b/build/lib/mplfinance/_styledata/yahoo.py @@ -0,0 +1,23 @@ +style = {'base_mpl_style': 'fast', + 'marketcolors' : {'candle': {'up': '#00b060', 'down': '#fe3032'}, + 'edge' : {'up': '#00b060', 'down': '#fe3032'}, + 'wick' : {'up': '#606060', 'down': '#606060'}, + 'ohlc' : {'up': '#00b060', 'down': '#fe3032'}, + 'volume': {'up': '#4dc790', 'down': '#fd6b6c'}, + 'vcedge': {'up': '#1f77b4', 'down': '#1f77b4'}, + 'vcdopcod' : True, + 'alpha' : 0.9}, + 'mavcolors' : None, + 'facecolor' : '#fafafa', + 'gridcolor' : '#d0d0d0', + 'gridstyle' : '-', + 'y_on_right' : True, + 'rc' : {'axes.labelcolor': '#101010', + 'axes.edgecolor' : 'f0f0f0', + 'axes.grid.axis' : 'y', + 'ytick.color' : '#101010', + 'xtick.color' : '#101010', + 'figure.titlesize': 'x-large', + 'figure.titleweight':'semibold', + }, + 'base_mpf_style': 'yahoo'} diff --git a/build/lib/mplfinance/_styles.py b/build/lib/mplfinance/_styles.py new file mode 100644 index 00000000..15d0538b --- /dev/null +++ b/build/lib/mplfinance/_styles.py @@ -0,0 +1,382 @@ +import matplotlib.pyplot as plt +import copy +import pprint +import os.path as path + +from mplfinance._arg_validators import _process_kwargs, _validate_vkwargs_dict +from mplfinance._styledata import _styles +from mplfinance._helpers import _mpf_is_color_like + + +def _get_mpfstyle(style): + ''' + Return a copy of the specified pre-defined mpfstyle. We return + a copy, because returning the original will effectively return + a pointer which allows style's definition to be modified. + ''' + return copy.deepcopy(_styles[style]) + +def _apply_mpfstyle(style): + + plt.style.use('default') + + if style['base_mpl_style'] is not None: + plt.style.use(style['base_mpl_style']) + + if style['rc'] is not None: + plt.rcParams.update(style['rc']) + + if style['facecolor'] is not None: + plt.rcParams.update({'axes.facecolor' : style['facecolor'] }) + + if 'edgecolor' in style and style['edgecolor'] is not None: + plt.rcParams.update({'axes.edgecolor' : style['edgecolor'] }) + + if 'figcolor' in style and style['figcolor'] is not None: + plt.rcParams.update({'figure.facecolor' : style['figcolor'] }) + plt.rcParams.update({'savefig.facecolor': style['figcolor'] }) + + explicit_grid = False + if style['gridcolor'] is not None: + explicit_grid = True + plt.rcParams.update({'grid.color' : style['gridcolor'] }) + + if style['gridstyle'] is not None: + explicit_grid = True + plt.rcParams.update({'grid.linestyle' : style['gridstyle'] }) + + plt.rcParams.update({'axes.grid.axis' : 'both' }) + if 'gridaxis' in style and style['gridaxis'] is not None: + gax = style['gridaxis'] + explicit_grid = True + if gax == 'horizontal'[0:len(gax)]: + plt.rcParams.update({'axes.grid.axis' : 'y' }) + elif gax == 'vertical'[0:len(gax)]: + plt.rcParams.update({'axes.grid.axis' : 'x' }) + + if explicit_grid: + plt.rcParams.update({'axes.grid' : True }) + + +def _valid_make_mpf_style_kwargs(): + vkwargs = { + 'base_mpf_style': { 'Default' : None, + 'Description' : 'mplfinance style to use as base of new mplfinance style', + 'Validator' : lambda value: value in _styles.keys() }, + + 'base_mpl_style': { 'Default' : None, + 'Description' : 'matplotlib style to use as base of new mplfinance style', + 'Validator' : lambda value: isinstance(value,(str,list))}, # and is in plt.style.available + + 'marketcolors' : { 'Default' : None, + 'Description' : 'market colors object, from `mpf.make_market_colors()`', + 'Validator' : lambda value: isinstance(value,dict) }, + + 'mavcolors' : { 'Default' : None, + 'Description' : 'sequence of colors to use for moving averages', + 'Validator' : lambda value: isinstance(value,list) }, # TODO: all([_mpf_is_color_like(v) for v in value.values()]) + + + 'facecolor' : { 'Default' : None, + 'Description' : 'background color for Axes', + 'Validator' : lambda value: isinstance(value,str) }, + + 'edgecolor' : { 'Default' : None, + 'Description' : 'edge color for Axes', + 'Validator' : lambda value: isinstance(value,str) }, + + 'figcolor' : { 'Default' : None, + 'Description' : 'background color for Figure.', + 'Validator' : lambda value: isinstance(value,str) }, + + 'gridcolor' : { 'Default' : None, + 'Description' : 'color for grid lines', + 'Validator' : lambda value: isinstance(value,str) }, + + 'gridstyle' : { 'Default' : None, + 'Description' : "grid line style ('-', '--', '-.', ':', '', offset, on-off-seq)."+ + " (see also: https://matplotlib.org/stable/gallery/lines_bars_and_markers/linestyles.html)", + 'Validator' : lambda value: isinstance(value,str) }, + + 'gridaxis' : { 'Default' : None, + 'Description' : "grid lines 'vertical', 'horizontal', or 'both'", + 'Validator' : lambda value: value in [ 'vertical'[0:len(value)], 'horizontal'[0:len(value)], 'both'[0:len(value)] ] }, + + 'y_on_right' : { 'Default' : None, + 'Description' : 'True|False primary Axes y-ticks and labels on right.', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'rc' : { 'Default' : None, + 'Description' : 'rcparams overrides (dict) (all other rcparams unchanged)', + 'Validator' : lambda value: isinstance(value,dict) }, + + 'legacy_rc' : { 'Default' : None, # Just in case someone depended upon old behavior + 'Description' : 'rcparams to set (dict) (all other rcparams cleared)', + 'Validator' : lambda value: isinstance(value,dict) }, + + 'style_name' : { 'Default' : None, + 'Description' : 'name for this style; useful when calling `mpf.write_style_file(style,filename)`', + 'Validator' : lambda value: isinstance(value,str) }, + + } + + _validate_vkwargs_dict(vkwargs) + + return vkwargs + + +def available_styles(): + return list(_styles.keys()) + +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 + +def _valid_mpf_color_spec(value): + 'value must be a color, "inherit"-like, or dict of colors' + return ( _mpf_is_color_like(value) or + ( isinstance(value,str) and value == 'inherit'[0:len(value)]) or + ( isinstance(value,dict) and + all([_mpf_is_color_like(v) for v in value.values()]) + ) + ) + +def _valid_mpf_style(value): + if value in available_styles(): + return True + if not isinstance(value,dict): + return False + if 'marketcolors' not in value: + return False + if not isinstance(value['marketcolors'],dict): + return False + # {'candle': {'up': 'b', 'down': 'g'}, + # 'edge': {'up': 'k', 'down': 'k'}, + # 'wick': {'up': 'k', 'down': 'k'}, + # 'ohlc': {'up': 'k', 'down': 'k'}, + # 'volume': {'up': '#1f77b4', 'down': '#1f77b4'}, + # 'vcedge': {'up': '#1f77b4', 'down': '#1f77b4'}, + # 'vcdopcod': False, + # 'alpha': 0.9} + for item in ('candle','edge','wick','ohlc','volume'): + if item not in value['marketcolors']: + return False + itemcolors = value['marketcolors'][item] + if not isinstance(itemcolors,dict): + return False + if 'up' not in itemcolors or 'down' not in itemcolors: + return False + return True + + +def _valid_make_marketcolors_kwargs(): + vkwargs = { + 'up' : { 'Default' : None, + 'Description' : 'color to indicate up', + 'Validator' : lambda value: _mpf_is_color_like(value) }, + + 'down' : { 'Default' : None, + 'Description' : 'color to indicate down', + 'Validator' : lambda value: _mpf_is_color_like(value) }, + + 'hollow' : { 'Default' : None, + 'Description' : "color for hollow candles (for `type=hollow`)", + 'Validator' : lambda value: _mpf_is_color_like(value) }, + + 'alpha' : { 'Default' : None, + 'Description' : 'opacity 0.0 (transparent) to 1.0 (opaque);'+ + ' applies to candles,renko,pnf (but not ohlc bars)', + 'Validator' : lambda value: (isinstance(value,float) + and 0.0 <= value and 1.0 >= value ) }, + + 'edge' : { 'Default' : None, + 'Description' : 'color of candle edge; may also be "i" or "inherit"'+ + ' to take color from base_mpf_style', + 'Validator' : lambda value: _valid_mpf_color_spec(value) }, + + 'wick' : { 'Default' : None, + 'Description' : "color of candle wick; may be single color,"+ + " or may be dict with keys 'up' and 'down'", + 'Validator' : lambda value: isinstance(value,dict) + or isinstance(value,str) + or _mpf_is_color_like(value) }, + + 'ohlc' : { 'Default' : None, + 'Description' : "color of ohlc bars; may be single color,"+ + " or may be dict with keys 'up' and 'down'", + 'Validator' : lambda value: isinstance(value,dict) + or isinstance(value,str) + or _mpf_is_color_like(value) }, + + 'volume' : { 'Default' : None, + 'Description' : "color of volume bars; may be single color,"+ + " or may be dict with keys 'up' and 'down'", + 'Validator' : lambda value: isinstance(value,dict) + or isinstance(value,str) + or _mpf_is_color_like(value) }, + + 'vcdopcod' : { 'Default' : False, + 'Description' : 'True/False volume color depends on price change from previous day', + 'Validator' : lambda value: isinstance(value,bool) }, + + + 'inherit' : { 'Default' : False, + 'Description' : 'inherit color from base_mpf_style for: edge,volume,ohlc,wick', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'base_mpf_style': { 'Default' : None, + 'Description' : 'mplfinance style market colors as basis for new market colors object', + 'Validator' : lambda value: isinstance(value,str) }, + } + + _validate_vkwargs_dict(vkwargs) + + return vkwargs + + +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 + +def write_style_file(style,filename): + pp = pprint.PrettyPrinter(indent=4,sort_dicts=False,compact=True) + strl = pp.pformat(style).splitlines() + + if not isinstance(style,dict): + raise TypeError('Specified style must be in `dict` format') + + if path.exists(filename): + print('"'+filename+'" exists.') + answer = input(' Overwrite(Y/N)? ') + a = answer.lower() + if a != 'y' and a != 'yes': + raise FileExistsError + + f = open(filename,'w') + f.write('style = '+strl[0].replace('{','dict(',1).replace("'","",2).replace(':',' =',1)+'\n') + for line in strl[1:-1]: + if "'" in line[0:5]: + f.write(' '+line.replace("'","",2).replace(':',' =',1)+'\n') + else: + f.write(' '+line+'\n') + line = strl[-1] + if "'" in line[0:5]: + line = line.replace("'","",2).replace(':',' =',1)[::-1] + else: + line = line[::-1] + f.write(' '+line.replace('}',')',1)[::-1]+'\n') + f.close() + print('Wrote style file "'+filename+'"') + return diff --git a/build/lib/mplfinance/_utils.py b/build/lib/mplfinance/_utils.py new file mode 100644 index 00000000..a4f80dfc --- /dev/null +++ b/build/lib/mplfinance/_utils.py @@ -0,0 +1,1515 @@ +""" +A collection of utilities for analyzing and plotting financial data. +""" + +import numpy as np +import pandas as pd +import matplotlib.dates as mdates +import datetime + +from itertools import cycle + +from matplotlib import colors as mcolors, pyplot as plt +from matplotlib.patches import Ellipse +from matplotlib.collections import LineCollection, PolyCollection, PatchCollection + +from mplfinance._arg_validators import _process_kwargs, _validate_vkwargs_dict +from mplfinance._arg_validators import _alines_validator, _bypass_kwarg_validation +from mplfinance._arg_validators import _xlim_validator, _is_datelike +from mplfinance._styles import _get_mpfstyle +from mplfinance._helpers import _mpf_to_rgba + +from six.moves import zip + +def _check_input(opens, closes, highs, lows): + """Checks that *opens*, *highs*, *lows* and *closes* have the same length. + NOTE: this code assumes if any value open, high, low, close is + missing (*-1*) they all are missing + + Parameters + ---------- + opens : sequence + sequence of opening values + highs : sequence + sequence of high values + lows : sequence + sequence of low values + closes : sequence + sequence of closing values + + Raises + ------ + ValueError + if the input sequences don't have the same length + if the input sequences don't have NaN is same locations + """ + same_length = len(opens) == len(highs) == len(lows) == len(closes) + if not same_length: + raise ValueError('O,H,L,C must have the same length!') + + o = np.where(np.isnan(opens))[0] + h = np.where(np.isnan(highs))[0] + l = np.where(np.isnan(lows))[0] + c = np.where(np.isnan(closes))[0] + + # First check that they have the same number of NaN: + same_numnans = len(o) == len(h) == len(l) == len(c) + if not same_numnans: + raise ValueError('O,H,L,C must have the same amount of missing data!') + + same_missing = ((o == h).all() and + (o == l).all() and + (o == c).all() + ) + if not same_missing: + raise ValueError('O,H,L,C must have the same missing data!') + + +def _check_and_convert_xlim_configuration(data, config): + ''' + Check, if user entered `xlim` kwarg, if user entered dates + then we may need to convert them to iloc or matplotlib dates. + ''' + if config['xlim'] is None: + return None + + xlim = config['xlim'] + + if not _xlim_validator(xlim): + raise ValueError('Bad xlim configuration #1') + + if all([_is_datelike(dt) for dt in xlim]): + if config['show_nontrading']: + xlim = [ _date_to_mdate(dt) for dt in xlim] + else: + xlim = [ _date_to_iloc_extrapolate(data.index.to_series(),dt) for dt in xlim] + + return xlim + + +def _construct_mpf_collections(ptype,dates,xdates,opens,highs,lows,closes,volumes,config,style): + collections = None + if ptype == 'candle' or ptype == 'candlestick': + collections = _construct_candlestick_collections(xdates, opens, highs, lows, closes, + marketcolors=style['marketcolors'],config=config ) + + elif ptype =='hollow_and_filled': + collections = _construct_hollow_candlestick_collections(xdates, opens, highs, lows, closes, + marketcolors=style['marketcolors'],config=config ) + + elif ptype == 'ohlc' or ptype == 'bars' or ptype == 'ohlc_bars': + collections = _construct_ohlc_collections(xdates, opens, highs, lows, closes, + marketcolors=style['marketcolors'],config=config ) + elif ptype == 'renko': + collections = _construct_renko_collections( + dates, highs, lows, volumes, config['renko_params'], closes, marketcolors=style['marketcolors']) + + elif ptype == 'pnf': + collections = _construct_pointnfig_collections( + dates, highs, lows, volumes, config['pnf_params'], closes, marketcolors=style['marketcolors']) + else: + raise TypeError('Unknown ptype="',str(ptype),'"') + + return collections + + +def _calculate_atr(atr_length, highs, lows, closes): + """Calculate the average true range + atr_length : time period to calculate over + all_highs : list of highs + all_lows : list of lows + all_closes : list of closes + """ + if atr_length < 1: + raise ValueError("Specified atr_length may not be less than 1") + elif atr_length >= len(closes): + raise ValueError("Specified atr_length is larger than the length of the dataset: " + str(len(closes))) + atr = 0 + for i in range(len(highs)-atr_length, len(highs)): + high = highs[i] + low = lows[i] + close_prev = closes[i-1] + tr = max(abs(high-low), abs(high-close_prev), abs(low-close_prev)) + atr += tr + return atr/atr_length + +def combine_adjacent(arr): + """Sum like signed adjacent elements + arr : starting array + + Returns + ------- + output: new summed array + indexes: indexes indicating the first + element summed for each group in arr + """ + output, indexes = [], [] + curr_i = 0 + while len(arr) > 0: + curr_sign = arr[0]/abs(arr[0]) + index = 0 + while index < len(arr) and arr[index]/abs(arr[index]) == curr_sign: + index += 1 + output.append(sum(arr[:index])) + indexes.append(curr_i) + curr_i += index + + for _ in range(index): + arr.pop(0) + return output, indexes + +def coalesce_volume_dates(in_volumes, in_dates, indexes): + """Sums volumes between the indexes and ouputs + dates at the indexes + in_volumes : original volume list + in_dates : original dates list + indexes : list of indexes + + Returns + ------- + volumes: new volume array + dates: new dates array + """ + volumes, dates = [], [] + for i in range(len(indexes)): + dates.append(in_dates[indexes[i]]) + to_sum_to = indexes[i+1] if i+1 < len(indexes) else len(in_volumes) + volumes.append(sum(in_volumes[indexes[i]:to_sum_to])) + return volumes, dates + + +def _updown_colors(upcolor,downcolor,opens,closes,use_prev_close=False): + # ----------------------------------------------------- + # Note that `nan` values result in `opn < cls` == False + # In other words, nans don't get plotted by collections + # but this function will choose DOWN COLOR for nans. + # ----------------------------------------------------- + if upcolor == downcolor: + return [upcolor]*len(opens) + cmap = {True : upcolor, False : downcolor} + if not use_prev_close: + return [ cmap[opn < cls] for opn,cls in zip(opens,closes) ] + else: + first = cmap[opens[0] < closes[0]] + _list = [ cmap[pre < cls] for cls,pre in zip(closes[1:], closes) ] + return [first] + _list + +def _make_updown_color_list(key,marketcolors,opens,closes,overrides=None): + length = len(opens) + ups = [marketcolors[key][ 'up' ]]*length + downs = [marketcolors[key]['down']]*length + if overrides is not None: + for ix,mco in enumerate(overrides): + if mco is None: continue + if mcolors.is_color_like(mco): + ups[ix] = mco + downs[ix] = mco + else: # assume it is correctly a marketcolors object (dict) + ups[ix] = mco[key][ 'up' ] + downs[ix] = mco[key]['down'] + return [ups[ix] if opens[ix] < closes[ix] else downs[ix] for ix in range(length)] + + +def _updownhollow_colors(upcolor,downcolor,hollowcolor,opens,closes): + if upcolor == downcolor: + return upcolor + umap = {True : hollowcolor, False : upcolor } + dmap = {True : hollowcolor, False : downcolor} + first = umap[closes[0] > opens[0]] + _list = [ umap[cls > opn] if cls > cls0 else dmap[cls > opn] for cls0,opn,cls in zip(closes[0:-1],opens[1:],closes[1:]) ] + return [first] + _list + + +def _date_to_iloc(dtseries,date): + '''Convert a `date` to a location, given a date series w/a datetime index. + If `date` does not exactly match a date in the series then interpolate between two dates. + If `date` is outside the range of dates in the series, then raise an exception + . + ''' + d1s = dtseries.loc[date:] + if len(d1s) < 1: + sdtrange = str(dtseries[0])+' to '+str(dtseries[-1]) + raise ValueError('User specified line date "'+str(date)+'" is beyond (greater than) range of plotted data ('+sdtrange+').') + d1 = d1s.index[0] + d2s = dtseries.loc[:date] + if len(d2s) < 1: + sdtrange = str(dtseries[0])+' to '+str(dtseries[-1]) + raise ValueError('User specified line date "'+str(date)+'" is before (less than) range of plotted data ('+sdtrange+').') + d2 = dtseries.loc[:date].index[-1] + # If there are duplicate dates in the series, for example in a renko plot + # then .get_loc(date) will return a slice containing all the dups, so: + loc1 = dtseries.index.get_loc(d1) + if isinstance(loc1,slice): loc1 = loc1.start + loc2 = dtseries.index.get_loc(d2) + if isinstance(loc2,slice): loc2 = loc2.stop - 1 + return (loc1+loc2)/2.0 + +def _date_to_iloc_linear(dtseries,date,trace=False): + '''Find the location of a date using linear extrapolation. + Use the endpoints of `dtseries` to calculate the slope + and yintercept for the line: + iloc = (slope)*(dtseries) + (yintercept) + Then use them to calculate the location of `date` + ''' + d1 = _date_to_mdate(dtseries.index[0]) + d2 = _date_to_mdate(dtseries.index[-1]) + + if trace: print('d1,d2=',d1,d2) + i1 = 0.0 + i2 = len(dtseries) - 1.0 + if trace: print('i1,i2=',i1,i2) + + slope = (i2 - i1) / (d2 - d1) + yitrcpt1 = i1 - (slope*d1) + if trace: print('slope,yitrcpt=',slope,yitrcpt1) + yitrcpt2 = i2 - (slope*d2) + if trace: print('slope,yitrcpt=',slope,yitrcpt2) + if yitrcpt1 != yitrcpt2: + print('WARNING: yintercepts NOT equal!!!(',yitrcpt1,yitrcpt2,')') + yitrcpt = (yitrcpt1 + yitrcpt2) / 2.0 + else: + yitrcpt = yitrcpt1 + return (slope * _date_to_mdate(date)) + yitrcpt + +def _date_to_iloc_5_7ths(dtseries,date,direction,trace=False): + first = _date_to_mdate(dtseries.index[0]) + last = _date_to_mdate(dtseries.index[-1]) + avg_days_between_points = (last - first)/float(len(dtseries)) + if avg_days_between_points < 0.33: # intraday (not daily) + return None + if direction == 'forward': + delta = _date_to_mdate(date) - _date_to_mdate(dtseries.index[-1]) + loc_5_7ths = len(dtseries) - 1 + (5/7.)*delta + elif direction == 'backward': + delta = _date_to_mdate(dtseries.index[0]) - _date_to_mdate(date) + loc_5_7ths = - (5./7.)*delta + else: + raise ValueError('_date_to_iloc_5_7ths got BAD direction value='+str(direction)) + return loc_5_7ths + +def _date_to_iloc_extrapolate(dtseries,date): + '''Convert a `date` to a location, given a date series w/a datetime index. + If `date` does not exactly match a date in the series then interpolate between two dates. + If `date` is outside the range of dates in the series, then extrapolate: + Extrapolation results in increased error as the distance of the extrapolation increases. + We have two methods to extrapolate: + (1) Determine a linear equation based on the data provided in `dtseries`, + and use that equation to calculate the location for the date. + (2) Multiply by 5/7 the number of days between the edge date of dtseries and the + date for which we are requesting a location. + THIS ASSUMES DAILY data AND a 5 DAY TRADING WEEK. + Empirical observation (scratch_pad/date_to_iloc_extrapolation.ipynb) shows that + the systematic error of these two methods tends to be in opposite directions: + taking the average of the two methods reduces systematic errorr: However, + since method (2) applies only to DAILY data, we take the average of the two + methods only for daily data. For intraday data we use only method (1). + ''' + + d1s = dtseries.loc[date:] + if len(d1s) < 1: + # extrapolate forward: + loc_linear = _date_to_iloc_linear(dtseries,date) + loc_5_7ths = _date_to_iloc_5_7ths(dtseries,date,'forward') + if loc_5_7ths is not None: + return (loc_linear + loc_5_7ths)/2.0 + else: + return loc_linear + d1 = d1s.index[0] + d2s = dtseries.loc[:date] + if len(d2s) < 1: + # extrapolate backward: + loc_linear = _date_to_iloc_linear(dtseries,date) + loc_5_7ths = _date_to_iloc_5_7ths(dtseries,date,'backward') + if loc_5_7ths is not None: + return (loc_linear + loc_5_7ths)/2.0 + else: + return loc_linear + # Below here we *interpolate* (not extrapolate) + d2 = dtseries.loc[:date].index[-1] + # If there are duplicate dates in the series, for example in a renko plot + # then .get_loc(date) will return a slice containing all the dups, so: + loc1 = dtseries.index.get_loc(d1) + if isinstance(loc1,slice): loc1 = loc1.start + loc2 = dtseries.index.get_loc(d2) + if isinstance(loc2,slice): loc2 = loc2.stop - 1 + return (loc1+loc2)/2.0 + + +def _date_to_mdate(date): + if isinstance(date,str): + pydt = pd.to_datetime(date).to_pydatetime() + elif isinstance(date,pd.Timestamp): + pydt = date.to_pydatetime() + elif isinstance(date,(datetime.datetime,datetime.date)): + pydt = date + else: + return None + return mdates.date2num(pydt) + +def _convert_segment_dates(segments,dtindex): + ''' + Convert line segment dates to matplotlib dates + Inputted segment dates may be: pandas-parseable date-time string, pandas timestamp, + or a python datetime or date, or (if dtindex is not None) integer index + A "segment" is a "sequence of lines", + see: https://matplotlib.org/api/collections_api.html#matplotlib.collections.LineCollection + ''' + #import pdb + #pdb.set_trace() + if dtindex is not None: + dtseries = dtindex.to_series() + converted = [] + for line in segments: + new_line = [] + for dt,value in line: + if dtindex is not None: + date = _date_to_iloc(dtseries,dt) + else: + date = _date_to_mdate(dt) + if date is None: + raise TypeError('NON-DATE in segment line='+str(line)) + new_line.append((date,value)) + converted.append(new_line) + return converted + +def _valid_renko_kwargs(): + ''' + Construct and return the "valid renko kwargs table" for the mplfinance.plot(type='renko') + function. A valid kwargs table is a `dict` of `dict`s. The keys of the outer dict are + the valid key-words for the function. The value for each key is a dict containing 3 + specific keys: "Default", "Description" and "Validator" with the following values: + "Default" - The default value for the kwarg if none is specified. + "Description" - The description for the kwarg. + "Validator" - A function that takes the caller specified value for the kwarg, + and validates that it is the correct type, and (for kwargs with + a limited set of allowed values) may also validate that the + kwarg value is one of the allowed values. + ''' + vkwargs = { + 'brick_size' : { 'Default' : 'atr', + 'Description' : 'size of each brick on y-axis (typically price).'+ + ' specify a number, or specify "atr" for average true range.', + 'Validator' : lambda value: isinstance(value,(float,int)) + or value == 'atr' }, + 'atr_length' : { 'Default' : 14, + 'Description' : 'number of periods for atr calculation (if brick size is "atr")', + 'Validator' : lambda value: isinstance(value,int) + or value == 'total' }, + } + + _validate_vkwargs_dict(vkwargs) + + return vkwargs + + +def _valid_pnf_kwargs(): + ''' + Construct and return the "valid pnf kwargs table" for the mplfinance.plot(type='pnf') + function. A valid kwargs table is a `dict` of `dict`s. The keys of the outer dict are + the valid key-words for the function. The value for each key is a dict containing 3 + specific keys: "Default", "Description" and "Validator" with the following values: + "Default" - The default value for the kwarg if none is specified. + "Description" - The description for the kwarg. + "Validator" - A function that takes the caller specified value for the kwarg, + and validates that it is the correct type, and (for kwargs with + a limited set of allowed values) may also validate that the + kwarg value is one of the allowed values. + ''' + vkwargs = { + 'box_size' : { 'Default' : 'atr', + 'Description' : 'size of each box on y-axis (typically price).'+ + ' specify a number, or specify "atr" for average true range.', + 'Validator' : lambda value: isinstance(value,(float,int)) + or value == 'atr' }, + 'atr_length' : { 'Default' : 14, + 'Description' : 'number of periods for atr calculation (if box size is "atr")', + 'Validator' : lambda value: isinstance(value,int) + or value == 'total' }, + + 'reversal' : { 'Default' : 1, + 'Description' : 'number of boxes, in opposite direction, needed to reverse'+ + ' a trend (i.e. to start a new column).', + 'Validator' : lambda value: isinstance(value,int) }, + } + + _validate_vkwargs_dict(vkwargs) + + return vkwargs + + +def _valid_lines_kwargs(): + ''' + Construct and return the "valid lines (hlines,vlines,alines,tlines) kwargs table" + for the mplfinance.plot() `[h|v|a|t]lines=` kwarg functions. + A valid kwargs table is a `dict` of `dict`s. The keys of the outer dict are + the valid key-words for the function. The value for each key is a dict containing 3 + specific keys: "Default", "Description" and "Validator" with the following values: + "Default" - The default value for the kwarg if none is specified. + "Description" - The description for the kwarg. + "Validator" - A function that takes the caller specified value for the kwarg, + and validates that it is the correct type, and (for kwargs with + a limited set of allowed values) may also validate that the + kwarg value is one of the allowed values. + ''' + valid_linestyles = ['-','solid','--','dashed','-.','dashdot',':','dotted',None,' ',''] + vkwargs = { + 'hlines' : { 'Default' : None, + 'Description' : 'Draw one or more HORIZONTAL LINES across entire plot, by'+ + ' specifying a price, or sequence of prices. May also be a dict'+ + ' with key `hlines` specifying a price or sequence of prices, plus'+ + ' one or more of the following keys: `colors`, `linestyle`,'+ + ' `linewidths`, `alpha`.', + 'Validator' : _bypass_kwarg_validation }, + + 'vlines' : { 'Default' : None, + 'Description' : 'Draw one or more VERTICAL LINES across entire plot, by'+ + ' specifying a date[time], or sequence of date[time]. May also'+ + ' be a dict with key `vlines` specifying a date[time] or sequence'+ + ' of date[time], plus one or more of the following keys:'+ + ' `colors`, `linestyle`, `linewidths`, `alpha`.', + 'Validator' : _bypass_kwarg_validation }, + + 'alines' : { 'Default' : None, + 'Description' : 'Draw one or more ARBITRARY LINES anywhere on the plot, by'+ + ' specifying a sequence of two or more date/price pairs, or by'+ + ' specifying a sequence of sequences of two or more date/price pairs.'+ + ' May also be a dict with key `alines` (as date/price pairs described above),'+ + ' plus one or more of the following keys:'+ + ' `colors`, `linestyle`, `linewidths`, `alpha`.', + 'Validator' : _bypass_kwarg_validation }, + + 'tlines' : { 'Default' : None, + 'Description' : 'Draw one or more TREND LINES by specifying one or more pairs of date[times]'+ + ' between which each trend line should be drawn. May also be a dict with key'+ + ' `tlines` as just described, plus one or more of the following keys:'+ + ' `colors`, `linestyle`, `linewidths`, `alpha`, `tline_use`,`tline_method`.', + 'Validator' : _bypass_kwarg_validation }, + + 'colors' : { 'Default' : None, + 'Description' : 'Color of [hvat]lines (or sequence of colors, if each line to be a different color)', + 'Validator' : lambda value: value is None + or mcolors.is_color_like(value) + or (isinstance(value,(list,tuple)) + and all([mcolors.is_color_like(v) for v in value]) ) }, + + 'linestyle' : { 'Default' : '-', + 'Description' : 'line style of [hvat]lines (or sequence of line styles, if each line to have a different linestyle)', + 'Validator' : lambda value: value is None or value in valid_linestyles or + all([v in valid_linestyles for v in value]) }, + + 'linewidths': { 'Default' : None, + 'Description' : 'line width of [hvat]lines (or sequence of line widths, if each line to have a different width)', + 'Validator' : lambda value: value is None + or isinstance(value,(float,int)) + or all([isinstance(v,(float,int)) for v in value]) }, + + 'alpha': {'Default': 1.0, + 'Description': 'Opacity of [hvat]lines (or sequence of opacities,' + + 'if each line is to have a different opacity)' + + 'float from 0.0 to 1.0 ' + ' (1.0 means fully opaque; 0.0 means transparent.', + 'Validator': lambda value: isinstance(value, (float, int)) + or all([isinstance(v, (float, int)) for v in value])}, + + + 'tline_use' : { 'Default' : 'close', + 'Description' : 'value to use for TREND LINE ("open","high","low","close") or sequence of'+ + ' any combination of "open", "high", "low", "close" to use a average of the'+ + ' specified values to determine the trend line.', + 'Validator' : lambda value: isinstance(value,str) + or (isinstance(value,(list,tuple)) + and all([isinstance(v,str) for v in value]) ) }, + + 'tline_method': { 'Default' : 'point-to-point', + 'Description' : 'method for TREND LINE determination: "point-to-point" or "least-squares"', + 'Validator' : lambda value: value in ['point-to-point','least-squares'] } + } + + _validate_vkwargs_dict(vkwargs) + + return vkwargs + + +def _construct_ohlc_collections(dates, opens, highs, lows, closes, marketcolors=None, config=None): + """Represent the time, open, high, low, close as a vertical line + ranging from low to high. The left tick is the open and the right + tick is the close. + *opens*, *highs*, *lows* and *closes* must have the same length. + NOTE: this code assumes if any value open, high, low, close is + missing (*-1*) they all are missing + + Parameters + ---------- + opens : sequence + sequence of opening values + highs : sequence + sequence of high values + lows : sequence + sequence of low values + closes : sequence + sequence of closing values + marketcolors : dict of colors: 'up', 'down' + + Returns + ------- + ret : list + a list or tuple of matplotlib collections to be added to the axes + """ + + _check_input(opens, highs, lows, closes) + + if marketcolors is None: + mktcolors = _get_mpfstyle('classic')['marketcolors']['ohlc'] + else: + mktcolors = marketcolors['ohlc'] + + rangeSegments = [((dt, low), (dt, high)) for dt, low, high in zip(dates, lows, highs)] + + datalen = len(dates) + + avg_dist_between_points = (dates[-1] - dates[0]) / float(datalen) + + ticksize = config['_width_config']['ohlc_ticksize'] + + # the ticks will be from ticksize to 0 in points at the origin and + # we'll translate these to the date, open location + openSegments = [((dt-ticksize, op), (dt, op)) for dt, op in zip(dates, opens)] + + # the ticks will be from 0 to ticksize in points at the origin and + # we'll translate these to the date, close location + closeSegments = [((dt, close), (dt+ticksize, close)) for dt, close in zip(dates, closes)] + + if mktcolors['up'] == mktcolors['down'] and config['marketcolor_overrides'] is None: + colors = mktcolors['up'] + else: + overrides = config['marketcolor_overrides'] + colors = _make_updown_color_list('ohlc',marketcolors,opens,closes,overrides) + + lw = config['_width_config']['ohlc_linewidth'] + + rangeCollection = LineCollection(rangeSegments, + colors=colors, + linewidths=lw, + ) + + openCollection = LineCollection(openSegments, + colors=colors, + linewidths=lw, + ) + + closeCollection = LineCollection(closeSegments, + colors=colors, + linewidths=lw + ) + + return [rangeCollection, openCollection, closeCollection] + + +def _construct_candlestick_collections(dates, opens, highs, lows, closes, marketcolors=None, config=None): + """Represent the open, close as a bar line and high low range as a + vertical line. + + NOTE: this code assumes if any value open, low, high, close is + missing they all are missing + + + Parameters + ---------- + opens : sequence + sequence of opening values + highs : sequence + sequence of high values + lows : sequence + sequence of low values + closes : sequence + sequence of closing values + marketcolors : dict of colors: up, down, edge, wick, alpha + alpha : float + bar transparency + + Returns + ------- + ret : list + (lineCollection, barCollection) + """ + + _check_input(opens, highs, lows, closes) + + if marketcolors is None: + marketcolors = _get_mpfstyle('classic')['marketcolors'] + + datalen = len(dates) + + avg_dist_between_points = (dates[-1] - dates[0]) / float(datalen) + + delta = config['_width_config']['candle_width'] / 2.0 + + barVerts = [((date - delta, open), + (date - delta, close), + (date + delta, close), + (date + delta, open)) + for date, open, close in zip(dates, opens, closes)] + + rangeSegLow = [((date, low), (date, min(open,close))) + for date, low, open, close in zip(dates, lows, opens, closes)] + + rangeSegHigh = [((date, high), (date, max(open,close))) + for date, high, open, close in zip(dates, highs, opens, closes)] + + rangeSegments = rangeSegLow + rangeSegHigh + + alpha = marketcolors['alpha'] + + overrides = config['marketcolor_overrides'] + faceonly = config['mco_faceonly'] + + colors = _make_updown_color_list('candle',marketcolors,opens,closes,overrides) + colors = [ _mpf_to_rgba(c,alpha) for c in colors ] # include alpha + if faceonly: overrides = None + edgecolor = _make_updown_color_list('edge',marketcolors,opens,closes,overrides) + wickcolor = _make_updown_color_list('wick',marketcolors,opens,closes,overrides) + + lw = config['_width_config']['candle_linewidth'] + + rangeCollection = LineCollection(rangeSegments, + colors=wickcolor, + linewidths=lw, + ) + + barCollection = PolyCollection(barVerts, + facecolors=colors, + edgecolors=edgecolor, + linewidths=lw + ) + + return [rangeCollection, barCollection] + + +def _construct_hollow_candlestick_collections(dates, opens, highs, lows, closes, marketcolors=None, config=None): + """Represent today's open to close as a "bar" line (candle body) + and high low range as a vertical line (candle wick) + + If config['type']=='hollow_and_filled' (hollow and filled candles) then candle edge and + wick color depend on PREVIOUS close to today's close (up or down), and the center of the + candle body (hollow or filled) depends on the today's open to close (up or down). + + NOTE: this code assumes if any value open, low, high, close is + missing they all are missing + + Parameters + ---------- + opens : sequence + sequence of opening values + highs : sequence + sequence of high values + lows : sequence + sequence of low values + closes : sequence + sequence of closing values + marketcolors : dict of colors: up, down, edge, wick, alpha + alpha : float + bar (candle body) transparency + + Returns + ------- + ret : list + (lineCollection, barCollection) + """ + + _check_input(opens, highs, lows, closes) + + if marketcolors is None: + marketcolors = _get_mpfstyle('classic')['marketcolors'] + + datalen = len(dates) + + avg_dist_between_points = (dates[-1] - dates[0]) / float(datalen) + + delta = config['_width_config']['candle_width'] / 2.0 + + barVerts = [((date - delta, open), + (date - delta, close), + (date + delta, close), + (date + delta, open)) + for date, open, close in zip(dates, opens, closes)] + + rangeSegLow = [((date, low), (date, min(open,close))) + for date, low, open, close in zip(dates, lows, opens, closes)] + + rangeSegHigh = [((date, high), (date, max(open,close))) + for date, high, open, close in zip(dates, highs, opens, closes)] + + rangeSegments = rangeSegLow + rangeSegHigh + + alpha = marketcolors['alpha'] + + uc = mcolors.to_rgba(marketcolors['candle'][ 'up' ], alpha) + dc = mcolors.to_rgba(marketcolors['candle']['down'], alpha) + + hc = mcolors.to_rgba(marketcolors['hollow']) if 'hollow' in marketcolors else (0,0,0,0) + + colors = _updownhollow_colors(uc, dc, hc, opens, closes) # for candle body. + + edgecolor = _updown_colors(uc, dc, opens, closes, use_prev_close=True) + + wickcolor = _updown_colors(uc, dc, opens, closes, use_prev_close=True) + + # For hollow candles, we scale the candle linewidth up a little: + lw = 1.25 * config['_width_config']['candle_linewidth'] + + rangeCollection = LineCollection(rangeSegments, + colors=wickcolor, + linewidths=lw, + ) + + barCollection = PolyCollection(barVerts, + facecolors=colors, + edgecolors=edgecolor, + linewidths=lw + ) + + return [rangeCollection, barCollection] + + +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'] + + 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 + ) + calculated_values = dict(dates=new_dates,volumes=new_volumes, + values=brick_values,size=brick_size) + return [rectCollection,], calculated_values + + +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. During this same step we also combine like signed elements + and associated volume/date data ignoring any zero values that are created by + subtracting 1 from the box value. Next we recreate the box array utilizing a + rolling_change and volume_cache to store and sum the changes that don't break + the reversal threshold. + + 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'] + + box_size = pointnfig_params['box_size'] + atr_length = pointnfig_params['atr_length'] + reversal = pointnfig_params['reversal'] + + 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)) + + if reversal < 1 or reversal > 9: + raise ValueError("Specified reversal must be an integer in the range [1,9]") + + 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) + + adjusted_boxes = [boxes[0]] + temp_volumes, temp_dates = [new_volumes[0]], [new_dates[0]] + volume_cache = 0 + + # Clean data to subtract 1 from all box # not including the first boxes element and combine like signed adjacent values (after ignoring zeros) + for i in range(1, len(boxes)): + adjusted_value = boxes[i]- int((boxes[i]/abs(boxes[i]))) + + # not equal to 0 and different signs + if adjusted_value != 0 and adjusted_boxes[-1]*adjusted_value < 0: + + # Append adjusted_value, volumes, and date to associated lists + adjusted_boxes.append(adjusted_value) + temp_volumes.append(new_volumes[i] + volume_cache) + temp_dates.append(new_dates[i]) + + # reset volume_cache once we use it + volume_cache = 0 + + # not equal to 0 and same signs + elif adjusted_value != 0 and adjusted_boxes[-1]*adjusted_value > 0: + + # Add adjusted_value and volume values to last added elements + adjusted_boxes[-1] += adjusted_value + temp_volumes[-1] += new_volumes[i] + volume_cache + + # reset volume_cache once we use it + volume_cache = 0 + + else: # adjusted_value == 0 + volume_cache += new_volumes[i] + + boxes = [adjusted_boxes[0]] + new_volumes = [temp_volumes[0]] + new_dates = [temp_dates[0]] + + rolling_change = 0 + volume_cache = 0 + biggest_difference = 0 # only used for the last column + + #Clean data to account for reversal size (added to allow overriding the default reversal of 1) + for i in range(1, len(adjusted_boxes)): + + # Add to rolling_change and volume_cache which stores the box and volume values + rolling_change += adjusted_boxes[i] + volume_cache += temp_volumes[i] + + # if rolling_change is the same sign as the previous box and the abs value is bigger than the + # abs value of biggest_difference then we should replace biggest_difference with rolling_change + if rolling_change*boxes[-1] > 0 and abs(rolling_change) > abs(biggest_difference): + biggest_difference = rolling_change + + # Add to new list if the rolling change is >= the reversal + if abs(rolling_change) >= reversal: + + # if rolling_change is the same sign as the previous # of boxes then combine + if rolling_change*boxes[-1] > 0: + boxes[-1] += rolling_change + new_volumes[-1] += volume_cache + + # otherwise add new box + else: # < 0 (== 0 can't happen since neither rolling_change or boxes[-1] can be 0) + boxes.append(rolling_change) + new_volumes.append(volume_cache) + new_dates.append(temp_dates[i]) + + # reset rolling_change and volume_cache once we've used them + rolling_change = 0 + volume_cache = 0 + + # reset biggest_difference as we start from the beginning every time there is a reversal + biggest_difference = 0 + + # Adjust the last box column if the left over rolling_change is the same sign as the column + boxes[-1] += biggest_difference + new_volumes[-1] += volume_cache + + 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( 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 + ) + calculated_values = dict(dates=new_dates,counts=boxes,values=box_values, + volumes=new_volumes,size=box_size) + return [cirCollection, xCollection], calculated_values + + +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 widths 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()) + + 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 + + +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 widths 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 + + +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 widths 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 + +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 widths 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)) ) + df.index = df.index.tz_localize(None) + + # 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 + ''' + si = dfslice[tline_use].mean(axis=1) + s = si.dropna() + if len(s) < 2: + err = 'NOT enough data for Least Squares' + if (len(si) > 2): + err += ', due to presence of NaNs' + raise ValueError(err) + 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).replace(tzinfo=None), mdates.num2date(x2).replace(tzinfo=None) + 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) + + +from matplotlib.ticker import Formatter +class IntegerIndexDateTimeFormatter(Formatter): + """ + Formatter for axis that is indexed by integer, where the integers + represent the index location of the datetime object that should be + formatted at that lcoation. This formatter is used typically when + plotting datetime on an axis but the user does NOT want to see gaps + where days (or times) are missing. To use: plot the data against + a range of integers equal in length to the array of datetimes that + you would otherwise plot on that axis. Construct this formatter + by providing the arrange of datetimes (as matplotlib floats). When + the formatter receives an integer in the range, it will look up the + datetime and format it. + + """ + def __init__(self, dates, fmt='%b %d, %H:%M'): + self.dates = dates + self.len = len(dates) + self.fmt = fmt + + def __call__(self, x, pos=0): + #import pdb; pdb.set_trace() + 'Return label for time x at position pos' + # not sure what 'pos' is for: see + # https://matplotlib.org/gallery/ticks_and_spines/date_index_formatter.html + ix = int(np.round(x)) + + if ix >= self.len or ix < 0: + date = None + dateformat = '' + else: + date = self.dates[ix] + dateformat = mdates.num2date(date).strftime(self.fmt) + #print('x=',x,'pos=',pos,'dates[',ix,']=',date,'dateformat=',dateformat) + return dateformat + +def _mscatter(x,y,ax=None, m=None, **kw): + import matplotlib.markers as mmarkers + if not ax: ax=plt.gca() + sc = ax.scatter(x,y,**kw) + if (m is not None) and (len(m)==len(x)): + paths = [] + for marker in m: + if isinstance(marker, mmarkers.MarkerStyle): + marker_obj = marker + else: + marker_obj = mmarkers.MarkerStyle(marker) + path = marker_obj.get_path().transformed( + marker_obj.get_transform()) + paths.append(path) + sc.set_paths(paths) + return sc diff --git a/build/lib/mplfinance/_version.py b/build/lib/mplfinance/_version.py new file mode 100644 index 00000000..44439d7d --- /dev/null +++ b/build/lib/mplfinance/_version.py @@ -0,0 +1,6 @@ +version_info = (0, 12, 9, 'beta', 2) + +_specifier_ = {'alpha': 'a','beta': 'b','candidate': 'rc','final': ''} + +__version__ = '%s.%s.%s%s'%(version_info[0], version_info[1], version_info[2], + '' if version_info[3]=='final' else _specifier_[version_info[3]]+str(version_info[4])) diff --git a/build/lib/mplfinance/_widths.py b/build/lib/mplfinance/_widths.py new file mode 100644 index 00000000..3b6813c4 --- /dev/null +++ b/build/lib/mplfinance/_widths.py @@ -0,0 +1,197 @@ +import pandas as pd +from mplfinance._arg_validators import _process_kwargs, _validate_vkwargs_dict + +def _get_widths_df(): + ''' + Provide a dataframe of width data that appropriate scales widths of + various aspects of the plot (candles,ohlc bars,volume bars) based on + the amount or density of data. These numbers were arrived at by + carefully testing many use-cases of plots with various styles, + and observing which numbers gave the "best" appearance. + ''' + numpoints = [n for n in range(30,241,30)] + volume_width = (0.98, 0.96, 0.95, 0.925, 0.9, 0.9, 0.875, 0.825 ) + volume_linewidth = tuple([0.65]*8) + candle_width = (0.65, 0.575, 0.50, 0.445, 0.435, 0.425, 0.420, 0.415) + candle_linewidth = (1.00, 0.875, 0.75, 0.625, 0.500, 0.438, 0.435, 0.435) + ohlc_tickwidth = tuple([0.35]*8) + ohlc_linewidth = (1.50, 1.175, 0.85, 0.525, 0.525, 0.525, 0.525, 0.525) + line_width = (2.25, 1.8, 1.3, 0.813, 0.807, 0.801, 0.796, 0.791) + widths = {} + widths['vw'] = volume_width + widths['vlw'] = volume_linewidth + widths['cw'] = candle_width + widths['clw'] = candle_linewidth + widths['ow'] = ohlc_tickwidth + widths['olw'] = ohlc_linewidth + widths['lw'] = line_width + return pd.DataFrame(widths,index=numpoints) + +_widths = _get_widths_df() + + +def _valid_scale_width_kwargs(): + vkwargs = { + 'ohlc' : { 'Default' : None, + 'Description' : 'length of horizontal open/close tickmarks on ohlc bars', + 'Validator' : lambda value: isinstance(value,(float,int)) }, + + 'volume' : { 'Default' : None, + 'Description' : 'width of volume bars', + 'Validator' : lambda value: isinstance(value,(float,int)) }, + + 'candle' : { 'Default' : None, + 'Description' : 'width of candles', + 'Validator' : lambda value: isinstance(value,(float,int)) }, + + 'lines' : { 'Default' : None, + 'Description' : 'width of lines (for line plots and moving averages)', + 'Validator' : lambda value: isinstance(value,(float,int)) }, + + 'volume_linewidth' : { 'Default' : None, + 'Description' : 'width of edges of volume bars', + 'Validator' : lambda value: isinstance(value,(float,int)) }, + + 'ohlc_linewidth' : { 'Default' : None, + 'Description' : 'width (thickness) of ohlc bars', + 'Validator' : lambda value: isinstance(value,(float,int)) }, + + 'candle_linewidth' : { 'Default' : None, + 'Description' : 'width of candle edges and wicks', + 'Validator' : lambda value: isinstance(value,(float,int)) }, + } + + _validate_vkwargs_dict(vkwargs) + + return vkwargs + + +def _valid_update_width_kwargs(): + vkwargs = { + + 'ohlc_ticksize' : { 'Default' : None, + 'Description' : 'length of horizontal open/close tickmarks on ohlc bars', + 'Validator' : lambda value: isinstance(value,(float,int)) }, + + 'ohlc_linewidth' : { 'Default' : None, + 'Description' : 'width (thickness) of ohlc bars', + 'Validator' : lambda value: isinstance(value,(float,int)) }, + + 'volume_width' : { 'Default' : None, + 'Description' : 'width of volume bars', + 'Validator' : lambda value: isinstance(value,(float,int)) }, + + 'volume_linewidth' : { 'Default' : None, + 'Description' : 'width of edges of volume bars', + 'Validator' : lambda value: isinstance(value,(float,int)) }, + + 'candle_width' : { 'Default' : None, + 'Description' : 'width of candles', + 'Validator' : lambda value: isinstance(value,(float,int)) }, + + 'candle_linewidth' : { 'Default' : None, + 'Description' : 'width of candle edges and wicks', + 'Validator' : lambda value: isinstance(value,(float,int)) }, + + 'line_width' : { 'Default' : None, + 'Description' : 'width of lines (for line plots and moving averages)', + 'Validator' : lambda value: isinstance(value,(float,int)) }, + } + + _validate_vkwargs_dict(vkwargs) + + return vkwargs + + +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 + + +def _dfinterpolate(df,key,column): + ''' + Given a DataFrame, with all values and the Index as floats, + and given a float key, find the row that matches the key, or + find the two rows surrounding that key, and return the interpolated + value for the specified column, based on where the key falls between + the two rows. If they key is an exact match for a key in the index, + the return the exact value from the column. If the key is less than + or greater than any key in the index, then return either the first + or last value for the column. + ''' + s = df[column] + s1 = s.loc[:key] + if len(s1) < 1: + return s.iloc[0] + j1 = s1.index[-1] + v1 = s1.iloc[-1] + + s2 = s.loc[key:] + if len(s2) < 1: + return s.iloc[-1] + j2 = s2.index[0] + v2 = s2.iloc[0] + + if j1 == j2: + return v1 + delta = j2 - j1 + portion = (key - j1)/delta + ans = v1 + (v2-v1)*portion + return ans diff --git a/build/lib/mplfinance/original_flavor.py b/build/lib/mplfinance/original_flavor.py new file mode 100644 index 00000000..f6963963 --- /dev/null +++ b/build/lib/mplfinance/original_flavor.py @@ -0,0 +1,885 @@ +""" +A collection of functions for analyzing and plotting +financial data. User contributions welcome! + +""" +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import numpy as np +from matplotlib import colors as mcolors +from matplotlib.collections import LineCollection, PolyCollection +from matplotlib.lines import TICKLEFT, TICKRIGHT, Line2D +from matplotlib.patches import Rectangle +from matplotlib.transforms import Affine2D + +from six.moves import xrange, zip + + +def plot_day_summary_oclh(ax, quotes, ticksize=3, + colorup='k', colordown='r'): + """Plots day summary + + Represent the time, open, close, high, low as a vertical line + ranging from low to high. The left tick is the open and the right + tick is the close. + + + + Parameters + ---------- + ax : `Axes` + an `Axes` instance to plot to + quotes : sequence of (time, open, close, high, low, ...) sequences + data to plot. time must be in float date format - see date2num + ticksize : int + open/close tick marker in points + colorup : color + the color of the lines where close >= open + colordown : color + the color of the lines where close < open + + Returns + ------- + lines : list + list of tuples of the lines added (one tuple per quote) + """ + return _plot_day_summary(ax, quotes, ticksize=ticksize, + colorup=colorup, colordown=colordown, + ochl=True) + + +def plot_day_summary_ohlc(ax, quotes, ticksize=3, + colorup='k', colordown='r'): + """Plots day summary + + Represent the time, open, high, low, close as a vertical line + ranging from low to high. The left tick is the open and the right + tick is the close. + + + + Parameters + ---------- + ax : `Axes` + an `Axes` instance to plot to + quotes : sequence of (time, open, high, low, close, ...) sequences + data to plot. time must be in float date format - see date2num + ticksize : int + open/close tick marker in points + colorup : color + the color of the lines where close >= open + colordown : color + the color of the lines where close < open + + Returns + ------- + lines : list + list of tuples of the lines added (one tuple per quote) + """ + return _plot_day_summary(ax, quotes, ticksize=ticksize, + colorup=colorup, colordown=colordown, + ochl=False) + + +def _plot_day_summary(ax, quotes, ticksize=3, + colorup='k', colordown='r', + ochl=True): + """Plots day summary + + + Represent the time, open, high, low, close as a vertical line + ranging from low to high. The left tick is the open and the right + tick is the close. + + + + Parameters + ---------- + ax : `Axes` + an `Axes` instance to plot to + quotes : sequence of quote sequences + data to plot. time must be in float date format - see date2num + (time, open, high, low, close, ...) vs + (time, open, close, high, low, ...) + set by `ochl` + ticksize : int + open/close tick marker in points + colorup : color + the color of the lines where close >= open + colordown : color + the color of the lines where close < open + ochl: bool + argument to select between ochl and ohlc ordering of quotes + + Returns + ------- + lines : list + list of tuples of the lines added (one tuple per quote) + """ + # unfortunately this has a different return type than plot_day_summary2_* + lines = [] + for q in quotes: + if ochl: + t, open, close, high, low = q[:5] + else: + t, open, high, low, close = q[:5] + + if close >= open: + color = colorup + else: + color = colordown + + vline = Line2D(xdata=(t, t), ydata=(low, high), + color=color, + antialiased=False, # no need to antialias vert lines + ) + + oline = Line2D(xdata=(t, t), ydata=(open, open), + color=color, + antialiased=False, + marker=TICKLEFT, + markersize=ticksize, + ) + + cline = Line2D(xdata=(t, t), ydata=(close, close), + color=color, + antialiased=False, + markersize=ticksize, + marker=TICKRIGHT) + + lines.extend((vline, oline, cline)) + ax.add_line(vline) + ax.add_line(oline) + ax.add_line(cline) + + ax.autoscale_view() + + return lines + + +def candlestick_ochl(ax, quotes, width=0.2, colorup='k', colordown='r', + alpha=1.0): + """ + Plot the time, open, close, high, low as a vertical line ranging + from low to high. Use a rectangular bar to represent the + open-close span. If close >= open, use colorup to color the bar, + otherwise use colordown + + Parameters + ---------- + ax : `Axes` + an Axes instance to plot to + quotes : sequence of (time, open, close, high, low, ...) sequences + As long as the first 5 elements are these values, + the record can be as long as you want (e.g., it may store volume). + + time must be in float days format - see date2num + + width : float + fraction of a day for the rectangle width + colorup : color + the color of the rectangle where close >= open + colordown : color + the color of the rectangle where close < open + alpha : float + the rectangle alpha level + + Returns + ------- + ret : tuple + returns (lines, patches) where lines is a list of lines + added and patches is a list of the rectangle patches added + + """ + return _candlestick(ax, quotes, width=width, colorup=colorup, + colordown=colordown, + alpha=alpha, ochl=True) + + +def candlestick_ohlc(ax, quotes, width=0.2, colorup='k', colordown='r', + alpha=1.0): + """ + Plot the time, open, high, low, close as a vertical line ranging + from low to high. Use a rectangular bar to represent the + open-close span. If close >= open, use colorup to color the bar, + otherwise use colordown + + Parameters + ---------- + ax : `Axes` + an Axes instance to plot to + quotes : sequence of (time, open, high, low, close, ...) sequences + As long as the first 5 elements are these values, + the record can be as long as you want (e.g., it may store volume). + + time must be in float days format - see date2num + + width : float + fraction of a day for the rectangle width + colorup : color + the color of the rectangle where close >= open + colordown : color + the color of the rectangle where close < open + alpha : float + the rectangle alpha level + + Returns + ------- + ret : tuple + returns (lines, patches) where lines is a list of lines + added and patches is a list of the rectangle patches added + + """ + return _candlestick(ax, quotes, width=width, colorup=colorup, + colordown=colordown, + alpha=alpha, ochl=False) + + +def _candlestick(ax, quotes, width=0.2, colorup='k', colordown='r', + alpha=1.0, ochl=True): + """ + Plot the time, open, high, low, close as a vertical line ranging + from low to high. Use a rectangular bar to represent the + open-close span. If close >= open, use colorup to color the bar, + otherwise use colordown + + Parameters + ---------- + ax : `Axes` + an Axes instance to plot to + quotes : sequence of quote sequences + data to plot. time must be in float date format - see date2num + (time, open, high, low, close, ...) vs + (time, open, close, high, low, ...) + set by `ochl` + width : float + fraction of a day for the rectangle width + colorup : color + the color of the rectangle where close >= open + colordown : color + the color of the rectangle where close < open + alpha : float + the rectangle alpha level + ochl: bool + argument to select between ochl and ohlc ordering of quotes + + Returns + ------- + ret : tuple + returns (lines, patches) where lines is a list of lines + added and patches is a list of the rectangle patches added + + """ + + OFFSET = width / 2.0 + + lines = [] + patches = [] + for q in quotes: + if ochl: + t, open, close, high, low = q[:5] + else: + t, open, high, low, close = q[:5] + + if close >= open: + color = colorup + lower = open + height = close - open + else: + color = colordown + lower = close + height = open - close + + vline = Line2D( + xdata=(t, t), ydata=(low, high), + color=color, + linewidth=0.5, + antialiased=True, + ) + + rect = Rectangle( + xy=(t - OFFSET, lower), + width=width, + height=height, + facecolor=color, + edgecolor=color, + ) + rect.set_alpha(alpha) + + lines.append(vline) + patches.append(rect) + ax.add_line(vline) + ax.add_patch(rect) + ax.autoscale_view() + + return lines, patches + + +def _check_input(opens, closes, highs, lows, miss=-1): + """Checks that *opens*, *highs*, *lows* and *closes* have the same length. + NOTE: this code assumes if any value open, high, low, close is + missing (*-1*) they all are missing + + Parameters + ---------- + ax : `Axes` + an Axes instance to plot to + opens : sequence + sequence of opening values + highs : sequence + sequence of high values + lows : sequence + sequence of low values + closes : sequence + sequence of closing values + miss : int + identifier of the missing data + + Raises + ------ + ValueError + if the input sequences don't have the same length + """ + + def _missing(sequence, miss=-1): + """Returns the index in *sequence* of the missing data, identified by + *miss* + + Parameters + ---------- + sequence : + sequence to evaluate + miss : + identifier of the missing data + + Returns + ------- + where_miss: numpy.ndarray + indices of the missing data + """ + return np.where(np.array(sequence) == miss)[0] + + same_length = len(opens) == len(highs) == len(lows) == len(closes) + _missopens = _missing(opens) + same_missing = ((_missopens == _missing(highs)).all() and + (_missopens == _missing(lows)).all() and + (_missopens == _missing(closes)).all()) + + if not (same_length and same_missing): + msg = ("*opens*, *highs*, *lows* and *closes* must have the same" + " length. NOTE: this code assumes if any value open, high," + " low, close is missing (*-1*) they all must be missing.") + raise ValueError(msg) + + +def plot_day_summary2_ochl(ax, opens, closes, highs, lows, ticksize=4, + colorup='k', colordown='r'): + """Represent the time, open, close, high, low, as a vertical line + ranging from low to high. The left tick is the open and the right + tick is the close. + + Parameters + ---------- + ax : `Axes` + an Axes instance to plot to + opens : sequence + sequence of opening values + closes : sequence + sequence of closing values + highs : sequence + sequence of high values + lows : sequence + sequence of low values + ticksize : int + size of open and close ticks in points + colorup : color + the color of the lines where close >= open + colordown : color + the color of the lines where close < open + + Returns + ------- + ret : list + a list of lines added to the axes + """ + + return plot_day_summary2_ohlc(ax, opens, highs, lows, closes, ticksize, + colorup, colordown) + + +def plot_day_summary2_ohlc(ax, opens, highs, lows, closes, ticksize=4, + colorup='k', colordown='r'): + """Represent the time, open, high, low, close as a vertical line + ranging from low to high. The left tick is the open and the right + tick is the close. + *opens*, *highs*, *lows* and *closes* must have the same length. + NOTE: this code assumes if any value open, high, low, close is + missing (*-1*) they all are missing + + Parameters + ---------- + ax : `Axes` + an Axes instance to plot to + opens : sequence + sequence of opening values + highs : sequence + sequence of high values + lows : sequence + sequence of low values + closes : sequence + sequence of closing values + ticksize : int + size of open and close ticks in points + colorup : color + the color of the lines where close >= open + colordown : color + the color of the lines where close < open + + Returns + ------- + ret : list + a list of lines added to the axes + """ + + _check_input(opens, highs, lows, closes) + + rangeSegments = [((i, low), (i, high)) for i, low, high in + zip(xrange(len(lows)), lows, highs) if low != -1] + + # the ticks will be from ticksize to 0 in points at the origin and + # we'll translate these to the i, close location + openSegments = [((-ticksize, 0), (0, 0))] + + # the ticks will be from 0 to ticksize in points at the origin and + # we'll translate these to the i, close location + closeSegments = [((0, 0), (ticksize, 0))] + + offsetsOpen = [(i, open) for i, open in + zip(xrange(len(opens)), opens) if open != -1] + + offsetsClose = [(i, close) for i, close in + zip(xrange(len(closes)), closes) if close != -1] + + scale = ax.figure.dpi * (1.0 / 72.0) + + tickTransform = Affine2D().scale(scale, 0.0) + + colorup = mcolors.to_rgba(colorup) + colordown = mcolors.to_rgba(colordown) + colord = {True: colorup, False: colordown} + colors = [colord[open < close] for open, close in + zip(opens, closes) if open != -1 and close != -1] + + useAA = 0, # use tuple here + lw = 1, # and here + rangeCollection = LineCollection(rangeSegments, + colors=colors, + linewidths=lw, + antialiaseds=useAA, + ) + + openCollection = LineCollection(openSegments, + colors=colors, + antialiaseds=useAA, + linewidths=lw, + offsets=offsetsOpen, + transOffset=ax.transData, + ) + openCollection.set_transform(tickTransform) + + closeCollection = LineCollection(closeSegments, + colors=colors, + antialiaseds=useAA, + linewidths=lw, + offsets=offsetsClose, + transOffset=ax.transData, + ) + closeCollection.set_transform(tickTransform) + + minpy, maxx = (0, len(rangeSegments)) + miny = min([low for low in lows if low != -1]) + maxy = max([high for high in highs if high != -1]) + corners = (minpy, miny), (maxx, maxy) + ax.update_datalim(corners) + ax.autoscale_view() + + # add these last + ax.add_collection(rangeCollection) + ax.add_collection(openCollection) + ax.add_collection(closeCollection) + return rangeCollection, openCollection, closeCollection + + +def candlestick2_ochl(ax, opens, closes, highs, lows, width=4, + colorup='k', colordown='r', + alpha=0.75): + """Represent the open, close as a bar line and high low range as a + vertical line. + + Preserves the original argument order. + + + Parameters + ---------- + ax : `Axes` + an Axes instance to plot to + opens : sequence + sequence of opening values + closes : sequence + sequence of closing values + highs : sequence + sequence of high values + lows : sequence + sequence of low values + width : int + size of open and close ticks in points + colorup : color + the color of the lines where close >= open + colordown : color + the color of the lines where close < open + alpha : float + bar transparency + + Returns + ------- + ret : tuple + (lineCollection, barCollection) + """ + + return candlestick2_ohlc(ax, opens, highs, lows, closes, width=width, + colorup=colorup, colordown=colordown, + alpha=alpha) + + +def candlestick2_ohlc(ax, opens, highs, lows, closes, width=4, + colorup='k', colordown='r', + alpha=0.75): + """Represent the open, close as a bar line and high low range as a + vertical line. + + NOTE: this code assumes if any value open, low, high, close is + missing they all are missing + + + Parameters + ---------- + ax : `Axes` + an Axes instance to plot to + opens : sequence + sequence of opening values + highs : sequence + sequence of high values + lows : sequence + sequence of low values + closes : sequence + sequence of closing values + width : float + size of open and close ticks in points + colorup : color + the color of the lines where close >= open + colordown : color + the color of the lines where close < open + alpha : float + bar transparency + + Returns + ------- + ret : tuple + (lineCollection, barCollection) + """ + + _check_input(opens, highs, lows, closes) + + delta = width / 2. + barVerts = [((i - delta, open), + (i - delta, close), + (i + delta, close), + (i + delta, open)) + for i, open, close in zip(xrange(len(opens)), opens, closes) + if open != -1 and close != -1] + + rangeSegments = [((i, low), (i, high)) + for i, low, high in zip(xrange(len(lows)), lows, highs) + if low != -1] + + colorup = mcolors.to_rgba(colorup, alpha) + colordown = mcolors.to_rgba(colordown, alpha) + colord = {True: colorup, False: colordown} + colors = [colord[open < close] + for open, close in zip(opens, closes) + if open != -1 and close != -1] + + useAA = 0, # use tuple here + lw = 0.5, # and here + rangeCollection = LineCollection(rangeSegments, + colors=colors, + linewidths=lw, + antialiaseds=useAA, + ) + + barCollection = PolyCollection(barVerts, + facecolors=colors, + edgecolors=colors, + antialiaseds=useAA, + linewidths=lw, + ) + + minx, maxx = 0, len(rangeSegments) + 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) + ax.update_datalim(corners) + ax.autoscale_view() + + # add these last + ax.add_collection(rangeCollection) + ax.add_collection(barCollection) + return rangeCollection, barCollection + + +def volume_overlay(ax, opens, closes, volumes, + colorup='k', colordown='r', + width=4, alpha=1.0): + """Add a volume overlay to the current axes. The opens and closes + are used to determine the color of the bar. -1 is missing. If a + value is missing on one it must be missing on all + + Parameters + ---------- + ax : `Axes` + an Axes instance to plot to + opens : sequence + a sequence of opens + closes : sequence + a sequence of closes + volumes : sequence + a sequence of volumes + width : int + the bar width in points + colorup : color + the color of the lines where close >= open + colordown : color + the color of the lines where close < open + alpha : float + bar transparency + + Returns + ------- + ret : `barCollection` + The `barrCollection` added to the axes + + """ + + colorup = mcolors.to_rgba(colorup, alpha) + colordown = mcolors.to_rgba(colordown, alpha) + colord = {True: colorup, False: colordown} + colors = [colord[open < close] + for open, close in zip(opens, closes) + if open != -1 and close != -1] + + delta = width / 2. + bars = [((i - delta, 0), (i - delta, v), (i + delta, v), (i + delta, 0)) + for i, v in enumerate(volumes) + if v != -1] + + barCollection = PolyCollection(bars, + facecolors=colors, + edgecolors=((0, 0, 0, 1), ), + antialiaseds=(0,), + linewidths=(0.5,), + ) + + ax.add_collection(barCollection) + corners = (0, 0), (len(bars), max(volumes)) + ax.update_datalim(corners) + ax.autoscale_view() + + # add these last + return barCollection + + +def volume_overlay2(ax, closes, volumes, + colorup='k', colordown='r', + width=4, alpha=1.0): + """ + Add a volume overlay to the current axes. The closes are used to + determine the color of the bar. -1 is missing. If a value is + missing on one it must be missing on all + + nb: first point is not displayed - it is used only for choosing the + right color + + + Parameters + ---------- + ax : `Axes` + an Axes instance to plot to + closes : sequence + a sequence of closes + volumes : sequence + a sequence of volumes + width : int + the bar width in points + colorup : color + the color of the lines where close >= open + colordown : color + the color of the lines where close < open + alpha : float + bar transparency + + Returns + ------- + ret : `barCollection` + The `barrCollection` added to the axes + + """ + + return volume_overlay(ax, closes[:-1], closes[1:], volumes[1:], + colorup, colordown, width, alpha) + + +def volume_overlay3(ax, quotes, + colorup='k', colordown='r', + width=4, alpha=1.0): + """Add a volume overlay to the current axes. quotes is a list of (d, + open, high, low, close, volume) and close-open is used to + determine the color of the bar + + Parameters + ---------- + ax : `Axes` + an Axes instance to plot to + quotes : sequence of (time, open, high, low, close, ...) sequences + data to plot. time must be in float date format - see date2num + width : int + the bar width in points + colorup : color + the color of the lines where close1 >= close0 + colordown : color + the color of the lines where close1 < close0 + alpha : float + bar transparency + + Returns + ------- + ret : `barCollection` + The `barrCollection` added to the axes + + + """ + + colorup = mcolors.to_rgba(colorup, alpha) + colordown = mcolors.to_rgba(colordown, alpha) + colord = {True: colorup, False: colordown} + + dates, opens, highs, lows, closes, volumes = list(zip(*quotes)) + colors = [colord[close1 >= close0] + for close0, close1 in zip(closes[:-1], closes[1:]) + if close0 != -1 and close1 != -1] + colors.insert(0, colord[closes[0] >= opens[0]]) + + right = width / 2.0 + left = -width / 2.0 + + bars = [((left, 0), (left, volume), (right, volume), (right, 0)) + for d, open, high, low, close, volume in quotes] + + sx = ax.figure.dpi * (1.0 / 72.0) # scale for points + sy = ax.bbox.height / ax.viewLim.height + + barTransform = Affine2D().scale(sx, sy) + + dates = [d for d, open, high, low, close, volume in quotes] + offsetsBars = [(d, 0) for d in dates] + + useAA = 0, # use tuple here + lw = 0.5, # and here + barCollection = PolyCollection(bars, + facecolors=colors, + edgecolors=((0, 0, 0, 1),), + antialiaseds=useAA, + linewidths=lw, + offsets=offsetsBars, + transOffset=ax.transData, + ) + barCollection.set_transform(barTransform) + + minpy, maxx = (min(dates), max(dates)) + miny = 0 + maxy = max([volume for d, open, high, low, close, volume in quotes]) + corners = (minpy, miny), (maxx, maxy) + ax.update_datalim(corners) + # print 'datalim', ax.dataLim.bounds + # print 'viewlim', ax.viewLim.bounds + + ax.add_collection(barCollection) + ax.autoscale_view() + + return barCollection + + +def index_bar(ax, vals, + facecolor='b', edgecolor='l', + width=4, alpha=1.0, ): + """Add a bar collection graph with height vals (-1 is missing). + + Parameters + ---------- + ax : `Axes` + an Axes instance to plot to + vals : sequence + a sequence of values + facecolor : color + the color of the bar face + edgecolor : color + the color of the bar edges + width : int + the bar width in points + alpha : float + bar transparency + + Returns + ------- + ret : `barCollection` + The `barrCollection` added to the axes + + """ + + facecolors = (mcolors.to_rgba(facecolor, alpha),) + edgecolors = (mcolors.to_rgba(edgecolor, alpha),) + + right = width / 2.0 + left = -width / 2.0 + + bars = [((left, 0), (left, v), (right, v), (right, 0)) + for v in vals if v != -1] + + sx = ax.figure.dpi * (1.0 / 72.0) # scale for points + sy = ax.bbox.height / ax.viewLim.height + + barTransform = Affine2D().scale(sx, sy) + + offsetsBars = [(i, 0) for i, v in enumerate(vals) if v != -1] + + barCollection = PolyCollection(bars, + facecolors=facecolors, + edgecolors=edgecolors, + antialiaseds=(0,), + linewidths=(0.5,), + offsets=offsetsBars, + transOffset=ax.transData, + ) + barCollection.set_transform(barTransform) + + minpy, maxx = (0, len(offsetsBars)) + miny = 0 + maxy = max([v for v in vals if v != -1]) + corners = (minpy, miny), (maxx, maxy) + ax.update_datalim(corners) + ax.autoscale_view() + + # add these last + ax.add_collection(barCollection) + return barCollection diff --git a/build/lib/mplfinance/plotting.py b/build/lib/mplfinance/plotting.py new file mode 100644 index 00000000..073864a0 --- /dev/null +++ b/build/lib/mplfinance/plotting.py @@ -0,0 +1,1307 @@ +import matplotlib.dates as mdates +import matplotlib.pyplot as plt +import matplotlib.colors as mcolors +import matplotlib.axes as mpl_axes +import matplotlib.figure as mpl_fig +import pandas as pd +import numpy as np +import copy +import io +import os +import math +import warnings +import statistics as stat + +from itertools import cycle +#from pandas.plotting import register_matplotlib_converters +#register_matplotlib_converters() + +from mplfinance._utils import _construct_aline_collections +from mplfinance._utils import _construct_hline_collections +from mplfinance._utils import _construct_vline_collections +from mplfinance._utils import _construct_tline_collections +from mplfinance._utils import _construct_mpf_collections + +from mplfinance._widths import _determine_width_config + +from mplfinance._utils import _updown_colors +from mplfinance._utils import IntegerIndexDateTimeFormatter +from mplfinance._utils import _mscatter +from mplfinance._utils import _check_and_convert_xlim_configuration + +from mplfinance import _styles + +from mplfinance._arg_validators import _check_and_prepare_data, _mav_validator +from mplfinance._arg_validators import _get_valid_plot_types, _fill_between_validator +from mplfinance._arg_validators import _process_kwargs, _validate_vkwargs_dict +from mplfinance._arg_validators import _kwarg_not_implemented, _bypass_kwarg_validation +from mplfinance._arg_validators import _hlines_validator, _vlines_validator +from mplfinance._arg_validators import _alines_validator, _tlines_validator +from mplfinance._arg_validators import _scale_padding_validator, _yscale_validator +from mplfinance._arg_validators import _valid_panel_id, _check_for_external_axes +from mplfinance._arg_validators import _xlim_validator, _mco_validator, _is_marketcolor_object + +from mplfinance._panels import _build_panels +from mplfinance._panels import _set_ticks_on_bottom_panel_only + +from mplfinance._helpers import _determine_format_string +from mplfinance._helpers import _list_of_dict +from mplfinance._helpers import _num_or_seq_of_num +from mplfinance._helpers import _adjust_color_brightness + +VALID_PMOVE_TYPES = ['renko', 'pnf'] + +DEFAULT_FIGRATIO = (8.00,5.75) + +def with_rc_context(func): + ''' + This decoractor creates an rcParams context around a function, so that any changes + the function makes to rcParams will be reversed when the decorated function returns + (therefore those changes have no effect outside of the decorated function). + ''' + def decorator(*args, **kwargs): + with plt.rc_context(): + return func(*args, **kwargs) + return decorator + +def _warn_no_xgaps_deprecated(value): + warnings.warn('\n\n ================================================================= '+ + '\n\n WARNING: `no_xgaps` is /deprecated/:'+ + '\n Default value is now `no_xgaps=True`'+ + '\n However, to set `no_xgaps=False` and silence this warning,'+ + '\n use instead: `show_nontrading=True`.'+ + '\n\n ================================================================ ', + category=DeprecationWarning) + return isinstance(value,bool) + +def _warn_set_ylim_deprecated(value): + warnings.warn('\n\n ================================================================= '+ + '\n\n WARNING: `set_ylim=(ymin,ymax)` kwarg '+ + '\n has been replaced with: '+ + '\n `ylim=(ymin,ymax)`.'+ + '\n\n ================================================================ ', + category=DeprecationWarning) + return isinstance(value,bool) + + +def _valid_plot_kwargs(): + ''' + Construct and return the "valid kwargs table" for the mplfinance.plot() function. + A valid kwargs table is a `dict` of `dict`s. The keys of the outer dict are the + valid key-words for the function. The value for each key is a dict containing + 2 specific keys: "Default", and "Validator" with the following values: + "Default" - The default value for the kwarg if none is specified. + "Validator" - A function that takes the caller specified value for the kwarg, + and validates that it is the correct type, and (for kwargs with + a limited set of allowed values) may also validate that the + kwarg value is one of the allowed values. + ''' + + vkwargs = { + 'columns' : { 'Default' : None, # use default names: ('Open', 'High', 'Low', 'Close', 'Volume') + 'Description' : ('Column names to be used when plotting the data.'+ + ' Default: ("Open", "High", "Low", "Close", "Volume")'), + 'Validator' : lambda value: isinstance(value, (tuple, list)) + and len(value) == 5 + and all(isinstance(c, str) for c in value) }, + 'type' : { 'Default' : 'ohlc', + 'Description' : 'Plot type: '+str(_get_valid_plot_types()), + 'Validator' : lambda value: value in _get_valid_plot_types() }, + + 'style' : { 'Default' : None, + 'Description' : 'plot style; see `mpf.available_styles()`', + 'Validator' : _styles._valid_mpf_style }, + + 'volume' : { 'Default' : False, + 'Description' : 'Plot volume: True, False, or set to Axes object on which to plot.', + 'Validator' : lambda value: isinstance(value,bool) or isinstance(value,mpl_axes.Axes) }, + + 'mav' : { 'Default' : None, + 'Description' : 'Moving Average window size(s); (int or tuple of ints)', + 'Validator' : _mav_validator }, + + 'renko_params' : { 'Default' : dict(), + 'Description' : 'dict of renko parameters; call `mpf.kwarg_help("renko_params")`', + 'Validator' : lambda value: isinstance(value,dict) }, + + 'pnf_params' : { 'Default' : dict(), + 'Description' : 'dict of point-and-figure parameters; call `mpf.kwarg_help("pnf_params")`', + 'Validator' : lambda value: isinstance(value,dict) }, + + 'study' : { 'Default' : None, + 'Description' : 'kwarg not implemented', + 'Validator' : lambda value: _kwarg_not_implemented(value) }, + + 'marketcolor_overrides' : { 'Default' : None, + 'Description' : 'sequence of color objects to override market colors.'+ + 'sequence must be same length as ohlc(v) DataFrame. Each'+ + 'color object may be a color, marketcolor object, or None.', + 'Validator' : _mco_validator }, + + 'mco_faceonly' : { 'Default' : False, # If True: Override only the face of the candle + 'Description' : 'True/False marketcolor_overrides only apply to face of candle.', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'no_xgaps' : { 'Default' : True, # None means follow default logic below: + 'Description' : 'deprecated', + 'Validator' : lambda value: _warn_no_xgaps_deprecated(value) }, + + 'show_nontrading' : { 'Default' : False, + 'Description' : 'True/False show spaces for non-trading days/periods', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'figscale' : { 'Default' : None, # scale base figure size up or down. + 'Description' : 'Scale figure size up (if > 1) or down (if < 1)', + 'Validator' : lambda value: isinstance(value,float) or isinstance(value,int) }, + + 'figratio' : { 'Default' : None, # aspect ratio; scaled to 8.0 height + 'Description' : 'Aspect ratio of the figure. Default: (8.00,5.75)', + 'Validator' : lambda value: isinstance(value,(tuple,list)) + and len(value) == 2 + and isinstance(value[0],(float,int)) + and isinstance(value[1],(float,int)) }, + + 'figsize' : { 'Default' : None, # figure size; overrides figratio and figscale + 'Description' : ('Figure size: overrides both figscale and figratio,'+ + ' else defaults to figratio*figscale'), + 'Validator' : lambda value: isinstance(value,(tuple,list)) + and len(value) == 2 + and isinstance(value[0],(float,int)) + and isinstance(value[1],(float,int)) }, + + 'fontscale' : { 'Default' : None, # scale all fonts up or down + 'Description' : 'Scale font sizes up (if > 1) or down (if < 1)', + 'Validator' : lambda value: isinstance(value,float) or isinstance(value,int) }, + + 'linecolor' : { 'Default' : None, # line color in line plot + 'Description' : 'Line color for `type=line`', + 'Validator' : lambda value: mcolors.is_color_like(value) }, + + 'title' : { 'Default' : None, # Figure Title + 'Description' : 'Figure Title (see also `axtitle`)', + 'Validator' : lambda value: isinstance(value,(str,dict)) }, + + 'axtitle' : { 'Default' : None, # Axes Title (subplot title) + 'Description' : 'Axes Title (subplot title)', + 'Validator' : lambda value: isinstance(value,(str,dict)) }, + + 'xlabel' : { 'Default' : 'Date', # x-axis label + 'Description' : 'label for x-axis of main plot', + 'Validator' : lambda value: isinstance(value,str) }, + + 'ylabel' : { 'Default' : 'Price', # y-axis label + 'Description' : 'label for y-axis of main plot', + 'Validator' : lambda value: isinstance(value,str) }, + + 'ylabel_lower' : { 'Default' : None, # y-axis label default logic below + 'Description' : 'label for y-axis of volume', + 'Validator' : lambda value: isinstance(value,str) }, + + 'addplot' : { 'Default' : None, + 'Description' : 'addplot object or sequence of addplot objects (from `mpf.make_addplot()`)', + 'Validator' : lambda value: isinstance(value,dict) or (isinstance(value,list) and all([isinstance(d,dict) for d in value])) }, + + 'savefig' : { 'Default' : None, + 'Description' : 'file name, or BytesIO, or dict with key `fname` plus other keys allowed as '+ + ' kwargs to matplotlib `Figure.savefig()`', + 'Validator' : lambda value: isinstance(value,dict) or isinstance(value,str) or isinstance(value, io.BytesIO) or isinstance(value, os.PathLike) }, + + 'block' : { 'Default' : None, + 'Description' : 'True/False wait for figure to be closed before returning', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'returnfig' : { 'Default' : False, + 'Description' : 'return Figure and list of Axes objects created by mplfinance;'+ + ' user must display plot when ready, usually by calling `mpf.show()`', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'return_calculated_values' : { 'Default' : None, + 'Description' : 'set to a variable containing an empty dict; `mpf.plot()` will fill'+ + ' the dict with various mplfinance calculated values', + 'Validator' : lambda value: isinstance(value, dict) and len(value) == 0}, + + 'set_ylim' : { 'Default' : None, + 'Description' : 'deprecated', + 'Validator' : lambda value: _warn_set_ylim_deprecated(value) }, + + 'ylim' : { 'Default' : None, + 'Description' : 'Limits for y-axis as tuple (min,max), i.e. (bottom,top)', + 'Validator' : lambda value: isinstance(value, (list,tuple)) and len(value) == 2 + and all([isinstance(v,(int,float)) for v in value])}, + + 'xlim' : { 'Default' : None, + 'Description' : 'Limits for x-axis as tuple (min,max), i.e. (left,right)', + 'Validator' : lambda value: _xlim_validator(value) }, + + 'set_ylim_panelB' : { 'Default' : None, + 'Description' : 'deprecated', + 'Validator' : lambda value: _warn_set_ylim_deprecated(value) }, + + 'hlines' : { 'Default' : None, + 'Description' : 'Draw one or more HORIZONTAL LINES across entire plot, by'+ + ' specifying a price, or sequence of prices. May also be a dict'+ + ' with key `hlines` specifying a price or sequence of prices, plus'+ + ' one or more of the following keys: `colors`, `linestyle`,'+ + ' `linewidths`, `alpha`.', + 'Validator' : lambda value: _hlines_validator(value) }, + + 'vlines' : { 'Default' : None, + 'Description' : 'Draw one or more VERTICAL LINES across entire plot, by'+ + ' specifying a date[time], or sequence of date[time]. May also'+ + ' be a dict with key `vlines` specifying a date[time] or sequence'+ + ' of date[time], plus one or more of the following keys:'+ + ' `colors`, `linestyle`, `linewidths`, `alpha`.', + 'Validator' : lambda value: _vlines_validator(value) }, + + 'alines' : { 'Default' : None, + 'Description' : 'Draw one or more ARBITRARY LINES anywhere on the plot, by'+ + ' specifying a sequence of two or more date/price pairs, or by'+ + ' specifying a sequence of sequences of two or more date/price pairs.'+ + ' May also be a dict with key `alines` (as date/price pairs described above),'+ + ' plus one or more of the following keys:'+ + ' `colors`, `linestyle`, `linewidths`, `alpha`.', + 'Validator' : lambda value: _alines_validator(value) }, + + 'tlines' : { 'Default' : None, + 'Description' : 'Draw one or more TREND LINES by specifying one or more pairs of date[times]'+ + ' between which each trend line should be drawn. May also be a dict with key'+ + ' `tlines` as just described, plus one or more of the following keys:'+ + ' `colors`, `linestyle`, `linewidths`, `alpha`, `tline_use`,`tline_method`.', + 'Validator' : lambda value: _tlines_validator(value) }, + + 'panel_ratios' : { 'Default' : None, + 'Description' : 'sequence of numbers indicating relative sizes of panels; sequence len'+ + ' must be same as number of panels, or len 2 where first entry is for'+ + ' main panel, and second entry is for all other panels', + 'Validator' : lambda value: isinstance(value,(tuple,list)) and len(value) <= 32 and + all([isinstance(v,(int,float)) for v in value]) }, + + 'main_panel' : { 'Default' : 0, + 'Description' : 'integer - which panel is the main panel for `.plot()`', + 'Validator' : lambda value: _valid_panel_id(value) }, + + 'volume_panel' : { 'Default' : 1, + 'Description' : 'integer - which panel is the volume panel', + 'Validator' : lambda value: _valid_panel_id(value) }, + + 'num_panels' : { 'Default' : None, + 'Description' : 'total number of panels', + 'Validator' : lambda value: isinstance(value,int) and value in range(1,32+1) }, + + 'datetime_format' : { 'Default' : None, + 'Description' : 'x-axis tick format as valid `strftime()` format string', + 'Validator' : lambda value: isinstance(value,str) }, + + 'xrotation' : { 'Default' : 45, + 'Description' : 'Angle (degrees) for x-axis tick labels; 90=vertical', + 'Validator' : lambda value: isinstance(value,(int,float)) }, + + 'axisoff' : { 'Default' : False, + 'Description' : '`axisoff=True` means do NOT display any axis.', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'closefig' : { 'Default' : 'auto', + 'Description' : 'True|False close the Figure before returning', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'fill_between' : { 'Default' : None, + 'Description' : 'fill between specification as y-value, or sequence of'+ + ' y-values, or dict containing key "y1" plus any additional'+ + ' kwargs for `fill_between()`', + 'Validator' : _fill_between_validator }, + + 'tight_layout' : { 'Default' : False, + 'Description' : 'True|False implement tight layout (minimal padding around Figure)'+ + ' (see also `scale_padding` kwarg)', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'scale_padding' : { 'Default' : 1.0, # Issue#193 + 'Description' : 'Increase, > 1.0, or decrease, < 1.0, padding around figure.'+ + ' May also be a dict containing one or more of the following keys:'+ + ' "top", "bottom", "left", "right", to individual scale padding'+ + ' on each side of Figure.', + 'Validator' : lambda value: _scale_padding_validator(value) }, + + 'width_adjuster_version' : { 'Default' : 'v1', + 'Description' : 'specify version of object width adjustment algorithm: "v0" or "v1"'+ + ' (See also "widths" tutorial in mplfinance examples folder).', + 'Validator' : lambda value: value in ('v0', 'v1') }, + + 'scale_width_adjustment' : { 'Default' : None, + 'Description' : 'scale width of plot objects wider, > 1.0, or narrower, < 1.0'+ + ' may also be a dict to scale individual widths.'+ + ' (See also "widths" tutorial in mplfinance examples folder).', + 'Validator' : lambda value: isinstance(value,dict) and len(value) > 0 }, + + 'update_width_config' : { 'Default' : None, + 'Description' : 'dict - update individual items in width configuration.'+ + ' (See also "widths" tutorial in mplfinance examples folder).', + 'Validator' : lambda value: isinstance(value,dict) and len(value) > 0 }, + + 'return_width_config' : { 'Default' : None, + 'Description' : 'empty dict variable to be filled with width configuration settings.', + 'Validator' : lambda value: isinstance(value,dict) and len(value)==0 }, + + 'saxbelow' : { 'Default' : True, # Issue#115 Comment#639446764 + 'Description' : 'set the volume Axes below (behind) all other Axes objects', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'ax' : { 'Default' : None, + 'Description' : 'Matplotlib Axes object on which to plot', + 'Validator' : lambda value: isinstance(value,mpl_axes.Axes) }, + + 'volume_exponent' : { 'Default' : None, + 'Description' : 'integer exponent on the volume axis'+ + ' (or set to "legacy" for old mplfinance style)', + 'Validator' : lambda value: isinstance(value,int) or value == 'legacy'}, + + 'tz_localize' : { 'Default' : True, + 'Description' : 'True|False localize the times in the DatetimeIndex', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'yscale' : { 'Default' : None, + 'Description' : 'y-axis scale: "linear", "log", "symlog", or "logit"', + 'Validator' : lambda value: _yscale_validator(value) }, + + 'volume_yscale' : { 'Default' : None, + 'Description' : 'Volume y-axis scale: "linear", "log", "symlog", or "logit"', + 'Validator' : lambda value: _yscale_validator(value) }, + + 'volume_ylim' : { 'Default' : None, + 'Description' : 'Volume y-axis limits as tuple (min,max), i.e. (bottom,top)', + 'Validator' : lambda value: isinstance(value, (list,tuple)) and len(value) == 2 + and all([isinstance(v,(int,float)) for v in value])}, + + 'volume_alpha' : { 'Default' : 1, # alpha of Volume bars + 'Description' : 'opacity for Volume bar: 0.0 (transparent) to 1.0 (opaque)', + 'Validator' : lambda value: isinstance(value,(int,float)) or + all([isinstance(v,(int,float)) for v in value]) }, + + 'warn_too_much_data' : { 'Default' : 599, + 'Description' : 'Tolerance for data amount in plot. Default=599 rows.'+ + ' Values greater than \'warn_too_much_data\' will trigger a warning.', + 'Validator' : lambda value: isinstance(value,int) }, + } + + _validate_vkwargs_dict(vkwargs) + + return vkwargs + +###@with_rc_context +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) + + config['xlim'] = _check_and_convert_xlim_configuration(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) + + if config['marketcolor_overrides'] is not None: + if len(config['marketcolor_overrides']) != len(dates): + raise ValueError('`marketcolor_overrides` must be same length as dataframe.') + + 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?') + + if not external_axes_mode: + fig = plt.figure() + _adjust_figsize(fig,config) + else: + fig = None + + _adjust_fontsize(config) + + if config['volume'] and volumes is None: + raise ValueError('Request for volume, but NO volume data.') + + if external_axes_mode: + panels = None + axA1 = config['ax'] + axA1.set_axisbelow(config['saxbelow']) + if config['volume']: + volumeAxes = config['volume'] + volumeAxes.set_axisbelow(config['saxbelow']) + else: + panels = _build_panels(fig, config) + axA1 = panels.at[config['main_panel'],'axes'][0] + if config['volume']: + if config['volume_panel'] == config['main_panel']: + # ohlc and volume on same panel: move volume to secondary axes: + volumeAxes = panels.at[config['volume_panel'],'axes'][1] + volumeAxes.set_zorder(axA1.get_zorder()-0.1) # Make sure ohlc is above volume + axA1.patch.set_visible(False) # Let volume show through + panels.at[config['main_panel'],'used2nd'] = True + else: + volumeAxes = panels.at[config['volume_panel'],'axes'][0] + else: + volumeAxes = 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)) + + # 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, calculated_values = collections + volumes = calculated_values['volumes'] + pmove_dates = calculated_values['dates'] + pmove_values = calculated_values['values'] + if all([isinstance(v,(list,tuple)) for v in pmove_values]): + pmove_avgvals = [sum(v)/len(v) for v in pmove_values] + else: + pmove_avgvals = pmove_values + pmove_size = calculated_values['size'] + pmove_counts = calculated_values['counts'] if 'counts' in calculated_values else None + formatter = IntegerIndexDateTimeFormatter(pmove_dates, fmtstring) + xdates = np.arange(len(pmove_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,pmove_avgvals) + 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 = pmove_avgvals + _highs = [value+pmove_size for value in pmove_avgvals] + + 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) + if miny > 0.0: + # don't let it go negative: + setminy = max(0.9*miny,miny-ydelta) + else: + setminy = miny-ydelta + axA1.set_ylim(setminy,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 == 'renko': + retdict['renko_bricks' ] = pmove_values + retdict['renko_dates' ] = mdates.num2date(pmove_dates) + retdict['renko_size' ] = pmove_size + retdict['renko_volumes'] = volumes if config['volume'] else None + elif ptype == 'pnf': + retdict['pnf_dates' ] = mdates.num2date(pmove_dates) + retdict['pnf_counts' ] = pmove_counts + retdict['pnf_values' ] = pmove_values + retdict['pnf_avgvals' ] = pmove_avgvals + retdict['pnf_size' ] = pmove_size + retdict['pnf_volumes' ] = volumes if config['volume'] else None + 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(pmove_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) + valp = config['volume_alpha'] + volumeAxes.bar(xdates,volumes,width=w,linewidth=lw,color=vcolors,ec=adjc,alpha=valp) + if config['volume_ylim'] is not None: + vymin = config['volume_ylim'][0] + vymax = config['volume_ylim'][1] + else: + 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) + + ysd = config['yscale'] + if isinstance(ysd,dict): + yscale = ysd['yscale'] + del ysd['yscale'] + axA1.set_yscale(yscale,**ysd) + elif isinstance(ysd,str): + axA1.set_yscale(ysd) + + + 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,xdates) + 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,xdates) + + # 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: + fblist = copy.deepcopy(config['fill_between']) + if _num_or_seq_of_num(fblist): + fblist = [dict(y1=fblist),] + elif isinstance(fblist,dict): + fblist = [fblist,] + if not _list_of_dict(fblist): + raise TypeError('Bad type for `fill_between` specifier.') + for fb in fblist: + if 'x' in fb: + raise ValueError('fill_between dict may not contain `x`') + panid = config['main_panel'] + if 'panel' in fb: + panid = fb['panel'] + del fb['panel'] + fb['x'] = xdates # add 'x' to caller's fb dict + 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']) + axA1.set_xlabel(config['xlabel']) + + if config['volume']: + if external_axes_mode: + volumeAxes.tick_params(axis='x',rotation=xrotation) + volumeAxes.xaxis.set_major_formatter(formatter) + + vscale = 'linear' + ysd = config['volume_yscale'] + if isinstance(ysd,dict): + yscale = ysd['yscale'] + del ysd['yscale'] + volumeAxes.set_yscale(yscale,**ysd) + vscale = yscale + elif isinstance(ysd,str): + volumeAxes.set_yscale(ysd) + vscale = ysd + offset = '' + if vscale == 'linear': + 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: `y=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(va='bottom', y=0.89) + else: + title_kwargs = dict(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: + fig.savefig(**save,bbox_inches='tight') + else: + fig.savefig(**save) + else: + if config['tight_layout']: + fig.savefig(save,bbox_inches='tight') + else: + fig.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) + + # rcp = copy.deepcopy(plt.rcParams) + # rcpdf = rcParams_to_df(rcp) + # print('type(rcpdf)=',type(rcpdf)) + # print('rcpdfhead(3)=',rcpdf.head(3)) + # return # rcpdf + +def _adjust_figsize(fig,config): + if fig is None: + return + 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.set_size_inches(fsize) + +def _adjust_fontsize(config): + if config['fontscale'] is None: + return + if not isinstance(plt.rcParams['font.size'],(float,int)): + warnings.warn('\n\n ================================================================= '+ + '\n\n WARNING: Unable to scale fonts: plt.rcParams["font.size"] is NOT a float!'+ + '\n\n ================================================================ ', + category=UserWarning) + return + plt.rcParams['font.size'] *= config['fontscale'] + # -------------------------------------------- + # From: matplotlib.font_manager.font_scalings: + # font_scalings = { + # 'xx-small': 0.579, + # 'x-small': 0.694, + # 'small': 0.833, + # 'medium': 1.0, + # 'large': 1.200, + # 'x-large': 1.440, + # 'xx-large': 1.728, + # 'larger': 1.2, + # 'smaller': 0.833, + # None: 1.0, + # } + # -------------------------------------------- + fontstuff = ['axes.labelsize','axes.titlesize', 'figure.titlesize','legend.fontsize', + 'legend.title_fontsize','xtick.labelsize','ytick.labelsize'] + for item in fontstuff: + if isinstance(plt.rcParams[item],(float,int)): + plt.rcParams[item] *= config['fontscale'] + +def _addplot_collections(panid,panels,apdict,xdates,config): + + apdata = apdict['data'] + aptype = apdict['type'] + external_axes_mode = apdict['ax'] is not None + + #--------------------------------------------------------------# + # Note: _auto_secondary_y() sets the 'magnitude' column in the + # `panels` dataframe, which is needed for automatically + # determining if secondary_y is needed. Therefore we call + # _auto_secondary_y() for *all* addplots, even those that + # are set to True or False (not 'auto') for secondary_y + # because their magnitudes may be needed if *any* apdicts + # contain secondary_y='auto'. + # In theory we could first loop through all apdicts to see + # if any have secondary_y='auto', but since that is the + # default value, we will just assume we have at least one. + + valid_apc_types = ['ohlc','candle'] + if aptype not in valid_apc_types: + raise TypeError('Invalid aptype='+str(aptype)+'. Must be one of '+str(valid_apc_types)) + if not isinstance(apdata,pd.DataFrame): + raise TypeError('addplot type "'+aptype+'" MUST be accompanied by addplot data of type `pd.DataFrame`') + d,o,h,l,c,v = _check_and_prepare_data(apdata,config) + + mc = apdict['marketcolors'] + if _is_marketcolor_object(mc): + apstyle = config['style'].copy() + apstyle['marketcolors'] = mc + else: + apstyle = config['style'] + + collections = _construct_mpf_collections(aptype,d,xdates,o,h,l,c,v,config,apstyle) + + if not external_axes_mode: + lo = math.log(max(math.fabs(np.nanmin(l)),1e-7),10) - 0.5 + hi = math.log(max(math.fabs(np.nanmax(h)),1e-7),10) + 0.5 + secondary_y = _auto_secondary_y( panels, panid, lo, hi ) + if 'auto' != apdict['secondary_y']: + secondary_y = apdict['secondary_y'] + if secondary_y: + ax = panels.at[panid,'axes'][1] + panels.at[panid,'used2nd'] = True + else: + ax = panels.at[panid,'axes'][0] + else: + ax = apdict['ax'] + + for coll in collections: + ax.add_collection(coll) + if apdict['mav'] is not None: + apmavprices = _plot_mav(ax,config,xdates,c,apdict['mav']) + ax.autoscale_view() + return ax + +def _addplot_columns(panid,panels,ydata,apdict,xdates,config): + external_axes_mode = apdict['ax'] is not None + if not external_axes_mode: + secondary_y = False + if apdict['secondary_y'] == 'auto': + 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 = _auto_secondary_y( panels, panid, ymlo, ymhi ) + 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] + else: + ax = apdict['ax'] + + aptype = apdict['type'] + if aptype == 'scatter': + size = apdict['markersize'] + mark = apdict['marker'] + color = apdict['color'] + alpha = apdict['alpha'] + edgecolors = apdict['edgecolors'] + linewidths = apdict['linewidths'] + + if isinstance(mark,(list,tuple,np.ndarray)): + _mscatter(xdates, ydata, ax=ax, m=mark, s=size, color=color, alpha=alpha, edgecolors=edgecolors, linewidths=linewidths) + else: + ax.scatter(xdates, ydata, s=size, marker=mark, color=color, alpha=alpha, edgecolors=edgecolors, linewidths=linewidths) + elif aptype == 'bar': + width = 0.8 if apdict['width'] is None else 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'] + width = apdict['width'] if apdict['width'] is not None else 1.6*config['_width_config']['line_width'] + alpha = apdict['alpha'] + ax.plot(xdates,ydata,linestyle=ls,color=color,linewidth=width,alpha=alpha) + elif aptype == 'step': + stepwhere = apdict['stepwhere'] + ls = apdict['linestyle'] + color = apdict['color'] + width = apdict['width'] if apdict['width'] is not None else 1.6*config['_width_config']['line_width'] + alpha = apdict['alpha'] + ax.step(xdates,ydata,where = stepwhere,linestyle=ls,color=color,linewidth=width,alpha=alpha) + else: + raise ValueError('addplot type "'+str(aptype)+'" NOT yet supported.') + + if apdict['mav'] is not None: + apmavprices = _plot_mav(ax,config,xdates,ydata,apdict['mav']) + + return ax + +def _addplot_apply_supplements(ax,apdict,xdates): + if (apdict['ylabel'] is not None): + ax.set_ylabel(apdict['ylabel']) + if (apdict['xlabel'] is not None): + ax.set_xlabel(apdict['xlabel']) + if apdict['ylim'] is not None: + ax.set_ylim(apdict['ylim'][0],apdict['ylim'][1]) + if apdict['title'] is not None: + ax.set_title(apdict['title']) + ysd = apdict['yscale'] + if isinstance(ysd,dict): + yscale = ysd['yscale'] + del ysd['yscale'] + ax.set_yscale(yscale,**ysd) + elif isinstance(ysd,str): + ax.set_yscale(ysd) + # added by Wen + if "fill_between" in apdict and apdict['fill_between'] is not None: + # deep copy because mplfinance code sometimes modifies the fill_between dict + fblist = copy.deepcopy(apdict['fill_between']) + if isinstance(fblist,dict): + fblist = [fblist,] + if _list_of_dict(fblist): + for fb in fblist: + if 'x' in fb: + raise ValueError('fill_between dict may not contain `x`') + fb['x'] = xdates # add 'x' to caller's fb dict + ax.fill_between(**fb) + else: + raise ValueError('Invalid addplot fill_between: must be `dict` or `list of dict`') + +def _set_ylabels_side(ax_pri,ax_sec,primary_on_right): + # put the primary axis on one side, + # and the twinx() on the "other" side: + if primary_on_right == True: + ax_pri.yaxis.set_label_position('right') + ax_pri.yaxis.tick_right() + if ax_sec is not None: + ax_sec.yaxis.set_label_position('left') + ax_sec.yaxis.tick_left() + else: # treat non-True as False, whether False, None, or anything else. + ax_pri.yaxis.set_label_position('left') + ax_pri.yaxis.tick_left() + if ax_sec is not None: + ax_sec.yaxis.set_label_position('right') + ax_sec.yaxis.tick_right() + +def _plot_mav(ax,config,xdates,prices,apmav=None,apwidth=None): + style = config['style'] + if apmav is not None: + mavgs = apmav + else: + mavgs = config['mav'] + mavp_list = [] + if mavgs is not None: + shift = None + if isinstance(mavgs,dict): + shift = mavgs['shift'] + mavgs = mavgs['period'] + 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 idx,mav in enumerate(mavgs): + mean = pd.Series(prices).rolling(mav).mean() + if shift is not None: + mean = mean.shift(periods=shift[idx]) + mavprices = mean.values + lw = config['_width_config']['line_width'] + if mavc: + ax.plot(xdates, mavprices, linewidth=lw, color=next(mavc)) + else: + ax.plot(xdates, mavprices, linewidth=lw) + mavp_list.append(mavprices) + return mavp_list + +def _auto_secondary_y( panels, panid, ylo, yhi ): + # 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. + secondary_y = False + p = panid,'mag' + if panels.at[p] is None: + panels.at[p] = {'lo':ylo,'hi':yhi} + elif ylo < panels.at[p]['lo'] or yhi > 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) + return secondary_y + +def _valid_addplot_kwargs(): + + valid_linestyles = ('-','solid','--','dashed','-.','dashdot','.','dotted',None,' ','') + valid_types = ('line','scatter','bar', 'ohlc', 'candle','step') + valid_stepwheres = ('pre','post','mid') + valid_edgecolors = ('face', 'none', None) + + vkwargs = { + 'scatter' : { 'Default' : False, + 'Description' : "Deprecated. (Use kwarg `type='scatter' instead.", + 'Validator' : lambda value: isinstance(value,bool) }, + + 'type' : { 'Default' : 'line', + 'Description' : 'addplot type: "line","scatter","bar", "ohlc", "candle","step"', + 'Validator' : lambda value: value in valid_types }, + + 'mav' : { 'Default' : None, + 'Description' : 'Moving Average window size(s); (int or tuple of ints)', + 'Validator' : _mav_validator }, + + 'panel' : { 'Default' : 0, + 'Description' : 'Panel (int 0-31) to use for this addplot', + 'Validator' : lambda value: _valid_panel_id(value) }, + + 'marker' : { 'Default' : 'o', + 'Description' : "marker for `type='scatter'` plot", + 'Validator' : lambda value: _bypass_kwarg_validation(value) }, + + 'markersize' : { 'Default' : 18, + 'Description' : 'size of marker for `type="scatter"`; default=18', + 'Validator' : lambda value: isinstance(value,(int,float)) }, + + 'color' : { 'Default' : None, + 'Description' : 'color (or sequence of colors) of line(s), scatter marker(s), or bar(s).', + 'Validator' : lambda value: mcolors.is_color_like(value) or + (isinstance(value,(list,tuple,np.ndarray)) and all([mcolors.is_color_like(v) for v in value])) }, + + 'linestyle' : { 'Default' : None, + 'Description' : 'line style for `type=line` ('+str(valid_linestyles)+')', + 'Validator' : lambda value: value in valid_linestyles }, + + 'linewidths' : { 'Default': None, + 'Description' : 'edge widths of scatter markers', + 'Validator' : lambda value: isinstance(value,(int,float)) }, + + 'edgecolors' : { 'Default': None, + 'Description' : 'edgecolors of scatter markers', + 'Validator': lambda value: mcolors.is_color_like(value) or value in valid_edgecolors}, + + 'width' : { 'Default' : None, # width of `bar` or `line` + 'Description' : 'width of bar or line for `type="bar"` or `type="line"', + 'Validator' : lambda value: isinstance(value,(int,float)) or + all([isinstance(v,(int,float)) for v in value]) }, + + 'bottom' : { 'Default' : 0, # bottom for `type=bar` plots + 'Description' : 'bottom value for `type=bar` bars. Default=0', + 'Validator' : lambda value: isinstance(value,(int,float)) or + all([isinstance(v,(int,float)) for v in value]) }, + 'alpha' : { 'Default' : 1, # alpha of `bar`, `line`, or `scatter` + 'Description' : 'opacity for 0.0 (transparent) to 1.0 (opaque)', + 'Validator' : lambda value: isinstance(value,(int,float)) or + all([isinstance(v,(int,float)) for v in value]) }, + + 'secondary_y' : { 'Default' : 'auto', + 'Description' : "True|False|'auto' place the additional plot data on a"+ + " secondary y-axis. 'auto' compares the magnitude or the"+ + " addplot data, to data already on the axis, and if it appears"+ + " they are of different magnitudes, then it uses a secondary y-axis."+ + " True or False always override 'auto'.", + 'Validator' : lambda value: isinstance(value,bool) or value == 'auto' }, + + 'y_on_right' : { 'Default' : None, + 'Description' : 'True|False put y-axis tick labels on the right, for this addplot'+ + ' regardless of what the mplfinance style says to to.', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'ylabel' : { 'Default' : None, + 'Description' : 'label for y-axis (for this addplot)', + 'Validator' : lambda value: isinstance(value,str) }, + + 'ylim' : {'Default' : None, + 'Description' : 'Limits for addplot y-axis as tuple (min,max), i.e. (bottom,top)', + 'Validator' : lambda value: isinstance(value, (list,tuple)) and len(value) == 2 + and all([isinstance(v,(int,float)) for v in value])}, + + 'title' : { 'Default' : None, + 'Description' : 'Axes Title (subplot title) for this addplot.', + 'Validator' : lambda value: isinstance(value,str) }, + + 'ax' : { 'Default' : None, + 'Description' : 'Matplotlib Axes object on which to plot this addplot', + 'Validator' : lambda value: isinstance(value,mpl_axes.Axes) }, + + 'yscale' : { 'Default' : None, + 'Description' : 'addplot y-axis scale: "linear", "log", "symlog", or "logit"', + 'Validator' : lambda value: _yscale_validator(value) }, + + 'stepwhere' : { 'Default' : 'pre', + 'Description' : "'pre','post', or 'mid': where to place step relative"+ + " to data for `type='step'`", + 'Validator' : lambda value : value in valid_stepwheres }, + + 'marketcolors': { 'Default' : None, # use 'style' for default, instead. + 'Description' : "marketcolors for this addplot (instead of the mplfinance"+ + " style\'s marketcolors). For addplot `type='ohlc'`"+ + " and type='candle'", + 'Validator' : lambda value: _is_marketcolor_object(value) }, + 'fill_between': { 'Default' : None, # added by Wen + 'Description' : " fill region", + 'Validator' : _fill_between_validator }, + + } + + _validate_vkwargs_dict(vkwargs) + + return vkwargs + + +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()) + + # kwarg `type` replaces kwarg `scatter` + if config['scatter'] == True and config['type'] == 'line': + config['type'] = 'scatter' + + return dict( data=data, **config) diff --git a/dist/mplfinance-0.12.9b2-py3.10.egg b/dist/mplfinance-0.12.9b2-py3.10.egg new file mode 100644 index 0000000000000000000000000000000000000000..9fbe8be5d35e48c7adbbf480c38e88f18bfc4191 GIT binary patch literal 148059 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 bO>j9$7+}8&c=ZSV(E{&Axb(pLJH-D1o8XLc literal 0 HcmV?d00001 diff --git a/src/mplfinance/plotting.py b/src/mplfinance/plotting.py index b3d4f341..073864a0 100644 --- a/src/mplfinance/plotting.py +++ b/src/mplfinance/plotting.py @@ -184,6 +184,10 @@ def _valid_plot_kwargs(): 'axtitle' : { 'Default' : None, # Axes Title (subplot title) 'Description' : 'Axes Title (subplot title)', 'Validator' : lambda value: isinstance(value,(str,dict)) }, + + 'xlabel' : { 'Default' : 'Date', # x-axis label + 'Description' : 'label for x-axis of main plot', + 'Validator' : lambda value: isinstance(value,str) }, 'ylabel' : { 'Default' : 'Price', # y-axis label 'Description' : 'label for y-axis of main plot', @@ -779,6 +783,7 @@ def plot( data, **kwargs ): # working in `addplot`). axA1.set_ylabel(config['ylabel']) + axA1.set_xlabel(config['xlabel']) if config['volume']: if external_axes_mode: @@ -1070,6 +1075,8 @@ def _addplot_columns(panid,panels,ydata,apdict,xdates,config): def _addplot_apply_supplements(ax,apdict,xdates): if (apdict['ylabel'] is not None): ax.set_ylabel(apdict['ylabel']) + if (apdict['xlabel'] is not None): + ax.set_xlabel(apdict['xlabel']) if apdict['ylim'] is not None: ax.set_ylim(apdict['ylim'][0],apdict['ylim'][1]) if apdict['title'] is not None: From 4be48185c54be9e9e977c4eadbbc947b4d00044d Mon Sep 17 00:00:00 2001 From: Vedant Gawande Date: Sat, 22 Oct 2022 00:35:42 +0530 Subject: [PATCH 10/22] Added Date field on x-axis --- build/lib/mplfinance/__init__.py | 7 - build/lib/mplfinance/_arg_validators.py | 445 ----- build/lib/mplfinance/_helpers.py | 121 -- build/lib/mplfinance/_kwarg_help.py | 154 -- build/lib/mplfinance/_mpf_warnings.py | 16 - build/lib/mplfinance/_mplrcputils.py | 87 - build/lib/mplfinance/_mplwraps.py | 121 -- build/lib/mplfinance/_panels.py | 232 --- build/lib/mplfinance/_styledata/__init__.py | 45 - build/lib/mplfinance/_styledata/binance.py | 29 - build/lib/mplfinance/_styledata/blueskies.py | 23 - build/lib/mplfinance/_styledata/brasil.py | 25 - build/lib/mplfinance/_styledata/charles.py | 30 - build/lib/mplfinance/_styledata/checkers.py | 26 - build/lib/mplfinance/_styledata/classic.py | 28 - build/lib/mplfinance/_styledata/default.py | 29 - build/lib/mplfinance/_styledata/ibd.py | 38 - build/lib/mplfinance/_styledata/kenan.py | 33 - build/lib/mplfinance/_styledata/mike.py | 36 - .../lib/mplfinance/_styledata/nightclouds.py | 23 - build/lib/mplfinance/_styledata/sas.py | 4 - .../mplfinance/_styledata/starsandstripes.py | 24 - build/lib/mplfinance/_styledata/yahoo.py | 23 - build/lib/mplfinance/_styles.py | 382 ----- build/lib/mplfinance/_utils.py | 1515 ----------------- build/lib/mplfinance/_version.py | 6 - build/lib/mplfinance/_widths.py | 197 --- build/lib/mplfinance/original_flavor.py | 885 ---------- build/lib/mplfinance/plotting.py | 1307 -------------- dist/mplfinance-0.12.9b2-py3.10.egg | Bin 148059 -> 0 bytes 30 files changed, 5891 deletions(-) delete mode 100644 build/lib/mplfinance/__init__.py delete mode 100644 build/lib/mplfinance/_arg_validators.py delete mode 100644 build/lib/mplfinance/_helpers.py delete mode 100644 build/lib/mplfinance/_kwarg_help.py delete mode 100644 build/lib/mplfinance/_mpf_warnings.py delete mode 100644 build/lib/mplfinance/_mplrcputils.py delete mode 100644 build/lib/mplfinance/_mplwraps.py delete mode 100644 build/lib/mplfinance/_panels.py delete mode 100644 build/lib/mplfinance/_styledata/__init__.py delete mode 100644 build/lib/mplfinance/_styledata/binance.py delete mode 100644 build/lib/mplfinance/_styledata/blueskies.py delete mode 100644 build/lib/mplfinance/_styledata/brasil.py delete mode 100644 build/lib/mplfinance/_styledata/charles.py delete mode 100644 build/lib/mplfinance/_styledata/checkers.py delete mode 100644 build/lib/mplfinance/_styledata/classic.py delete mode 100644 build/lib/mplfinance/_styledata/default.py delete mode 100644 build/lib/mplfinance/_styledata/ibd.py delete mode 100644 build/lib/mplfinance/_styledata/kenan.py delete mode 100644 build/lib/mplfinance/_styledata/mike.py delete mode 100644 build/lib/mplfinance/_styledata/nightclouds.py delete mode 100644 build/lib/mplfinance/_styledata/sas.py delete mode 100644 build/lib/mplfinance/_styledata/starsandstripes.py delete mode 100644 build/lib/mplfinance/_styledata/yahoo.py delete mode 100644 build/lib/mplfinance/_styles.py delete mode 100644 build/lib/mplfinance/_utils.py delete mode 100644 build/lib/mplfinance/_version.py delete mode 100644 build/lib/mplfinance/_widths.py delete mode 100644 build/lib/mplfinance/original_flavor.py delete mode 100644 build/lib/mplfinance/plotting.py delete mode 100644 dist/mplfinance-0.12.9b2-py3.10.egg diff --git a/build/lib/mplfinance/__init__.py b/build/lib/mplfinance/__init__.py deleted file mode 100644 index f1e1865c..00000000 --- a/build/lib/mplfinance/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -import mplfinance._mpf_warnings -from mplfinance.plotting import plot, make_addplot -from mplfinance._styles import make_mpf_style, make_marketcolors -from mplfinance._styles import available_styles, write_style_file -from mplfinance._version import __version__ -from mplfinance._mplwraps import figure, show -from mplfinance._kwarg_help import kwarg_help diff --git a/build/lib/mplfinance/_arg_validators.py b/build/lib/mplfinance/_arg_validators.py deleted file mode 100644 index d7398232..00000000 --- a/build/lib/mplfinance/_arg_validators.py +++ /dev/null @@ -1,445 +0,0 @@ -import matplotlib.dates as mdates -import pandas as pd -import numpy as np -import datetime -from mplfinance._helpers import _list_of_dict, _mpf_is_color_like -from mplfinance._helpers import _num_or_seq_of_num -import matplotlib as mpl -import warnings - -def _check_and_prepare_data(data, config): - ''' - Check and Prepare the data input: - For now, data must be a Pandas DataFrame with a DatetimeIndex - and columns named 'Open', 'High', 'Low', 'Close', and optionally 'Volume' - - Later (if there is demand for it) we may accept all of the following data formats: - 1. Pandas DataFrame with DatetimeIndex (as described above) - 2. Pandas Series with DatetimeIndex: - Values are close prices, and Series generates a line plot - 3. Tuple of Lists, or List of Lists: - The inner Lists are each columns, in the order: DateTime, Open, High, Low, Close, Volume - 4. Tuple of Tuples or List of Tuples: - The inner tuples are each row, containing values in the order: DateTime, Open, High, Low, Close, Volume - - Return a Tuple of Lists: datetimes, opens, highs, lows, closes, volumes - ''' - if not isinstance(data, pd.core.frame.DataFrame): - raise TypeError('Expect data as DataFrame') - - if not isinstance(data.index,pd.core.indexes.datetimes.DatetimeIndex): - raise TypeError('Expect data.index as DatetimeIndex') - - if (len(data.index) > config['warn_too_much_data'] and - (config['type']=='candle' or config['type']=='ohlc' or config['type']=='hollow_and_filled') - ): - warnings.warn('\n\n ================================================================= '+ - '\n\n WARNING: YOU ARE PLOTTING SO MUCH DATA THAT IT MAY NOT BE'+ - '\n POSSIBLE TO SEE DETAILS (Candles, Ohlc-Bars, Etc.)'+ - '\n For more information see:'+ - '\n - https://github.com/matplotlib/mplfinance/wiki/Plotting-Too-Much-Data'+ - '\n '+ - '\n TO SILENCE THIS WARNING, set `type=\'line\'` in `mpf.plot()`'+ - '\n OR set kwarg `warn_too_much_data=N` where N is an integer '+ - '\n LARGER than the number of data points you want to plot.'+ - '\n\n ================================================================ ', - category=UserWarning) - - # We will not be fully case-insensitive (since Pandas columns as NOT case-insensitive) - # but because so many people have requested it, for the default column names we will - # try both Capitalized and lower case: - columns = config['columns'] - if columns is None: - columns = ('Open', 'High', 'Low', 'Close', 'Volume') - if all([c.lower() in data for c in columns[0:4]]): - columns = ('open', 'high', 'low', 'close', 'volume') - - o, h, l, c, v = columns - cols = [o, h, l, c] - - if config['tz_localize']: - dates = mdates.date2num(data.index.tz_localize(None).to_pydatetime()) - else: # Just in case someone was depending on this bug (Issue 236) - dates = mdates.date2num(data.index.to_pydatetime()) - opens = data[o].values - highs = data[h].values - lows = data[l].values - closes = data[c].values - if v in data.columns: - volumes = data[v].values - cols.append(v) - else: - volumes = None - - for col in cols: - if not all( isinstance(v,(float,int)) for v in data[col] ): - raise ValueError('Data for column "'+str(col)+'" must be ALL float or int.') - - return dates, opens, highs, lows, closes, volumes - -def _get_valid_plot_types(plottype=None): - - _alias_types = { - 'candlestick' : 'candle', - 'ohlc_bars' : 'ohlc', - 'hollow_candle' : 'hollow_and_filled', - 'hollow' : 'hollow_and_filled', - 'hnf' : 'hollow_and_filled', - } - - _valid_types = ['candle','ohlc', 'line','renko','pnf','hollow_and_filled'] - - _valid_types_all = _valid_types.copy() - _valid_types_all.extend(_alias_types.keys()) - - if plottype is None: - return _valid_types_all - elif plottype in _alias_types: - return _alias_types[plottype] - return plottype - - -def _mav_validator(mav_value): - ''' - Value for mav (moving average) keyword may be: - scalar int greater than 1, or tuple of ints, or list of ints (each greater than 1) - or a dict of `period` and `shift` each of which may be: - scalar int, or tuple of ints, or list of ints: each `period` int must be greater than 1 - ''' - def _valid_mav(value, is_period=True): - if not isinstance(value,(tuple,list,int)): - return False - if isinstance(value,int): - return (value >= 2 or not is_period) - # Must be a tuple or list here: - for num in value: - if not isinstance(num,int) or (is_period and num < 2): - return False - return True - - if not isinstance(mav_value,(tuple,list,int,dict)): - return False - - if not isinstance(mav_value,dict): - return _valid_mav(mav_value) - - else: #isinstance(mav_value,dict) - if 'period' not in mav_value: return False - - period = mav_value['period'] - if not _valid_mav(period): return False - - if 'shift' not in mav_value: return True - - shift = mav_value['shift'] - if not _valid_mav(shift, False): return False - if isinstance(period,int) and isinstance(shift,int): return True - if isinstance(period,(tuple,list)) and isinstance(shift,(tuple,list)): - if len(period) != len(shift): return False - return True - return False - - -def _hlines_validator(value): - if isinstance(value,dict): - if 'hlines' in value: - value = value['hlines'] - else: - return False - return ( isinstance(value,(float,int)) or (isinstance(value,(list,tuple)) and - all([isinstance(v,(float,int)) for v in value])) ) - -def _is_datelike(value): - if isinstance(value, (pd.Timestamp,datetime.datetime,datetime.date)): - return True - if isinstance(value,str): - try: - dt = pd.to_datetime(value) - return True - except: - return False - return False - -def _xlim_validator(value): - return (isinstance(value, (list,tuple)) and len(value) == 2 - and (all([isinstance(v,(int,float)) for v in value]) - or all([_is_datelike(v) for v in value]))) - -def _vlines_validator(value): - '''Validate `vlines` kwarg value: must be "datelike" or sequence of "datelike" - ''' - if isinstance(value,dict): - if 'vlines' in value: - value = value['vlines'] - else: - return False - if _is_datelike(value): return True - if not isinstance(value,(list,tuple)): return False - if not all([_is_datelike(v) for v in value]): return False - return True - -def _alines_validator(value, returnStandardizedValue=False): - ''' - Value for segments to be passed into LineCollection constructor must be: - - a sequence of `lines`, where - - a `lines` is a sequence of 2 or more vertices, where - - a vertex is a `pair`, aka a sequence of two values, an x and a y point. - - From matplotlib.collections: - `segments` are: - A sequence of (line0, line1, line2), where: - - linen = (x0, y0), (x1, y1), ... (xm, ym) - - or the equivalent numpy array with two columns. Each line can be a different length. - - The above is from the matplotlib LineCollection documentation. - It basically says that the "segments" passed into the LineCollection constructor - must be a Sequence of Sequences of 2 or more xy Pairs. However here in `mplfinance` - we want to allow that (seq of seq of xy pairs) _as well as_ just a sequence of pairs. - Therefore here in the validator we will allow both: - (a) seq of at least 2 date,float pairs (this is a 'line' as defined above) - (b) seq of seqs of at least 2 date,float pairs (this is a 'seqment' as defined above) - ''' - if isinstance(value,dict): - if 'alines' in value: - value = value['alines'] - else: - return False - - if not isinstance(value,(list,tuple)): - return False if not returnStandardizedValue else None - - if not all([isinstance(line,(list,tuple)) and len(line) > 1 for line in value]): - return False if not returnStandardizedValue else None - - # now, were the above really `lines`, or were they simply `vertices` - if all( [ isinstance(point,(list,tuple)) and len(point)==2 and - _is_datelike(point[0]) and isinstance(point[1],(float,int)) - for line in value for point in line ] ): - # they were lines: - return True if not returnStandardizedValue else value - - # here, if valid, we have a sequence of vertices (points) - if all( [ isinstance(point,(list,tuple)) and len(point)==2 and - _is_datelike(point[0]) and isinstance(point[1],(float,int)) - for point in value ] ): - return True if not returnStandardizedValue else [value,] - - return False if not returnStandardizedValue else None - -def _tlines_validator(value): - ''' - Validate `tlines` kwarg value: must be sequence of "datelike" pairs. - ''' - def _tlines_subvalidator(value): - if isinstance(value,dict): - if 'tlines' in value: - value = value['tlines'] - else: - return False - if not isinstance(value,(list,tuple)): - return False - if not all([isinstance(pair,(list,tuple)) and len(pair) == 2 and - _is_datelike(pair[0]) and _is_datelike(pair[1]) for pair in value]): - return False - return True - - if isinstance(value,(list,tuple)) and all([isinstance(v,dict) for v in value]): - for v in value: - if not _tlines_subvalidator(v): - return False - return True - else: - return _tlines_subvalidator(value) - -def _bypass_kwarg_validation(value): - ''' For some kwargs, we either don't know enough, or - the validation is too complex to make it worth while, - so we bypass kwarg validation. If the kwarg is - invalid, then eventually an exception will be - raised at the time the kwarg value is actually used. - ''' - return True - -def _kwarg_not_implemented(value): - ''' If you want to list a kwarg in a valid_kwargs dict for a given - function, but you have not yet, or don't yet want to, implement - the kwarg; or you simply want to (temporarily) disable the kwarg, - then use this function as the kwarg validator - ''' - raise NotImplementedError('kwarg NOT implemented.') - -def _validate_vkwargs_dict(vkwargs): - """ - Check that we didn't make a typo in any of the things - that should be the same for all vkwargs dict items: - - {kwarg : {'Default': ... , 'Description': ... , 'Validator': ...} } - """ - for key, value in vkwargs.items(): - # has been changed to 3 to support descriptions - if len(value) != 3: - raise ValueError('Items != 3 in valid kwarg table, for kwarg "'+key+'"') - if 'Default' not in value: - raise ValueError('Missing "Default" value for kwarg "'+key+'"') - if 'Description' not in value: - raise ValueError('Missing "Description" value for kwarg "'+key+'"') - if 'Validator' not in value: - raise ValueError('Missing "Validator" function for kwarg "'+key+'"') - -def _process_kwargs(kwargs, vkwargs): - ''' - Given a "valid kwargs table" and some kwargs, verify that each key-word - is valid per the kwargs table, and that the value of the kwarg is the - correct type. Fill a configuration dictionary with the default value - for each kwarg, and then substitute in any values that were provided - as kwargs and return the configuration dictionary. - ''' - # initialize configuration from valid_kwargs_table: - config = {} - for key, value in vkwargs.items(): - config[key] = value['Default'] - - # now validate kwargs, and for any valid kwargs - # replace the appropriate value in config: - for key in kwargs.keys(): - if key not in vkwargs: - raise KeyError('Unrecognized kwarg="'+str(key)+'"') - else: - value = kwargs[key] - try: - valid = vkwargs[key]['Validator'](value) - except Exception as ex: - ex.extra_info = 'kwarg "'+key+'" validator raised exception to value: "'+str(value)+'"' - raise - if not valid: - import inspect - v = inspect.getsource(vkwargs[key]['Validator']).strip() - raise TypeError('kwarg "'+key+'" validator returned False for value: "'+str(value)+'"\n '+v) - - # --------------------------------------------------------------- - # At this point in the loop, if we have not raised an exception, - # then kwarg is valid as far as we can tell, therefore, - # go ahead and replace the appropriate value in config: - - config[key] = value - - return config - -def _valid_panel_id(panid): - return panid in ['main','lower'] or (isinstance(panid,int) and panid >= 0 and panid < 32) - -def _scale_padding_validator(value): - if isinstance(value,(int,float)): - return True - elif isinstance(value,dict): - valid_keys=('left','right','top','bottom') - for key in value: - if key not in valid_keys: - raise ValueError('Invalid key "'+str(key)+'" found in `scale_padding` dict.') - if not isinstance(value[key],(int,float)): - raise ValueError('`scale_padding` dict contains non-number at key "'+str(key)+'"') - return True - else: - raise ValueError('`scale_padding` kwarg must be a number, or dict of (left,right,top,bottom) numbers.') - return False - -def _yscale_validator(value): - if isinstance(value,str) and value in ("linear", "log", "symlog", "logit"): - return True - - if not isinstance(value,dict): - return False - - # At this point, value is a dict: - if not 'yscale' in value: - return False - - yscale = value['yscale'] - if not (isinstance(yscale,str) and yscale in ("linear", "log", "symlog", "logit")): - return False - - return True - - -def _is_marketcolor_object(obj): - if not isinstance(obj,dict): return False - market_colors_keys = ('candle','edge','wick','ohlc') - return all([k in obj for k in market_colors_keys]) - - -def _mco_validator(value): # marketcolor overrides validator - if isinstance(value,dict): # not yet supported, but maybe we will have other - if 'colors' not in value: # kwargs related to mktcolor overrides (ex: `mco_faceonly`) - raise ValueError('`marketcolor_overrides` as dict must contain `colors` key.') - colors = value['colors'] - else: - colors = value - if not isinstance(colors,(list,tuple,np.ndarray)): - return False - return all([(c is None or - _mpf_is_color_like(c) or - _is_marketcolor_object(c) ) for c in colors]) - -def _check_for_external_axes(config): - ''' - Check that all `fig` and `ax` kwargs are either ALL None, - or ALL are valid instances of Figures/Axes: - - An external Axes object can be passed in three places: - - mpf.plot() `ax=` kwarg - - mpf.plot() `volume=` kwarg - - mpf.make_addplot() `ax=` kwarg - ALL three places MUST be an Axes object, OR - ALL three places MUST be None. But it may not be mixed. - ''' - ap_axlist = [] - 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 apd in addplot: - ap_axlist.append(apd['ax']) - - if len(ap_axlist) > 0: - if config['ax'] is None: - if not all([ax is None for ax in ap_axlist]): - raise ValueError('make_addplot() `ax` kwarg NOT all None, while plot() `ax` kwarg IS None') - else: # config['ax'] is NOT None: - if not isinstance(config['ax'],mpl.axes.Axes): - raise ValueError('plot() ax kwarg must be of type `matplotlib.axis.Axes`') - if not all([isinstance(ax,mpl.axes.Axes) for ax in ap_axlist]): - raise ValueError('make_addplot() `ax` kwargs must all be of type `matplotlib.axis.Axes`') - - # At this point, if we have not raised an exception, then plot(ax=) and make_addplot(ax=) - # are in sync: either they are all None, or they are all of type `matplotlib.axes.Axes`. - # Therefore we only need plot(ax=), i.e. config['ax'], as we check `volume`: ### and `fig`: - - if config['ax'] is None: - if isinstance(config['volume'],mpl.axes.Axes): - raise ValueError('`volume` set to external Axes requires all other Axes be external.') - #if config['fig'] is not None: - # raise ValueError('`fig` kwarg must be None if `ax` kwarg is None.') - else: - if not isinstance(config['volume'],mpl.axes.Axes) and config['volume'] != False: - raise ValueError('`volume` must be of type `matplotlib.axis.Axes`') - #if not isinstance(config['fig'],mpl.figure.Figure): - # raise ValueError('`fig` kwarg must be of type `matplotlib.figure.Figure`') - - external_axes_mode = True if isinstance(config['ax'],mpl.axes.Axes) else False - return external_axes_mode - -def _valid_fb_dict(value): - return (isinstance(value,dict) and - 'y1' in value and - _num_or_seq_of_num(value['y1'])) - -def _fill_between_validator(value): - if _num_or_seq_of_num(value): return True - if _valid_fb_dict(value): return True - if _list_of_dict(value): - return all([_valid_fb_dict(v) for v in value]) - return False diff --git a/build/lib/mplfinance/_helpers.py b/build/lib/mplfinance/_helpers.py deleted file mode 100644 index c10ddf92..00000000 --- a/build/lib/mplfinance/_helpers.py +++ /dev/null @@ -1,121 +0,0 @@ -""" -Some helper functions for mplfinance. -NOTE: This is the lowest level in mplfinance: - This file should have NO dependencies on - any other mplfinance files. -""" - -import datetime -import matplotlib.dates as mdates -import matplotlib.colors as mcolors -import numpy as np - -def _adjust_color_brightness(color,amount=0.5): - - def _adjcb(c1, amount): - import matplotlib.colors as mc - import colorsys - # mc.is_color_like(value) - try: - c = mc.cnames[c1] - except: - c = c1 - c = colorsys.rgb_to_hls(*mc.to_rgb(c)) - return colorsys.hls_to_rgb(c[0], max(0, min(1, amount * c[1])), c[2]) - - if not isinstance(color,(list,tuple)): - return _adjcb(color,amount) - - cout = [] - cadj = {} - for c1 in color: - if c1 in cadj: - cout.append(cadj[c1]) - else: - newc = _adjcb(c1,amount) - cadj[c1] = newc - cout.append(cadj[c1]) - return cout - - -def _determine_format_string( dates, datetime_format=None ): - """ - Determine the datetime format string based on the averge number - of days between data points, or if the user passed in kwarg - datetime_format, use that as an override. - """ - avg_days_between_points = (dates[-1] - dates[0]) / float(len(dates)) - - if datetime_format is not None: - return datetime_format - - # 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' - return fmtstring - - -def _list_of_dict(x): - ''' - Return True if x is a list of dict's - ''' - return isinstance(x,list) and all([isinstance(item,dict) for item in x]) - -def _num_or_seq_of_num(value): - return ( isinstance(value,(int,float,np.integer,np.floating)) or - (isinstance(value,(list,tuple,np.ndarray)) and - all([isinstance(v,(int,float,np.integer,np.floating)) for v in value])) - ) - -def roundTime(dt=None, roundTo=60): - """Round a datetime object to any time lapse in seconds - dt : datetime.datetime object, default now. - roundTo : Closest number of seconds to round to, default 1 minute. - Author: Thierry Husson 2012 - Use it as you want but don't blame me. - """ - if dt is None : dt = datetime.datetime.now() - seconds = (dt.replace(tzinfo=None) - dt.min).seconds - rounding = (seconds+roundTo/2) // roundTo * roundTo - return dt + datetime.timedelta(0,rounding-seconds,-dt.microsecond) - - -def _is_uint8_rgb_or_rgba(tup): - """ Deterine if rgb or rgba is in (0-255) format: - Matplotlib expects rgb (and rgba) tuples to contain - three (or four) floats between 0.0 and 1.0 - - Some people express rgb as tuples of three integers - between 0 and 255. - (In rgba, alpha is still a float from 0.0 to 1.0) - """ - if isinstance(tup,str): return False - if not np.iterable(tup): return False - L = len(tup) - if L < 3 or L > 4: return False - if L == 4 and (tup[3] < 0 or tup[3] > 1): return False - return not any([not isinstance(v,(int,np.unsignedinteger)) or v<0 or v>255 for v in tup[0:3]]) - -def _mpf_is_color_like(c): - """Determine if an object is a color. - - Identical to `matplotlib.colors.is_color_like()` - BUT ALSO considers int (0-255) rgb and rgba colors. - """ - if mcolors.is_color_like(c): return True - return _is_uint8_rgb_or_rgba(c) - -def _mpf_to_rgba(c, alpha=None): - cnew = c - if _is_uint8_rgb_or_rgba(c) and any(e>1 for e in c[:3]): - cnew = tuple([e/255. for e in c[:3]]) - if len(c) == 4: cnew += c[3:] - return mcolors.to_rgba(cnew, alpha) diff --git a/build/lib/mplfinance/_kwarg_help.py b/build/lib/mplfinance/_kwarg_help.py deleted file mode 100644 index c4ea52b5..00000000 --- a/build/lib/mplfinance/_kwarg_help.py +++ /dev/null @@ -1,154 +0,0 @@ -import mplfinance as mpf -import pandas as pd -import textwrap - -def df_wrapcols(df,wrap_columns=None): - - if wrap_columns is None: return df - if not isinstance(wrap_columns,dict): - raise TypeError('wrap_columns must be a dict of column_names and wrap_lengths') - - for col in wrap_columns: - if col not in df.columns: - raise ValueError('column "'+str(col)+'" not found in df.columns') - - index = [] - column_data = {} - for col in df.columns: - column_data[col] = [] - - for ix in df.index: - row = df.loc[ix,] - - row_data = {} - for col in row.index: - cstr = str(row[col]) - if col in wrap_columns: - wlen = wrap_columns[col] - tw = textwrap.wrap(cstr,wlen) if not cstr.isspace() else [' '] - else: - tw = [cstr] - row_data[col] = tw - - cmax = max(row_data,key=lambda k: len(row_data[k])) - rlen = len(row_data[cmax]) - for r in range(rlen): - for col in row.index: - extension = [' ']*(rlen - len(row_data[col])) - row_data[col].extend(extension) - column_data[col].append(row_data[col][r]) - ixstr = str(ix)+'.'+str(r) if r > 0 else str(ix) - index.append(ixstr) - - return pd.DataFrame(column_data,index=index) - -def make_left_formatter(maxwidth): - wm3 = maxwidth-3 - w = maxwidth - def left_formatter(value): - if not isinstance(value,str): - return f'{value:<}' - elif value[0:maxwidth] == '-'*maxwidth: - return f'{value:<{w}.{w}s}' - elif len(value) > maxwidth: - return f'{value:<{wm3}.{wm3}s}...' - else: - return f'{value:<{w}.{w}s}' - return left_formatter - - -def kwarg_help( func_name=None, kwarg_names=None, sort=False ): - - func_kwarg_map = { - 'plot' : mpf.plotting._valid_plot_kwargs, - 'make_addplot' : mpf.plotting._valid_addplot_kwargs, - 'make_marketcolors' : mpf._styles._valid_make_marketcolors_kwargs, - 'make_mpf_style' : mpf._styles._valid_make_mpf_style_kwargs, - 'renko_params' : mpf._utils._valid_renko_kwargs, - 'pnf_params' : mpf._utils._valid_pnf_kwargs, - 'lines' : mpf._utils._valid_lines_kwargs, - 'scale_width_adjustment': mpf._widths._valid_scale_width_kwargs, - 'update_width_config': mpf._widths._valid_update_width_kwargs, - } - - func_kwarg_aliases = { - 'addplot' : mpf.plotting._valid_addplot_kwargs, - 'marketcolors' : mpf._styles._valid_make_marketcolors_kwargs, - 'mpf_style' : mpf._styles._valid_make_mpf_style_kwargs, - 'style' : mpf._styles._valid_make_mpf_style_kwargs, - 'renko' : mpf._utils._valid_renko_kwargs, - 'pnf' : mpf._utils._valid_pnf_kwargs, - 'hlines' : mpf._utils._valid_lines_kwargs, - 'alines' : mpf._utils._valid_lines_kwargs, - 'tlines' : mpf._utils._valid_lines_kwargs, - 'vlines' : mpf._utils._valid_lines_kwargs, - } - - if func_name is None: - print('\nUsage: `kwarg_help(func_name)` or `kwarg_help(func_name,kwarg_names)`') - print(' kwarg_help is available for the following func_names:') - s = str(list(func_kwarg_map.keys())) - text = textwrap.wrap(s,68) - for t in text: - print(' ',t) - print() - return - - fkmap = {**func_kwarg_map, **func_kwarg_aliases} - - if func_name not in fkmap: - raise ValueError('Function name "'+func_name+'" NOT a valid function name') - - if kwarg_names is not None and isinstance(kwarg_names,str): - kwarg_names = [ kwarg_names, ] - - if ( kwarg_names is not None - and (not isinstance(kwarg_names,(list,tuple)) - or not all([isinstance(k,str) for k in kwarg_names]) - ) - ): - raise ValueError('kwarg_names must be a sequence (list,tuple) of strings') - - vks = fkmap[func_name]() - - df = (pd.DataFrame(vks).T).drop('Validator',axis=1) - df.index.name = 'Kwarg' - if sort: df.sort_index(inplace=True) - df.reset_index(inplace=True) - - if kwarg_names is not None: - for k in kwarg_names: - if k not in df['Kwarg'].values: - print(' Warning: "'+k+'" is not a valid `kwarg_name` for `func_name` "'+func_name,'"') - df = df[ df['Kwarg'].isin(kwarg_names) ] - if len(df) < 1: - raise ValueError(' None of specified `kwarg_names` are valid for `func_name` "'+func_name,'"') - - df['Default'] = ["'"+d+"'" if isinstance(d,str) else str(d) for d in df['Default']] - - klen = df['Kwarg'].str.len().max()+1 - dlen = df['Default'].str.len().max()+1 - - wraplen = max( 40, 80-(klen+dlen) ) - df = df_wrapcols(df,wrap_columns={'Description':wraplen}) - - dividers = [] - for col in df.columns: - dividers.append('-'*int(df[col].str.len().max())) - dfd = pd.DataFrame(dividers).T - dfd.columns = df.columns - dfd.index = pd.Index(['---']) - - df = dfd.append(df) - - formatters = { 'Kwarg' : make_left_formatter( klen ), - 'Default' : make_left_formatter( dlen ), - 'Description' : make_left_formatter( wraplen ), - } - - print('\n ','-'*78) - print(' Kwargs for func_name "'+func_name+'":') - - s = df.to_string(formatters=formatters,index=False,justify='left') - - print('\n ',s.replace('\n','\n ')) diff --git a/build/lib/mplfinance/_mpf_warnings.py b/build/lib/mplfinance/_mpf_warnings.py deleted file mode 100644 index dd687d02..00000000 --- a/build/lib/mplfinance/_mpf_warnings.py +++ /dev/null @@ -1,16 +0,0 @@ -import sys as __sys -if not __sys.warnoptions: - import os as __os - import warnings as __warnings - __warnings.filterwarnings("default",category=DeprecationWarning,module='mplfinance') # Change the filter in this process - __os.environ["PYTHONWARNINGS"] = "default::DeprecationWarning:mplfinance" # Also affect subprocesses - -if __sys.version_info <= (3, 6): - __warnings.filterwarnings("default",category=ImportWarning,module='mplfinance') # Change the filter in this process - __os.environ["PYTHONWARNINGS"] = "default::ImportWarning:mplfinance" # Also affect subprocesses - __warnings.warn('\n\n ================================================================= '+ - '\n\n WARNING: `mplfinance` is NOT supported for Python versions '+ - '\n less than 3.6' - '\n\n ================================================================= ', - category=ImportWarning) - diff --git a/build/lib/mplfinance/_mplrcputils.py b/build/lib/mplfinance/_mplrcputils.py deleted file mode 100644 index ec39a05c..00000000 --- a/build/lib/mplfinance/_mplrcputils.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python -""" -rcparams utilities -""" - -import pandas as pd -import matplotlib.pyplot as plt -import sys - -__author__ = "Daniel Goldfarb" -__version__ = "0.1.0" -__license__ = "MIT" - -def rcParams_to_df(rcp,name=None): - keys = [] - vals = [] - for item in rcp: - keys.append(item) - vals.append(rcp[item]) - df = pd.DataFrame(vals,index=pd.Index(keys,name='rcParamsKey')) - if name is not None: - df.columns = [name] - else: - df.columns = ['Value'] - return df - -def compare_styles(s1,s2): - with plt.rc_context(): - plt.style.use('default') - plt.style.use(s1) - df1 = rcParams_to_df(plt.rcParams,name=s1) - - with plt.rc_context(): - plt.style.use('default') - plt.style.use(s2) - df2 = rcParams_to_df(plt.rcParams,name=s2) - - df = pd.concat([df1,df2],axis=1) - dif = df[df[s1] != df[s2]].dropna(how='all') - return (dif,df,df1,df2) - -def main(): - """ Main entry point of the app """ - def usage(): - print('\n Usage: rcparams \n') - print(' Available commands: ') - print(' rcparams find ') - print(' rcparams compare ') - print('') - exit(1) - commands = ('find','compare') - - if len(sys.argv) < 3 : - print('\n Too few arguments!') - usage() - - command = sys.argv[1] - if command not in commands: - print('\n Unrecognized command \"'+command+'\"') - usage() - - if command == 'find': - findstr = sys.argv[2] - df = rcParams_to_df(plt.rcParams) - if findstr == '--all': - for key in df.index: - print(key+':',df.loc[key,'Value']) - else: - print(df[df.index.str.contains(findstr)]) - - elif command == 'compare': - if len(sys.argv) < 4 : - print('\n Need two styles to compare!') - usage() - style1 = sys.argv[2] - style2 = sys.argv[3] - dif,df,df1,df2 = compare_styles(style1,style2) - print('\n==== dif ====\n',dif) - - else: - print('\n Unrecognized command \"'+command+'\"') - usage() - - -if __name__ == "__main__": - """ This is executed when run from the command line """ - main() diff --git a/build/lib/mplfinance/_mplwraps.py b/build/lib/mplfinance/_mplwraps.py deleted file mode 100644 index bad47fbb..00000000 --- a/build/lib/mplfinance/_mplwraps.py +++ /dev/null @@ -1,121 +0,0 @@ -import matplotlib.pyplot as plt -import matplotlib.figure as mplfigure -import matplotlib.axes as mpl_axes -from mplfinance import _styles -import numpy as np - -""" - This file contains: - - (1) A wrapper of method `matplotlib.pyplot.figure()` that creates a - `mplfinance.Mpf_Figure` which is derived from `matplotlib.figure.Figure` - The wrapper function is the same as `matplotlib.pyplot.figure()` except - that it additionally accepts kwarg `style=` to set the mplfinance style. - - (2) Class `mplfinance.Mpf_Figure` derived from `matplotlib.figure.Figure` - which has the following overrides: - - Attribute `mpfstyle` indicating the mplfinance style used at Figure creation. - - Methods (listed below) which are identical to the same method in class - `matplotlib.figure.Figure` except that the `mplfinance.Mpf_Figure` versions: - - accept kwarg `style=` to set the mplfinance style of Subplot Axes, or - - if `style=` is not specified, then the attribute - `mplfinance.Mpf_Figure.mpfstyle` is used for the Subplot Axes style. - - Figure.add_subplot() - - Figure.add_axes() - - Figure.subplot() (this is analogous to pyplot.subplot() which calls Figure.add_subplot()) - - Figure.subplots() - - (3) A wrapper to matplot.pyplot.show(), because it happens often enough, when using mplfinance, - that sometimes one has to import matplotlib.pyplot *ONLY* for the purpose of calling .show() -""" - -show = plt.show # Not a true wrapper, rather an assignment. - -def _check_for_and_apply_style(kwargs): - - if 'style' in kwargs: - style = kwargs['style'] - del kwargs['style'] - else: - style = 'default' - - if not _styles._valid_mpf_style(style): - raise TypeError('Invalid mplfinance style') - - if isinstance(style,str): - 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?') - - return style - - -def figure(*args,**kwargs): - - style = _check_for_and_apply_style(kwargs) - - f = plt.figure(FigureClass=Mpf_Figure,*args,**kwargs) - f.mpfstyle = style - return f - - -class Mpf_Figure(mplfigure.Figure): - - def add_subplot(self,*args,**kwargs): - - if 'style' in kwargs or not hasattr(self,'mpfstyle'): - style = _check_for_and_apply_style(kwargs) - else: - style = _check_for_and_apply_style(dict(style=self.mpfstyle)) - - ax = mplfigure.Figure.add_subplot(self,*args,**kwargs) - ax.mpfstyle = style - return ax - - def add_axes(self,*args,**kwargs): - - if 'style' in kwargs or not hasattr(self,'mpfstyle'): - style = _check_for_and_apply_style(kwargs) - else: - style = _check_for_and_apply_style(dict(style=self.mpfstyle)) - - ax = mplfigure.Figure.add_axes(self,*args,**kwargs) - ax.mpfstyle = style - return ax - - def subplot(self,*args,**kwargs): - - plt.figure(self.number) # make it the current Figure - - if 'style' in kwargs or not hasattr(self,'mpfstyle'): - style = _check_for_and_apply_style(kwargs) - else: - style = _check_for_and_apply_style(dict(style=self.mpfstyle)) - - ax = plt.subplot(*args,**kwargs) - ax.mpfstyle = style - return ax - - - def subplots(self,*args,**kwargs): - - if 'style' in kwargs or not hasattr(self,'mpfstyle'): - style = _check_for_and_apply_style(kwargs) - self.mpfstyle = style - else: - style = _check_for_and_apply_style(dict(style=self.mpfstyle)) - - axlist = mplfigure.Figure.subplots(self,*args,**kwargs) - - if isinstance(axlist,mpl_axes.Axes): - axlist.mpfstyle = style - elif isinstance(axlist,np.ndarray): - for ax in axlist.flatten(): - ax.mpfstyle = style - else: - raise TypeError('Unexpected type ('+str(type(axlist))+') '+ - 'returned from "matplotlib.figure.Figure.subplots()"') - return axlist diff --git a/build/lib/mplfinance/_panels.py b/build/lib/mplfinance/_panels.py deleted file mode 100644 index 7d8524a2..00000000 --- a/build/lib/mplfinance/_panels.py +++ /dev/null @@ -1,232 +0,0 @@ -from mplfinance._helpers import _list_of_dict -from mplfinance._arg_validators import _valid_panel_id -import pandas as pd - -def _build_panels( figure, config ): - """ - Create and return a DataFrame containing panel information - and Axes objects for each panel, etc. - - We allow up to 32 panels, identified by their panel id (panid) - which is an integer 0 through 31. - - Parameters - ---------- - figure : pyplot.Figure - figure on which to create the Axes for the panels - - config : dict - config dict from `mplfinance.plot()` - - Config - ------ - The following items are used from `config`: - - num_panels : integer (0-31) or None - number of panels to create - - addplot : dict or None - value for the `addplot=` kwarg passed into `mplfinance.plot()` - - volume_panel : integer (0-31) or None - panel id (0-number_of_panels) - - main_panel : integer (0-31) or None - panel id (0-number_of_panels) - - panel_ratios : sequence or None - sequence of relative sizes for the panels; - - NOTE: If len(panel_ratios) == number of panels (regardless - of whether number of panels was specified or inferred), - then panel ratios are the relative sizes of each panel, - in panel id order, 0 through N (where N = number of panels). - - If len(panel_ratios) != number of panels, then len(panel_ratios) - must equal 2, and panel_ratios[0] is the relative size for the 'main' - panel, and panel_ratios[1] is the relative size for all other panels. - - If the number of panels == 1, the panel_ratios is ignored. - - -Returns - ---------- - panels : pandas.DataFrame - dataframe indexed by panel id (panid) and having the following columns: - axes : tuple of matplotlib.Axes (primary and secondary) for each column. - used secondary : bool indicating whether or not the seconday Axes is in use. - relative size : height of panel as proportion of sum of all relative sizes - - """ - - num_panels = config['num_panels'] - addplot = config['addplot'] - volume = config['volume'] - volume_panel = config['volume_panel'] - num_panels = config['num_panels'] - main_panel = config['main_panel'] - panel_ratios = config['panel_ratios'] - - if not _valid_panel_id(main_panel): - raise ValueError('main_panel id must be integer 0 to 31, but is '+str(main_panel)) - - if num_panels is None: # then infer the number of panels: - pset = {0} # start with a set including only panel zero - 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))) - - backwards_panel_compatibility = {'main':0,'lower':1,'A':0,'B':1,'C':2} - - for apdict in addplot: - panel = apdict['panel'] - if panel in backwards_panel_compatibility: - panel = backwards_panel_compatibility[panel] - if not _valid_panel_id(panel): - raise ValueError('addplot panel must be integer 0 to 31, but is "'+str(panel)+'"') - pset.add(panel) - - if volume is True: - if not _valid_panel_id(volume_panel): - raise ValueError('volume_panel must be integer 0 to 31, but is "'+str(volume_panel)+'"') - pset.add(volume_panel) - - pset.add(main_panel) - - pset = sorted(pset) - missing = [m for m in range(len(pset)) if m not in pset] - if len(missing) != 0: - raise ValueError('inferred panel list is missing panels: '+str(missing)) - - else: - if not isinstance(num_panels,int) or num_panels < 1 or num_panels > 32: - raise ValueError('num_panels must be integer 1 to 32, but is "'+str(volume_panel)+'"') - pset = range(0,num_panels) - - _nones = [None]*len(pset) - panels = pd.DataFrame(dict(axes=_nones, - relsize=_nones, - lift=_nones, - height=_nones, - used2nd=[False]*len(pset), - title=_nones, - y_on_right=_nones), - index=pset) - panels.index.name = 'panid' - - # Now determine the height for each panel: - # ( figure, num_panels='infer', addplot=None, volume_panel=None, main_panel=0, panel_ratios=None ): - - if panel_ratios is not None: - if not isinstance(panel_ratios,(list,tuple)): - raise TypeError('panel_ratios must be a list or tuple') - if len(panel_ratios) != len(panels) and not (len(panel_ratios)==2 and len(panels) > 2): - err = 'len(panel_ratios) must be 2, or must be same as number of panels' - err += '\nlen(panel_ratios)='+str(len(panel_ratios))+' num panels='+str(len(panels)) - raise ValueError(err) - if len(panel_ratios) == 2 and len(panels) > 2: - pratios = [panel_ratios[1]]*len(panels) - pratios[main_panel] = panel_ratios[0] - else: - pratios = panel_ratios - else: - pratios = [2]*len(panels) - pratios[main_panel] = 5 - - panels['relsize'] = pratios - #print('len(panels)=',len(panels)) - #print('len(pratios)=',len(pratios)) - - #print('pratios=') - #print(pratios) - - #print('panels=') - #print(panels) - - # TODO: Throughout this section, right_pad is intentionally *less* - # than left_pad. This assumes that the y-axis labels are on - # the left, which is true for many mpf_styles, but *not* all. - # Ideally need to determine which side has the axis labels. - # And keep in mind, if secondary_y is in effect, then both - # sides can have axis labels. - - left_pad = 0.18 - right_pad = 0.10 - top_pad = 0.12 - bot_pad = 0.18 - - scale_left = scale_right = scale_top = scale_bot = 1.0 - - scale_padding = config['scale_padding'] - if isinstance(scale_padding,dict): - if 'left' in scale_padding: scale_left = scale_padding['left'] - if 'right' in scale_padding: scale_right = scale_padding['right'] - if 'top' in scale_padding: scale_top = scale_padding['top'] - if 'bottom' in scale_padding: scale_bot = scale_padding['bottom'] - else: # isinstance(scale_padding,(int,float): - scale_left = scale_right = scale_top = scale_bot = scale_padding - - if config['tight_layout']: - right_pad *= 0.4 - top_pad *= 0.4 - scale_left *= 0.6 - scale_right *= 0.6 - scale_top *= 0.6 - scale_bot *= 0.6 - - left_pad *= scale_left - right_pad *= scale_right - top_pad *= scale_top - bot_pad *= scale_bot - - plot_height = 1.0 - (bot_pad + top_pad ) - plot_width = 1.0 - (left_pad + right_pad) - - # print('scale_padding=',scale_padding) - # print('left_pad =',left_pad) - # print('right_pad=',right_pad) - # print('top_pad =',top_pad) - # print('bot_pad =',bot_pad) - # print('plot_height =',plot_height) - # print('plot_width =',plot_width) - - psum = sum(pratios) - for panid,size in enumerate(pratios): - panels.at[panid,'height'] = plot_height * size / psum - - # Now create the Axes: - - for panid,row in panels.iterrows(): - height = row.height - lift = panels['height'].loc[panid+1:].sum() - panels.at[panid,'lift'] = lift - if panid == 0: - # rect = [left, bottom, width, height] - ax0 = figure.add_axes( [left_pad, bot_pad+lift, plot_width, height] ) - else: - ax0 = figure.add_axes( [left_pad, bot_pad+lift, plot_width, height], sharex=panels.at[0,'axes'][0] ) - ax1 = ax0.twinx() - ax1.grid(False) - if config['saxbelow']: # issue#115 issuecomment-639446764 - ax0.set_axisbelow(True) # so grid does not show through plot data on any panel. - elif panid == volume_panel: - ax0.set_axisbelow(True) # so grid does not show through volume bars. - panels.at[panid,'axes'] = (ax0,ax1) - - return panels - - -def _set_ticks_on_bottom_panel_only(panels,formatter,rotation=45): - - bot = panels.index.values[-1] - ax = panels.at[bot,'axes'][0] - ax.tick_params(axis='x',rotation=rotation) - ax.xaxis.set_major_formatter(formatter) - - if len(panels) == 1: return - - for panid in panels.index.values[::-1][1:]: - panels.at[panid,'axes'][0].tick_params(axis='x',labelbottom=False) - diff --git a/build/lib/mplfinance/_styledata/__init__.py b/build/lib/mplfinance/_styledata/__init__.py deleted file mode 100644 index 0dcbf598..00000000 --- a/build/lib/mplfinance/_styledata/__init__.py +++ /dev/null @@ -1,45 +0,0 @@ -''' -__init__ for mplfinance._styledata module -''' - -from mplfinance._styledata import default -from mplfinance._styledata import nightclouds -from mplfinance._styledata import classic -from mplfinance._styledata import mike -from mplfinance._styledata import charles -from mplfinance._styledata import blueskies -from mplfinance._styledata import starsandstripes -from mplfinance._styledata import sas -from mplfinance._styledata import brasil -from mplfinance._styledata import yahoo -from mplfinance._styledata import checkers -from mplfinance._styledata import binance -from mplfinance._styledata import kenan -from mplfinance._styledata import ibd - -_style_names = [n for n in dir() if not n.startswith('_')] - -_styles = {} -for name in _style_names: - cmd = f'_styles.update({name} = {name}.style)' - eval(cmd) - -def _validate_style(style): - keys = ['base_mpl_style','marketcolors','mavcolors','y_on_right', - 'gridcolor','gridstyle','facecolor','rc' ] - for key in keys: - if key not in style.keys(): - err = f'Key "{key}" not found in style:\n\n {style}' - raise ValueError(err) - - mktckeys = ['candle','edge','wick','ohlc','volume','alpha'] - for key in mktckeys: - if key not in style['marketcolors'].keys(): - err = f'Key "{key}" not found in marketcolors for style:\n\n {style}' - raise ValueError(err) - -#print('type(_styles)=',type(_styles)) -#print('_styles=',_styles) -for s in _styles.keys(): - _validate_style(_styles[s]) - diff --git a/build/lib/mplfinance/_styledata/binance.py b/build/lib/mplfinance/_styledata/binance.py deleted file mode 100644 index 880096a8..00000000 --- a/build/lib/mplfinance/_styledata/binance.py +++ /dev/null @@ -1,29 +0,0 @@ -style = dict(style_name = 'binance', - base_mpl_style= 'seaborn-darkgrid', - marketcolors = {'candle' : {'up':'#70a800', 'down':'#ea0070'}, - 'edge' : {'up':'#70a800', 'down':'#ea0070'}, - 'wick' : {'up':'#70a800', 'down':'#ea0070'}, - 'ohlc' : {'up':'#70a800', 'down':'#ea0070'}, - 'volume' : {'up':'#70a800', 'down':'#ea0070'}, - 'vcedge' : {'up':'#70a800', 'down':'#ea0070'}, - 'vcdopcod': False, - 'alpha' : 0.9, - }, - mavcolors = ['#ffc201','#ff10ff','#cd0468','#1f77b4', - '#ff7f0e','#2ca02c','#40e0d0'], - y_on_right = False, - gridcolor = '#d0d0d0', - gridstyle = '--', - facecolor = '#ffffff', - rc = [ ('axes.edgecolor' , '#e6e6e6' ), - ('axes.linewidth' , 1.5 ), - ('axes.labelsize' , 'medium' ), - ('axes.labelweight', 'semibold'), - ('lines.linewidth' , 2.0 ), - ('font.weight' , 'medium' ), - ('font.size' , 12.0 ), - ('figure.titlesize', 'x-large' ), - ('figure.titleweight','semibold'), - ], - base_mpf_style= 'binance' - ) diff --git a/build/lib/mplfinance/_styledata/blueskies.py b/build/lib/mplfinance/_styledata/blueskies.py deleted file mode 100644 index 26985778..00000000 --- a/build/lib/mplfinance/_styledata/blueskies.py +++ /dev/null @@ -1,23 +0,0 @@ -style = dict(style_name = 'blueskies', - base_mpl_style='fast', - marketcolors = {'candle' : {'up':'w', 'down':'#0095ff'}, - 'edge' : {'up':'k', 'down':'#0095ff'}, - 'wick' : {'up':'k', 'down':'#0095ff'}, - 'ohlc' : {'up':'#0095ff', 'down':'#0095ff'}, - 'volume' : {'up':'w', 'down':'#0095ff'}, - 'vcdopcod': False, - 'alpha' : 1.0, - }, - mavcolors = None, - y_on_right = False, - facecolor = '#dbf1ff', - gridcolor = None, - gridstyle = None, - rc = [('patch.linewidth' , 1.0 ), - ('patch.force_edgecolor', True ), - ('lines.linewidth' , 1.0 ), - ('figure.titlesize' , 'x-large' ), - ('figure.titleweight' , 'semibold'), - ], - base_mpf_style='blueskies', - ) diff --git a/build/lib/mplfinance/_styledata/brasil.py b/build/lib/mplfinance/_styledata/brasil.py deleted file mode 100644 index f3e73e75..00000000 --- a/build/lib/mplfinance/_styledata/brasil.py +++ /dev/null @@ -1,25 +0,0 @@ -style = {'base_mpl_style': 'fast', - 'marketcolors': {'candle': {'up': '#fedf00', 'down': '#002776'}, - 'edge' : {'up': '#fedf00', 'down': '#002776'}, - 'wick' : {'up': '#fedf00', 'down': '#002776'}, - 'ohlc' : {'up': '#fedf00', 'down': '#002776'}, - 'volume': {'up': '#fedf00', 'down': '#002776'}, - 'vcedge': {'up': '#1f77b4', 'down': '#1f77b4'}, - 'vcdopcod': False, - 'alpha': 0.9}, - 'mavcolors' : None, - 'facecolor' : None, - 'gridcolor' : None, - 'gridstyle' : None, - 'y_on_right': True, - 'rc': {'axes.grid' : True, - 'axes.grid.axis': 'y', - 'grid.color' : '#fedf00', - 'grid.linestyle': '--', - 'axes.facecolor': '#009b3a', - 'axes.edgecolor': '#002776', - 'figure.titlesize' : 'x-large', - 'figure.titleweight': 'semibold', - }, - 'base_mpf_style': 'brasil' - } diff --git a/build/lib/mplfinance/_styledata/charles.py b/build/lib/mplfinance/_styledata/charles.py deleted file mode 100644 index 0347a605..00000000 --- a/build/lib/mplfinance/_styledata/charles.py +++ /dev/null @@ -1,30 +0,0 @@ -style = dict(style_name = 'charles', - base_mpl_style= 'fast', - marketcolors = {'candle' : {'up':'#006340', 'down':'#a02128'}, - 'edge' : {'up':'#006340', 'down':'#a02128'}, - 'wick' : {'up':'#006340', 'down':'#a02128'}, - 'ohlc' : {'up':'#006340', 'down':'#a02128'}, - 'volume' : {'up':'#007a00', 'down':'#d50d18'}, - 'vcdopcod': True, # Volume Color Depends On Price Change On Day - 'alpha' : 1.0, - }, - mavcolors = ['#ef5714','#ef5714','#9f4878','#9f4878'], - y_on_right = True, - gridcolor = '#a0a0a0', - gridstyle = '--', - facecolor = 'w', - rc = [ ('axes.edgecolor' , 'white' ), - ('axes.linewidth' , 1.5 ), - ('axes.labelsize' , 'large' ), - ('axes.labelweight', 'semibold'), - ('axes.grid' , True ), - ('axes.grid.axis' , 'y' ), - ('grid.linewidth' , 0.4 ), - ('lines.linewidth' , 2.0 ), - ('font.weight' , 'medium' ), - ('font.size' , 10.0 ), - ('figure.titlesize', 'x-large' ), - ('figure.titleweight','semibold'), - ], - base_mpf_style= 'charles' - ) diff --git a/build/lib/mplfinance/_styledata/checkers.py b/build/lib/mplfinance/_styledata/checkers.py deleted file mode 100644 index f599758d..00000000 --- a/build/lib/mplfinance/_styledata/checkers.py +++ /dev/null @@ -1,26 +0,0 @@ -style = {'base_mpl_style': 'ggplot', - 'marketcolors' : {'candle': {'up': '#000000', 'down': '#ff0000'}, - 'edge' : {'up': '#000000', 'down': '#ff0000'}, - 'wick' : {'up': '#606060', 'down': '#606060'}, - 'ohlc' : {'up': '#000000', 'down': '#ff0000'}, - 'volume': {'up': '#6f6f6f', 'down': '#ff4040'}, - 'vcedge': {'up': '#1f77b4', 'down': '#1f77b4'}, - 'vcdopcod' : False, - 'alpha' : 0.9}, - 'mavcolors' : None, - 'facecolor' : 'w', - 'gridcolor' : '#c0c0c0', - 'gridstyle' : '-', - 'y_on_right' : True, - 'rc' : {'axes.grid.axis': 'both', - 'axes.grid' : True, - 'axes.edgecolor': '#c0c0c0', - 'axes.labelcolor': 'k', - 'ytick.color' : 'k', - 'xtick.color' : 'k', - 'lines.markeredgecolor': 'k', - 'patch.force_edgecolor': True, - 'figure.titlesize' : 'x-large', - 'figure.titleweight' : 'semibold', - }, - 'base_mpf_style': 'checkers'} diff --git a/build/lib/mplfinance/_styledata/classic.py b/build/lib/mplfinance/_styledata/classic.py deleted file mode 100644 index 0a2fe461..00000000 --- a/build/lib/mplfinance/_styledata/classic.py +++ /dev/null @@ -1,28 +0,0 @@ -style = dict(style_name = 'classic', - base_mpl_style= 'fast', - marketcolors = {'candle' : {'up':'w', 'down':'k'}, - 'edge' : {'up':'k', 'down':'k'}, - 'wick' : {'up':'k', 'down':'k'}, - 'ohlc' : {'up':'k', 'down':'k'}, - 'volume' : {'up':'#181818', 'down':'#181818'}, - 'vcedge' : {'up':'#181818', 'down':'#181818'}, - 'vcdopcod': False, # Volume Color is Per Price Change On Day - 'alpha' : 0.9, - }, - mavcolors = ['#1a1a1a','#262626','#333333','#404040'], - y_on_right = True, - gridcolor = '#cccccc', - gridstyle = '--', - facecolor = 'w', - rc = [ ('axes.edgecolor' , 'black' ), - ('axes.linewidth' , 1.5 ), - ('axes.labelsize' , 'large' ), - ('axes.labelweight', 'semibold'), - ('lines.linewidth' , 2.0 ), - ('font.weight' , 'medium' ), - ('font.size' , 12.0 ), - ('figure.titlesize', 'x-large' ), - ('figure.titleweight','semibold'), - ], - base_mpf_style= 'classic' - ) diff --git a/build/lib/mplfinance/_styledata/default.py b/build/lib/mplfinance/_styledata/default.py deleted file mode 100644 index 7e2690ad..00000000 --- a/build/lib/mplfinance/_styledata/default.py +++ /dev/null @@ -1,29 +0,0 @@ -style = dict(style_name = 'default', - base_mpl_style= 'seaborn-darkgrid', - marketcolors = {'candle' : {'up':'w', 'down':'k'}, - 'edge' : {'up':'k', 'down':'k'}, - 'wick' : {'up':'k', 'down':'k'}, - 'ohlc' : {'up':'k', 'down':'k'}, - 'volume' : {'up':'#1f77b4', 'down':'#1f77b4'}, - 'vcedge' : {'up':'#1f77b4', 'down':'#1f77b4'}, - 'vcdopcod': False, # Volume Color is Per Price Change On Day - 'alpha' : 0.9, - }, - mavcolors = ['#40e0d0','#ff00ff','#ffd700','#1f77b4', - '#ff7f0e','#2ca02c','#e377c2'], - y_on_right = False, - gridcolor = None, - gridstyle = None, - facecolor = '#DCE3EF', - rc = [ ('axes.edgecolor' , 'black' ), - ('axes.linewidth' , 1.5 ), - ('axes.labelsize' , 'large' ), - ('axes.labelweight', 'semibold'), - ('lines.linewidth' , 2.0 ), - ('font.weight' , 'medium' ), - ('font.size' , 12.0 ), - ('figure.titlesize', 'x-large' ), - ('figure.titleweight','semibold'), - ], - base_mpf_style= 'default' - ) diff --git a/build/lib/mplfinance/_styledata/ibd.py b/build/lib/mplfinance/_styledata/ibd.py deleted file mode 100644 index c233f885..00000000 --- a/build/lib/mplfinance/_styledata/ibd.py +++ /dev/null @@ -1,38 +0,0 @@ -style = dict(style_name = 'ibd', - base_mpl_style= 'fast', - marketcolors = {'candle' : {'up':'#2A3FE5', 'down':'#DB39AD'}, - 'edge' : {'up':'#2A3FE5', 'down':'#DB39AD'}, - 'wick' : {'up':'#2A3FE5', 'down':'#DB39AD'}, - 'ohlc' : {'up':'#2A3FE5', 'down':'#DB39AD'}, - 'volume' : {'up':'#2A3FE5', 'down':'#DB39AD'}, - 'vcedge' : {'up':'#2A3FE5', 'down':'#DB39AD'}, - 'vcdopcod': True, # Volume Color is Per Price Change On Day - 'alpha' : 1.0, - }, - mavcolors = ['green','red','black','blue'], - y_on_right = True, - gridcolor = None, - gridstyle = None, - facecolor = None, - rc = [ ('axes.titlesize', 8), - ('axes.labelsize', 8) , - ('lines.linewidth', 3), - ('lines.markersize', 4), - ('ytick.left', False), - ('ytick.right', True), - ('ytick.labelleft', False), - ('ytick.labelright', True), - ('xtick.labelsize', 6), - ('ytick.labelsize', 7), - ('axes.linewidth', 0.8), - ('grid.alpha', 0.2), - ('axes.grid' , True ), - ('axes.grid.axis' , 'y' ), - ('grid.color' , '#b0b0b0' ), - ('grid.linestyle' , 'solid' ), - ('grid.linewidth' , 0.8 ), - ('figure.titlesize', 'x-large' ), - ('figure.titleweight','semibold'), - ], - base_mpf_style= 'ibd' - ) diff --git a/build/lib/mplfinance/_styledata/kenan.py b/build/lib/mplfinance/_styledata/kenan.py deleted file mode 100644 index 98ed0620..00000000 --- a/build/lib/mplfinance/_styledata/kenan.py +++ /dev/null @@ -1,33 +0,0 @@ -style = { 'style_name': 'kenan', - 'base_mpl_style': 'seaborn-darkgrid', - 'marketcolors': { 'candle': {'up': 'k', 'down': 'r'}, - 'edge': {'up': 'k', 'down': 'r'}, - 'wick': {'up': 'k', 'down': 'r'}, - 'ohlc': {'up': 'k', 'down': 'k'}, - 'volume': {'up': '#1f77b4', 'down': '#1f77b4'}, - 'vcedge': {'up': '#1f77b4', 'down': '#1f77b4'}, - 'vcdopcod': False, - 'alpha': 0.9, - 'hollow': 'w'}, - 'mavcolors': [ '#40e0d0', - '#ff00ff', - '#ffd700', - '#1f77b4', - '#ff7f0e', - '#2ca02c', - '#e377c2'], - 'y_on_right': False, - 'gridcolor': None, - 'gridstyle': None, - 'facecolor': '#DCE3EF', - 'rc': [ ('axes.edgecolor', 'black'), - ('axes.linewidth', 1.5), - ('axes.labelsize', 'large'), - ('axes.labelweight', 'semibold'), - ('lines.linewidth', 2.0), - ('font.weight', 'medium'), - ('font.size', 12.0), - ('figure.titlesize', 'x-large' ), - ('figure.titleweight','semibold'), - ], - 'base_mpf_style': 'default'} diff --git a/build/lib/mplfinance/_styledata/mike.py b/build/lib/mplfinance/_styledata/mike.py deleted file mode 100644 index ef026e24..00000000 --- a/build/lib/mplfinance/_styledata/mike.py +++ /dev/null @@ -1,36 +0,0 @@ -style = dict(style_name = 'mike', - base_mpl_style= 'dark_background', - marketcolors = {'candle' : {'up':'#000000', 'down':'#0080ff'}, - 'edge' : {'up':'#ffffff', 'down':'#0080ff'}, - 'wick' : {'up':'#ffffff', 'down':'#ffffff'}, - 'ohlc' : {'up':'#ffffff', 'down':'#ffffff'}, - 'volume' : {'up':'#7189aa', 'down':'#7189aa'}, - 'vcdopcod': False, # Volume Color Depends On Price Change On Day - 'alpha' : 1.0, - }, - mavcolors = ['#ec009c','#78ff8f','#fcf120'], - y_on_right = True, - gridcolor = None, - gridstyle = None, - facecolor = None, - rc = [ ('axes.edgecolor' , 'white' ), - ('axes.linewidth' , 1.5 ), - ('axes.labelsize' , 'large' ), - ('axes.labelweight', 'semibold'), - ('axes.grid' , True ), - ('axes.grid.axis' , 'both' ), - ('axes.grid.which' , 'major' ), - ('grid.alpha' , 0.9 ), - ('grid.color' , '#b0b0b0' ), - ('grid.linestyle' , '--' ), - ('grid.linewidth' , 0.8 ), - ('figure.facecolor', '#0a0a0a' ), - ('patch.linewidth' , 1.0 ), - ('lines.linewidth' , 1.0 ), - ('font.weight' , 'medium' ), - ('font.size' , 10.0 ), - ('figure.titlesize', 'x-large' ), - ('figure.titleweight','semibold'), - ], - base_mpf_style= 'mike' - ) diff --git a/build/lib/mplfinance/_styledata/nightclouds.py b/build/lib/mplfinance/_styledata/nightclouds.py deleted file mode 100644 index 2507d12e..00000000 --- a/build/lib/mplfinance/_styledata/nightclouds.py +++ /dev/null @@ -1,23 +0,0 @@ -style = dict(style_name = 'nightclouds', - base_mpl_style='dark_background', - marketcolors = {'candle' : {'up':'w', 'down':'#0095ff'}, - 'edge' : {'up':'w', 'down':'#0095ff'}, - 'wick' : {'up':'w', 'down':'w'}, - 'ohlc' : {'up':'w', 'down':'w'}, - 'volume' : {'up':'w', 'down':'#0095ff'}, - 'vcdopcod': False, - 'alpha' : 1.0, - }, - mavcolors = ['#40e0d0','#ff00ff','#ffd700','#1f77b4', - '#ff7f0e','#2ca02c','#e377c2'], - y_on_right = False, - facecolor = '#0b0b0b', - gridcolor = '#999999', - gridstyle = '--', - rc = [('patch.linewidth' , 1.0 ), - ('lines.linewidth' , 1.0 ), - ('figure.titlesize', 'x-large' ), - ('figure.titleweight','semibold'), - ], - base_mpf_style='nightclouds', - ) diff --git a/build/lib/mplfinance/_styledata/sas.py b/build/lib/mplfinance/_styledata/sas.py deleted file mode 100644 index e88e310a..00000000 --- a/build/lib/mplfinance/_styledata/sas.py +++ /dev/null @@ -1,4 +0,0 @@ -# style sas is just an abbreviation for starsandstripes: - -from mplfinance._styledata import starsandstripes -style = starsandstripes.style diff --git a/build/lib/mplfinance/_styledata/starsandstripes.py b/build/lib/mplfinance/_styledata/starsandstripes.py deleted file mode 100644 index 09120cd6..00000000 --- a/build/lib/mplfinance/_styledata/starsandstripes.py +++ /dev/null @@ -1,24 +0,0 @@ -style = {'base_mpl_style': 'fast', - 'marketcolors': {'candle': {'up': '#082865', 'down': '#ae0019'}, - 'edge' : {'up': '#082865', 'down': '#ae0019'}, - 'wick' : {'up': '#082865', 'down': '#ae0019'}, - 'ohlc' : {'up': '#082865', 'down': '#ae0019'}, - 'volume': {'up': '#082865', 'down': '#ae0019'}, - 'vcedge': {'up': '#1f77b4', 'down': '#1f77b4'}, - 'vcdopcod': False, - 'alpha': 0.9}, - 'mavcolors': None, - 'facecolor': None, - 'gridcolor': None, - 'gridstyle': None, - 'y_on_right': True, - 'rc': {'axes.edgecolor': '#082865', - 'axes.grid' : True, - 'axes.grid.axis': 'y', - 'grid.color' : '#082865', - 'grid.linestyle': '--', - 'figure.titlesize' :'x-large', - 'figure.titleweight':'semibold', - }, - 'base_mpf_style': 'starsandstripes' - } diff --git a/build/lib/mplfinance/_styledata/yahoo.py b/build/lib/mplfinance/_styledata/yahoo.py deleted file mode 100644 index 85f1c576..00000000 --- a/build/lib/mplfinance/_styledata/yahoo.py +++ /dev/null @@ -1,23 +0,0 @@ -style = {'base_mpl_style': 'fast', - 'marketcolors' : {'candle': {'up': '#00b060', 'down': '#fe3032'}, - 'edge' : {'up': '#00b060', 'down': '#fe3032'}, - 'wick' : {'up': '#606060', 'down': '#606060'}, - 'ohlc' : {'up': '#00b060', 'down': '#fe3032'}, - 'volume': {'up': '#4dc790', 'down': '#fd6b6c'}, - 'vcedge': {'up': '#1f77b4', 'down': '#1f77b4'}, - 'vcdopcod' : True, - 'alpha' : 0.9}, - 'mavcolors' : None, - 'facecolor' : '#fafafa', - 'gridcolor' : '#d0d0d0', - 'gridstyle' : '-', - 'y_on_right' : True, - 'rc' : {'axes.labelcolor': '#101010', - 'axes.edgecolor' : 'f0f0f0', - 'axes.grid.axis' : 'y', - 'ytick.color' : '#101010', - 'xtick.color' : '#101010', - 'figure.titlesize': 'x-large', - 'figure.titleweight':'semibold', - }, - 'base_mpf_style': 'yahoo'} diff --git a/build/lib/mplfinance/_styles.py b/build/lib/mplfinance/_styles.py deleted file mode 100644 index 15d0538b..00000000 --- a/build/lib/mplfinance/_styles.py +++ /dev/null @@ -1,382 +0,0 @@ -import matplotlib.pyplot as plt -import copy -import pprint -import os.path as path - -from mplfinance._arg_validators import _process_kwargs, _validate_vkwargs_dict -from mplfinance._styledata import _styles -from mplfinance._helpers import _mpf_is_color_like - - -def _get_mpfstyle(style): - ''' - Return a copy of the specified pre-defined mpfstyle. We return - a copy, because returning the original will effectively return - a pointer which allows style's definition to be modified. - ''' - return copy.deepcopy(_styles[style]) - -def _apply_mpfstyle(style): - - plt.style.use('default') - - if style['base_mpl_style'] is not None: - plt.style.use(style['base_mpl_style']) - - if style['rc'] is not None: - plt.rcParams.update(style['rc']) - - if style['facecolor'] is not None: - plt.rcParams.update({'axes.facecolor' : style['facecolor'] }) - - if 'edgecolor' in style and style['edgecolor'] is not None: - plt.rcParams.update({'axes.edgecolor' : style['edgecolor'] }) - - if 'figcolor' in style and style['figcolor'] is not None: - plt.rcParams.update({'figure.facecolor' : style['figcolor'] }) - plt.rcParams.update({'savefig.facecolor': style['figcolor'] }) - - explicit_grid = False - if style['gridcolor'] is not None: - explicit_grid = True - plt.rcParams.update({'grid.color' : style['gridcolor'] }) - - if style['gridstyle'] is not None: - explicit_grid = True - plt.rcParams.update({'grid.linestyle' : style['gridstyle'] }) - - plt.rcParams.update({'axes.grid.axis' : 'both' }) - if 'gridaxis' in style and style['gridaxis'] is not None: - gax = style['gridaxis'] - explicit_grid = True - if gax == 'horizontal'[0:len(gax)]: - plt.rcParams.update({'axes.grid.axis' : 'y' }) - elif gax == 'vertical'[0:len(gax)]: - plt.rcParams.update({'axes.grid.axis' : 'x' }) - - if explicit_grid: - plt.rcParams.update({'axes.grid' : True }) - - -def _valid_make_mpf_style_kwargs(): - vkwargs = { - 'base_mpf_style': { 'Default' : None, - 'Description' : 'mplfinance style to use as base of new mplfinance style', - 'Validator' : lambda value: value in _styles.keys() }, - - 'base_mpl_style': { 'Default' : None, - 'Description' : 'matplotlib style to use as base of new mplfinance style', - 'Validator' : lambda value: isinstance(value,(str,list))}, # and is in plt.style.available - - 'marketcolors' : { 'Default' : None, - 'Description' : 'market colors object, from `mpf.make_market_colors()`', - 'Validator' : lambda value: isinstance(value,dict) }, - - 'mavcolors' : { 'Default' : None, - 'Description' : 'sequence of colors to use for moving averages', - 'Validator' : lambda value: isinstance(value,list) }, # TODO: all([_mpf_is_color_like(v) for v in value.values()]) - - - 'facecolor' : { 'Default' : None, - 'Description' : 'background color for Axes', - 'Validator' : lambda value: isinstance(value,str) }, - - 'edgecolor' : { 'Default' : None, - 'Description' : 'edge color for Axes', - 'Validator' : lambda value: isinstance(value,str) }, - - 'figcolor' : { 'Default' : None, - 'Description' : 'background color for Figure.', - 'Validator' : lambda value: isinstance(value,str) }, - - 'gridcolor' : { 'Default' : None, - 'Description' : 'color for grid lines', - 'Validator' : lambda value: isinstance(value,str) }, - - 'gridstyle' : { 'Default' : None, - 'Description' : "grid line style ('-', '--', '-.', ':', '', offset, on-off-seq)."+ - " (see also: https://matplotlib.org/stable/gallery/lines_bars_and_markers/linestyles.html)", - 'Validator' : lambda value: isinstance(value,str) }, - - 'gridaxis' : { 'Default' : None, - 'Description' : "grid lines 'vertical', 'horizontal', or 'both'", - 'Validator' : lambda value: value in [ 'vertical'[0:len(value)], 'horizontal'[0:len(value)], 'both'[0:len(value)] ] }, - - 'y_on_right' : { 'Default' : None, - 'Description' : 'True|False primary Axes y-ticks and labels on right.', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'rc' : { 'Default' : None, - 'Description' : 'rcparams overrides (dict) (all other rcparams unchanged)', - 'Validator' : lambda value: isinstance(value,dict) }, - - 'legacy_rc' : { 'Default' : None, # Just in case someone depended upon old behavior - 'Description' : 'rcparams to set (dict) (all other rcparams cleared)', - 'Validator' : lambda value: isinstance(value,dict) }, - - 'style_name' : { 'Default' : None, - 'Description' : 'name for this style; useful when calling `mpf.write_style_file(style,filename)`', - 'Validator' : lambda value: isinstance(value,str) }, - - } - - _validate_vkwargs_dict(vkwargs) - - return vkwargs - - -def available_styles(): - return list(_styles.keys()) - -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 - -def _valid_mpf_color_spec(value): - 'value must be a color, "inherit"-like, or dict of colors' - return ( _mpf_is_color_like(value) or - ( isinstance(value,str) and value == 'inherit'[0:len(value)]) or - ( isinstance(value,dict) and - all([_mpf_is_color_like(v) for v in value.values()]) - ) - ) - -def _valid_mpf_style(value): - if value in available_styles(): - return True - if not isinstance(value,dict): - return False - if 'marketcolors' not in value: - return False - if not isinstance(value['marketcolors'],dict): - return False - # {'candle': {'up': 'b', 'down': 'g'}, - # 'edge': {'up': 'k', 'down': 'k'}, - # 'wick': {'up': 'k', 'down': 'k'}, - # 'ohlc': {'up': 'k', 'down': 'k'}, - # 'volume': {'up': '#1f77b4', 'down': '#1f77b4'}, - # 'vcedge': {'up': '#1f77b4', 'down': '#1f77b4'}, - # 'vcdopcod': False, - # 'alpha': 0.9} - for item in ('candle','edge','wick','ohlc','volume'): - if item not in value['marketcolors']: - return False - itemcolors = value['marketcolors'][item] - if not isinstance(itemcolors,dict): - return False - if 'up' not in itemcolors or 'down' not in itemcolors: - return False - return True - - -def _valid_make_marketcolors_kwargs(): - vkwargs = { - 'up' : { 'Default' : None, - 'Description' : 'color to indicate up', - 'Validator' : lambda value: _mpf_is_color_like(value) }, - - 'down' : { 'Default' : None, - 'Description' : 'color to indicate down', - 'Validator' : lambda value: _mpf_is_color_like(value) }, - - 'hollow' : { 'Default' : None, - 'Description' : "color for hollow candles (for `type=hollow`)", - 'Validator' : lambda value: _mpf_is_color_like(value) }, - - 'alpha' : { 'Default' : None, - 'Description' : 'opacity 0.0 (transparent) to 1.0 (opaque);'+ - ' applies to candles,renko,pnf (but not ohlc bars)', - 'Validator' : lambda value: (isinstance(value,float) - and 0.0 <= value and 1.0 >= value ) }, - - 'edge' : { 'Default' : None, - 'Description' : 'color of candle edge; may also be "i" or "inherit"'+ - ' to take color from base_mpf_style', - 'Validator' : lambda value: _valid_mpf_color_spec(value) }, - - 'wick' : { 'Default' : None, - 'Description' : "color of candle wick; may be single color,"+ - " or may be dict with keys 'up' and 'down'", - 'Validator' : lambda value: isinstance(value,dict) - or isinstance(value,str) - or _mpf_is_color_like(value) }, - - 'ohlc' : { 'Default' : None, - 'Description' : "color of ohlc bars; may be single color,"+ - " or may be dict with keys 'up' and 'down'", - 'Validator' : lambda value: isinstance(value,dict) - or isinstance(value,str) - or _mpf_is_color_like(value) }, - - 'volume' : { 'Default' : None, - 'Description' : "color of volume bars; may be single color,"+ - " or may be dict with keys 'up' and 'down'", - 'Validator' : lambda value: isinstance(value,dict) - or isinstance(value,str) - or _mpf_is_color_like(value) }, - - 'vcdopcod' : { 'Default' : False, - 'Description' : 'True/False volume color depends on price change from previous day', - 'Validator' : lambda value: isinstance(value,bool) }, - - - 'inherit' : { 'Default' : False, - 'Description' : 'inherit color from base_mpf_style for: edge,volume,ohlc,wick', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'base_mpf_style': { 'Default' : None, - 'Description' : 'mplfinance style market colors as basis for new market colors object', - 'Validator' : lambda value: isinstance(value,str) }, - } - - _validate_vkwargs_dict(vkwargs) - - return vkwargs - - -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 - -def write_style_file(style,filename): - pp = pprint.PrettyPrinter(indent=4,sort_dicts=False,compact=True) - strl = pp.pformat(style).splitlines() - - if not isinstance(style,dict): - raise TypeError('Specified style must be in `dict` format') - - if path.exists(filename): - print('"'+filename+'" exists.') - answer = input(' Overwrite(Y/N)? ') - a = answer.lower() - if a != 'y' and a != 'yes': - raise FileExistsError - - f = open(filename,'w') - f.write('style = '+strl[0].replace('{','dict(',1).replace("'","",2).replace(':',' =',1)+'\n') - for line in strl[1:-1]: - if "'" in line[0:5]: - f.write(' '+line.replace("'","",2).replace(':',' =',1)+'\n') - else: - f.write(' '+line+'\n') - line = strl[-1] - if "'" in line[0:5]: - line = line.replace("'","",2).replace(':',' =',1)[::-1] - else: - line = line[::-1] - f.write(' '+line.replace('}',')',1)[::-1]+'\n') - f.close() - print('Wrote style file "'+filename+'"') - return diff --git a/build/lib/mplfinance/_utils.py b/build/lib/mplfinance/_utils.py deleted file mode 100644 index a4f80dfc..00000000 --- a/build/lib/mplfinance/_utils.py +++ /dev/null @@ -1,1515 +0,0 @@ -""" -A collection of utilities for analyzing and plotting financial data. -""" - -import numpy as np -import pandas as pd -import matplotlib.dates as mdates -import datetime - -from itertools import cycle - -from matplotlib import colors as mcolors, pyplot as plt -from matplotlib.patches import Ellipse -from matplotlib.collections import LineCollection, PolyCollection, PatchCollection - -from mplfinance._arg_validators import _process_kwargs, _validate_vkwargs_dict -from mplfinance._arg_validators import _alines_validator, _bypass_kwarg_validation -from mplfinance._arg_validators import _xlim_validator, _is_datelike -from mplfinance._styles import _get_mpfstyle -from mplfinance._helpers import _mpf_to_rgba - -from six.moves import zip - -def _check_input(opens, closes, highs, lows): - """Checks that *opens*, *highs*, *lows* and *closes* have the same length. - NOTE: this code assumes if any value open, high, low, close is - missing (*-1*) they all are missing - - Parameters - ---------- - opens : sequence - sequence of opening values - highs : sequence - sequence of high values - lows : sequence - sequence of low values - closes : sequence - sequence of closing values - - Raises - ------ - ValueError - if the input sequences don't have the same length - if the input sequences don't have NaN is same locations - """ - same_length = len(opens) == len(highs) == len(lows) == len(closes) - if not same_length: - raise ValueError('O,H,L,C must have the same length!') - - o = np.where(np.isnan(opens))[0] - h = np.where(np.isnan(highs))[0] - l = np.where(np.isnan(lows))[0] - c = np.where(np.isnan(closes))[0] - - # First check that they have the same number of NaN: - same_numnans = len(o) == len(h) == len(l) == len(c) - if not same_numnans: - raise ValueError('O,H,L,C must have the same amount of missing data!') - - same_missing = ((o == h).all() and - (o == l).all() and - (o == c).all() - ) - if not same_missing: - raise ValueError('O,H,L,C must have the same missing data!') - - -def _check_and_convert_xlim_configuration(data, config): - ''' - Check, if user entered `xlim` kwarg, if user entered dates - then we may need to convert them to iloc or matplotlib dates. - ''' - if config['xlim'] is None: - return None - - xlim = config['xlim'] - - if not _xlim_validator(xlim): - raise ValueError('Bad xlim configuration #1') - - if all([_is_datelike(dt) for dt in xlim]): - if config['show_nontrading']: - xlim = [ _date_to_mdate(dt) for dt in xlim] - else: - xlim = [ _date_to_iloc_extrapolate(data.index.to_series(),dt) for dt in xlim] - - return xlim - - -def _construct_mpf_collections(ptype,dates,xdates,opens,highs,lows,closes,volumes,config,style): - collections = None - if ptype == 'candle' or ptype == 'candlestick': - collections = _construct_candlestick_collections(xdates, opens, highs, lows, closes, - marketcolors=style['marketcolors'],config=config ) - - elif ptype =='hollow_and_filled': - collections = _construct_hollow_candlestick_collections(xdates, opens, highs, lows, closes, - marketcolors=style['marketcolors'],config=config ) - - elif ptype == 'ohlc' or ptype == 'bars' or ptype == 'ohlc_bars': - collections = _construct_ohlc_collections(xdates, opens, highs, lows, closes, - marketcolors=style['marketcolors'],config=config ) - elif ptype == 'renko': - collections = _construct_renko_collections( - dates, highs, lows, volumes, config['renko_params'], closes, marketcolors=style['marketcolors']) - - elif ptype == 'pnf': - collections = _construct_pointnfig_collections( - dates, highs, lows, volumes, config['pnf_params'], closes, marketcolors=style['marketcolors']) - else: - raise TypeError('Unknown ptype="',str(ptype),'"') - - return collections - - -def _calculate_atr(atr_length, highs, lows, closes): - """Calculate the average true range - atr_length : time period to calculate over - all_highs : list of highs - all_lows : list of lows - all_closes : list of closes - """ - if atr_length < 1: - raise ValueError("Specified atr_length may not be less than 1") - elif atr_length >= len(closes): - raise ValueError("Specified atr_length is larger than the length of the dataset: " + str(len(closes))) - atr = 0 - for i in range(len(highs)-atr_length, len(highs)): - high = highs[i] - low = lows[i] - close_prev = closes[i-1] - tr = max(abs(high-low), abs(high-close_prev), abs(low-close_prev)) - atr += tr - return atr/atr_length - -def combine_adjacent(arr): - """Sum like signed adjacent elements - arr : starting array - - Returns - ------- - output: new summed array - indexes: indexes indicating the first - element summed for each group in arr - """ - output, indexes = [], [] - curr_i = 0 - while len(arr) > 0: - curr_sign = arr[0]/abs(arr[0]) - index = 0 - while index < len(arr) and arr[index]/abs(arr[index]) == curr_sign: - index += 1 - output.append(sum(arr[:index])) - indexes.append(curr_i) - curr_i += index - - for _ in range(index): - arr.pop(0) - return output, indexes - -def coalesce_volume_dates(in_volumes, in_dates, indexes): - """Sums volumes between the indexes and ouputs - dates at the indexes - in_volumes : original volume list - in_dates : original dates list - indexes : list of indexes - - Returns - ------- - volumes: new volume array - dates: new dates array - """ - volumes, dates = [], [] - for i in range(len(indexes)): - dates.append(in_dates[indexes[i]]) - to_sum_to = indexes[i+1] if i+1 < len(indexes) else len(in_volumes) - volumes.append(sum(in_volumes[indexes[i]:to_sum_to])) - return volumes, dates - - -def _updown_colors(upcolor,downcolor,opens,closes,use_prev_close=False): - # ----------------------------------------------------- - # Note that `nan` values result in `opn < cls` == False - # In other words, nans don't get plotted by collections - # but this function will choose DOWN COLOR for nans. - # ----------------------------------------------------- - if upcolor == downcolor: - return [upcolor]*len(opens) - cmap = {True : upcolor, False : downcolor} - if not use_prev_close: - return [ cmap[opn < cls] for opn,cls in zip(opens,closes) ] - else: - first = cmap[opens[0] < closes[0]] - _list = [ cmap[pre < cls] for cls,pre in zip(closes[1:], closes) ] - return [first] + _list - -def _make_updown_color_list(key,marketcolors,opens,closes,overrides=None): - length = len(opens) - ups = [marketcolors[key][ 'up' ]]*length - downs = [marketcolors[key]['down']]*length - if overrides is not None: - for ix,mco in enumerate(overrides): - if mco is None: continue - if mcolors.is_color_like(mco): - ups[ix] = mco - downs[ix] = mco - else: # assume it is correctly a marketcolors object (dict) - ups[ix] = mco[key][ 'up' ] - downs[ix] = mco[key]['down'] - return [ups[ix] if opens[ix] < closes[ix] else downs[ix] for ix in range(length)] - - -def _updownhollow_colors(upcolor,downcolor,hollowcolor,opens,closes): - if upcolor == downcolor: - return upcolor - umap = {True : hollowcolor, False : upcolor } - dmap = {True : hollowcolor, False : downcolor} - first = umap[closes[0] > opens[0]] - _list = [ umap[cls > opn] if cls > cls0 else dmap[cls > opn] for cls0,opn,cls in zip(closes[0:-1],opens[1:],closes[1:]) ] - return [first] + _list - - -def _date_to_iloc(dtseries,date): - '''Convert a `date` to a location, given a date series w/a datetime index. - If `date` does not exactly match a date in the series then interpolate between two dates. - If `date` is outside the range of dates in the series, then raise an exception - . - ''' - d1s = dtseries.loc[date:] - if len(d1s) < 1: - sdtrange = str(dtseries[0])+' to '+str(dtseries[-1]) - raise ValueError('User specified line date "'+str(date)+'" is beyond (greater than) range of plotted data ('+sdtrange+').') - d1 = d1s.index[0] - d2s = dtseries.loc[:date] - if len(d2s) < 1: - sdtrange = str(dtseries[0])+' to '+str(dtseries[-1]) - raise ValueError('User specified line date "'+str(date)+'" is before (less than) range of plotted data ('+sdtrange+').') - d2 = dtseries.loc[:date].index[-1] - # If there are duplicate dates in the series, for example in a renko plot - # then .get_loc(date) will return a slice containing all the dups, so: - loc1 = dtseries.index.get_loc(d1) - if isinstance(loc1,slice): loc1 = loc1.start - loc2 = dtseries.index.get_loc(d2) - if isinstance(loc2,slice): loc2 = loc2.stop - 1 - return (loc1+loc2)/2.0 - -def _date_to_iloc_linear(dtseries,date,trace=False): - '''Find the location of a date using linear extrapolation. - Use the endpoints of `dtseries` to calculate the slope - and yintercept for the line: - iloc = (slope)*(dtseries) + (yintercept) - Then use them to calculate the location of `date` - ''' - d1 = _date_to_mdate(dtseries.index[0]) - d2 = _date_to_mdate(dtseries.index[-1]) - - if trace: print('d1,d2=',d1,d2) - i1 = 0.0 - i2 = len(dtseries) - 1.0 - if trace: print('i1,i2=',i1,i2) - - slope = (i2 - i1) / (d2 - d1) - yitrcpt1 = i1 - (slope*d1) - if trace: print('slope,yitrcpt=',slope,yitrcpt1) - yitrcpt2 = i2 - (slope*d2) - if trace: print('slope,yitrcpt=',slope,yitrcpt2) - if yitrcpt1 != yitrcpt2: - print('WARNING: yintercepts NOT equal!!!(',yitrcpt1,yitrcpt2,')') - yitrcpt = (yitrcpt1 + yitrcpt2) / 2.0 - else: - yitrcpt = yitrcpt1 - return (slope * _date_to_mdate(date)) + yitrcpt - -def _date_to_iloc_5_7ths(dtseries,date,direction,trace=False): - first = _date_to_mdate(dtseries.index[0]) - last = _date_to_mdate(dtseries.index[-1]) - avg_days_between_points = (last - first)/float(len(dtseries)) - if avg_days_between_points < 0.33: # intraday (not daily) - return None - if direction == 'forward': - delta = _date_to_mdate(date) - _date_to_mdate(dtseries.index[-1]) - loc_5_7ths = len(dtseries) - 1 + (5/7.)*delta - elif direction == 'backward': - delta = _date_to_mdate(dtseries.index[0]) - _date_to_mdate(date) - loc_5_7ths = - (5./7.)*delta - else: - raise ValueError('_date_to_iloc_5_7ths got BAD direction value='+str(direction)) - return loc_5_7ths - -def _date_to_iloc_extrapolate(dtseries,date): - '''Convert a `date` to a location, given a date series w/a datetime index. - If `date` does not exactly match a date in the series then interpolate between two dates. - If `date` is outside the range of dates in the series, then extrapolate: - Extrapolation results in increased error as the distance of the extrapolation increases. - We have two methods to extrapolate: - (1) Determine a linear equation based on the data provided in `dtseries`, - and use that equation to calculate the location for the date. - (2) Multiply by 5/7 the number of days between the edge date of dtseries and the - date for which we are requesting a location. - THIS ASSUMES DAILY data AND a 5 DAY TRADING WEEK. - Empirical observation (scratch_pad/date_to_iloc_extrapolation.ipynb) shows that - the systematic error of these two methods tends to be in opposite directions: - taking the average of the two methods reduces systematic errorr: However, - since method (2) applies only to DAILY data, we take the average of the two - methods only for daily data. For intraday data we use only method (1). - ''' - - d1s = dtseries.loc[date:] - if len(d1s) < 1: - # extrapolate forward: - loc_linear = _date_to_iloc_linear(dtseries,date) - loc_5_7ths = _date_to_iloc_5_7ths(dtseries,date,'forward') - if loc_5_7ths is not None: - return (loc_linear + loc_5_7ths)/2.0 - else: - return loc_linear - d1 = d1s.index[0] - d2s = dtseries.loc[:date] - if len(d2s) < 1: - # extrapolate backward: - loc_linear = _date_to_iloc_linear(dtseries,date) - loc_5_7ths = _date_to_iloc_5_7ths(dtseries,date,'backward') - if loc_5_7ths is not None: - return (loc_linear + loc_5_7ths)/2.0 - else: - return loc_linear - # Below here we *interpolate* (not extrapolate) - d2 = dtseries.loc[:date].index[-1] - # If there are duplicate dates in the series, for example in a renko plot - # then .get_loc(date) will return a slice containing all the dups, so: - loc1 = dtseries.index.get_loc(d1) - if isinstance(loc1,slice): loc1 = loc1.start - loc2 = dtseries.index.get_loc(d2) - if isinstance(loc2,slice): loc2 = loc2.stop - 1 - return (loc1+loc2)/2.0 - - -def _date_to_mdate(date): - if isinstance(date,str): - pydt = pd.to_datetime(date).to_pydatetime() - elif isinstance(date,pd.Timestamp): - pydt = date.to_pydatetime() - elif isinstance(date,(datetime.datetime,datetime.date)): - pydt = date - else: - return None - return mdates.date2num(pydt) - -def _convert_segment_dates(segments,dtindex): - ''' - Convert line segment dates to matplotlib dates - Inputted segment dates may be: pandas-parseable date-time string, pandas timestamp, - or a python datetime or date, or (if dtindex is not None) integer index - A "segment" is a "sequence of lines", - see: https://matplotlib.org/api/collections_api.html#matplotlib.collections.LineCollection - ''' - #import pdb - #pdb.set_trace() - if dtindex is not None: - dtseries = dtindex.to_series() - converted = [] - for line in segments: - new_line = [] - for dt,value in line: - if dtindex is not None: - date = _date_to_iloc(dtseries,dt) - else: - date = _date_to_mdate(dt) - if date is None: - raise TypeError('NON-DATE in segment line='+str(line)) - new_line.append((date,value)) - converted.append(new_line) - return converted - -def _valid_renko_kwargs(): - ''' - Construct and return the "valid renko kwargs table" for the mplfinance.plot(type='renko') - function. A valid kwargs table is a `dict` of `dict`s. The keys of the outer dict are - the valid key-words for the function. The value for each key is a dict containing 3 - specific keys: "Default", "Description" and "Validator" with the following values: - "Default" - The default value for the kwarg if none is specified. - "Description" - The description for the kwarg. - "Validator" - A function that takes the caller specified value for the kwarg, - and validates that it is the correct type, and (for kwargs with - a limited set of allowed values) may also validate that the - kwarg value is one of the allowed values. - ''' - vkwargs = { - 'brick_size' : { 'Default' : 'atr', - 'Description' : 'size of each brick on y-axis (typically price).'+ - ' specify a number, or specify "atr" for average true range.', - 'Validator' : lambda value: isinstance(value,(float,int)) - or value == 'atr' }, - 'atr_length' : { 'Default' : 14, - 'Description' : 'number of periods for atr calculation (if brick size is "atr")', - 'Validator' : lambda value: isinstance(value,int) - or value == 'total' }, - } - - _validate_vkwargs_dict(vkwargs) - - return vkwargs - - -def _valid_pnf_kwargs(): - ''' - Construct and return the "valid pnf kwargs table" for the mplfinance.plot(type='pnf') - function. A valid kwargs table is a `dict` of `dict`s. The keys of the outer dict are - the valid key-words for the function. The value for each key is a dict containing 3 - specific keys: "Default", "Description" and "Validator" with the following values: - "Default" - The default value for the kwarg if none is specified. - "Description" - The description for the kwarg. - "Validator" - A function that takes the caller specified value for the kwarg, - and validates that it is the correct type, and (for kwargs with - a limited set of allowed values) may also validate that the - kwarg value is one of the allowed values. - ''' - vkwargs = { - 'box_size' : { 'Default' : 'atr', - 'Description' : 'size of each box on y-axis (typically price).'+ - ' specify a number, or specify "atr" for average true range.', - 'Validator' : lambda value: isinstance(value,(float,int)) - or value == 'atr' }, - 'atr_length' : { 'Default' : 14, - 'Description' : 'number of periods for atr calculation (if box size is "atr")', - 'Validator' : lambda value: isinstance(value,int) - or value == 'total' }, - - 'reversal' : { 'Default' : 1, - 'Description' : 'number of boxes, in opposite direction, needed to reverse'+ - ' a trend (i.e. to start a new column).', - 'Validator' : lambda value: isinstance(value,int) }, - } - - _validate_vkwargs_dict(vkwargs) - - return vkwargs - - -def _valid_lines_kwargs(): - ''' - Construct and return the "valid lines (hlines,vlines,alines,tlines) kwargs table" - for the mplfinance.plot() `[h|v|a|t]lines=` kwarg functions. - A valid kwargs table is a `dict` of `dict`s. The keys of the outer dict are - the valid key-words for the function. The value for each key is a dict containing 3 - specific keys: "Default", "Description" and "Validator" with the following values: - "Default" - The default value for the kwarg if none is specified. - "Description" - The description for the kwarg. - "Validator" - A function that takes the caller specified value for the kwarg, - and validates that it is the correct type, and (for kwargs with - a limited set of allowed values) may also validate that the - kwarg value is one of the allowed values. - ''' - valid_linestyles = ['-','solid','--','dashed','-.','dashdot',':','dotted',None,' ',''] - vkwargs = { - 'hlines' : { 'Default' : None, - 'Description' : 'Draw one or more HORIZONTAL LINES across entire plot, by'+ - ' specifying a price, or sequence of prices. May also be a dict'+ - ' with key `hlines` specifying a price or sequence of prices, plus'+ - ' one or more of the following keys: `colors`, `linestyle`,'+ - ' `linewidths`, `alpha`.', - 'Validator' : _bypass_kwarg_validation }, - - 'vlines' : { 'Default' : None, - 'Description' : 'Draw one or more VERTICAL LINES across entire plot, by'+ - ' specifying a date[time], or sequence of date[time]. May also'+ - ' be a dict with key `vlines` specifying a date[time] or sequence'+ - ' of date[time], plus one or more of the following keys:'+ - ' `colors`, `linestyle`, `linewidths`, `alpha`.', - 'Validator' : _bypass_kwarg_validation }, - - 'alines' : { 'Default' : None, - 'Description' : 'Draw one or more ARBITRARY LINES anywhere on the plot, by'+ - ' specifying a sequence of two or more date/price pairs, or by'+ - ' specifying a sequence of sequences of two or more date/price pairs.'+ - ' May also be a dict with key `alines` (as date/price pairs described above),'+ - ' plus one or more of the following keys:'+ - ' `colors`, `linestyle`, `linewidths`, `alpha`.', - 'Validator' : _bypass_kwarg_validation }, - - 'tlines' : { 'Default' : None, - 'Description' : 'Draw one or more TREND LINES by specifying one or more pairs of date[times]'+ - ' between which each trend line should be drawn. May also be a dict with key'+ - ' `tlines` as just described, plus one or more of the following keys:'+ - ' `colors`, `linestyle`, `linewidths`, `alpha`, `tline_use`,`tline_method`.', - 'Validator' : _bypass_kwarg_validation }, - - 'colors' : { 'Default' : None, - 'Description' : 'Color of [hvat]lines (or sequence of colors, if each line to be a different color)', - 'Validator' : lambda value: value is None - or mcolors.is_color_like(value) - or (isinstance(value,(list,tuple)) - and all([mcolors.is_color_like(v) for v in value]) ) }, - - 'linestyle' : { 'Default' : '-', - 'Description' : 'line style of [hvat]lines (or sequence of line styles, if each line to have a different linestyle)', - 'Validator' : lambda value: value is None or value in valid_linestyles or - all([v in valid_linestyles for v in value]) }, - - 'linewidths': { 'Default' : None, - 'Description' : 'line width of [hvat]lines (or sequence of line widths, if each line to have a different width)', - 'Validator' : lambda value: value is None - or isinstance(value,(float,int)) - or all([isinstance(v,(float,int)) for v in value]) }, - - 'alpha': {'Default': 1.0, - 'Description': 'Opacity of [hvat]lines (or sequence of opacities,' - + 'if each line is to have a different opacity)' - + 'float from 0.0 to 1.0 ' + ' (1.0 means fully opaque; 0.0 means transparent.', - 'Validator': lambda value: isinstance(value, (float, int)) - or all([isinstance(v, (float, int)) for v in value])}, - - - 'tline_use' : { 'Default' : 'close', - 'Description' : 'value to use for TREND LINE ("open","high","low","close") or sequence of'+ - ' any combination of "open", "high", "low", "close" to use a average of the'+ - ' specified values to determine the trend line.', - 'Validator' : lambda value: isinstance(value,str) - or (isinstance(value,(list,tuple)) - and all([isinstance(v,str) for v in value]) ) }, - - 'tline_method': { 'Default' : 'point-to-point', - 'Description' : 'method for TREND LINE determination: "point-to-point" or "least-squares"', - 'Validator' : lambda value: value in ['point-to-point','least-squares'] } - } - - _validate_vkwargs_dict(vkwargs) - - return vkwargs - - -def _construct_ohlc_collections(dates, opens, highs, lows, closes, marketcolors=None, config=None): - """Represent the time, open, high, low, close as a vertical line - ranging from low to high. The left tick is the open and the right - tick is the close. - *opens*, *highs*, *lows* and *closes* must have the same length. - NOTE: this code assumes if any value open, high, low, close is - missing (*-1*) they all are missing - - Parameters - ---------- - opens : sequence - sequence of opening values - highs : sequence - sequence of high values - lows : sequence - sequence of low values - closes : sequence - sequence of closing values - marketcolors : dict of colors: 'up', 'down' - - Returns - ------- - ret : list - a list or tuple of matplotlib collections to be added to the axes - """ - - _check_input(opens, highs, lows, closes) - - if marketcolors is None: - mktcolors = _get_mpfstyle('classic')['marketcolors']['ohlc'] - else: - mktcolors = marketcolors['ohlc'] - - rangeSegments = [((dt, low), (dt, high)) for dt, low, high in zip(dates, lows, highs)] - - datalen = len(dates) - - avg_dist_between_points = (dates[-1] - dates[0]) / float(datalen) - - ticksize = config['_width_config']['ohlc_ticksize'] - - # the ticks will be from ticksize to 0 in points at the origin and - # we'll translate these to the date, open location - openSegments = [((dt-ticksize, op), (dt, op)) for dt, op in zip(dates, opens)] - - # the ticks will be from 0 to ticksize in points at the origin and - # we'll translate these to the date, close location - closeSegments = [((dt, close), (dt+ticksize, close)) for dt, close in zip(dates, closes)] - - if mktcolors['up'] == mktcolors['down'] and config['marketcolor_overrides'] is None: - colors = mktcolors['up'] - else: - overrides = config['marketcolor_overrides'] - colors = _make_updown_color_list('ohlc',marketcolors,opens,closes,overrides) - - lw = config['_width_config']['ohlc_linewidth'] - - rangeCollection = LineCollection(rangeSegments, - colors=colors, - linewidths=lw, - ) - - openCollection = LineCollection(openSegments, - colors=colors, - linewidths=lw, - ) - - closeCollection = LineCollection(closeSegments, - colors=colors, - linewidths=lw - ) - - return [rangeCollection, openCollection, closeCollection] - - -def _construct_candlestick_collections(dates, opens, highs, lows, closes, marketcolors=None, config=None): - """Represent the open, close as a bar line and high low range as a - vertical line. - - NOTE: this code assumes if any value open, low, high, close is - missing they all are missing - - - Parameters - ---------- - opens : sequence - sequence of opening values - highs : sequence - sequence of high values - lows : sequence - sequence of low values - closes : sequence - sequence of closing values - marketcolors : dict of colors: up, down, edge, wick, alpha - alpha : float - bar transparency - - Returns - ------- - ret : list - (lineCollection, barCollection) - """ - - _check_input(opens, highs, lows, closes) - - if marketcolors is None: - marketcolors = _get_mpfstyle('classic')['marketcolors'] - - datalen = len(dates) - - avg_dist_between_points = (dates[-1] - dates[0]) / float(datalen) - - delta = config['_width_config']['candle_width'] / 2.0 - - barVerts = [((date - delta, open), - (date - delta, close), - (date + delta, close), - (date + delta, open)) - for date, open, close in zip(dates, opens, closes)] - - rangeSegLow = [((date, low), (date, min(open,close))) - for date, low, open, close in zip(dates, lows, opens, closes)] - - rangeSegHigh = [((date, high), (date, max(open,close))) - for date, high, open, close in zip(dates, highs, opens, closes)] - - rangeSegments = rangeSegLow + rangeSegHigh - - alpha = marketcolors['alpha'] - - overrides = config['marketcolor_overrides'] - faceonly = config['mco_faceonly'] - - colors = _make_updown_color_list('candle',marketcolors,opens,closes,overrides) - colors = [ _mpf_to_rgba(c,alpha) for c in colors ] # include alpha - if faceonly: overrides = None - edgecolor = _make_updown_color_list('edge',marketcolors,opens,closes,overrides) - wickcolor = _make_updown_color_list('wick',marketcolors,opens,closes,overrides) - - lw = config['_width_config']['candle_linewidth'] - - rangeCollection = LineCollection(rangeSegments, - colors=wickcolor, - linewidths=lw, - ) - - barCollection = PolyCollection(barVerts, - facecolors=colors, - edgecolors=edgecolor, - linewidths=lw - ) - - return [rangeCollection, barCollection] - - -def _construct_hollow_candlestick_collections(dates, opens, highs, lows, closes, marketcolors=None, config=None): - """Represent today's open to close as a "bar" line (candle body) - and high low range as a vertical line (candle wick) - - If config['type']=='hollow_and_filled' (hollow and filled candles) then candle edge and - wick color depend on PREVIOUS close to today's close (up or down), and the center of the - candle body (hollow or filled) depends on the today's open to close (up or down). - - NOTE: this code assumes if any value open, low, high, close is - missing they all are missing - - Parameters - ---------- - opens : sequence - sequence of opening values - highs : sequence - sequence of high values - lows : sequence - sequence of low values - closes : sequence - sequence of closing values - marketcolors : dict of colors: up, down, edge, wick, alpha - alpha : float - bar (candle body) transparency - - Returns - ------- - ret : list - (lineCollection, barCollection) - """ - - _check_input(opens, highs, lows, closes) - - if marketcolors is None: - marketcolors = _get_mpfstyle('classic')['marketcolors'] - - datalen = len(dates) - - avg_dist_between_points = (dates[-1] - dates[0]) / float(datalen) - - delta = config['_width_config']['candle_width'] / 2.0 - - barVerts = [((date - delta, open), - (date - delta, close), - (date + delta, close), - (date + delta, open)) - for date, open, close in zip(dates, opens, closes)] - - rangeSegLow = [((date, low), (date, min(open,close))) - for date, low, open, close in zip(dates, lows, opens, closes)] - - rangeSegHigh = [((date, high), (date, max(open,close))) - for date, high, open, close in zip(dates, highs, opens, closes)] - - rangeSegments = rangeSegLow + rangeSegHigh - - alpha = marketcolors['alpha'] - - uc = mcolors.to_rgba(marketcolors['candle'][ 'up' ], alpha) - dc = mcolors.to_rgba(marketcolors['candle']['down'], alpha) - - hc = mcolors.to_rgba(marketcolors['hollow']) if 'hollow' in marketcolors else (0,0,0,0) - - colors = _updownhollow_colors(uc, dc, hc, opens, closes) # for candle body. - - edgecolor = _updown_colors(uc, dc, opens, closes, use_prev_close=True) - - wickcolor = _updown_colors(uc, dc, opens, closes, use_prev_close=True) - - # For hollow candles, we scale the candle linewidth up a little: - lw = 1.25 * config['_width_config']['candle_linewidth'] - - rangeCollection = LineCollection(rangeSegments, - colors=wickcolor, - linewidths=lw, - ) - - barCollection = PolyCollection(barVerts, - facecolors=colors, - edgecolors=edgecolor, - linewidths=lw - ) - - return [rangeCollection, barCollection] - - -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'] - - 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 - ) - calculated_values = dict(dates=new_dates,volumes=new_volumes, - values=brick_values,size=brick_size) - return [rectCollection,], calculated_values - - -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. During this same step we also combine like signed elements - and associated volume/date data ignoring any zero values that are created by - subtracting 1 from the box value. Next we recreate the box array utilizing a - rolling_change and volume_cache to store and sum the changes that don't break - the reversal threshold. - - 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'] - - box_size = pointnfig_params['box_size'] - atr_length = pointnfig_params['atr_length'] - reversal = pointnfig_params['reversal'] - - 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)) - - if reversal < 1 or reversal > 9: - raise ValueError("Specified reversal must be an integer in the range [1,9]") - - 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) - - adjusted_boxes = [boxes[0]] - temp_volumes, temp_dates = [new_volumes[0]], [new_dates[0]] - volume_cache = 0 - - # Clean data to subtract 1 from all box # not including the first boxes element and combine like signed adjacent values (after ignoring zeros) - for i in range(1, len(boxes)): - adjusted_value = boxes[i]- int((boxes[i]/abs(boxes[i]))) - - # not equal to 0 and different signs - if adjusted_value != 0 and adjusted_boxes[-1]*adjusted_value < 0: - - # Append adjusted_value, volumes, and date to associated lists - adjusted_boxes.append(adjusted_value) - temp_volumes.append(new_volumes[i] + volume_cache) - temp_dates.append(new_dates[i]) - - # reset volume_cache once we use it - volume_cache = 0 - - # not equal to 0 and same signs - elif adjusted_value != 0 and adjusted_boxes[-1]*adjusted_value > 0: - - # Add adjusted_value and volume values to last added elements - adjusted_boxes[-1] += adjusted_value - temp_volumes[-1] += new_volumes[i] + volume_cache - - # reset volume_cache once we use it - volume_cache = 0 - - else: # adjusted_value == 0 - volume_cache += new_volumes[i] - - boxes = [adjusted_boxes[0]] - new_volumes = [temp_volumes[0]] - new_dates = [temp_dates[0]] - - rolling_change = 0 - volume_cache = 0 - biggest_difference = 0 # only used for the last column - - #Clean data to account for reversal size (added to allow overriding the default reversal of 1) - for i in range(1, len(adjusted_boxes)): - - # Add to rolling_change and volume_cache which stores the box and volume values - rolling_change += adjusted_boxes[i] - volume_cache += temp_volumes[i] - - # if rolling_change is the same sign as the previous box and the abs value is bigger than the - # abs value of biggest_difference then we should replace biggest_difference with rolling_change - if rolling_change*boxes[-1] > 0 and abs(rolling_change) > abs(biggest_difference): - biggest_difference = rolling_change - - # Add to new list if the rolling change is >= the reversal - if abs(rolling_change) >= reversal: - - # if rolling_change is the same sign as the previous # of boxes then combine - if rolling_change*boxes[-1] > 0: - boxes[-1] += rolling_change - new_volumes[-1] += volume_cache - - # otherwise add new box - else: # < 0 (== 0 can't happen since neither rolling_change or boxes[-1] can be 0) - boxes.append(rolling_change) - new_volumes.append(volume_cache) - new_dates.append(temp_dates[i]) - - # reset rolling_change and volume_cache once we've used them - rolling_change = 0 - volume_cache = 0 - - # reset biggest_difference as we start from the beginning every time there is a reversal - biggest_difference = 0 - - # Adjust the last box column if the left over rolling_change is the same sign as the column - boxes[-1] += biggest_difference - new_volumes[-1] += volume_cache - - 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( 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 - ) - calculated_values = dict(dates=new_dates,counts=boxes,values=box_values, - volumes=new_volumes,size=box_size) - return [cirCollection, xCollection], calculated_values - - -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 widths 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()) - - 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 - - -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 widths 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 - - -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 widths 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 - -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 widths 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)) ) - df.index = df.index.tz_localize(None) - - # 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 - ''' - si = dfslice[tline_use].mean(axis=1) - s = si.dropna() - if len(s) < 2: - err = 'NOT enough data for Least Squares' - if (len(si) > 2): - err += ', due to presence of NaNs' - raise ValueError(err) - 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).replace(tzinfo=None), mdates.num2date(x2).replace(tzinfo=None) - 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) - - -from matplotlib.ticker import Formatter -class IntegerIndexDateTimeFormatter(Formatter): - """ - Formatter for axis that is indexed by integer, where the integers - represent the index location of the datetime object that should be - formatted at that lcoation. This formatter is used typically when - plotting datetime on an axis but the user does NOT want to see gaps - where days (or times) are missing. To use: plot the data against - a range of integers equal in length to the array of datetimes that - you would otherwise plot on that axis. Construct this formatter - by providing the arrange of datetimes (as matplotlib floats). When - the formatter receives an integer in the range, it will look up the - datetime and format it. - - """ - def __init__(self, dates, fmt='%b %d, %H:%M'): - self.dates = dates - self.len = len(dates) - self.fmt = fmt - - def __call__(self, x, pos=0): - #import pdb; pdb.set_trace() - 'Return label for time x at position pos' - # not sure what 'pos' is for: see - # https://matplotlib.org/gallery/ticks_and_spines/date_index_formatter.html - ix = int(np.round(x)) - - if ix >= self.len or ix < 0: - date = None - dateformat = '' - else: - date = self.dates[ix] - dateformat = mdates.num2date(date).strftime(self.fmt) - #print('x=',x,'pos=',pos,'dates[',ix,']=',date,'dateformat=',dateformat) - return dateformat - -def _mscatter(x,y,ax=None, m=None, **kw): - import matplotlib.markers as mmarkers - if not ax: ax=plt.gca() - sc = ax.scatter(x,y,**kw) - if (m is not None) and (len(m)==len(x)): - paths = [] - for marker in m: - if isinstance(marker, mmarkers.MarkerStyle): - marker_obj = marker - else: - marker_obj = mmarkers.MarkerStyle(marker) - path = marker_obj.get_path().transformed( - marker_obj.get_transform()) - paths.append(path) - sc.set_paths(paths) - return sc diff --git a/build/lib/mplfinance/_version.py b/build/lib/mplfinance/_version.py deleted file mode 100644 index 44439d7d..00000000 --- a/build/lib/mplfinance/_version.py +++ /dev/null @@ -1,6 +0,0 @@ -version_info = (0, 12, 9, 'beta', 2) - -_specifier_ = {'alpha': 'a','beta': 'b','candidate': 'rc','final': ''} - -__version__ = '%s.%s.%s%s'%(version_info[0], version_info[1], version_info[2], - '' if version_info[3]=='final' else _specifier_[version_info[3]]+str(version_info[4])) diff --git a/build/lib/mplfinance/_widths.py b/build/lib/mplfinance/_widths.py deleted file mode 100644 index 3b6813c4..00000000 --- a/build/lib/mplfinance/_widths.py +++ /dev/null @@ -1,197 +0,0 @@ -import pandas as pd -from mplfinance._arg_validators import _process_kwargs, _validate_vkwargs_dict - -def _get_widths_df(): - ''' - Provide a dataframe of width data that appropriate scales widths of - various aspects of the plot (candles,ohlc bars,volume bars) based on - the amount or density of data. These numbers were arrived at by - carefully testing many use-cases of plots with various styles, - and observing which numbers gave the "best" appearance. - ''' - numpoints = [n for n in range(30,241,30)] - volume_width = (0.98, 0.96, 0.95, 0.925, 0.9, 0.9, 0.875, 0.825 ) - volume_linewidth = tuple([0.65]*8) - candle_width = (0.65, 0.575, 0.50, 0.445, 0.435, 0.425, 0.420, 0.415) - candle_linewidth = (1.00, 0.875, 0.75, 0.625, 0.500, 0.438, 0.435, 0.435) - ohlc_tickwidth = tuple([0.35]*8) - ohlc_linewidth = (1.50, 1.175, 0.85, 0.525, 0.525, 0.525, 0.525, 0.525) - line_width = (2.25, 1.8, 1.3, 0.813, 0.807, 0.801, 0.796, 0.791) - widths = {} - widths['vw'] = volume_width - widths['vlw'] = volume_linewidth - widths['cw'] = candle_width - widths['clw'] = candle_linewidth - widths['ow'] = ohlc_tickwidth - widths['olw'] = ohlc_linewidth - widths['lw'] = line_width - return pd.DataFrame(widths,index=numpoints) - -_widths = _get_widths_df() - - -def _valid_scale_width_kwargs(): - vkwargs = { - 'ohlc' : { 'Default' : None, - 'Description' : 'length of horizontal open/close tickmarks on ohlc bars', - 'Validator' : lambda value: isinstance(value,(float,int)) }, - - 'volume' : { 'Default' : None, - 'Description' : 'width of volume bars', - 'Validator' : lambda value: isinstance(value,(float,int)) }, - - 'candle' : { 'Default' : None, - 'Description' : 'width of candles', - 'Validator' : lambda value: isinstance(value,(float,int)) }, - - 'lines' : { 'Default' : None, - 'Description' : 'width of lines (for line plots and moving averages)', - 'Validator' : lambda value: isinstance(value,(float,int)) }, - - 'volume_linewidth' : { 'Default' : None, - 'Description' : 'width of edges of volume bars', - 'Validator' : lambda value: isinstance(value,(float,int)) }, - - 'ohlc_linewidth' : { 'Default' : None, - 'Description' : 'width (thickness) of ohlc bars', - 'Validator' : lambda value: isinstance(value,(float,int)) }, - - 'candle_linewidth' : { 'Default' : None, - 'Description' : 'width of candle edges and wicks', - 'Validator' : lambda value: isinstance(value,(float,int)) }, - } - - _validate_vkwargs_dict(vkwargs) - - return vkwargs - - -def _valid_update_width_kwargs(): - vkwargs = { - - 'ohlc_ticksize' : { 'Default' : None, - 'Description' : 'length of horizontal open/close tickmarks on ohlc bars', - 'Validator' : lambda value: isinstance(value,(float,int)) }, - - 'ohlc_linewidth' : { 'Default' : None, - 'Description' : 'width (thickness) of ohlc bars', - 'Validator' : lambda value: isinstance(value,(float,int)) }, - - 'volume_width' : { 'Default' : None, - 'Description' : 'width of volume bars', - 'Validator' : lambda value: isinstance(value,(float,int)) }, - - 'volume_linewidth' : { 'Default' : None, - 'Description' : 'width of edges of volume bars', - 'Validator' : lambda value: isinstance(value,(float,int)) }, - - 'candle_width' : { 'Default' : None, - 'Description' : 'width of candles', - 'Validator' : lambda value: isinstance(value,(float,int)) }, - - 'candle_linewidth' : { 'Default' : None, - 'Description' : 'width of candle edges and wicks', - 'Validator' : lambda value: isinstance(value,(float,int)) }, - - 'line_width' : { 'Default' : None, - 'Description' : 'width of lines (for line plots and moving averages)', - 'Validator' : lambda value: isinstance(value,(float,int)) }, - } - - _validate_vkwargs_dict(vkwargs) - - return vkwargs - - -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 - - -def _dfinterpolate(df,key,column): - ''' - Given a DataFrame, with all values and the Index as floats, - and given a float key, find the row that matches the key, or - find the two rows surrounding that key, and return the interpolated - value for the specified column, based on where the key falls between - the two rows. If they key is an exact match for a key in the index, - the return the exact value from the column. If the key is less than - or greater than any key in the index, then return either the first - or last value for the column. - ''' - s = df[column] - s1 = s.loc[:key] - if len(s1) < 1: - return s.iloc[0] - j1 = s1.index[-1] - v1 = s1.iloc[-1] - - s2 = s.loc[key:] - if len(s2) < 1: - return s.iloc[-1] - j2 = s2.index[0] - v2 = s2.iloc[0] - - if j1 == j2: - return v1 - delta = j2 - j1 - portion = (key - j1)/delta - ans = v1 + (v2-v1)*portion - return ans diff --git a/build/lib/mplfinance/original_flavor.py b/build/lib/mplfinance/original_flavor.py deleted file mode 100644 index f6963963..00000000 --- a/build/lib/mplfinance/original_flavor.py +++ /dev/null @@ -1,885 +0,0 @@ -""" -A collection of functions for analyzing and plotting -financial data. User contributions welcome! - -""" -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import numpy as np -from matplotlib import colors as mcolors -from matplotlib.collections import LineCollection, PolyCollection -from matplotlib.lines import TICKLEFT, TICKRIGHT, Line2D -from matplotlib.patches import Rectangle -from matplotlib.transforms import Affine2D - -from six.moves import xrange, zip - - -def plot_day_summary_oclh(ax, quotes, ticksize=3, - colorup='k', colordown='r'): - """Plots day summary - - Represent the time, open, close, high, low as a vertical line - ranging from low to high. The left tick is the open and the right - tick is the close. - - - - Parameters - ---------- - ax : `Axes` - an `Axes` instance to plot to - quotes : sequence of (time, open, close, high, low, ...) sequences - data to plot. time must be in float date format - see date2num - ticksize : int - open/close tick marker in points - colorup : color - the color of the lines where close >= open - colordown : color - the color of the lines where close < open - - Returns - ------- - lines : list - list of tuples of the lines added (one tuple per quote) - """ - return _plot_day_summary(ax, quotes, ticksize=ticksize, - colorup=colorup, colordown=colordown, - ochl=True) - - -def plot_day_summary_ohlc(ax, quotes, ticksize=3, - colorup='k', colordown='r'): - """Plots day summary - - Represent the time, open, high, low, close as a vertical line - ranging from low to high. The left tick is the open and the right - tick is the close. - - - - Parameters - ---------- - ax : `Axes` - an `Axes` instance to plot to - quotes : sequence of (time, open, high, low, close, ...) sequences - data to plot. time must be in float date format - see date2num - ticksize : int - open/close tick marker in points - colorup : color - the color of the lines where close >= open - colordown : color - the color of the lines where close < open - - Returns - ------- - lines : list - list of tuples of the lines added (one tuple per quote) - """ - return _plot_day_summary(ax, quotes, ticksize=ticksize, - colorup=colorup, colordown=colordown, - ochl=False) - - -def _plot_day_summary(ax, quotes, ticksize=3, - colorup='k', colordown='r', - ochl=True): - """Plots day summary - - - Represent the time, open, high, low, close as a vertical line - ranging from low to high. The left tick is the open and the right - tick is the close. - - - - Parameters - ---------- - ax : `Axes` - an `Axes` instance to plot to - quotes : sequence of quote sequences - data to plot. time must be in float date format - see date2num - (time, open, high, low, close, ...) vs - (time, open, close, high, low, ...) - set by `ochl` - ticksize : int - open/close tick marker in points - colorup : color - the color of the lines where close >= open - colordown : color - the color of the lines where close < open - ochl: bool - argument to select between ochl and ohlc ordering of quotes - - Returns - ------- - lines : list - list of tuples of the lines added (one tuple per quote) - """ - # unfortunately this has a different return type than plot_day_summary2_* - lines = [] - for q in quotes: - if ochl: - t, open, close, high, low = q[:5] - else: - t, open, high, low, close = q[:5] - - if close >= open: - color = colorup - else: - color = colordown - - vline = Line2D(xdata=(t, t), ydata=(low, high), - color=color, - antialiased=False, # no need to antialias vert lines - ) - - oline = Line2D(xdata=(t, t), ydata=(open, open), - color=color, - antialiased=False, - marker=TICKLEFT, - markersize=ticksize, - ) - - cline = Line2D(xdata=(t, t), ydata=(close, close), - color=color, - antialiased=False, - markersize=ticksize, - marker=TICKRIGHT) - - lines.extend((vline, oline, cline)) - ax.add_line(vline) - ax.add_line(oline) - ax.add_line(cline) - - ax.autoscale_view() - - return lines - - -def candlestick_ochl(ax, quotes, width=0.2, colorup='k', colordown='r', - alpha=1.0): - """ - Plot the time, open, close, high, low as a vertical line ranging - from low to high. Use a rectangular bar to represent the - open-close span. If close >= open, use colorup to color the bar, - otherwise use colordown - - Parameters - ---------- - ax : `Axes` - an Axes instance to plot to - quotes : sequence of (time, open, close, high, low, ...) sequences - As long as the first 5 elements are these values, - the record can be as long as you want (e.g., it may store volume). - - time must be in float days format - see date2num - - width : float - fraction of a day for the rectangle width - colorup : color - the color of the rectangle where close >= open - colordown : color - the color of the rectangle where close < open - alpha : float - the rectangle alpha level - - Returns - ------- - ret : tuple - returns (lines, patches) where lines is a list of lines - added and patches is a list of the rectangle patches added - - """ - return _candlestick(ax, quotes, width=width, colorup=colorup, - colordown=colordown, - alpha=alpha, ochl=True) - - -def candlestick_ohlc(ax, quotes, width=0.2, colorup='k', colordown='r', - alpha=1.0): - """ - Plot the time, open, high, low, close as a vertical line ranging - from low to high. Use a rectangular bar to represent the - open-close span. If close >= open, use colorup to color the bar, - otherwise use colordown - - Parameters - ---------- - ax : `Axes` - an Axes instance to plot to - quotes : sequence of (time, open, high, low, close, ...) sequences - As long as the first 5 elements are these values, - the record can be as long as you want (e.g., it may store volume). - - time must be in float days format - see date2num - - width : float - fraction of a day for the rectangle width - colorup : color - the color of the rectangle where close >= open - colordown : color - the color of the rectangle where close < open - alpha : float - the rectangle alpha level - - Returns - ------- - ret : tuple - returns (lines, patches) where lines is a list of lines - added and patches is a list of the rectangle patches added - - """ - return _candlestick(ax, quotes, width=width, colorup=colorup, - colordown=colordown, - alpha=alpha, ochl=False) - - -def _candlestick(ax, quotes, width=0.2, colorup='k', colordown='r', - alpha=1.0, ochl=True): - """ - Plot the time, open, high, low, close as a vertical line ranging - from low to high. Use a rectangular bar to represent the - open-close span. If close >= open, use colorup to color the bar, - otherwise use colordown - - Parameters - ---------- - ax : `Axes` - an Axes instance to plot to - quotes : sequence of quote sequences - data to plot. time must be in float date format - see date2num - (time, open, high, low, close, ...) vs - (time, open, close, high, low, ...) - set by `ochl` - width : float - fraction of a day for the rectangle width - colorup : color - the color of the rectangle where close >= open - colordown : color - the color of the rectangle where close < open - alpha : float - the rectangle alpha level - ochl: bool - argument to select between ochl and ohlc ordering of quotes - - Returns - ------- - ret : tuple - returns (lines, patches) where lines is a list of lines - added and patches is a list of the rectangle patches added - - """ - - OFFSET = width / 2.0 - - lines = [] - patches = [] - for q in quotes: - if ochl: - t, open, close, high, low = q[:5] - else: - t, open, high, low, close = q[:5] - - if close >= open: - color = colorup - lower = open - height = close - open - else: - color = colordown - lower = close - height = open - close - - vline = Line2D( - xdata=(t, t), ydata=(low, high), - color=color, - linewidth=0.5, - antialiased=True, - ) - - rect = Rectangle( - xy=(t - OFFSET, lower), - width=width, - height=height, - facecolor=color, - edgecolor=color, - ) - rect.set_alpha(alpha) - - lines.append(vline) - patches.append(rect) - ax.add_line(vline) - ax.add_patch(rect) - ax.autoscale_view() - - return lines, patches - - -def _check_input(opens, closes, highs, lows, miss=-1): - """Checks that *opens*, *highs*, *lows* and *closes* have the same length. - NOTE: this code assumes if any value open, high, low, close is - missing (*-1*) they all are missing - - Parameters - ---------- - ax : `Axes` - an Axes instance to plot to - opens : sequence - sequence of opening values - highs : sequence - sequence of high values - lows : sequence - sequence of low values - closes : sequence - sequence of closing values - miss : int - identifier of the missing data - - Raises - ------ - ValueError - if the input sequences don't have the same length - """ - - def _missing(sequence, miss=-1): - """Returns the index in *sequence* of the missing data, identified by - *miss* - - Parameters - ---------- - sequence : - sequence to evaluate - miss : - identifier of the missing data - - Returns - ------- - where_miss: numpy.ndarray - indices of the missing data - """ - return np.where(np.array(sequence) == miss)[0] - - same_length = len(opens) == len(highs) == len(lows) == len(closes) - _missopens = _missing(opens) - same_missing = ((_missopens == _missing(highs)).all() and - (_missopens == _missing(lows)).all() and - (_missopens == _missing(closes)).all()) - - if not (same_length and same_missing): - msg = ("*opens*, *highs*, *lows* and *closes* must have the same" - " length. NOTE: this code assumes if any value open, high," - " low, close is missing (*-1*) they all must be missing.") - raise ValueError(msg) - - -def plot_day_summary2_ochl(ax, opens, closes, highs, lows, ticksize=4, - colorup='k', colordown='r'): - """Represent the time, open, close, high, low, as a vertical line - ranging from low to high. The left tick is the open and the right - tick is the close. - - Parameters - ---------- - ax : `Axes` - an Axes instance to plot to - opens : sequence - sequence of opening values - closes : sequence - sequence of closing values - highs : sequence - sequence of high values - lows : sequence - sequence of low values - ticksize : int - size of open and close ticks in points - colorup : color - the color of the lines where close >= open - colordown : color - the color of the lines where close < open - - Returns - ------- - ret : list - a list of lines added to the axes - """ - - return plot_day_summary2_ohlc(ax, opens, highs, lows, closes, ticksize, - colorup, colordown) - - -def plot_day_summary2_ohlc(ax, opens, highs, lows, closes, ticksize=4, - colorup='k', colordown='r'): - """Represent the time, open, high, low, close as a vertical line - ranging from low to high. The left tick is the open and the right - tick is the close. - *opens*, *highs*, *lows* and *closes* must have the same length. - NOTE: this code assumes if any value open, high, low, close is - missing (*-1*) they all are missing - - Parameters - ---------- - ax : `Axes` - an Axes instance to plot to - opens : sequence - sequence of opening values - highs : sequence - sequence of high values - lows : sequence - sequence of low values - closes : sequence - sequence of closing values - ticksize : int - size of open and close ticks in points - colorup : color - the color of the lines where close >= open - colordown : color - the color of the lines where close < open - - Returns - ------- - ret : list - a list of lines added to the axes - """ - - _check_input(opens, highs, lows, closes) - - rangeSegments = [((i, low), (i, high)) for i, low, high in - zip(xrange(len(lows)), lows, highs) if low != -1] - - # the ticks will be from ticksize to 0 in points at the origin and - # we'll translate these to the i, close location - openSegments = [((-ticksize, 0), (0, 0))] - - # the ticks will be from 0 to ticksize in points at the origin and - # we'll translate these to the i, close location - closeSegments = [((0, 0), (ticksize, 0))] - - offsetsOpen = [(i, open) for i, open in - zip(xrange(len(opens)), opens) if open != -1] - - offsetsClose = [(i, close) for i, close in - zip(xrange(len(closes)), closes) if close != -1] - - scale = ax.figure.dpi * (1.0 / 72.0) - - tickTransform = Affine2D().scale(scale, 0.0) - - colorup = mcolors.to_rgba(colorup) - colordown = mcolors.to_rgba(colordown) - colord = {True: colorup, False: colordown} - colors = [colord[open < close] for open, close in - zip(opens, closes) if open != -1 and close != -1] - - useAA = 0, # use tuple here - lw = 1, # and here - rangeCollection = LineCollection(rangeSegments, - colors=colors, - linewidths=lw, - antialiaseds=useAA, - ) - - openCollection = LineCollection(openSegments, - colors=colors, - antialiaseds=useAA, - linewidths=lw, - offsets=offsetsOpen, - transOffset=ax.transData, - ) - openCollection.set_transform(tickTransform) - - closeCollection = LineCollection(closeSegments, - colors=colors, - antialiaseds=useAA, - linewidths=lw, - offsets=offsetsClose, - transOffset=ax.transData, - ) - closeCollection.set_transform(tickTransform) - - minpy, maxx = (0, len(rangeSegments)) - miny = min([low for low in lows if low != -1]) - maxy = max([high for high in highs if high != -1]) - corners = (minpy, miny), (maxx, maxy) - ax.update_datalim(corners) - ax.autoscale_view() - - # add these last - ax.add_collection(rangeCollection) - ax.add_collection(openCollection) - ax.add_collection(closeCollection) - return rangeCollection, openCollection, closeCollection - - -def candlestick2_ochl(ax, opens, closes, highs, lows, width=4, - colorup='k', colordown='r', - alpha=0.75): - """Represent the open, close as a bar line and high low range as a - vertical line. - - Preserves the original argument order. - - - Parameters - ---------- - ax : `Axes` - an Axes instance to plot to - opens : sequence - sequence of opening values - closes : sequence - sequence of closing values - highs : sequence - sequence of high values - lows : sequence - sequence of low values - width : int - size of open and close ticks in points - colorup : color - the color of the lines where close >= open - colordown : color - the color of the lines where close < open - alpha : float - bar transparency - - Returns - ------- - ret : tuple - (lineCollection, barCollection) - """ - - return candlestick2_ohlc(ax, opens, highs, lows, closes, width=width, - colorup=colorup, colordown=colordown, - alpha=alpha) - - -def candlestick2_ohlc(ax, opens, highs, lows, closes, width=4, - colorup='k', colordown='r', - alpha=0.75): - """Represent the open, close as a bar line and high low range as a - vertical line. - - NOTE: this code assumes if any value open, low, high, close is - missing they all are missing - - - Parameters - ---------- - ax : `Axes` - an Axes instance to plot to - opens : sequence - sequence of opening values - highs : sequence - sequence of high values - lows : sequence - sequence of low values - closes : sequence - sequence of closing values - width : float - size of open and close ticks in points - colorup : color - the color of the lines where close >= open - colordown : color - the color of the lines where close < open - alpha : float - bar transparency - - Returns - ------- - ret : tuple - (lineCollection, barCollection) - """ - - _check_input(opens, highs, lows, closes) - - delta = width / 2. - barVerts = [((i - delta, open), - (i - delta, close), - (i + delta, close), - (i + delta, open)) - for i, open, close in zip(xrange(len(opens)), opens, closes) - if open != -1 and close != -1] - - rangeSegments = [((i, low), (i, high)) - for i, low, high in zip(xrange(len(lows)), lows, highs) - if low != -1] - - colorup = mcolors.to_rgba(colorup, alpha) - colordown = mcolors.to_rgba(colordown, alpha) - colord = {True: colorup, False: colordown} - colors = [colord[open < close] - for open, close in zip(opens, closes) - if open != -1 and close != -1] - - useAA = 0, # use tuple here - lw = 0.5, # and here - rangeCollection = LineCollection(rangeSegments, - colors=colors, - linewidths=lw, - antialiaseds=useAA, - ) - - barCollection = PolyCollection(barVerts, - facecolors=colors, - edgecolors=colors, - antialiaseds=useAA, - linewidths=lw, - ) - - minx, maxx = 0, len(rangeSegments) - 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) - ax.update_datalim(corners) - ax.autoscale_view() - - # add these last - ax.add_collection(rangeCollection) - ax.add_collection(barCollection) - return rangeCollection, barCollection - - -def volume_overlay(ax, opens, closes, volumes, - colorup='k', colordown='r', - width=4, alpha=1.0): - """Add a volume overlay to the current axes. The opens and closes - are used to determine the color of the bar. -1 is missing. If a - value is missing on one it must be missing on all - - Parameters - ---------- - ax : `Axes` - an Axes instance to plot to - opens : sequence - a sequence of opens - closes : sequence - a sequence of closes - volumes : sequence - a sequence of volumes - width : int - the bar width in points - colorup : color - the color of the lines where close >= open - colordown : color - the color of the lines where close < open - alpha : float - bar transparency - - Returns - ------- - ret : `barCollection` - The `barrCollection` added to the axes - - """ - - colorup = mcolors.to_rgba(colorup, alpha) - colordown = mcolors.to_rgba(colordown, alpha) - colord = {True: colorup, False: colordown} - colors = [colord[open < close] - for open, close in zip(opens, closes) - if open != -1 and close != -1] - - delta = width / 2. - bars = [((i - delta, 0), (i - delta, v), (i + delta, v), (i + delta, 0)) - for i, v in enumerate(volumes) - if v != -1] - - barCollection = PolyCollection(bars, - facecolors=colors, - edgecolors=((0, 0, 0, 1), ), - antialiaseds=(0,), - linewidths=(0.5,), - ) - - ax.add_collection(barCollection) - corners = (0, 0), (len(bars), max(volumes)) - ax.update_datalim(corners) - ax.autoscale_view() - - # add these last - return barCollection - - -def volume_overlay2(ax, closes, volumes, - colorup='k', colordown='r', - width=4, alpha=1.0): - """ - Add a volume overlay to the current axes. The closes are used to - determine the color of the bar. -1 is missing. If a value is - missing on one it must be missing on all - - nb: first point is not displayed - it is used only for choosing the - right color - - - Parameters - ---------- - ax : `Axes` - an Axes instance to plot to - closes : sequence - a sequence of closes - volumes : sequence - a sequence of volumes - width : int - the bar width in points - colorup : color - the color of the lines where close >= open - colordown : color - the color of the lines where close < open - alpha : float - bar transparency - - Returns - ------- - ret : `barCollection` - The `barrCollection` added to the axes - - """ - - return volume_overlay(ax, closes[:-1], closes[1:], volumes[1:], - colorup, colordown, width, alpha) - - -def volume_overlay3(ax, quotes, - colorup='k', colordown='r', - width=4, alpha=1.0): - """Add a volume overlay to the current axes. quotes is a list of (d, - open, high, low, close, volume) and close-open is used to - determine the color of the bar - - Parameters - ---------- - ax : `Axes` - an Axes instance to plot to - quotes : sequence of (time, open, high, low, close, ...) sequences - data to plot. time must be in float date format - see date2num - width : int - the bar width in points - colorup : color - the color of the lines where close1 >= close0 - colordown : color - the color of the lines where close1 < close0 - alpha : float - bar transparency - - Returns - ------- - ret : `barCollection` - The `barrCollection` added to the axes - - - """ - - colorup = mcolors.to_rgba(colorup, alpha) - colordown = mcolors.to_rgba(colordown, alpha) - colord = {True: colorup, False: colordown} - - dates, opens, highs, lows, closes, volumes = list(zip(*quotes)) - colors = [colord[close1 >= close0] - for close0, close1 in zip(closes[:-1], closes[1:]) - if close0 != -1 and close1 != -1] - colors.insert(0, colord[closes[0] >= opens[0]]) - - right = width / 2.0 - left = -width / 2.0 - - bars = [((left, 0), (left, volume), (right, volume), (right, 0)) - for d, open, high, low, close, volume in quotes] - - sx = ax.figure.dpi * (1.0 / 72.0) # scale for points - sy = ax.bbox.height / ax.viewLim.height - - barTransform = Affine2D().scale(sx, sy) - - dates = [d for d, open, high, low, close, volume in quotes] - offsetsBars = [(d, 0) for d in dates] - - useAA = 0, # use tuple here - lw = 0.5, # and here - barCollection = PolyCollection(bars, - facecolors=colors, - edgecolors=((0, 0, 0, 1),), - antialiaseds=useAA, - linewidths=lw, - offsets=offsetsBars, - transOffset=ax.transData, - ) - barCollection.set_transform(barTransform) - - minpy, maxx = (min(dates), max(dates)) - miny = 0 - maxy = max([volume for d, open, high, low, close, volume in quotes]) - corners = (minpy, miny), (maxx, maxy) - ax.update_datalim(corners) - # print 'datalim', ax.dataLim.bounds - # print 'viewlim', ax.viewLim.bounds - - ax.add_collection(barCollection) - ax.autoscale_view() - - return barCollection - - -def index_bar(ax, vals, - facecolor='b', edgecolor='l', - width=4, alpha=1.0, ): - """Add a bar collection graph with height vals (-1 is missing). - - Parameters - ---------- - ax : `Axes` - an Axes instance to plot to - vals : sequence - a sequence of values - facecolor : color - the color of the bar face - edgecolor : color - the color of the bar edges - width : int - the bar width in points - alpha : float - bar transparency - - Returns - ------- - ret : `barCollection` - The `barrCollection` added to the axes - - """ - - facecolors = (mcolors.to_rgba(facecolor, alpha),) - edgecolors = (mcolors.to_rgba(edgecolor, alpha),) - - right = width / 2.0 - left = -width / 2.0 - - bars = [((left, 0), (left, v), (right, v), (right, 0)) - for v in vals if v != -1] - - sx = ax.figure.dpi * (1.0 / 72.0) # scale for points - sy = ax.bbox.height / ax.viewLim.height - - barTransform = Affine2D().scale(sx, sy) - - offsetsBars = [(i, 0) for i, v in enumerate(vals) if v != -1] - - barCollection = PolyCollection(bars, - facecolors=facecolors, - edgecolors=edgecolors, - antialiaseds=(0,), - linewidths=(0.5,), - offsets=offsetsBars, - transOffset=ax.transData, - ) - barCollection.set_transform(barTransform) - - minpy, maxx = (0, len(offsetsBars)) - miny = 0 - maxy = max([v for v in vals if v != -1]) - corners = (minpy, miny), (maxx, maxy) - ax.update_datalim(corners) - ax.autoscale_view() - - # add these last - ax.add_collection(barCollection) - return barCollection diff --git a/build/lib/mplfinance/plotting.py b/build/lib/mplfinance/plotting.py deleted file mode 100644 index 073864a0..00000000 --- a/build/lib/mplfinance/plotting.py +++ /dev/null @@ -1,1307 +0,0 @@ -import matplotlib.dates as mdates -import matplotlib.pyplot as plt -import matplotlib.colors as mcolors -import matplotlib.axes as mpl_axes -import matplotlib.figure as mpl_fig -import pandas as pd -import numpy as np -import copy -import io -import os -import math -import warnings -import statistics as stat - -from itertools import cycle -#from pandas.plotting import register_matplotlib_converters -#register_matplotlib_converters() - -from mplfinance._utils import _construct_aline_collections -from mplfinance._utils import _construct_hline_collections -from mplfinance._utils import _construct_vline_collections -from mplfinance._utils import _construct_tline_collections -from mplfinance._utils import _construct_mpf_collections - -from mplfinance._widths import _determine_width_config - -from mplfinance._utils import _updown_colors -from mplfinance._utils import IntegerIndexDateTimeFormatter -from mplfinance._utils import _mscatter -from mplfinance._utils import _check_and_convert_xlim_configuration - -from mplfinance import _styles - -from mplfinance._arg_validators import _check_and_prepare_data, _mav_validator -from mplfinance._arg_validators import _get_valid_plot_types, _fill_between_validator -from mplfinance._arg_validators import _process_kwargs, _validate_vkwargs_dict -from mplfinance._arg_validators import _kwarg_not_implemented, _bypass_kwarg_validation -from mplfinance._arg_validators import _hlines_validator, _vlines_validator -from mplfinance._arg_validators import _alines_validator, _tlines_validator -from mplfinance._arg_validators import _scale_padding_validator, _yscale_validator -from mplfinance._arg_validators import _valid_panel_id, _check_for_external_axes -from mplfinance._arg_validators import _xlim_validator, _mco_validator, _is_marketcolor_object - -from mplfinance._panels import _build_panels -from mplfinance._panels import _set_ticks_on_bottom_panel_only - -from mplfinance._helpers import _determine_format_string -from mplfinance._helpers import _list_of_dict -from mplfinance._helpers import _num_or_seq_of_num -from mplfinance._helpers import _adjust_color_brightness - -VALID_PMOVE_TYPES = ['renko', 'pnf'] - -DEFAULT_FIGRATIO = (8.00,5.75) - -def with_rc_context(func): - ''' - This decoractor creates an rcParams context around a function, so that any changes - the function makes to rcParams will be reversed when the decorated function returns - (therefore those changes have no effect outside of the decorated function). - ''' - def decorator(*args, **kwargs): - with plt.rc_context(): - return func(*args, **kwargs) - return decorator - -def _warn_no_xgaps_deprecated(value): - warnings.warn('\n\n ================================================================= '+ - '\n\n WARNING: `no_xgaps` is /deprecated/:'+ - '\n Default value is now `no_xgaps=True`'+ - '\n However, to set `no_xgaps=False` and silence this warning,'+ - '\n use instead: `show_nontrading=True`.'+ - '\n\n ================================================================ ', - category=DeprecationWarning) - return isinstance(value,bool) - -def _warn_set_ylim_deprecated(value): - warnings.warn('\n\n ================================================================= '+ - '\n\n WARNING: `set_ylim=(ymin,ymax)` kwarg '+ - '\n has been replaced with: '+ - '\n `ylim=(ymin,ymax)`.'+ - '\n\n ================================================================ ', - category=DeprecationWarning) - return isinstance(value,bool) - - -def _valid_plot_kwargs(): - ''' - Construct and return the "valid kwargs table" for the mplfinance.plot() function. - A valid kwargs table is a `dict` of `dict`s. The keys of the outer dict are the - valid key-words for the function. The value for each key is a dict containing - 2 specific keys: "Default", and "Validator" with the following values: - "Default" - The default value for the kwarg if none is specified. - "Validator" - A function that takes the caller specified value for the kwarg, - and validates that it is the correct type, and (for kwargs with - a limited set of allowed values) may also validate that the - kwarg value is one of the allowed values. - ''' - - vkwargs = { - 'columns' : { 'Default' : None, # use default names: ('Open', 'High', 'Low', 'Close', 'Volume') - 'Description' : ('Column names to be used when plotting the data.'+ - ' Default: ("Open", "High", "Low", "Close", "Volume")'), - 'Validator' : lambda value: isinstance(value, (tuple, list)) - and len(value) == 5 - and all(isinstance(c, str) for c in value) }, - 'type' : { 'Default' : 'ohlc', - 'Description' : 'Plot type: '+str(_get_valid_plot_types()), - 'Validator' : lambda value: value in _get_valid_plot_types() }, - - 'style' : { 'Default' : None, - 'Description' : 'plot style; see `mpf.available_styles()`', - 'Validator' : _styles._valid_mpf_style }, - - 'volume' : { 'Default' : False, - 'Description' : 'Plot volume: True, False, or set to Axes object on which to plot.', - 'Validator' : lambda value: isinstance(value,bool) or isinstance(value,mpl_axes.Axes) }, - - 'mav' : { 'Default' : None, - 'Description' : 'Moving Average window size(s); (int or tuple of ints)', - 'Validator' : _mav_validator }, - - 'renko_params' : { 'Default' : dict(), - 'Description' : 'dict of renko parameters; call `mpf.kwarg_help("renko_params")`', - 'Validator' : lambda value: isinstance(value,dict) }, - - 'pnf_params' : { 'Default' : dict(), - 'Description' : 'dict of point-and-figure parameters; call `mpf.kwarg_help("pnf_params")`', - 'Validator' : lambda value: isinstance(value,dict) }, - - 'study' : { 'Default' : None, - 'Description' : 'kwarg not implemented', - 'Validator' : lambda value: _kwarg_not_implemented(value) }, - - 'marketcolor_overrides' : { 'Default' : None, - 'Description' : 'sequence of color objects to override market colors.'+ - 'sequence must be same length as ohlc(v) DataFrame. Each'+ - 'color object may be a color, marketcolor object, or None.', - 'Validator' : _mco_validator }, - - 'mco_faceonly' : { 'Default' : False, # If True: Override only the face of the candle - 'Description' : 'True/False marketcolor_overrides only apply to face of candle.', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'no_xgaps' : { 'Default' : True, # None means follow default logic below: - 'Description' : 'deprecated', - 'Validator' : lambda value: _warn_no_xgaps_deprecated(value) }, - - 'show_nontrading' : { 'Default' : False, - 'Description' : 'True/False show spaces for non-trading days/periods', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'figscale' : { 'Default' : None, # scale base figure size up or down. - 'Description' : 'Scale figure size up (if > 1) or down (if < 1)', - 'Validator' : lambda value: isinstance(value,float) or isinstance(value,int) }, - - 'figratio' : { 'Default' : None, # aspect ratio; scaled to 8.0 height - 'Description' : 'Aspect ratio of the figure. Default: (8.00,5.75)', - 'Validator' : lambda value: isinstance(value,(tuple,list)) - and len(value) == 2 - and isinstance(value[0],(float,int)) - and isinstance(value[1],(float,int)) }, - - 'figsize' : { 'Default' : None, # figure size; overrides figratio and figscale - 'Description' : ('Figure size: overrides both figscale and figratio,'+ - ' else defaults to figratio*figscale'), - 'Validator' : lambda value: isinstance(value,(tuple,list)) - and len(value) == 2 - and isinstance(value[0],(float,int)) - and isinstance(value[1],(float,int)) }, - - 'fontscale' : { 'Default' : None, # scale all fonts up or down - 'Description' : 'Scale font sizes up (if > 1) or down (if < 1)', - 'Validator' : lambda value: isinstance(value,float) or isinstance(value,int) }, - - 'linecolor' : { 'Default' : None, # line color in line plot - 'Description' : 'Line color for `type=line`', - 'Validator' : lambda value: mcolors.is_color_like(value) }, - - 'title' : { 'Default' : None, # Figure Title - 'Description' : 'Figure Title (see also `axtitle`)', - 'Validator' : lambda value: isinstance(value,(str,dict)) }, - - 'axtitle' : { 'Default' : None, # Axes Title (subplot title) - 'Description' : 'Axes Title (subplot title)', - 'Validator' : lambda value: isinstance(value,(str,dict)) }, - - 'xlabel' : { 'Default' : 'Date', # x-axis label - 'Description' : 'label for x-axis of main plot', - 'Validator' : lambda value: isinstance(value,str) }, - - 'ylabel' : { 'Default' : 'Price', # y-axis label - 'Description' : 'label for y-axis of main plot', - 'Validator' : lambda value: isinstance(value,str) }, - - 'ylabel_lower' : { 'Default' : None, # y-axis label default logic below - 'Description' : 'label for y-axis of volume', - 'Validator' : lambda value: isinstance(value,str) }, - - 'addplot' : { 'Default' : None, - 'Description' : 'addplot object or sequence of addplot objects (from `mpf.make_addplot()`)', - 'Validator' : lambda value: isinstance(value,dict) or (isinstance(value,list) and all([isinstance(d,dict) for d in value])) }, - - 'savefig' : { 'Default' : None, - 'Description' : 'file name, or BytesIO, or dict with key `fname` plus other keys allowed as '+ - ' kwargs to matplotlib `Figure.savefig()`', - 'Validator' : lambda value: isinstance(value,dict) or isinstance(value,str) or isinstance(value, io.BytesIO) or isinstance(value, os.PathLike) }, - - 'block' : { 'Default' : None, - 'Description' : 'True/False wait for figure to be closed before returning', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'returnfig' : { 'Default' : False, - 'Description' : 'return Figure and list of Axes objects created by mplfinance;'+ - ' user must display plot when ready, usually by calling `mpf.show()`', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'return_calculated_values' : { 'Default' : None, - 'Description' : 'set to a variable containing an empty dict; `mpf.plot()` will fill'+ - ' the dict with various mplfinance calculated values', - 'Validator' : lambda value: isinstance(value, dict) and len(value) == 0}, - - 'set_ylim' : { 'Default' : None, - 'Description' : 'deprecated', - 'Validator' : lambda value: _warn_set_ylim_deprecated(value) }, - - 'ylim' : { 'Default' : None, - 'Description' : 'Limits for y-axis as tuple (min,max), i.e. (bottom,top)', - 'Validator' : lambda value: isinstance(value, (list,tuple)) and len(value) == 2 - and all([isinstance(v,(int,float)) for v in value])}, - - 'xlim' : { 'Default' : None, - 'Description' : 'Limits for x-axis as tuple (min,max), i.e. (left,right)', - 'Validator' : lambda value: _xlim_validator(value) }, - - 'set_ylim_panelB' : { 'Default' : None, - 'Description' : 'deprecated', - 'Validator' : lambda value: _warn_set_ylim_deprecated(value) }, - - 'hlines' : { 'Default' : None, - 'Description' : 'Draw one or more HORIZONTAL LINES across entire plot, by'+ - ' specifying a price, or sequence of prices. May also be a dict'+ - ' with key `hlines` specifying a price or sequence of prices, plus'+ - ' one or more of the following keys: `colors`, `linestyle`,'+ - ' `linewidths`, `alpha`.', - 'Validator' : lambda value: _hlines_validator(value) }, - - 'vlines' : { 'Default' : None, - 'Description' : 'Draw one or more VERTICAL LINES across entire plot, by'+ - ' specifying a date[time], or sequence of date[time]. May also'+ - ' be a dict with key `vlines` specifying a date[time] or sequence'+ - ' of date[time], plus one or more of the following keys:'+ - ' `colors`, `linestyle`, `linewidths`, `alpha`.', - 'Validator' : lambda value: _vlines_validator(value) }, - - 'alines' : { 'Default' : None, - 'Description' : 'Draw one or more ARBITRARY LINES anywhere on the plot, by'+ - ' specifying a sequence of two or more date/price pairs, or by'+ - ' specifying a sequence of sequences of two or more date/price pairs.'+ - ' May also be a dict with key `alines` (as date/price pairs described above),'+ - ' plus one or more of the following keys:'+ - ' `colors`, `linestyle`, `linewidths`, `alpha`.', - 'Validator' : lambda value: _alines_validator(value) }, - - 'tlines' : { 'Default' : None, - 'Description' : 'Draw one or more TREND LINES by specifying one or more pairs of date[times]'+ - ' between which each trend line should be drawn. May also be a dict with key'+ - ' `tlines` as just described, plus one or more of the following keys:'+ - ' `colors`, `linestyle`, `linewidths`, `alpha`, `tline_use`,`tline_method`.', - 'Validator' : lambda value: _tlines_validator(value) }, - - 'panel_ratios' : { 'Default' : None, - 'Description' : 'sequence of numbers indicating relative sizes of panels; sequence len'+ - ' must be same as number of panels, or len 2 where first entry is for'+ - ' main panel, and second entry is for all other panels', - 'Validator' : lambda value: isinstance(value,(tuple,list)) and len(value) <= 32 and - all([isinstance(v,(int,float)) for v in value]) }, - - 'main_panel' : { 'Default' : 0, - 'Description' : 'integer - which panel is the main panel for `.plot()`', - 'Validator' : lambda value: _valid_panel_id(value) }, - - 'volume_panel' : { 'Default' : 1, - 'Description' : 'integer - which panel is the volume panel', - 'Validator' : lambda value: _valid_panel_id(value) }, - - 'num_panels' : { 'Default' : None, - 'Description' : 'total number of panels', - 'Validator' : lambda value: isinstance(value,int) and value in range(1,32+1) }, - - 'datetime_format' : { 'Default' : None, - 'Description' : 'x-axis tick format as valid `strftime()` format string', - 'Validator' : lambda value: isinstance(value,str) }, - - 'xrotation' : { 'Default' : 45, - 'Description' : 'Angle (degrees) for x-axis tick labels; 90=vertical', - 'Validator' : lambda value: isinstance(value,(int,float)) }, - - 'axisoff' : { 'Default' : False, - 'Description' : '`axisoff=True` means do NOT display any axis.', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'closefig' : { 'Default' : 'auto', - 'Description' : 'True|False close the Figure before returning', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'fill_between' : { 'Default' : None, - 'Description' : 'fill between specification as y-value, or sequence of'+ - ' y-values, or dict containing key "y1" plus any additional'+ - ' kwargs for `fill_between()`', - 'Validator' : _fill_between_validator }, - - 'tight_layout' : { 'Default' : False, - 'Description' : 'True|False implement tight layout (minimal padding around Figure)'+ - ' (see also `scale_padding` kwarg)', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'scale_padding' : { 'Default' : 1.0, # Issue#193 - 'Description' : 'Increase, > 1.0, or decrease, < 1.0, padding around figure.'+ - ' May also be a dict containing one or more of the following keys:'+ - ' "top", "bottom", "left", "right", to individual scale padding'+ - ' on each side of Figure.', - 'Validator' : lambda value: _scale_padding_validator(value) }, - - 'width_adjuster_version' : { 'Default' : 'v1', - 'Description' : 'specify version of object width adjustment algorithm: "v0" or "v1"'+ - ' (See also "widths" tutorial in mplfinance examples folder).', - 'Validator' : lambda value: value in ('v0', 'v1') }, - - 'scale_width_adjustment' : { 'Default' : None, - 'Description' : 'scale width of plot objects wider, > 1.0, or narrower, < 1.0'+ - ' may also be a dict to scale individual widths.'+ - ' (See also "widths" tutorial in mplfinance examples folder).', - 'Validator' : lambda value: isinstance(value,dict) and len(value) > 0 }, - - 'update_width_config' : { 'Default' : None, - 'Description' : 'dict - update individual items in width configuration.'+ - ' (See also "widths" tutorial in mplfinance examples folder).', - 'Validator' : lambda value: isinstance(value,dict) and len(value) > 0 }, - - 'return_width_config' : { 'Default' : None, - 'Description' : 'empty dict variable to be filled with width configuration settings.', - 'Validator' : lambda value: isinstance(value,dict) and len(value)==0 }, - - 'saxbelow' : { 'Default' : True, # Issue#115 Comment#639446764 - 'Description' : 'set the volume Axes below (behind) all other Axes objects', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'ax' : { 'Default' : None, - 'Description' : 'Matplotlib Axes object on which to plot', - 'Validator' : lambda value: isinstance(value,mpl_axes.Axes) }, - - 'volume_exponent' : { 'Default' : None, - 'Description' : 'integer exponent on the volume axis'+ - ' (or set to "legacy" for old mplfinance style)', - 'Validator' : lambda value: isinstance(value,int) or value == 'legacy'}, - - 'tz_localize' : { 'Default' : True, - 'Description' : 'True|False localize the times in the DatetimeIndex', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'yscale' : { 'Default' : None, - 'Description' : 'y-axis scale: "linear", "log", "symlog", or "logit"', - 'Validator' : lambda value: _yscale_validator(value) }, - - 'volume_yscale' : { 'Default' : None, - 'Description' : 'Volume y-axis scale: "linear", "log", "symlog", or "logit"', - 'Validator' : lambda value: _yscale_validator(value) }, - - 'volume_ylim' : { 'Default' : None, - 'Description' : 'Volume y-axis limits as tuple (min,max), i.e. (bottom,top)', - 'Validator' : lambda value: isinstance(value, (list,tuple)) and len(value) == 2 - and all([isinstance(v,(int,float)) for v in value])}, - - 'volume_alpha' : { 'Default' : 1, # alpha of Volume bars - 'Description' : 'opacity for Volume bar: 0.0 (transparent) to 1.0 (opaque)', - 'Validator' : lambda value: isinstance(value,(int,float)) or - all([isinstance(v,(int,float)) for v in value]) }, - - 'warn_too_much_data' : { 'Default' : 599, - 'Description' : 'Tolerance for data amount in plot. Default=599 rows.'+ - ' Values greater than \'warn_too_much_data\' will trigger a warning.', - 'Validator' : lambda value: isinstance(value,int) }, - } - - _validate_vkwargs_dict(vkwargs) - - return vkwargs - -###@with_rc_context -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) - - config['xlim'] = _check_and_convert_xlim_configuration(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) - - if config['marketcolor_overrides'] is not None: - if len(config['marketcolor_overrides']) != len(dates): - raise ValueError('`marketcolor_overrides` must be same length as dataframe.') - - 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?') - - if not external_axes_mode: - fig = plt.figure() - _adjust_figsize(fig,config) - else: - fig = None - - _adjust_fontsize(config) - - if config['volume'] and volumes is None: - raise ValueError('Request for volume, but NO volume data.') - - if external_axes_mode: - panels = None - axA1 = config['ax'] - axA1.set_axisbelow(config['saxbelow']) - if config['volume']: - volumeAxes = config['volume'] - volumeAxes.set_axisbelow(config['saxbelow']) - else: - panels = _build_panels(fig, config) - axA1 = panels.at[config['main_panel'],'axes'][0] - if config['volume']: - if config['volume_panel'] == config['main_panel']: - # ohlc and volume on same panel: move volume to secondary axes: - volumeAxes = panels.at[config['volume_panel'],'axes'][1] - volumeAxes.set_zorder(axA1.get_zorder()-0.1) # Make sure ohlc is above volume - axA1.patch.set_visible(False) # Let volume show through - panels.at[config['main_panel'],'used2nd'] = True - else: - volumeAxes = panels.at[config['volume_panel'],'axes'][0] - else: - volumeAxes = 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)) - - # 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, calculated_values = collections - volumes = calculated_values['volumes'] - pmove_dates = calculated_values['dates'] - pmove_values = calculated_values['values'] - if all([isinstance(v,(list,tuple)) for v in pmove_values]): - pmove_avgvals = [sum(v)/len(v) for v in pmove_values] - else: - pmove_avgvals = pmove_values - pmove_size = calculated_values['size'] - pmove_counts = calculated_values['counts'] if 'counts' in calculated_values else None - formatter = IntegerIndexDateTimeFormatter(pmove_dates, fmtstring) - xdates = np.arange(len(pmove_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,pmove_avgvals) - 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 = pmove_avgvals - _highs = [value+pmove_size for value in pmove_avgvals] - - 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) - if miny > 0.0: - # don't let it go negative: - setminy = max(0.9*miny,miny-ydelta) - else: - setminy = miny-ydelta - axA1.set_ylim(setminy,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 == 'renko': - retdict['renko_bricks' ] = pmove_values - retdict['renko_dates' ] = mdates.num2date(pmove_dates) - retdict['renko_size' ] = pmove_size - retdict['renko_volumes'] = volumes if config['volume'] else None - elif ptype == 'pnf': - retdict['pnf_dates' ] = mdates.num2date(pmove_dates) - retdict['pnf_counts' ] = pmove_counts - retdict['pnf_values' ] = pmove_values - retdict['pnf_avgvals' ] = pmove_avgvals - retdict['pnf_size' ] = pmove_size - retdict['pnf_volumes' ] = volumes if config['volume'] else None - 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(pmove_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) - valp = config['volume_alpha'] - volumeAxes.bar(xdates,volumes,width=w,linewidth=lw,color=vcolors,ec=adjc,alpha=valp) - if config['volume_ylim'] is not None: - vymin = config['volume_ylim'][0] - vymax = config['volume_ylim'][1] - else: - 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) - - ysd = config['yscale'] - if isinstance(ysd,dict): - yscale = ysd['yscale'] - del ysd['yscale'] - axA1.set_yscale(yscale,**ysd) - elif isinstance(ysd,str): - axA1.set_yscale(ysd) - - - 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,xdates) - 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,xdates) - - # 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: - fblist = copy.deepcopy(config['fill_between']) - if _num_or_seq_of_num(fblist): - fblist = [dict(y1=fblist),] - elif isinstance(fblist,dict): - fblist = [fblist,] - if not _list_of_dict(fblist): - raise TypeError('Bad type for `fill_between` specifier.') - for fb in fblist: - if 'x' in fb: - raise ValueError('fill_between dict may not contain `x`') - panid = config['main_panel'] - if 'panel' in fb: - panid = fb['panel'] - del fb['panel'] - fb['x'] = xdates # add 'x' to caller's fb dict - 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']) - axA1.set_xlabel(config['xlabel']) - - if config['volume']: - if external_axes_mode: - volumeAxes.tick_params(axis='x',rotation=xrotation) - volumeAxes.xaxis.set_major_formatter(formatter) - - vscale = 'linear' - ysd = config['volume_yscale'] - if isinstance(ysd,dict): - yscale = ysd['yscale'] - del ysd['yscale'] - volumeAxes.set_yscale(yscale,**ysd) - vscale = yscale - elif isinstance(ysd,str): - volumeAxes.set_yscale(ysd) - vscale = ysd - offset = '' - if vscale == 'linear': - 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: `y=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(va='bottom', y=0.89) - else: - title_kwargs = dict(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: - fig.savefig(**save,bbox_inches='tight') - else: - fig.savefig(**save) - else: - if config['tight_layout']: - fig.savefig(save,bbox_inches='tight') - else: - fig.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) - - # rcp = copy.deepcopy(plt.rcParams) - # rcpdf = rcParams_to_df(rcp) - # print('type(rcpdf)=',type(rcpdf)) - # print('rcpdfhead(3)=',rcpdf.head(3)) - # return # rcpdf - -def _adjust_figsize(fig,config): - if fig is None: - return - 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.set_size_inches(fsize) - -def _adjust_fontsize(config): - if config['fontscale'] is None: - return - if not isinstance(plt.rcParams['font.size'],(float,int)): - warnings.warn('\n\n ================================================================= '+ - '\n\n WARNING: Unable to scale fonts: plt.rcParams["font.size"] is NOT a float!'+ - '\n\n ================================================================ ', - category=UserWarning) - return - plt.rcParams['font.size'] *= config['fontscale'] - # -------------------------------------------- - # From: matplotlib.font_manager.font_scalings: - # font_scalings = { - # 'xx-small': 0.579, - # 'x-small': 0.694, - # 'small': 0.833, - # 'medium': 1.0, - # 'large': 1.200, - # 'x-large': 1.440, - # 'xx-large': 1.728, - # 'larger': 1.2, - # 'smaller': 0.833, - # None: 1.0, - # } - # -------------------------------------------- - fontstuff = ['axes.labelsize','axes.titlesize', 'figure.titlesize','legend.fontsize', - 'legend.title_fontsize','xtick.labelsize','ytick.labelsize'] - for item in fontstuff: - if isinstance(plt.rcParams[item],(float,int)): - plt.rcParams[item] *= config['fontscale'] - -def _addplot_collections(panid,panels,apdict,xdates,config): - - apdata = apdict['data'] - aptype = apdict['type'] - external_axes_mode = apdict['ax'] is not None - - #--------------------------------------------------------------# - # Note: _auto_secondary_y() sets the 'magnitude' column in the - # `panels` dataframe, which is needed for automatically - # determining if secondary_y is needed. Therefore we call - # _auto_secondary_y() for *all* addplots, even those that - # are set to True or False (not 'auto') for secondary_y - # because their magnitudes may be needed if *any* apdicts - # contain secondary_y='auto'. - # In theory we could first loop through all apdicts to see - # if any have secondary_y='auto', but since that is the - # default value, we will just assume we have at least one. - - valid_apc_types = ['ohlc','candle'] - if aptype not in valid_apc_types: - raise TypeError('Invalid aptype='+str(aptype)+'. Must be one of '+str(valid_apc_types)) - if not isinstance(apdata,pd.DataFrame): - raise TypeError('addplot type "'+aptype+'" MUST be accompanied by addplot data of type `pd.DataFrame`') - d,o,h,l,c,v = _check_and_prepare_data(apdata,config) - - mc = apdict['marketcolors'] - if _is_marketcolor_object(mc): - apstyle = config['style'].copy() - apstyle['marketcolors'] = mc - else: - apstyle = config['style'] - - collections = _construct_mpf_collections(aptype,d,xdates,o,h,l,c,v,config,apstyle) - - if not external_axes_mode: - lo = math.log(max(math.fabs(np.nanmin(l)),1e-7),10) - 0.5 - hi = math.log(max(math.fabs(np.nanmax(h)),1e-7),10) + 0.5 - secondary_y = _auto_secondary_y( panels, panid, lo, hi ) - if 'auto' != apdict['secondary_y']: - secondary_y = apdict['secondary_y'] - if secondary_y: - ax = panels.at[panid,'axes'][1] - panels.at[panid,'used2nd'] = True - else: - ax = panels.at[panid,'axes'][0] - else: - ax = apdict['ax'] - - for coll in collections: - ax.add_collection(coll) - if apdict['mav'] is not None: - apmavprices = _plot_mav(ax,config,xdates,c,apdict['mav']) - ax.autoscale_view() - return ax - -def _addplot_columns(panid,panels,ydata,apdict,xdates,config): - external_axes_mode = apdict['ax'] is not None - if not external_axes_mode: - secondary_y = False - if apdict['secondary_y'] == 'auto': - 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 = _auto_secondary_y( panels, panid, ymlo, ymhi ) - 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] - else: - ax = apdict['ax'] - - aptype = apdict['type'] - if aptype == 'scatter': - size = apdict['markersize'] - mark = apdict['marker'] - color = apdict['color'] - alpha = apdict['alpha'] - edgecolors = apdict['edgecolors'] - linewidths = apdict['linewidths'] - - if isinstance(mark,(list,tuple,np.ndarray)): - _mscatter(xdates, ydata, ax=ax, m=mark, s=size, color=color, alpha=alpha, edgecolors=edgecolors, linewidths=linewidths) - else: - ax.scatter(xdates, ydata, s=size, marker=mark, color=color, alpha=alpha, edgecolors=edgecolors, linewidths=linewidths) - elif aptype == 'bar': - width = 0.8 if apdict['width'] is None else 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'] - width = apdict['width'] if apdict['width'] is not None else 1.6*config['_width_config']['line_width'] - alpha = apdict['alpha'] - ax.plot(xdates,ydata,linestyle=ls,color=color,linewidth=width,alpha=alpha) - elif aptype == 'step': - stepwhere = apdict['stepwhere'] - ls = apdict['linestyle'] - color = apdict['color'] - width = apdict['width'] if apdict['width'] is not None else 1.6*config['_width_config']['line_width'] - alpha = apdict['alpha'] - ax.step(xdates,ydata,where = stepwhere,linestyle=ls,color=color,linewidth=width,alpha=alpha) - else: - raise ValueError('addplot type "'+str(aptype)+'" NOT yet supported.') - - if apdict['mav'] is not None: - apmavprices = _plot_mav(ax,config,xdates,ydata,apdict['mav']) - - return ax - -def _addplot_apply_supplements(ax,apdict,xdates): - if (apdict['ylabel'] is not None): - ax.set_ylabel(apdict['ylabel']) - if (apdict['xlabel'] is not None): - ax.set_xlabel(apdict['xlabel']) - if apdict['ylim'] is not None: - ax.set_ylim(apdict['ylim'][0],apdict['ylim'][1]) - if apdict['title'] is not None: - ax.set_title(apdict['title']) - ysd = apdict['yscale'] - if isinstance(ysd,dict): - yscale = ysd['yscale'] - del ysd['yscale'] - ax.set_yscale(yscale,**ysd) - elif isinstance(ysd,str): - ax.set_yscale(ysd) - # added by Wen - if "fill_between" in apdict and apdict['fill_between'] is not None: - # deep copy because mplfinance code sometimes modifies the fill_between dict - fblist = copy.deepcopy(apdict['fill_between']) - if isinstance(fblist,dict): - fblist = [fblist,] - if _list_of_dict(fblist): - for fb in fblist: - if 'x' in fb: - raise ValueError('fill_between dict may not contain `x`') - fb['x'] = xdates # add 'x' to caller's fb dict - ax.fill_between(**fb) - else: - raise ValueError('Invalid addplot fill_between: must be `dict` or `list of dict`') - -def _set_ylabels_side(ax_pri,ax_sec,primary_on_right): - # put the primary axis on one side, - # and the twinx() on the "other" side: - if primary_on_right == True: - ax_pri.yaxis.set_label_position('right') - ax_pri.yaxis.tick_right() - if ax_sec is not None: - ax_sec.yaxis.set_label_position('left') - ax_sec.yaxis.tick_left() - else: # treat non-True as False, whether False, None, or anything else. - ax_pri.yaxis.set_label_position('left') - ax_pri.yaxis.tick_left() - if ax_sec is not None: - ax_sec.yaxis.set_label_position('right') - ax_sec.yaxis.tick_right() - -def _plot_mav(ax,config,xdates,prices,apmav=None,apwidth=None): - style = config['style'] - if apmav is not None: - mavgs = apmav - else: - mavgs = config['mav'] - mavp_list = [] - if mavgs is not None: - shift = None - if isinstance(mavgs,dict): - shift = mavgs['shift'] - mavgs = mavgs['period'] - 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 idx,mav in enumerate(mavgs): - mean = pd.Series(prices).rolling(mav).mean() - if shift is not None: - mean = mean.shift(periods=shift[idx]) - mavprices = mean.values - lw = config['_width_config']['line_width'] - if mavc: - ax.plot(xdates, mavprices, linewidth=lw, color=next(mavc)) - else: - ax.plot(xdates, mavprices, linewidth=lw) - mavp_list.append(mavprices) - return mavp_list - -def _auto_secondary_y( panels, panid, ylo, yhi ): - # 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. - secondary_y = False - p = panid,'mag' - if panels.at[p] is None: - panels.at[p] = {'lo':ylo,'hi':yhi} - elif ylo < panels.at[p]['lo'] or yhi > 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) - return secondary_y - -def _valid_addplot_kwargs(): - - valid_linestyles = ('-','solid','--','dashed','-.','dashdot','.','dotted',None,' ','') - valid_types = ('line','scatter','bar', 'ohlc', 'candle','step') - valid_stepwheres = ('pre','post','mid') - valid_edgecolors = ('face', 'none', None) - - vkwargs = { - 'scatter' : { 'Default' : False, - 'Description' : "Deprecated. (Use kwarg `type='scatter' instead.", - 'Validator' : lambda value: isinstance(value,bool) }, - - 'type' : { 'Default' : 'line', - 'Description' : 'addplot type: "line","scatter","bar", "ohlc", "candle","step"', - 'Validator' : lambda value: value in valid_types }, - - 'mav' : { 'Default' : None, - 'Description' : 'Moving Average window size(s); (int or tuple of ints)', - 'Validator' : _mav_validator }, - - 'panel' : { 'Default' : 0, - 'Description' : 'Panel (int 0-31) to use for this addplot', - 'Validator' : lambda value: _valid_panel_id(value) }, - - 'marker' : { 'Default' : 'o', - 'Description' : "marker for `type='scatter'` plot", - 'Validator' : lambda value: _bypass_kwarg_validation(value) }, - - 'markersize' : { 'Default' : 18, - 'Description' : 'size of marker for `type="scatter"`; default=18', - 'Validator' : lambda value: isinstance(value,(int,float)) }, - - 'color' : { 'Default' : None, - 'Description' : 'color (or sequence of colors) of line(s), scatter marker(s), or bar(s).', - 'Validator' : lambda value: mcolors.is_color_like(value) or - (isinstance(value,(list,tuple,np.ndarray)) and all([mcolors.is_color_like(v) for v in value])) }, - - 'linestyle' : { 'Default' : None, - 'Description' : 'line style for `type=line` ('+str(valid_linestyles)+')', - 'Validator' : lambda value: value in valid_linestyles }, - - 'linewidths' : { 'Default': None, - 'Description' : 'edge widths of scatter markers', - 'Validator' : lambda value: isinstance(value,(int,float)) }, - - 'edgecolors' : { 'Default': None, - 'Description' : 'edgecolors of scatter markers', - 'Validator': lambda value: mcolors.is_color_like(value) or value in valid_edgecolors}, - - 'width' : { 'Default' : None, # width of `bar` or `line` - 'Description' : 'width of bar or line for `type="bar"` or `type="line"', - 'Validator' : lambda value: isinstance(value,(int,float)) or - all([isinstance(v,(int,float)) for v in value]) }, - - 'bottom' : { 'Default' : 0, # bottom for `type=bar` plots - 'Description' : 'bottom value for `type=bar` bars. Default=0', - 'Validator' : lambda value: isinstance(value,(int,float)) or - all([isinstance(v,(int,float)) for v in value]) }, - 'alpha' : { 'Default' : 1, # alpha of `bar`, `line`, or `scatter` - 'Description' : 'opacity for 0.0 (transparent) to 1.0 (opaque)', - 'Validator' : lambda value: isinstance(value,(int,float)) or - all([isinstance(v,(int,float)) for v in value]) }, - - 'secondary_y' : { 'Default' : 'auto', - 'Description' : "True|False|'auto' place the additional plot data on a"+ - " secondary y-axis. 'auto' compares the magnitude or the"+ - " addplot data, to data already on the axis, and if it appears"+ - " they are of different magnitudes, then it uses a secondary y-axis."+ - " True or False always override 'auto'.", - 'Validator' : lambda value: isinstance(value,bool) or value == 'auto' }, - - 'y_on_right' : { 'Default' : None, - 'Description' : 'True|False put y-axis tick labels on the right, for this addplot'+ - ' regardless of what the mplfinance style says to to.', - 'Validator' : lambda value: isinstance(value,bool) }, - - 'ylabel' : { 'Default' : None, - 'Description' : 'label for y-axis (for this addplot)', - 'Validator' : lambda value: isinstance(value,str) }, - - 'ylim' : {'Default' : None, - 'Description' : 'Limits for addplot y-axis as tuple (min,max), i.e. (bottom,top)', - 'Validator' : lambda value: isinstance(value, (list,tuple)) and len(value) == 2 - and all([isinstance(v,(int,float)) for v in value])}, - - 'title' : { 'Default' : None, - 'Description' : 'Axes Title (subplot title) for this addplot.', - 'Validator' : lambda value: isinstance(value,str) }, - - 'ax' : { 'Default' : None, - 'Description' : 'Matplotlib Axes object on which to plot this addplot', - 'Validator' : lambda value: isinstance(value,mpl_axes.Axes) }, - - 'yscale' : { 'Default' : None, - 'Description' : 'addplot y-axis scale: "linear", "log", "symlog", or "logit"', - 'Validator' : lambda value: _yscale_validator(value) }, - - 'stepwhere' : { 'Default' : 'pre', - 'Description' : "'pre','post', or 'mid': where to place step relative"+ - " to data for `type='step'`", - 'Validator' : lambda value : value in valid_stepwheres }, - - 'marketcolors': { 'Default' : None, # use 'style' for default, instead. - 'Description' : "marketcolors for this addplot (instead of the mplfinance"+ - " style\'s marketcolors). For addplot `type='ohlc'`"+ - " and type='candle'", - 'Validator' : lambda value: _is_marketcolor_object(value) }, - 'fill_between': { 'Default' : None, # added by Wen - 'Description' : " fill region", - 'Validator' : _fill_between_validator }, - - } - - _validate_vkwargs_dict(vkwargs) - - return vkwargs - - -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()) - - # kwarg `type` replaces kwarg `scatter` - if config['scatter'] == True and config['type'] == 'line': - config['type'] = 'scatter' - - return dict( data=data, **config) diff --git a/dist/mplfinance-0.12.9b2-py3.10.egg b/dist/mplfinance-0.12.9b2-py3.10.egg deleted file mode 100644 index 9fbe8be5d35e48c7adbbf480c38e88f18bfc4191..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 148059 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 bO>j9$7+}8&c=ZSV(E{&Axb(pLJH-D1o8XLc From 8d93bec6d29bad1ebd2887b9901c09b83a8965df Mon Sep 17 00:00:00 2001 From: andrewrgarcia Date: Mon, 24 Oct 2022 15:41:25 -0400 Subject: [PATCH 11/22] Revert "made suggested changes and tested new ema option against baseline" This reverts commit 95f0e332daa3e7260dbfc31775ff77a4242eefdf. --- src/mplfinance/plotting.py | 1348 +++++++++++++++----------------- tests/test_ema.py | 85 -- tests/test_images/test_ema.png | Bin 57100 -> 0 bytes 3 files changed, 639 insertions(+), 794 deletions(-) delete mode 100644 tests/test_ema.py delete mode 100644 tests/test_images/test_ema.png diff --git a/src/mplfinance/plotting.py b/src/mplfinance/plotting.py index a04e9c65..c8bcb406 100644 --- a/src/mplfinance/plotting.py +++ b/src/mplfinance/plotting.py @@ -1,10 +1,10 @@ -import matplotlib.dates as mdates +import matplotlib.dates as mdates import matplotlib.pyplot as plt import matplotlib.colors as mcolors -import matplotlib.axes as mpl_axes +import matplotlib.axes as mpl_axes import matplotlib.figure as mpl_fig import pandas as pd -import numpy as np +import numpy as np import copy import io import os @@ -14,7 +14,7 @@ from itertools import cycle #from pandas.plotting import register_matplotlib_converters -# register_matplotlib_converters() +#register_matplotlib_converters() from mplfinance._utils import _construct_aline_collections from mplfinance._utils import _construct_hline_collections @@ -51,8 +51,7 @@ VALID_PMOVE_TYPES = ['renko', 'pnf'] -DEFAULT_FIGRATIO = (8.00, 5.75) - +DEFAULT_FIGRATIO = (8.00,5.75) def with_rc_context(func): ''' @@ -65,26 +64,24 @@ def decorator(*args, **kwargs): return func(*args, **kwargs) return decorator - def _warn_no_xgaps_deprecated(value): - warnings.warn('\n\n ================================================================= ' + - '\n\n WARNING: `no_xgaps` is /deprecated/:' + - '\n Default value is now `no_xgaps=True`' + - '\n However, to set `no_xgaps=False` and silence this warning,' + - '\n use instead: `show_nontrading=True`.' + + warnings.warn('\n\n ================================================================= '+ + '\n\n WARNING: `no_xgaps` is /deprecated/:'+ + '\n Default value is now `no_xgaps=True`'+ + '\n However, to set `no_xgaps=False` and silence this warning,'+ + '\n use instead: `show_nontrading=True`.'+ '\n\n ================================================================ ', category=DeprecationWarning) - return isinstance(value, bool) - + return isinstance(value,bool) def _warn_set_ylim_deprecated(value): - warnings.warn('\n\n ================================================================= ' + - '\n\n WARNING: `set_ylim=(ymin,ymax)` kwarg ' + - '\n has been replaced with: ' + - '\n `ylim=(ymin,ymax)`.' + + warnings.warn('\n\n ================================================================= '+ + '\n\n WARNING: `set_ylim=(ymin,ymax)` kwarg '+ + '\n has been replaced with: '+ + '\n `ylim=(ymin,ymax)`.'+ '\n\n ================================================================ ', category=DeprecationWarning) - return isinstance(value, bool) + return isinstance(value,bool) def _valid_plot_kwargs(): @@ -101,299 +98,297 @@ def _valid_plot_kwargs(): ''' vkwargs = { - 'columns': {'Default': None, # use default names: ('Open', 'High', 'Low', 'Close', 'Volume') - 'Description': ('Column names to be used when plotting the data.' + - ' Default: ("Open", "High", "Low", "Close", "Volume")'), - 'Validator': lambda value: isinstance(value, (tuple, list)) - and len(value) == 5 - and all(isinstance(c, str) for c in value)}, - 'type': {'Default': 'ohlc', - 'Description': 'Plot type: '+str(_get_valid_plot_types()), - 'Validator': lambda value: value in _get_valid_plot_types()}, - - 'style': {'Default': None, - 'Description': 'plot style; see `mpf.available_styles()`', - 'Validator': _styles._valid_mpf_style}, - - 'volume': {'Default': False, - 'Description': 'Plot volume: True, False, or set to Axes object on which to plot.', - 'Validator': lambda value: isinstance(value, bool) or isinstance(value, mpl_axes.Axes)}, - - 'mav': {'Default': None, - 'Description': 'Moving Average window size(s); (int or tuple of ints)', - 'Validator': _mav_validator}, - - 'ema': {'Default': None, - 'Description': 'Exponential Moving Average window size(s); (int or tuple of ints)', - 'Validator': _mav_validator}, - - 'renko_params': {'Default': dict(), - 'Description': 'dict of renko parameters; call `mpf.kwarg_help("renko_params")`', - 'Validator': lambda value: isinstance(value, dict)}, - - 'pnf_params': {'Default': dict(), - 'Description': 'dict of point-and-figure parameters; call `mpf.kwarg_help("pnf_params")`', - 'Validator': lambda value: isinstance(value, dict)}, - - 'study': {'Default': None, - 'Description': 'kwarg not implemented', - 'Validator': lambda value: _kwarg_not_implemented(value)}, - - 'marketcolor_overrides': {'Default': None, - 'Description': 'sequence of color objects to override market colors.' + - 'sequence must be same length as ohlc(v) DataFrame. Each' + - 'color object may be a color, marketcolor object, or None.', - 'Validator': _mco_validator}, - - 'mco_faceonly': {'Default': False, # If True: Override only the face of the candle - 'Description': 'True/False marketcolor_overrides only apply to face of candle.', - 'Validator': lambda value: isinstance(value, bool)}, - - 'no_xgaps': {'Default': True, # None means follow default logic below: - 'Description': 'deprecated', - 'Validator': lambda value: _warn_no_xgaps_deprecated(value)}, - - 'show_nontrading': {'Default': False, - 'Description': 'True/False show spaces for non-trading days/periods', - 'Validator': lambda value: isinstance(value, bool)}, - - 'figscale': {'Default': None, # scale base figure size up or down. - 'Description': 'Scale figure size up (if > 1) or down (if < 1)', - 'Validator': lambda value: isinstance(value, float) or isinstance(value, int)}, - - 'figratio': {'Default': None, # aspect ratio; scaled to 8.0 height - 'Description': 'Aspect ratio of the figure. Default: (8.00,5.75)', - 'Validator': lambda value: isinstance(value, (tuple, list)) - and len(value) == 2 - and isinstance(value[0], (float, int)) - and isinstance(value[1], (float, int))}, - - 'figsize': {'Default': None, # figure size; overrides figratio and figscale - 'Description': ('Figure size: overrides both figscale and figratio,' + - ' else defaults to figratio*figscale'), - 'Validator': lambda value: isinstance(value, (tuple, list)) - and len(value) == 2 - and isinstance(value[0], (float, int)) - and isinstance(value[1], (float, int))}, - - 'fontscale': {'Default': None, # scale all fonts up or down - 'Description': 'Scale font sizes up (if > 1) or down (if < 1)', - 'Validator': lambda value: isinstance(value, float) or isinstance(value, int)}, - - 'linecolor': {'Default': None, # line color in line plot - 'Description': 'Line color for `type=line`', - 'Validator': lambda value: mcolors.is_color_like(value)}, - - 'title': {'Default': None, # Figure Title - 'Description': 'Figure Title (see also `axtitle`)', - 'Validator': lambda value: isinstance(value, (str, dict))}, - - 'axtitle': {'Default': None, # Axes Title (subplot title) - 'Description': 'Axes Title (subplot title)', - 'Validator': lambda value: isinstance(value, (str, dict))}, - - 'ylabel': {'Default': 'Price', # y-axis label - 'Description': 'label for y-axis of main plot', - 'Validator': lambda value: isinstance(value, str)}, - - 'ylabel_lower': {'Default': None, # y-axis label default logic below - 'Description': 'label for y-axis of volume', - 'Validator': lambda value: isinstance(value, str)}, - - 'addplot': {'Default': None, - 'Description': 'addplot object or sequence of addplot objects (from `mpf.make_addplot()`)', - 'Validator': lambda value: isinstance(value, dict) or (isinstance(value, list) and all([isinstance(d, dict) for d in value]))}, - - 'savefig': {'Default': None, - 'Description': 'file name, or BytesIO, or dict with key `fname` plus other keys allowed as ' + - ' kwargs to matplotlib `Figure.savefig()`', - 'Validator': lambda value: isinstance(value, dict) or isinstance(value, str) or isinstance(value, io.BytesIO) or isinstance(value, os.PathLike)}, - - 'block': {'Default': None, - 'Description': 'True/False wait for figure to be closed before returning', - 'Validator': lambda value: isinstance(value, bool)}, - - 'returnfig': {'Default': False, - 'Description': 'return Figure and list of Axes objects created by mplfinance;' + - ' user must display plot when ready, usually by calling `mpf.show()`', - 'Validator': lambda value: isinstance(value, bool)}, - - 'return_calculated_values': {'Default': None, - 'Description': 'set to a variable containing an empty dict; `mpf.plot()` will fill' + - ' the dict with various mplfinance calculated values', - 'Validator': lambda value: isinstance(value, dict) and len(value) == 0}, - - 'set_ylim': {'Default': None, - 'Description': 'deprecated', - 'Validator': lambda value: _warn_set_ylim_deprecated(value)}, - - 'ylim': {'Default': None, - 'Description': 'Limits for y-axis as tuple (min,max), i.e. (bottom,top)', - 'Validator': lambda value: isinstance(value, (list, tuple)) and len(value) == 2 - and all([isinstance(v, (int, float)) for v in value])}, - - 'xlim': {'Default': None, - 'Description': 'Limits for x-axis as tuple (min,max), i.e. (left,right)', - 'Validator': lambda value: _xlim_validator(value)}, - - 'set_ylim_panelB': {'Default': None, - 'Description': 'deprecated', - 'Validator': lambda value: _warn_set_ylim_deprecated(value)}, - - 'hlines': {'Default': None, - 'Description': 'Draw one or more HORIZONTAL LINES across entire plot, by' + - ' specifying a price, or sequence of prices. May also be a dict' + - ' with key `hlines` specifying a price or sequence of prices, plus' + - ' one or more of the following keys: `colors`, `linestyle`,' + - ' `linewidths`, `alpha`.', - 'Validator': lambda value: _hlines_validator(value)}, - - 'vlines': {'Default': None, - 'Description': 'Draw one or more VERTICAL LINES across entire plot, by' + - ' specifying a date[time], or sequence of date[time]. May also' + - ' be a dict with key `vlines` specifying a date[time] or sequence' + - ' of date[time], plus one or more of the following keys:' + - ' `colors`, `linestyle`, `linewidths`, `alpha`.', - 'Validator': lambda value: _vlines_validator(value)}, - - 'alines': {'Default': None, - 'Description': 'Draw one or more ARBITRARY LINES anywhere on the plot, by' + - ' specifying a sequence of two or more date/price pairs, or by' + - ' specifying a sequence of sequences of two or more date/price pairs.' + - ' May also be a dict with key `alines` (as date/price pairs described above),' + - ' plus one or more of the following keys:' + - ' `colors`, `linestyle`, `linewidths`, `alpha`.', - 'Validator': lambda value: _alines_validator(value)}, - - 'tlines': {'Default': None, - 'Description': 'Draw one or more TREND LINES by specifying one or more pairs of date[times]' + - ' between which each trend line should be drawn. May also be a dict with key' + - ' `tlines` as just described, plus one or more of the following keys:' + - ' `colors`, `linestyle`, `linewidths`, `alpha`, `tline_use`,`tline_method`.', - 'Validator': lambda value: _tlines_validator(value)}, - - 'panel_ratios': {'Default': None, - 'Description': 'sequence of numbers indicating relative sizes of panels; sequence len' + - ' must be same as number of panels, or len 2 where first entry is for' + - ' main panel, and second entry is for all other panels', - 'Validator': lambda value: isinstance(value, (tuple, list)) and len(value) <= 32 and - all([isinstance(v, (int, float)) for v in value])}, - - 'main_panel': {'Default': 0, - 'Description': 'integer - which panel is the main panel for `.plot()`', - 'Validator': lambda value: _valid_panel_id(value)}, - - 'volume_panel': {'Default': 1, - 'Description': 'integer - which panel is the volume panel', - 'Validator': lambda value: _valid_panel_id(value)}, - - 'num_panels': {'Default': None, - 'Description': 'total number of panels', - 'Validator': lambda value: isinstance(value, int) and value in range(1, 32+1)}, - - 'datetime_format': {'Default': None, - 'Description': 'x-axis tick format as valid `strftime()` format string', - 'Validator': lambda value: isinstance(value, str)}, - - 'xrotation': {'Default': 45, - 'Description': 'Angle (degrees) for x-axis tick labels; 90=vertical', - 'Validator': lambda value: isinstance(value, (int, float))}, - - 'axisoff': {'Default': False, - 'Description': '`axisoff=True` means do NOT display any axis.', - 'Validator': lambda value: isinstance(value, bool)}, - - 'closefig': {'Default': 'auto', - 'Description': 'True|False close the Figure before returning', - 'Validator': lambda value: isinstance(value, bool)}, - - 'fill_between': {'Default': None, - 'Description': 'fill between specification as y-value, or sequence of' + - ' y-values, or dict containing key "y1" plus any additional' + - ' kwargs for `fill_between()`', - 'Validator': _fill_between_validator}, - - 'tight_layout': {'Default': False, - 'Description': 'True|False implement tight layout (minimal padding around Figure)' + - ' (see also `scale_padding` kwarg)', - 'Validator': lambda value: isinstance(value, bool)}, - - 'scale_padding': {'Default': 1.0, # Issue#193 - 'Description': 'Increase, > 1.0, or decrease, < 1.0, padding around figure.' + - ' May also be a dict containing one or more of the following keys:' + - ' "top", "bottom", "left", "right", to individual scale padding' + - ' on each side of Figure.', - 'Validator': lambda value: _scale_padding_validator(value)}, - - 'width_adjuster_version': {'Default': 'v1', - 'Description': 'specify version of object width adjustment algorithm: "v0" or "v1"' + - ' (See also "widths" tutorial in mplfinance examples folder).', - 'Validator': lambda value: value in ('v0', 'v1')}, - - 'scale_width_adjustment': {'Default': None, - 'Description': 'scale width of plot objects wider, > 1.0, or narrower, < 1.0' + - ' may also be a dict to scale individual widths.' + - ' (See also "widths" tutorial in mplfinance examples folder).', - 'Validator': lambda value: isinstance(value, dict) and len(value) > 0}, - - 'update_width_config': {'Default': None, - 'Description': 'dict - update individual items in width configuration.' + - ' (See also "widths" tutorial in mplfinance examples folder).', - 'Validator': lambda value: isinstance(value, dict) and len(value) > 0}, - - 'return_width_config': {'Default': None, - 'Description': 'empty dict variable to be filled with width configuration settings.', - 'Validator': lambda value: isinstance(value, dict) and len(value) == 0}, - - 'saxbelow': {'Default': True, # Issue#115 Comment#639446764 - 'Description': 'set the volume Axes below (behind) all other Axes objects', - 'Validator': lambda value: isinstance(value, bool)}, - - 'ax': {'Default': None, - 'Description': 'Matplotlib Axes object on which to plot', - 'Validator': lambda value: isinstance(value, mpl_axes.Axes)}, - - 'volume_exponent': {'Default': None, - 'Description': 'integer exponent on the volume axis' + - ' (or set to "legacy" for old mplfinance style)', - 'Validator': lambda value: isinstance(value, int) or value == 'legacy'}, - - 'tz_localize': {'Default': True, - 'Description': 'True|False localize the times in the DatetimeIndex', - 'Validator': lambda value: isinstance(value, bool)}, - - 'yscale': {'Default': None, - 'Description': 'y-axis scale: "linear", "log", "symlog", or "logit"', - 'Validator': lambda value: _yscale_validator(value)}, - - 'volume_yscale': {'Default': None, - 'Description': 'Volume y-axis scale: "linear", "log", "symlog", or "logit"', - 'Validator': lambda value: _yscale_validator(value)}, - - 'volume_ylim': {'Default': None, - 'Description': 'Volume y-axis limits as tuple (min,max), i.e. (bottom,top)', - 'Validator': lambda value: isinstance(value, (list, tuple)) and len(value) == 2 - and all([isinstance(v, (int, float)) for v in value])}, - - 'volume_alpha': {'Default': 1, # alpha of Volume bars - 'Description': 'opacity for Volume bar: 0.0 (transparent) to 1.0 (opaque)', - 'Validator': lambda value: isinstance(value, (int, float)) or - all([isinstance(v, (int, float)) for v in value])}, - - 'warn_too_much_data': {'Default': 599, - 'Description': 'Tolerance for data amount in plot. Default=599 rows.' + - ' Values greater than \'warn_too_much_data\' will trigger a warning.', - 'Validator': lambda value: isinstance(value, int)}, + 'columns' : { 'Default' : None, # use default names: ('Open', 'High', 'Low', 'Close', 'Volume') + 'Description' : ('Column names to be used when plotting the data.'+ + ' Default: ("Open", "High", "Low", "Close", "Volume")'), + 'Validator' : lambda value: isinstance(value, (tuple, list)) + and len(value) == 5 + and all(isinstance(c, str) for c in value) }, + 'type' : { 'Default' : 'ohlc', + 'Description' : 'Plot type: '+str(_get_valid_plot_types()), + 'Validator' : lambda value: value in _get_valid_plot_types() }, + + 'style' : { 'Default' : None, + 'Description' : 'plot style; see `mpf.available_styles()`', + 'Validator' : _styles._valid_mpf_style }, + + 'volume' : { 'Default' : False, + 'Description' : 'Plot volume: True, False, or set to Axes object on which to plot.', + 'Validator' : lambda value: isinstance(value,bool) or isinstance(value,mpl_axes.Axes) }, + + 'mav' : { 'Default' : None, + 'Description' : 'Moving Average window size(s); (int or tuple of ints)', + 'Validator' : _mav_validator }, + + 'ema' : { 'Default' : None, + 'Description' : 'Exponential Moving Average window size(s); (int or tuple of ints)', + 'Validator' : _mav_validator }, + + 'renko_params' : { 'Default' : dict(), + 'Description' : 'dict of renko parameters; call `mpf.kwarg_help("renko_params")`', + 'Validator' : lambda value: isinstance(value,dict) }, + + 'pnf_params' : { 'Default' : dict(), + 'Description' : 'dict of point-and-figure parameters; call `mpf.kwarg_help("pnf_params")`', + 'Validator' : lambda value: isinstance(value,dict) }, + + 'study' : { 'Default' : None, + 'Description' : 'kwarg not implemented', + 'Validator' : lambda value: _kwarg_not_implemented(value) }, + + 'marketcolor_overrides' : { 'Default' : None, + 'Description' : 'sequence of color objects to override market colors.'+ + 'sequence must be same length as ohlc(v) DataFrame. Each'+ + 'color object may be a color, marketcolor object, or None.', + 'Validator' : _mco_validator }, + + 'mco_faceonly' : { 'Default' : False, # If True: Override only the face of the candle + 'Description' : 'True/False marketcolor_overrides only apply to face of candle.', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'no_xgaps' : { 'Default' : True, # None means follow default logic below: + 'Description' : 'deprecated', + 'Validator' : lambda value: _warn_no_xgaps_deprecated(value) }, + + 'show_nontrading' : { 'Default' : False, + 'Description' : 'True/False show spaces for non-trading days/periods', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'figscale' : { 'Default' : None, # scale base figure size up or down. + 'Description' : 'Scale figure size up (if > 1) or down (if < 1)', + 'Validator' : lambda value: isinstance(value,float) or isinstance(value,int) }, + + 'figratio' : { 'Default' : None, # aspect ratio; scaled to 8.0 height + 'Description' : 'Aspect ratio of the figure. Default: (8.00,5.75)', + 'Validator' : lambda value: isinstance(value,(tuple,list)) + and len(value) == 2 + and isinstance(value[0],(float,int)) + and isinstance(value[1],(float,int)) }, + + 'figsize' : { 'Default' : None, # figure size; overrides figratio and figscale + 'Description' : ('Figure size: overrides both figscale and figratio,'+ + ' else defaults to figratio*figscale'), + 'Validator' : lambda value: isinstance(value,(tuple,list)) + and len(value) == 2 + and isinstance(value[0],(float,int)) + and isinstance(value[1],(float,int)) }, + + 'fontscale' : { 'Default' : None, # scale all fonts up or down + 'Description' : 'Scale font sizes up (if > 1) or down (if < 1)', + 'Validator' : lambda value: isinstance(value,float) or isinstance(value,int) }, + + 'linecolor' : { 'Default' : None, # line color in line plot + 'Description' : 'Line color for `type=line`', + 'Validator' : lambda value: mcolors.is_color_like(value) }, + + 'title' : { 'Default' : None, # Figure Title + 'Description' : 'Figure Title (see also `axtitle`)', + 'Validator' : lambda value: isinstance(value,(str,dict)) }, + + 'axtitle' : { 'Default' : None, # Axes Title (subplot title) + 'Description' : 'Axes Title (subplot title)', + 'Validator' : lambda value: isinstance(value,(str,dict)) }, + + 'ylabel' : { 'Default' : 'Price', # y-axis label + 'Description' : 'label for y-axis of main plot', + 'Validator' : lambda value: isinstance(value,str) }, + + 'ylabel_lower' : { 'Default' : None, # y-axis label default logic below + 'Description' : 'label for y-axis of volume', + 'Validator' : lambda value: isinstance(value,str) }, + + 'addplot' : { 'Default' : None, + 'Description' : 'addplot object or sequence of addplot objects (from `mpf.make_addplot()`)', + 'Validator' : lambda value: isinstance(value,dict) or (isinstance(value,list) and all([isinstance(d,dict) for d in value])) }, + + 'savefig' : { 'Default' : None, + 'Description' : 'file name, or BytesIO, or dict with key `fname` plus other keys allowed as '+ + ' kwargs to matplotlib `Figure.savefig()`', + 'Validator' : lambda value: isinstance(value,dict) or isinstance(value,str) or isinstance(value, io.BytesIO) or isinstance(value, os.PathLike) }, + + 'block' : { 'Default' : None, + 'Description' : 'True/False wait for figure to be closed before returning', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'returnfig' : { 'Default' : False, + 'Description' : 'return Figure and list of Axes objects created by mplfinance;'+ + ' user must display plot when ready, usually by calling `mpf.show()`', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'return_calculated_values' : { 'Default' : None, + 'Description' : 'set to a variable containing an empty dict; `mpf.plot()` will fill'+ + ' the dict with various mplfinance calculated values', + 'Validator' : lambda value: isinstance(value, dict) and len(value) == 0}, + + 'set_ylim' : { 'Default' : None, + 'Description' : 'deprecated', + 'Validator' : lambda value: _warn_set_ylim_deprecated(value) }, + + 'ylim' : { 'Default' : None, + 'Description' : 'Limits for y-axis as tuple (min,max), i.e. (bottom,top)', + 'Validator' : lambda value: isinstance(value, (list,tuple)) and len(value) == 2 + and all([isinstance(v,(int,float)) for v in value])}, + + 'xlim' : { 'Default' : None, + 'Description' : 'Limits for x-axis as tuple (min,max), i.e. (left,right)', + 'Validator' : lambda value: _xlim_validator(value) }, + + 'set_ylim_panelB' : { 'Default' : None, + 'Description' : 'deprecated', + 'Validator' : lambda value: _warn_set_ylim_deprecated(value) }, + + 'hlines' : { 'Default' : None, + 'Description' : 'Draw one or more HORIZONTAL LINES across entire plot, by'+ + ' specifying a price, or sequence of prices. May also be a dict'+ + ' with key `hlines` specifying a price or sequence of prices, plus'+ + ' one or more of the following keys: `colors`, `linestyle`,'+ + ' `linewidths`, `alpha`.', + 'Validator' : lambda value: _hlines_validator(value) }, + + 'vlines' : { 'Default' : None, + 'Description' : 'Draw one or more VERTICAL LINES across entire plot, by'+ + ' specifying a date[time], or sequence of date[time]. May also'+ + ' be a dict with key `vlines` specifying a date[time] or sequence'+ + ' of date[time], plus one or more of the following keys:'+ + ' `colors`, `linestyle`, `linewidths`, `alpha`.', + 'Validator' : lambda value: _vlines_validator(value) }, + + 'alines' : { 'Default' : None, + 'Description' : 'Draw one or more ARBITRARY LINES anywhere on the plot, by'+ + ' specifying a sequence of two or more date/price pairs, or by'+ + ' specifying a sequence of sequences of two or more date/price pairs.'+ + ' May also be a dict with key `alines` (as date/price pairs described above),'+ + ' plus one or more of the following keys:'+ + ' `colors`, `linestyle`, `linewidths`, `alpha`.', + 'Validator' : lambda value: _alines_validator(value) }, + + 'tlines' : { 'Default' : None, + 'Description' : 'Draw one or more TREND LINES by specifying one or more pairs of date[times]'+ + ' between which each trend line should be drawn. May also be a dict with key'+ + ' `tlines` as just described, plus one or more of the following keys:'+ + ' `colors`, `linestyle`, `linewidths`, `alpha`, `tline_use`,`tline_method`.', + 'Validator' : lambda value: _tlines_validator(value) }, + + 'panel_ratios' : { 'Default' : None, + 'Description' : 'sequence of numbers indicating relative sizes of panels; sequence len'+ + ' must be same as number of panels, or len 2 where first entry is for'+ + ' main panel, and second entry is for all other panels', + 'Validator' : lambda value: isinstance(value,(tuple,list)) and len(value) <= 32 and + all([isinstance(v,(int,float)) for v in value]) }, + + 'main_panel' : { 'Default' : 0, + 'Description' : 'integer - which panel is the main panel for `.plot()`', + 'Validator' : lambda value: _valid_panel_id(value) }, + + 'volume_panel' : { 'Default' : 1, + 'Description' : 'integer - which panel is the volume panel', + 'Validator' : lambda value: _valid_panel_id(value) }, + + 'num_panels' : { 'Default' : None, + 'Description' : 'total number of panels', + 'Validator' : lambda value: isinstance(value,int) and value in range(1,32+1) }, + + 'datetime_format' : { 'Default' : None, + 'Description' : 'x-axis tick format as valid `strftime()` format string', + 'Validator' : lambda value: isinstance(value,str) }, + + 'xrotation' : { 'Default' : 45, + 'Description' : 'Angle (degrees) for x-axis tick labels; 90=vertical', + 'Validator' : lambda value: isinstance(value,(int,float)) }, + + 'axisoff' : { 'Default' : False, + 'Description' : '`axisoff=True` means do NOT display any axis.', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'closefig' : { 'Default' : 'auto', + 'Description' : 'True|False close the Figure before returning', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'fill_between' : { 'Default' : None, + 'Description' : 'fill between specification as y-value, or sequence of'+ + ' y-values, or dict containing key "y1" plus any additional'+ + ' kwargs for `fill_between()`', + 'Validator' : _fill_between_validator }, + + 'tight_layout' : { 'Default' : False, + 'Description' : 'True|False implement tight layout (minimal padding around Figure)'+ + ' (see also `scale_padding` kwarg)', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'scale_padding' : { 'Default' : 1.0, # Issue#193 + 'Description' : 'Increase, > 1.0, or decrease, < 1.0, padding around figure.'+ + ' May also be a dict containing one or more of the following keys:'+ + ' "top", "bottom", "left", "right", to individual scale padding'+ + ' on each side of Figure.', + 'Validator' : lambda value: _scale_padding_validator(value) }, + + 'width_adjuster_version' : { 'Default' : 'v1', + 'Description' : 'specify version of object width adjustment algorithm: "v0" or "v1"'+ + ' (See also "widths" tutorial in mplfinance examples folder).', + 'Validator' : lambda value: value in ('v0', 'v1') }, + + 'scale_width_adjustment' : { 'Default' : None, + 'Description' : 'scale width of plot objects wider, > 1.0, or narrower, < 1.0'+ + ' may also be a dict to scale individual widths.'+ + ' (See also "widths" tutorial in mplfinance examples folder).', + 'Validator' : lambda value: isinstance(value,dict) and len(value) > 0 }, + + 'update_width_config' : { 'Default' : None, + 'Description' : 'dict - update individual items in width configuration.'+ + ' (See also "widths" tutorial in mplfinance examples folder).', + 'Validator' : lambda value: isinstance(value,dict) and len(value) > 0 }, + + 'return_width_config' : { 'Default' : None, + 'Description' : 'empty dict variable to be filled with width configuration settings.', + 'Validator' : lambda value: isinstance(value,dict) and len(value)==0 }, + + 'saxbelow' : { 'Default' : True, # Issue#115 Comment#639446764 + 'Description' : 'set the volume Axes below (behind) all other Axes objects', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'ax' : { 'Default' : None, + 'Description' : 'Matplotlib Axes object on which to plot', + 'Validator' : lambda value: isinstance(value,mpl_axes.Axes) }, + + 'volume_exponent' : { 'Default' : None, + 'Description' : 'integer exponent on the volume axis'+ + ' (or set to "legacy" for old mplfinance style)', + 'Validator' : lambda value: isinstance(value,int) or value == 'legacy'}, + + 'tz_localize' : { 'Default' : True, + 'Description' : 'True|False localize the times in the DatetimeIndex', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'yscale' : { 'Default' : None, + 'Description' : 'y-axis scale: "linear", "log", "symlog", or "logit"', + 'Validator' : lambda value: _yscale_validator(value) }, + + 'volume_yscale' : { 'Default' : None, + 'Description' : 'Volume y-axis scale: "linear", "log", "symlog", or "logit"', + 'Validator' : lambda value: _yscale_validator(value) }, + + 'volume_ylim' : { 'Default' : None, + 'Description' : 'Volume y-axis limits as tuple (min,max), i.e. (bottom,top)', + 'Validator' : lambda value: isinstance(value, (list,tuple)) and len(value) == 2 + and all([isinstance(v,(int,float)) for v in value])}, + + 'volume_alpha' : { 'Default' : 1, # alpha of Volume bars + 'Description' : 'opacity for Volume bar: 0.0 (transparent) to 1.0 (opaque)', + 'Validator' : lambda value: isinstance(value,(int,float)) or + all([isinstance(v,(int,float)) for v in value]) }, + + 'warn_too_much_data' : { 'Default' : 599, + 'Description' : 'Tolerance for data amount in plot. Default=599 rows.'+ + ' Values greater than \'warn_too_much_data\' will trigger a warning.', + 'Validator' : lambda value: isinstance(value,int) }, } _validate_vkwargs_dict(vkwargs) return vkwargs -# @with_rc_context - - -def plot(data, **kwargs): +###@with_rc_context +def plot( data, **kwargs ): """ Given a Pandas DataFrame containing columns Open,High,Low,Close and optionally Volume with a DatetimeIndex, plot the data. @@ -407,66 +402,61 @@ def plot(data, **kwargs): # translate alias types: config['type'] = _get_valid_plot_types(config['type']) - - dates, opens, highs, lows, closes, volumes = _check_and_prepare_data( - data, config) + + dates,opens,highs,lows,closes,volumes = _check_and_prepare_data(data, config) config['xlim'] = _check_and_convert_xlim_configuration(data, config) if config['type'] in VALID_PMOVE_TYPES and config['addplot'] is not None: - err = "`addplot` is not supported for `type='" + config['type'] + "'`" + err = "`addplot` is not supported for `type='" + config['type'] +"'`" raise ValueError(err) if config['marketcolor_overrides'] is not None: if len(config['marketcolor_overrides']) != len(dates): - raise ValueError( - '`marketcolor_overrides` must be same length as dataframe.') + raise ValueError('`marketcolor_overrides` must be same length as dataframe.') 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.' + + 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.' + + 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.' + + 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 + 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: + 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): + 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) + 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?') if not external_axes_mode: fig = plt.figure() - _adjust_figsize(fig, config) + _adjust_figsize(fig,config) else: fig = None @@ -477,31 +467,29 @@ def plot(data, **kwargs): if external_axes_mode: panels = None - axA1 = config['ax'] + axA1 = config['ax'] axA1.set_axisbelow(config['saxbelow']) if config['volume']: volumeAxes = config['volume'] volumeAxes.set_axisbelow(config['saxbelow']) else: panels = _build_panels(fig, config) - axA1 = panels.at[config['main_panel'], 'axes'][0] + axA1 = panels.at[config['main_panel'],'axes'][0] if config['volume']: if config['volume_panel'] == config['main_panel']: # ohlc and volume on same panel: move volume to secondary axes: - volumeAxes = panels.at[config['volume_panel'], 'axes'][1] - # Make sure ohlc is above volume - volumeAxes.set_zorder(axA1.get_zorder()-0.1) - # Let volume show through - axA1.patch.set_visible(False) - panels.at[config['main_panel'], 'used2nd'] = True + volumeAxes = panels.at[config['volume_panel'],'axes'][1] + volumeAxes.set_zorder(axA1.get_zorder()-0.1) # Make sure ohlc is above volume + axA1.patch.set_visible(False) # Let volume show through + panels.at[config['main_panel'],'used2nd'] = True else: - volumeAxes = panels.at[config['volume_panel'], 'axes'][0] + volumeAxes = panels.at[config['volume_panel'],'axes'][0] else: volumeAxes = None fmtstring = _determine_format_string(dates, config['datetime_format']) - ptype = config['type'] + ptype = config['type'] if config['show_nontrading']: formatter = mdates.DateFormatter(fmtstring) @@ -514,7 +502,7 @@ def plot(data, **kwargs): config['_width_config'] = _determine_width_config(xdates, config) rwc = config['return_width_config'] - if isinstance(rwc, dict) and len(rwc) == 0: + if isinstance(rwc,dict) and len(rwc)==0: config['return_width_config'].update(config['_width_config']) collections = None @@ -522,20 +510,19 @@ def plot(data, **kwargs): 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) + collections =_construct_mpf_collections(ptype,dates,xdates,opens,highs,lows,closes,volumes,config,style) if ptype in VALID_PMOVE_TYPES: collections, calculated_values = collections - volumes = calculated_values['volumes'] - pmove_dates = calculated_values['dates'] - pmove_values = calculated_values['values'] - if all([isinstance(v, (list, tuple)) for v in pmove_values]): + volumes = calculated_values['volumes'] + pmove_dates = calculated_values['dates'] + pmove_values = calculated_values['values'] + if all([isinstance(v,(list,tuple)) for v in pmove_values]): pmove_avgvals = [sum(v)/len(v) for v in pmove_values] else: pmove_avgvals = pmove_values - pmove_size = calculated_values['size'] - pmove_counts = calculated_values['counts'] if 'counts' in calculated_values else None + pmove_size = calculated_values['size'] + pmove_counts = calculated_values['counts'] if 'counts' in calculated_values else None formatter = IntegerIndexDateTimeFormatter(pmove_dates, fmtstring) xdates = np.arange(len(pmove_dates)) @@ -544,29 +531,26 @@ def plot(data, **kwargs): axA1.add_collection(collection) if ptype in VALID_PMOVE_TYPES: - mavprices = _plot_mav(axA1, config, xdates, pmove_avgvals) - emaprices = _plot_ema(axA1, config, xdates, pmove_avgvals) - + mavprices = _plot_mav(axA1,config,xdates,pmove_avgvals) else: - mavprices = _plot_mav(axA1, config, xdates, closes) - emaprices = _plot_ema(axA1, config, xdates, closes) + 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 + minx = xdates[0] - avg_dist_between_points maxx = xdates[-1] + avg_dist_between_points else: - minx = xdates[0] - (0.45 * avg_dist_between_points) + 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 + _lows = lows _highs = highs else: - _lows = pmove_avgvals + _lows = pmove_avgvals _highs = [value+pmove_size for value in pmove_avgvals] miny = np.nanmin(_lows) @@ -578,54 +562,43 @@ def plot(data, **kwargs): ydelta = 0.01 * (maxy-miny) if miny > 0.0: # don't let it go negative: - setminy = max(0.9*miny, miny-ydelta) + setminy = max(0.9*miny,miny-ydelta) else: setminy = miny-ydelta - axA1.set_ylim(setminy, maxy+ydelta) + axA1.set_ylim(setminy,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) + axA1.set_xlim(minx,maxx) if (config['ylim'] is None and config['xlim'] is None and - not config['tight_layout']): + 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 == 'renko': - retdict['renko_bricks'] = pmove_values - retdict['renko_dates'] = mdates.num2date(pmove_dates) - retdict['renko_size'] = pmove_size + retdict['renko_bricks' ] = pmove_values + retdict['renko_dates' ] = mdates.num2date(pmove_dates) + retdict['renko_size' ] = pmove_size retdict['renko_volumes'] = volumes if config['volume'] else None elif ptype == 'pnf': - retdict['pnf_dates'] = mdates.num2date(pmove_dates) - retdict['pnf_counts'] = pmove_counts - retdict['pnf_values'] = pmove_values - retdict['pnf_avgvals'] = pmove_avgvals - retdict['pnf_size'] = pmove_size - retdict['pnf_volumes'] = volumes if config['volume'] else None + retdict['pnf_dates' ] = mdates.num2date(pmove_dates) + retdict['pnf_counts' ] = pmove_counts + retdict['pnf_values' ] = pmove_values + retdict['pnf_avgvals' ] = pmove_avgvals + retdict['pnf_size' ] = pmove_size + retdict['pnf_volumes' ] = volumes if config['volume'] else None 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))) + warnings.warn('len(mav)='+str(len(mav))+' BUT len(mavprices)='+str(len(mavprices))) else: - for jj in range(0, len(mav)): + for jj in range(0,len(mav)): retdict['mav' + str(mav[jj])] = mavprices[jj] - - if config['ema'] is not None: - ema = config['ema'] - if len(ema) != len(emaprices): - warnings.warn('len(ema)='+str(len(ema)) + - ' BUT len(emaprices)='+str(len(emaprices))) - else: - for jj in range(0, len(ema)): - retdict['ema' + str(ema[jj])] = emaprices[jj] - retdict['minx'] = minx retdict['maxx'] = maxx retdict['miny'] = miny @@ -641,63 +614,58 @@ def plot(data, **kwargs): 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)) + 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]): + if isinstance(tlines,(list,tuple)) and all([isinstance(item,dict) for item in tlines]): pass else: - tlines = [tlines, ] + tlines = [tlines,] for tline_item in tlines: - line_collections.append(_construct_tline_collections( - tline_item, dtix, dates, opens, highs, lows, closes)) - + 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() + vup,vdown = style['marketcolors']['volume'].values() #-- print('vup,vdown=',vup,vdown) - vcolors = _updown_colors( - vup, vdown, opens, closes, use_prev_close=style['marketcolors']['vcdopcod']) + 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'] + w = config['_width_config']['volume_width'] lw = config['_width_config']['volume_linewidth'] - adjc = _adjust_color_brightness(vcolors, 0.90) + adjc = _adjust_color_brightness(vcolors,0.90) valp = config['volume_alpha'] - volumeAxes.bar(xdates, volumes, width=w, linewidth=lw, - color=vcolors, ec=adjc, alpha=valp) + volumeAxes.bar(xdates,volumes,width=w,linewidth=lw,color=vcolors,ec=adjc,alpha=valp) if config['volume_ylim'] is not None: vymin = config['volume_ylim'][0] vymax = config['volume_ylim'][1] else: vymin = 0.3 * np.nanmin(volumes) vymax = 1.1 * np.nanmax(volumes) - volumeAxes.set_ylim(vymin, vymax) + volumeAxes.set_ylim(vymin,vymax) xrotation = config['xrotation'] if not external_axes_mode: - _set_ticks_on_bottom_panel_only(panels, formatter, rotation=xrotation) + _set_ticks_on_bottom_panel_only(panels,formatter,rotation=xrotation) else: - axA1.tick_params(axis='x', rotation=xrotation) + axA1.tick_params(axis='x',rotation=xrotation) axA1.xaxis.set_major_formatter(formatter) ysd = config['yscale'] - if isinstance(ysd, dict): + if isinstance(ysd,dict): yscale = ysd['yscale'] - del ysd['yscale'] - axA1.set_yscale(yscale, **ysd) - elif isinstance(ysd, str): + del ysd['yscale'] + axA1.set_yscale(yscale,**ysd) + elif isinstance(ysd,str): axA1.set_yscale(ysd) + addplot = config['addplot'] if addplot is not None and ptype not in VALID_PMOVE_TYPES: @@ -711,67 +679,59 @@ def plot(data, **kwargs): # 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 + 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 + 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} + 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 + 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))) + raise TypeError('addplot must be `dict`, or `list of dict`, NOT '+str(type(addplot))) for apdict in addplot: - panid = apdict['panel'] + 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 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'] + 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, xdates) - else: + ax = _addplot_collections(panid,panels,apdict,xdates,config) + _addplot_apply_supplements(ax,apdict,xdates) + else: apdata = apdict['data'] - if isinstance(apdata, list) and not isinstance(apdata[0], (float, int)): + 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): + if isinstance(apdata,pd.DataFrame): havedf = True else: havedf = False # must be a single series or array - apdata = [apdata, ] # make it iterable + 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, xdates) + ydata = apdata.loc[:,column] if havedf else column + ax = _addplot_columns(panid,panels,ydata,apdict,xdates,config) + _addplot_apply_supplements(ax,apdict,xdates) # 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: fblist = copy.deepcopy(config['fill_between']) if _num_or_seq_of_num(fblist): - fblist = [dict(y1=fblist), ] - elif isinstance(fblist, dict): - fblist = [fblist, ] + fblist = [dict(y1=fblist),] + elif isinstance(fblist,dict): + fblist = [fblist,] if not _list_of_dict(fblist): raise TypeError('Bad type for `fill_between` specifier.') for fb in fblist: @@ -781,61 +741,62 @@ def plot(data, **kwargs): if 'panel' in fb: panid = fb['panel'] del fb['panel'] - fb['x'] = xdates # add 'x' to caller's fb dict - ax = panels.at[panid, 'axes'][0] + fb['x'] = xdates # add 'x' to caller's fb dict + 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(): + 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) + _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) + _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: 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']: + #if config['autofmt_xdate']: #print('CALLING fig.autofmt_xdate()') - # 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`). + # 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.tick_params(axis='x',rotation=xrotation) volumeAxes.xaxis.set_major_formatter(formatter) vscale = 'linear' ysd = config['volume_yscale'] - if isinstance(ysd, dict): + if isinstance(ysd,dict): yscale = ysd['yscale'] - del ysd['yscale'] - volumeAxes.set_yscale(yscale, **ysd) + del ysd['yscale'] + volumeAxes.set_yscale(yscale,**ysd) vscale = yscale - elif isinstance(ysd, str): + elif isinstance(ysd,str): volumeAxes.set_yscale(ysd) vscale = ysd offset = '' @@ -846,28 +807,24 @@ def plot(data, **kwargs): 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') + 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') + 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): + 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') + 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') + volumeAxes.ticklabel_format(useOffset=False,scilimits=scilims,axis='y') offset = ' $10^'+str(scilims[1])+'$' volumeAxes.yaxis.offsetText.set_visible(False) @@ -878,7 +835,7 @@ def plot(data, **kwargs): 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: `y=0.89` is based on the top of the top panel @@ -887,24 +844,24 @@ def plot(data, **kwargs): title_kwargs = dict(va='bottom', y=0.89) else: title_kwargs = dict(va='center') - if isinstance(config['title'], dict): + 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'] - # allows override default values set by mplfinance above - title_kwargs.update(title_dict) + 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) - + 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(): + for panid,row in panels.iterrows(): if not row['used2nd']: row['axes'][1].set_visible(False) @@ -922,27 +879,25 @@ def plot(data, **kwargs): if config['savefig'] is not None: save = config['savefig'] - if isinstance(save, dict): + if isinstance(save,dict): if config['tight_layout'] and 'bbox_inches' not in save: - fig.savefig(**save, bbox_inches='tight') + fig.savefig(**save,bbox_inches='tight') else: fig.savefig(**save) else: if config['tight_layout']: - fig.savefig(save, bbox_inches='tight') + fig.savefig(save,bbox_inches='tight') else: fig.savefig(save) - if config['closefig']: # True or 'auto' + if config['closefig']: # True or 'auto' plt.close(fig) elif not config['returnfig']: - # https://stackoverflow.com/a/13361748/1639359 - plt.show(block=config['block']) + 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) + if config['closefig'] == True: plt.close(fig) return (fig, axlist) # rcp = copy.deepcopy(plt.rcParams) @@ -951,33 +906,30 @@ def plot(data, **kwargs): # print('rcpdfhead(3)=',rcpdf.head(3)) # return # rcpdf - -def _adjust_figsize(fig, config): +def _adjust_figsize(fig,config): if fig is None: return if config['figsize'] is None: - w, h = config['figratio'] + 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)+')') + 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] + base = (w,h) + figscale = config['figscale'] + fsize = [d*figscale for d in base] else: fsize = config['figsize'] fig.set_size_inches(fsize) - def _adjust_fontsize(config): if config['fontscale'] is None: return - if not isinstance(plt.rcParams['font.size'], (float, int)): - warnings.warn('\n\n ================================================================= ' + - '\n\n WARNING: Unable to scale fonts: plt.rcParams["font.size"] is NOT a float!' + + if not isinstance(plt.rcParams['font.size'],(float,int)): + warnings.warn('\n\n ================================================================= '+ + '\n\n WARNING: Unable to scale fonts: plt.rcParams["font.size"] is NOT a float!'+ '\n\n ================================================================ ', category=UserWarning) return @@ -997,19 +949,18 @@ def _adjust_fontsize(config): # None: 1.0, # } # -------------------------------------------- - fontstuff = ['axes.labelsize', 'axes.titlesize', 'figure.titlesize', 'legend.fontsize', - 'legend.title_fontsize', 'xtick.labelsize', 'ytick.labelsize'] + fontstuff = ['axes.labelsize','axes.titlesize', 'figure.titlesize','legend.fontsize', + 'legend.title_fontsize','xtick.labelsize','ytick.labelsize'] for item in fontstuff: - if isinstance(plt.rcParams[item], (float, int)): + if isinstance(plt.rcParams[item],(float,int)): plt.rcParams[item] *= config['fontscale'] - -def _addplot_collections(panid, panels, apdict, xdates, config): +def _addplot_collections(panid,panels,apdict,xdates,config): apdata = apdict['data'] aptype = apdict['type'] external_axes_mode = apdict['ax'] is not None - + #--------------------------------------------------------------# # Note: _auto_secondary_y() sets the 'magnitude' column in the # `panels` dataframe, which is needed for automatically @@ -1022,152 +973,134 @@ def _addplot_collections(panid, panels, apdict, xdates, config): # if any have secondary_y='auto', but since that is the # default value, we will just assume we have at least one. - valid_apc_types = ['ohlc', 'candle'] + valid_apc_types = ['ohlc','candle'] if aptype not in valid_apc_types: - raise TypeError('Invalid aptype='+str(aptype) + - '. Must be one of '+str(valid_apc_types)) - if not isinstance(apdata, pd.DataFrame): - raise TypeError('addplot type "'+aptype + - '" MUST be accompanied by addplot data of type `pd.DataFrame`') - d, o, h, l, c, v = _check_and_prepare_data(apdata, config) - + raise TypeError('Invalid aptype='+str(aptype)+'. Must be one of '+str(valid_apc_types)) + if not isinstance(apdata,pd.DataFrame): + raise TypeError('addplot type "'+aptype+'" MUST be accompanied by addplot data of type `pd.DataFrame`') + d,o,h,l,c,v = _check_and_prepare_data(apdata,config) + mc = apdict['marketcolors'] if _is_marketcolor_object(mc): apstyle = config['style'].copy() apstyle['marketcolors'] = mc else: apstyle = config['style'] - - collections = _construct_mpf_collections( - aptype, d, xdates, o, h, l, c, v, config, apstyle) + + collections = _construct_mpf_collections(aptype,d,xdates,o,h,l,c,v,config,apstyle) if not external_axes_mode: - lo = math.log(max(math.fabs(np.nanmin(l)), 1e-7), 10) - 0.5 - hi = math.log(max(math.fabs(np.nanmax(h)), 1e-7), 10) + 0.5 - secondary_y = _auto_secondary_y(panels, panid, lo, hi) + lo = math.log(max(math.fabs(np.nanmin(l)),1e-7),10) - 0.5 + hi = math.log(max(math.fabs(np.nanmax(h)),1e-7),10) + 0.5 + secondary_y = _auto_secondary_y( panels, panid, lo, hi ) if 'auto' != apdict['secondary_y']: - secondary_y = apdict['secondary_y'] + secondary_y = apdict['secondary_y'] if secondary_y: - ax = panels.at[panid, 'axes'][1] - panels.at[panid, 'used2nd'] = True - else: - ax = panels.at[panid, 'axes'][0] + ax = panels.at[panid,'axes'][1] + panels.at[panid,'used2nd'] = True + else: + ax = panels.at[panid,'axes'][0] else: ax = apdict['ax'] for coll in collections: ax.add_collection(coll) if apdict['mav'] is not None: - apmavprices = _plot_mav(ax, config, xdates, c, apdict['mav']) - # if apdict['ema'] is not None: - # apemaprices = _plot_ema(ax, config, xdates, c, apdict['ema']) - + apmavprices = _plot_mav(ax,config,xdates,c,apdict['mav']) ax.autoscale_view() return ax - -def _addplot_columns(panid, panels, ydata, apdict, xdates, config): +def _addplot_columns(panid,panels,ydata,apdict,xdates,config): external_axes_mode = apdict['ax'] is not None if not external_axes_mode: secondary_y = False if apdict['secondary_y'] == 'auto': 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 = _auto_secondary_y(panels, panid, ymlo, ymhi) + 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 = _auto_secondary_y( panels, panid, ymlo, ymhi ) 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] + ax = panels.at[panid,'axes'][1] + panels.at[panid,'used2nd'] = True + else: + ax = panels.at[panid,'axes'][0] else: ax = apdict['ax'] aptype = apdict['type'] if aptype == 'scatter': - size = apdict['markersize'] - mark = apdict['marker'] + size = apdict['markersize'] + mark = apdict['marker'] color = apdict['color'] alpha = apdict['alpha'] - edgecolors = apdict['edgecolors'] + edgecolors = apdict['edgecolors'] linewidths = apdict['linewidths'] - if isinstance(mark, (list, tuple, np.ndarray)): - _mscatter(xdates, ydata, ax=ax, m=mark, s=size, color=color, - alpha=alpha, edgecolors=edgecolors, linewidths=linewidths) + if isinstance(mark,(list,tuple,np.ndarray)): + _mscatter(xdates, ydata, ax=ax, m=mark, s=size, color=color, alpha=alpha, edgecolors=edgecolors, linewidths=linewidths) else: - ax.scatter(xdates, ydata, s=size, marker=mark, color=color, - alpha=alpha, edgecolors=edgecolors, linewidths=linewidths) + ax.scatter(xdates, ydata, s=size, marker=mark, color=color, alpha=alpha, edgecolors=edgecolors, linewidths=linewidths) elif aptype == 'bar': - width = 0.8 if apdict['width'] is None else apdict['width'] + width = 0.8 if apdict['width'] is None else apdict['width'] bottom = apdict['bottom'] - color = apdict['color'] - alpha = apdict['alpha'] - ax.bar(xdates, ydata, width=width, - bottom=bottom, color=color, alpha=alpha) + 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'] - width = apdict['width'] if apdict['width'] is not None else 1.6 * \ - config['_width_config']['line_width'] - alpha = apdict['alpha'] - ax.plot(xdates, ydata, linestyle=ls, color=color, - linewidth=width, alpha=alpha) + ls = apdict['linestyle'] + color = apdict['color'] + width = apdict['width'] if apdict['width'] is not None else 1.6*config['_width_config']['line_width'] + alpha = apdict['alpha'] + ax.plot(xdates,ydata,linestyle=ls,color=color,linewidth=width,alpha=alpha) elif aptype == 'step': stepwhere = apdict['stepwhere'] ls = apdict['linestyle'] - color = apdict['color'] - width = apdict['width'] if apdict['width'] is not None else 1.6 * \ - config['_width_config']['line_width'] - alpha = apdict['alpha'] - ax.step(xdates, ydata, where=stepwhere, linestyle=ls, - color=color, linewidth=width, alpha=alpha) + color = apdict['color'] + width = apdict['width'] if apdict['width'] is not None else 1.6*config['_width_config']['line_width'] + alpha = apdict['alpha'] + ax.step(xdates,ydata,where = stepwhere,linestyle=ls,color=color,linewidth=width,alpha=alpha) else: raise ValueError('addplot type "'+str(aptype)+'" NOT yet supported.') if apdict['mav'] is not None: - apmavprices = _plot_mav(ax, config, xdates, ydata, apdict['mav']) - # if apdict['ema'] is not None: - # apemaprices = _plot_ema(ax, config, xdates, ydata, apdict['ema']) - return ax + apmavprices = _plot_mav(ax,config,xdates,ydata,apdict['mav']) + return ax -def _addplot_apply_supplements(ax, apdict, xdates): +def _addplot_apply_supplements(ax,apdict,xdates): if (apdict['ylabel'] is not None): ax.set_ylabel(apdict['ylabel']) if apdict['ylim'] is not None: - ax.set_ylim(apdict['ylim'][0], apdict['ylim'][1]) + ax.set_ylim(apdict['ylim'][0],apdict['ylim'][1]) if apdict['title'] is not None: ax.set_title(apdict['title']) ysd = apdict['yscale'] - if isinstance(ysd, dict): + if isinstance(ysd,dict): yscale = ysd['yscale'] - del ysd['yscale'] - ax.set_yscale(yscale, **ysd) - elif isinstance(ysd, str): + del ysd['yscale'] + ax.set_yscale(yscale,**ysd) + elif isinstance(ysd,str): ax.set_yscale(ysd) # added by Wen if "fill_between" in apdict and apdict['fill_between'] is not None: # deep copy because mplfinance code sometimes modifies the fill_between dict fblist = copy.deepcopy(apdict['fill_between']) - if isinstance(fblist, dict): - fblist = [fblist, ] + if isinstance(fblist,dict): + fblist = [fblist,] if _list_of_dict(fblist): for fb in fblist: if 'x' in fb: raise ValueError('fill_between dict may not contain `x`') - fb['x'] = xdates # add 'x' to caller's fb dict + fb['x'] = xdates # add 'x' to caller's fb dict ax.fill_between(**fb) else: - raise ValueError( - 'Invalid addplot fill_between: must be `dict` or `list of dict`') - + raise ValueError('Invalid addplot fill_between: must be `dict` or `list of dict`') -def _set_ylabels_side(ax_pri, ax_sec, primary_on_right): +def _set_ylabels_side(ax_pri,ax_sec,primary_on_right): # put the primary axis on one side, # and the twinx() on the "other" side: if primary_on_right == True: @@ -1183,8 +1116,7 @@ def _set_ylabels_side(ax_pri, ax_sec, primary_on_right): ax_sec.yaxis.set_label_position('right') ax_sec.yaxis.tick_right() - -def _plot_mav(ax, config, xdates, prices, apmav=None, apwidth=None): +def _plot_mav(ax,config,xdates,prices,apmav=None,apwidth=None): style = config['style'] if apmav is not None: mavgs = apmav @@ -1193,20 +1125,20 @@ def _plot_mav(ax, config, xdates, prices, apmav=None, apwidth=None): mavp_list = [] if mavgs is not None: shift = None - if isinstance(mavgs, dict): + if isinstance(mavgs,dict): shift = mavgs['shift'] mavgs = mavgs['period'] - if isinstance(mavgs, int): + 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 idx, mav in enumerate(mavgs): + for idx,mav in enumerate(mavgs): mean = pd.Series(prices).rolling(mav).mean() if shift is not None: mean = mean.shift(periods=shift[idx]) @@ -1220,7 +1152,7 @@ def _plot_mav(ax, config, xdates, prices, apmav=None, apwidth=None): return mavp_list -def _plot_ema(ax, config, xdates, prices, apmav=None, apwidth=None): +def _plot_ema(ax,config,xdates,prices,apmav=None,apwidth=None): '''ema: exponential moving average''' style = config['style'] if apmav is not None: @@ -1230,162 +1162,160 @@ def _plot_ema(ax, config, xdates, prices, apmav=None, apwidth=None): mavp_list = [] if mavgs is not None: shift = None - if isinstance(mavgs, dict): + if isinstance(mavgs,dict): shift = mavgs['shift'] mavgs = mavgs['period'] - if isinstance(mavgs, int): + 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 idx, mav in enumerate(mavgs): + for idx,mav in enumerate(mavgs): # mean = pd.Series(prices).rolling(mav).mean() - mean = pd.Series(prices).ewm(span=mav, adjust=False).mean() + mean = pd.Series(prices).ewm(span=mav,adjust=False).mean() if shift is not None: mean = mean.shift(periods=shift[idx]) - emaprices = mean.values + mavprices = mean.values lw = config['_width_config']['line_width'] if mavc: - ax.plot(xdates, emaprices, linewidth=lw, color=next(mavc)) + ax.plot(xdates, mavprices, linewidth=lw, color=next(mavc)) else: - ax.plot(xdates, emaprices, linewidth=lw) - mavp_list.append(emaprices) + ax.plot(xdates, mavprices, linewidth=lw) + mavp_list.append(mavprices) return mavp_list -def _auto_secondary_y(panels, panid, ylo, yhi): +def _auto_secondary_y( panels, panid, ylo, yhi ): # 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. secondary_y = False - p = panid, 'mag' + p = panid,'mag' if panels.at[p] is None: - panels.at[p] = {'lo': ylo, 'hi': yhi} + panels.at[p] = {'lo':ylo,'hi':yhi} elif ylo < panels.at[p]['lo'] or yhi > panels.at[p]['hi']: secondary_y = True - # if secondary_y: + #if secondary_y: # print('auto says USE secondary_y ... for panel',panid) - # else: + #else: # print('auto says do NOT use secondary_y ... for panel',panid) return secondary_y - def _valid_addplot_kwargs(): - valid_linestyles = ('-', 'solid', '--', 'dashed', '-.', - 'dashdot', '.', 'dotted', None, ' ', '') - valid_types = ('line', 'scatter', 'bar', 'ohlc', 'candle', 'step') - valid_stepwheres = ('pre', 'post', 'mid') + valid_linestyles = ('-','solid','--','dashed','-.','dashdot','.','dotted',None,' ','') + valid_types = ('line','scatter','bar', 'ohlc', 'candle','step') + valid_stepwheres = ('pre','post','mid') valid_edgecolors = ('face', 'none', None) vkwargs = { - 'scatter': {'Default': False, - 'Description': "Deprecated. (Use kwarg `type='scatter' instead.", - 'Validator': lambda value: isinstance(value, bool)}, - - 'type': {'Default': 'line', - 'Description': 'addplot type: "line","scatter","bar", "ohlc", "candle","step"', - 'Validator': lambda value: value in valid_types}, - - 'mav': {'Default': None, - 'Description': 'Moving Average window size(s); (int or tuple of ints)', - 'Validator': _mav_validator}, - - 'panel': {'Default': 0, - 'Description': 'Panel (int 0-31) to use for this addplot', - 'Validator': lambda value: _valid_panel_id(value)}, - - 'marker': {'Default': 'o', - 'Description': "marker for `type='scatter'` plot", - 'Validator': lambda value: _bypass_kwarg_validation(value)}, - - 'markersize': {'Default': 18, - 'Description': 'size of marker for `type="scatter"`; default=18', - 'Validator': lambda value: isinstance(value, (int, float))}, - - 'color': {'Default': None, - 'Description': 'color (or sequence of colors) of line(s), scatter marker(s), or bar(s).', - 'Validator': lambda value: mcolors.is_color_like(value) or - (isinstance(value, (list, tuple, np.ndarray)) and all([mcolors.is_color_like(v) for v in value]))}, - - 'linestyle': {'Default': None, - 'Description': 'line style for `type=line` ('+str(valid_linestyles)+')', - 'Validator': lambda value: value in valid_linestyles}, - - 'linewidths': {'Default': None, - 'Description': 'edge widths of scatter markers', - 'Validator': lambda value: isinstance(value, (int, float))}, - - 'edgecolors': {'Default': None, - 'Description': 'edgecolors of scatter markers', - 'Validator': lambda value: mcolors.is_color_like(value) or value in valid_edgecolors}, - - 'width': {'Default': None, # width of `bar` or `line` - 'Description': 'width of bar or line for `type="bar"` or `type="line"', - 'Validator': lambda value: isinstance(value, (int, float)) or - all([isinstance(v, (int, float)) for v in value])}, - - 'bottom': {'Default': 0, # bottom for `type=bar` plots - 'Description': 'bottom value for `type=bar` bars. Default=0', - 'Validator': lambda value: isinstance(value, (int, float)) or - all([isinstance(v, (int, float)) for v in value])}, - 'alpha': {'Default': 1, # alpha of `bar`, `line`, or `scatter` - 'Description': 'opacity for 0.0 (transparent) to 1.0 (opaque)', - 'Validator': lambda value: isinstance(value, (int, float)) or - all([isinstance(v, (int, float)) for v in value])}, - - 'secondary_y': {'Default': 'auto', - 'Description': "True|False|'auto' place the additional plot data on a" + - " secondary y-axis. 'auto' compares the magnitude or the" + - " addplot data, to data already on the axis, and if it appears" + - " they are of different magnitudes, then it uses a secondary y-axis." + - " True or False always override 'auto'.", - 'Validator': lambda value: isinstance(value, bool) or value == 'auto'}, - - 'y_on_right': {'Default': None, - 'Description': 'True|False put y-axis tick labels on the right, for this addplot' + - ' regardless of what the mplfinance style says to to.', - 'Validator': lambda value: isinstance(value, bool)}, - - 'ylabel': {'Default': None, - 'Description': 'label for y-axis (for this addplot)', - 'Validator': lambda value: isinstance(value, str)}, - - 'ylim': {'Default': None, - 'Description': 'Limits for addplot y-axis as tuple (min,max), i.e. (bottom,top)', - 'Validator': lambda value: isinstance(value, (list, tuple)) and len(value) == 2 - and all([isinstance(v, (int, float)) for v in value])}, - - 'title': {'Default': None, - 'Description': 'Axes Title (subplot title) for this addplot.', - 'Validator': lambda value: isinstance(value, str)}, - - 'ax': {'Default': None, - 'Description': 'Matplotlib Axes object on which to plot this addplot', - 'Validator': lambda value: isinstance(value, mpl_axes.Axes)}, - - 'yscale': {'Default': None, - 'Description': 'addplot y-axis scale: "linear", "log", "symlog", or "logit"', - 'Validator': lambda value: _yscale_validator(value)}, - - 'stepwhere': {'Default': 'pre', - 'Description': "'pre','post', or 'mid': where to place step relative" + - " to data for `type='step'`", - 'Validator': lambda value: value in valid_stepwheres}, - - 'marketcolors': {'Default': None, # use 'style' for default, instead. - 'Description': "marketcolors for this addplot (instead of the mplfinance" + - " style\'s marketcolors). For addplot `type='ohlc'`" + - " and type='candle'", - 'Validator': lambda value: _is_marketcolor_object(value)}, - 'fill_between': {'Default': None, # added by Wen - 'Description': " fill region", - 'Validator': _fill_between_validator}, + 'scatter' : { 'Default' : False, + 'Description' : "Deprecated. (Use kwarg `type='scatter' instead.", + 'Validator' : lambda value: isinstance(value,bool) }, + + 'type' : { 'Default' : 'line', + 'Description' : 'addplot type: "line","scatter","bar", "ohlc", "candle","step"', + 'Validator' : lambda value: value in valid_types }, + + 'mav' : { 'Default' : None, + 'Description' : 'Moving Average window size(s); (int or tuple of ints)', + 'Validator' : _mav_validator }, + + 'panel' : { 'Default' : 0, + 'Description' : 'Panel (int 0-31) to use for this addplot', + 'Validator' : lambda value: _valid_panel_id(value) }, + + 'marker' : { 'Default' : 'o', + 'Description' : "marker for `type='scatter'` plot", + 'Validator' : lambda value: _bypass_kwarg_validation(value) }, + + 'markersize' : { 'Default' : 18, + 'Description' : 'size of marker for `type="scatter"`; default=18', + 'Validator' : lambda value: isinstance(value,(int,float)) }, + + 'color' : { 'Default' : None, + 'Description' : 'color (or sequence of colors) of line(s), scatter marker(s), or bar(s).', + 'Validator' : lambda value: mcolors.is_color_like(value) or + (isinstance(value,(list,tuple,np.ndarray)) and all([mcolors.is_color_like(v) for v in value])) }, + + 'linestyle' : { 'Default' : None, + 'Description' : 'line style for `type=line` ('+str(valid_linestyles)+')', + 'Validator' : lambda value: value in valid_linestyles }, + + 'linewidths' : { 'Default': None, + 'Description' : 'edge widths of scatter markers', + 'Validator' : lambda value: isinstance(value,(int,float)) }, + + 'edgecolors' : { 'Default': None, + 'Description' : 'edgecolors of scatter markers', + 'Validator': lambda value: mcolors.is_color_like(value) or value in valid_edgecolors}, + + 'width' : { 'Default' : None, # width of `bar` or `line` + 'Description' : 'width of bar or line for `type="bar"` or `type="line"', + 'Validator' : lambda value: isinstance(value,(int,float)) or + all([isinstance(v,(int,float)) for v in value]) }, + + 'bottom' : { 'Default' : 0, # bottom for `type=bar` plots + 'Description' : 'bottom value for `type=bar` bars. Default=0', + 'Validator' : lambda value: isinstance(value,(int,float)) or + all([isinstance(v,(int,float)) for v in value]) }, + 'alpha' : { 'Default' : 1, # alpha of `bar`, `line`, or `scatter` + 'Description' : 'opacity for 0.0 (transparent) to 1.0 (opaque)', + 'Validator' : lambda value: isinstance(value,(int,float)) or + all([isinstance(v,(int,float)) for v in value]) }, + + 'secondary_y' : { 'Default' : 'auto', + 'Description' : "True|False|'auto' place the additional plot data on a"+ + " secondary y-axis. 'auto' compares the magnitude or the"+ + " addplot data, to data already on the axis, and if it appears"+ + " they are of different magnitudes, then it uses a secondary y-axis."+ + " True or False always override 'auto'.", + 'Validator' : lambda value: isinstance(value,bool) or value == 'auto' }, + + 'y_on_right' : { 'Default' : None, + 'Description' : 'True|False put y-axis tick labels on the right, for this addplot'+ + ' regardless of what the mplfinance style says to to.', + 'Validator' : lambda value: isinstance(value,bool) }, + + 'ylabel' : { 'Default' : None, + 'Description' : 'label for y-axis (for this addplot)', + 'Validator' : lambda value: isinstance(value,str) }, + + 'ylim' : {'Default' : None, + 'Description' : 'Limits for addplot y-axis as tuple (min,max), i.e. (bottom,top)', + 'Validator' : lambda value: isinstance(value, (list,tuple)) and len(value) == 2 + and all([isinstance(v,(int,float)) for v in value])}, + + 'title' : { 'Default' : None, + 'Description' : 'Axes Title (subplot title) for this addplot.', + 'Validator' : lambda value: isinstance(value,str) }, + + 'ax' : { 'Default' : None, + 'Description' : 'Matplotlib Axes object on which to plot this addplot', + 'Validator' : lambda value: isinstance(value,mpl_axes.Axes) }, + + 'yscale' : { 'Default' : None, + 'Description' : 'addplot y-axis scale: "linear", "log", "symlog", or "logit"', + 'Validator' : lambda value: _yscale_validator(value) }, + + 'stepwhere' : { 'Default' : 'pre', + 'Description' : "'pre','post', or 'mid': where to place step relative"+ + " to data for `type='step'`", + 'Validator' : lambda value : value in valid_stepwheres }, + + 'marketcolors': { 'Default' : None, # use 'style' for default, instead. + 'Description' : "marketcolors for this addplot (instead of the mplfinance"+ + " style\'s marketcolors). For addplot `type='ohlc'`"+ + " and type='candle'", + 'Validator' : lambda value: _is_marketcolor_object(value) }, + 'fill_between': { 'Default' : None, # added by Wen + 'Description' : " fill region", + 'Validator' : _fill_between_validator }, } @@ -1410,4 +1340,4 @@ def make_addplot(data, **kwargs): if config['scatter'] == True and config['type'] == 'line': config['type'] = 'scatter' - return dict(data=data, **config) + return dict( data=data, **config) diff --git a/tests/test_ema.py b/tests/test_ema.py deleted file mode 100644 index 7fa3b519..00000000 --- a/tests/test_ema.py +++ /dev/null @@ -1,85 +0,0 @@ - -import mplfinance as mpf -import requests # for making http requests to binance -import json # for parsing what binance sends back to us -import pandas as pd # for storing and manipulating the data we get back -import numpy as np # numerical python, i usually need this somewhere -# and so i import by habit nowadays - -import matplotlib.pyplot as plt # for charts and such -import datetime as dt # for dealing with times - -INTERVAL = '1d' - - -def get_bars(quote, interval=INTERVAL): - - root_url = 'https://api.binance.com/api/v1/klines' - url = root_url + '?symbol=' + quote + '&interval=' + interval - data = json.loads(requests.get(url).text) - df = pd.DataFrame(data) - df.columns = ['open_time', - 'o', 'h', 'l', 'c', 'v', - 'close_time', 'qav', 'num_trades', - 'taker_base_vol', 'taker_quote_vol', 'ignore'] - df.index = [dt.datetime.fromtimestamp(x/1000.0) for x in df.close_time] - - return df - - -def coinpair(quote, interval='1d', base='USDT'): - '''returns ohlc data of the quote cryptocurrency with - the base currency (i.e. 'market'); base for alts must be either USDT or BTC''' - - btcusd = 1 if quote == 'BTC' else \ - get_bars('BTCUSDT', interval=interval)['c'].astype('float') \ - if base == 'USDT' else 1 - - base0 = 'USDT' if quote == 'BTC' else 'BTC' - - df = get_bars(quote + base0, interval=interval) - - df['close'] = df['c'].astype('float')*btcusd - df['open'] = df['o'].astype('float')*btcusd - df['high'] = df['h'].astype('float')*btcusd - df['low'] = df['l'].astype('float')*btcusd - - df.drop(['o', 'h', 'l', 'c'], axis=1, inplace=True) - print(quote, base, 'on {} candles'.format(interval)) - - return df - - -def test_ema(): - - coin = 'BTC' - market = 'USDT' - candles = '1M' - - df = coinpair(coin, interval=candles, base=market) - - # mpf.plot(df,type='candle',figratio=(5,2),figscale=0.5,\ - # title=coin+market+" ({} candles)".format(candles),\ - # yscale='log' - # ) - # - - ema25 = df['close'].ewm(span=25.0, adjust=False).mean() - mav25 = df['close'].rolling(window=25).mean() - - # mpf.plot(df, type='ohlc', mav=25) - - ap = [ - mpf.make_addplot(df, panel=1, type='ohlc', color='c', - ylabel='mpf mav', mav=25, secondary_y=False), - mpf.make_addplot(ema25, panel=2, type='line', width=2, color='c', - ylabel='calculated', secondary_y=False), - mpf.make_addplot(mav25, panel=2, type='line', width=2, color='blue', - ylabel='calculated', secondary_y=False) - - ] - mpf.plot(df, ylabel="mpf ema", type='ohlc', - ema=25, addplot=ap, panel_ratios=(1, 1)) - - -test_ema() diff --git a/tests/test_images/test_ema.png b/tests/test_images/test_ema.png deleted file mode 100644 index 0cc180b2537f8cd9b4e34ba152c41e74bb343bc3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57100 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 Date: Mon, 24 Oct 2022 16:11:13 -0400 Subject: [PATCH 12/22] make necessary changes after reversion --- src/mplfinance/plotting.py | 17 +++++-- tests/test_ema.py | 79 +++++++++++++++++++++++++++++++++ tests/test_images/test_ema.png | Bin 0 -> 57067 bytes 3 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 tests/test_ema.py create mode 100644 tests/test_images/test_ema.png diff --git a/src/mplfinance/plotting.py b/src/mplfinance/plotting.py index c8bcb406..09184bf7 100644 --- a/src/mplfinance/plotting.py +++ b/src/mplfinance/plotting.py @@ -532,8 +532,10 @@ def plot( data, **kwargs ): if ptype in VALID_PMOVE_TYPES: mavprices = _plot_mav(axA1,config,xdates,pmove_avgvals) + emaprices = _plot_ema(axA1, config, xdates, pmove_avgvals) else: mavprices = _plot_mav(axA1,config,xdates,closes) + emaprices = _plot_ema(axA1, config, xdates, closes) avg_dist_between_points = (xdates[-1] - xdates[0]) / float(len(xdates)) if not config['tight_layout']: @@ -599,6 +601,13 @@ def plot( data, **kwargs ): else: for jj in range(0,len(mav)): retdict['mav' + str(mav[jj])] = mavprices[jj] + if config['ema'] is not None: + ema = config['ema'] + if len(ema) != len(emaprices): + warnings.warn('len(ema)='+str(len(ema))+' BUT len(emaprices)='+str(len(emaprices))) + else: + for jj in range(0, len(ema)): + retdict['ema' + str(ema[jj])] = emaprices[jj] retdict['minx'] = minx retdict['maxx'] = maxx retdict['miny'] = miny @@ -1180,13 +1189,13 @@ def _plot_ema(ax,config,xdates,prices,apmav=None,apwidth=None): mean = pd.Series(prices).ewm(span=mav,adjust=False).mean() if shift is not None: mean = mean.shift(periods=shift[idx]) - mavprices = mean.values + emaprices = mean.values lw = config['_width_config']['line_width'] if mavc: - ax.plot(xdates, mavprices, linewidth=lw, color=next(mavc)) + ax.plot(xdates, emaprices, linewidth=lw, color=next(mavc)) else: - ax.plot(xdates, mavprices, linewidth=lw) - mavp_list.append(mavprices) + ax.plot(xdates, emaprices, linewidth=lw) + mavp_list.append(emaprices) return mavp_list diff --git a/tests/test_ema.py b/tests/test_ema.py new file mode 100644 index 00000000..feba71ec --- /dev/null +++ b/tests/test_ema.py @@ -0,0 +1,79 @@ + +import mplfinance as mpf +import requests # for making http requests to binance +import json # for parsing what binance sends back to us +import pandas as pd # for storing and manipulating the data we get back +import numpy as np # numerical python, i usually need this somewhere +# and so i import by habit nowadays + +import matplotlib.pyplot as plt # for charts and such +import datetime as dt # for dealing with times + +INTERVAL = '1d' + + +def get_bars(quote, interval=INTERVAL): + + root_url = 'https://api.binance.com/api/v1/klines' + url = root_url + '?symbol=' + quote + '&interval=' + interval + data = json.loads(requests.get(url).text) + df = pd.DataFrame(data) + df.columns = ['open_time', + 'o', 'h', 'l', 'c', 'v', + 'close_time', 'qav', 'num_trades', + 'taker_base_vol', 'taker_quote_vol', 'ignore' + ] + + df.index = [dt.datetime.fromtimestamp(x/1000.0) for x in df.close_time] + + return df + + +def coinpair(quote, interval='1d', base='USDT'): + '''returns ohlc data of the quote cryptocurrency with + the base currency (i.e. 'market'); base for alts must be either USDT or BTC''' + + btcusd = 1 if quote == 'BTC' else \ + get_bars('BTCUSDT', interval=interval)['c'].astype('float') \ + if base == 'USDT' else 1 + + base0 = 'USDT' if quote == 'BTC' else 'BTC' + + df = get_bars(quote + base0, interval=interval) + + df['close'] = df['c'].astype('float')*btcusd + df['open' ] = df['o'].astype('float')*btcusd + df['high' ] = df['h'].astype('float')*btcusd + df['low' ] = df['l'].astype('float')*btcusd + + df.drop(['o', 'h', 'l', 'c'], axis=1, inplace=True) + print(quote, base, 'on {} candles'.format(interval)) + + return df + + +def test_ema(): + + coin = 'BTC' + market = 'USDT' + candles = '1M' + + df = coinpair(coin, interval=candles, base=market) + + ema25 = df['close'].ewm(span=25.0, adjust=False).mean() + mav25 = df['close'].rolling(window=25).mean() + + ap = [ + mpf.make_addplot(df, panel=1, type='ohlc', color='c', + ylabel='mpf mav', mav=25, secondary_y=False), + mpf.make_addplot(ema25, panel=2, type='line', width=2, color='c', + ylabel='calculated', secondary_y=False), + mpf.make_addplot(mav25, panel=2, type='line', width=2, color='blue', + ylabel='calculated', secondary_y=False) + ] + + mpf.plot(df, ylabel="mpf ema", type='ohlc', + ema=25, addplot=ap, panel_ratios=(1, 1)) + + +test_ema() diff --git a/tests/test_images/test_ema.png b/tests/test_images/test_ema.png new file mode 100644 index 0000000000000000000000000000000000000000..204a8f0a65d0131ffdd823becad5b11c1a3df011 GIT binary patch literal 57067 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°HQ1-gav*1{s+r+&jTF}S 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 SxRnI|F*I1He^qbw{{IJ_EL;Hq literal 0 HcmV?d00001 From 4a38e07907146853b83e8c7e7811891b2c5aec66 Mon Sep 17 00:00:00 2001 From: andrewrgarcia Date: Mon, 24 Oct 2022 17:26:46 -0400 Subject: [PATCH 13/22] replace former example with csvread of google data; remove binance API call --- tests/test_ema.py | 68 ++++----------------------------- tests/test_images/test_ema.png | Bin 57067 -> 49453 bytes 2 files changed, 8 insertions(+), 60 deletions(-) diff --git a/tests/test_ema.py b/tests/test_ema.py index feba71ec..b0b25b5c 100644 --- a/tests/test_ema.py +++ b/tests/test_ema.py @@ -1,67 +1,15 @@ - import mplfinance as mpf -import requests # for making http requests to binance -import json # for parsing what binance sends back to us -import pandas as pd # for storing and manipulating the data we get back -import numpy as np # numerical python, i usually need this somewhere -# and so i import by habit nowadays - -import matplotlib.pyplot as plt # for charts and such -import datetime as dt # for dealing with times - -INTERVAL = '1d' - - -def get_bars(quote, interval=INTERVAL): - - root_url = 'https://api.binance.com/api/v1/klines' - url = root_url + '?symbol=' + quote + '&interval=' + interval - data = json.loads(requests.get(url).text) - df = pd.DataFrame(data) - df.columns = ['open_time', - 'o', 'h', 'l', 'c', 'v', - 'close_time', 'qav', 'num_trades', - 'taker_base_vol', 'taker_quote_vol', 'ignore' - ] - - df.index = [dt.datetime.fromtimestamp(x/1000.0) for x in df.close_time] - - return df - - -def coinpair(quote, interval='1d', base='USDT'): - '''returns ohlc data of the quote cryptocurrency with - the base currency (i.e. 'market'); base for alts must be either USDT or BTC''' - - btcusd = 1 if quote == 'BTC' else \ - get_bars('BTCUSDT', interval=interval)['c'].astype('float') \ - if base == 'USDT' else 1 - - base0 = 'USDT' if quote == 'BTC' else 'BTC' - - df = get_bars(quote + base0, interval=interval) - - df['close'] = df['c'].astype('float')*btcusd - df['open' ] = df['o'].astype('float')*btcusd - df['high' ] = df['h'].astype('float')*btcusd - df['low' ] = df['l'].astype('float')*btcusd - - df.drop(['o', 'h', 'l', 'c'], axis=1, inplace=True) - print(quote, base, 'on {} candles'.format(interval)) - - return df - +import pandas as pd def test_ema(): - coin = 'BTC' - market = 'USDT' - candles = '1M' + df = pd.read_csv('./examples/data/yahoofinance-GOOG-20040819-20180120.csv', parse_dates=True) + df.index = pd.DatetimeIndex(df['Date']) - df = coinpair(coin, interval=candles, base=market) + df = df[-50:] # show last 50 data points only - ema25 = df['close'].ewm(span=25.0, adjust=False).mean() - mav25 = df['close'].rolling(window=25).mean() + ema25 = df['Close'].ewm(span=25.0, adjust=False).mean() + mav25 = df['Close'].rolling(window=25).mean() ap = [ mpf.make_addplot(df, panel=1, type='ohlc', color='c', @@ -73,7 +21,7 @@ def test_ema(): ] mpf.plot(df, ylabel="mpf ema", type='ohlc', - ema=25, addplot=ap, panel_ratios=(1, 1)) - + ema=25, addplot=ap, panel_ratios=(1, 1) + ) test_ema() diff --git a/tests/test_images/test_ema.png b/tests/test_images/test_ema.png index 204a8f0a65d0131ffdd823becad5b11c1a3df011..e21f39215b9fc08f68160f142db8881dc81201dc 100644 GIT binary patch literal 49453 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�tGnQX8DU9!AwZ;P7&P-u+8=MWkgteIo1tO*qqt!P#hZ?Qj+;qVX zVr{jWIea)H3PM(n4!>TqUHq1%J^|0EbOp|@DE1BD#T?zXXa(cy8)Z9t&Kw+8FB3B$ zw{7F|Qx~gJsIgv0-O9>pcmHWWKs~5HD?Nh16yq$cP#sM!jrbBnq^_V@@TN3qfZ4M0 zmQ&6z&_l};pt~v%l1j=RMqbwyEn2bNwn=SOUJxTCOe1Qz9LVgJy5pM?UphSR`+xv;;rg)4qayaYEq^Ar$q9cmQ+3Xe{f3Leh_R5{g=AVyAx+q>a zNn(&ro}&PViy#)xCeXsUz%7XmOngPD!)L63I0Yb*ftaqToo!_I`TZ23tsx$kLlmj> z+475Rv{WwiU}!i2zJEgc#)M}$FP)7tM(A!aIHeyrVD4$PlG&ehiDySwXea>a!neI|dK6*3*ds7`m|L|0Q%t?5c zC=G48LnVw!KE}{{hry1GHRH#x3!Ku;a(wZa&%ZwfNk$9p{_X%rl~|?vx(RV7C#-f^ zq_yN}mL92s(4M%};hU19?iztfJ*^T)>r=^dHGe`Yboa>S!nU4Yg7~GI3l2xAkdf(r z39*6#;zy?)vT+@ZAa)Oq#tOBiZT|VZ%2&sJ^c|Jtwtd%xIG=hu=wDxDX7HweBX{nA zu-^NDItxs~{T`QL{3#I{Il~yIKK^?`zn@gPds{88#E|8=T@tc z@0J9`caRUhmurOR7Tlbk8t^4y_rU<`rFIzW{0c(#h+8Lw;5-B JYUGSx{s**M`CtG5 literal 57067 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°HQ1-gav*1{s+r+&jTF}S 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 SxRnI|F*I1He^qbw{{IJ_EL;Hq From bd18e036bf71029d1b28bca0e6dc4af39805f565 Mon Sep 17 00:00:00 2001 From: andrewrgarcia Date: Mon, 24 Oct 2022 17:50:10 -0400 Subject: [PATCH 14/22] reference_image testing --- .../ema01.png} | Bin tests/test_ema.py | 42 +++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) rename tests/{test_images/test_ema.png => reference_images/ema01.png} (100%) diff --git a/tests/test_images/test_ema.png b/tests/reference_images/ema01.png similarity index 100% rename from tests/test_images/test_ema.png rename to tests/reference_images/ema01.png diff --git a/tests/test_ema.py b/tests/test_ema.py index b0b25b5c..497171d3 100644 --- a/tests/test_ema.py +++ b/tests/test_ema.py @@ -1,7 +1,23 @@ +import os +import os.path +import glob import mplfinance as mpf import pandas as pd +import matplotlib.pyplot as plt +from matplotlib.testing.compare import compare_images -def test_ema(): +print('mpf.__version__ =',mpf.__version__) # for the record +print('mpf.__file__ =',mpf.__file__) # for the record +print("plt.rcParams['backend'] =",plt.rcParams['backend']) # for the record + +base='ema' +tdir = os.path.join('tests','test_images') +refd = os.path.join('tests','reference_images') + +IMGCOMP_TOLERANCE = 10.0 # this works fine for linux +# IMGCOMP_TOLERANCE = 11.0 # required for a windows pass. (really 10.25 may do it). + +def create_ema_image(tname): df = pd.read_csv('./examples/data/yahoofinance-GOOG-20040819-20180120.csv', parse_dates=True) df.index = pd.DatetimeIndex(df['Date']) @@ -20,8 +36,30 @@ def test_ema(): ylabel='calculated', secondary_y=False) ] + # plot and save in `tname` path mpf.plot(df, ylabel="mpf ema", type='ohlc', - ema=25, addplot=ap, panel_ratios=(1, 1) + ema=25, addplot=ap, panel_ratios=(1, 1), savefig=tname ) + +def test_ema(): + + fname = base+'01.png' + tname = os.path.join(tdir,fname) + rname = os.path.join(refd,fname) + + create_ema_image(tname) + + tsize = os.path.getsize(tname) + print(glob.glob(tname),'[',tsize,'bytes',']') + + rsize = os.path.getsize(rname) + print(glob.glob(rname),'[',rsize,'bytes',']') + + result = compare_images(rname,tname,tol=IMGCOMP_TOLERANCE) + if result is not None: + print('result=',result) + assert result is None + + test_ema() From 07adb02ecf6f2e386897672248437fa74f8ab969 Mon Sep 17 00:00:00 2001 From: Daniel Goldfarb Date: Thu, 27 Oct 2022 11:47:33 -0400 Subject: [PATCH 15/22] add tests;place mav color cycle into config --- src/mplfinance/plotting.py | 25 +++++++---- tests/reference_images/ema02.png | Bin 0 -> 73712 bytes tests/reference_images/ema03.png | Bin 0 -> 80038 bytes tests/test_ema.py | 69 ++++++++++++++++++++++++++++--- 4 files changed, 80 insertions(+), 14 deletions(-) create mode 100644 tests/reference_images/ema02.png create mode 100644 tests/reference_images/ema03.png diff --git a/src/mplfinance/plotting.py b/src/mplfinance/plotting.py index 09184bf7..fbde618a 100644 --- a/src/mplfinance/plotting.py +++ b/src/mplfinance/plotting.py @@ -124,6 +124,12 @@ def _valid_plot_kwargs(): 'Description' : 'Exponential Moving Average window size(s); (int or tuple of ints)', 'Validator' : _mav_validator }, + 'mavcolors' : { 'Default' : None, + 'Description' : 'color cycle for moving averages (list or tuple of colors)'+ + '(overrides mpf style mavcolors).', + 'Validator' : lambda value: isinstance(value,(list,tuple)) and + all([mcolors.is_color_like(v) for v in value]) }, + 'renko_params' : { 'Default' : dict(), 'Description' : 'dict of renko parameters; call `mpf.kwarg_help("renko_params")`', 'Validator' : lambda value: isinstance(value,dict) }, @@ -454,6 +460,13 @@ def plot( data, **kwargs ): else: raise TypeError('style should be a `dict`; why is it not?') + if config['mavcolors'] is not None: + config['_ma_color_cycle'] = cycle(config['mavcolors']) + elif style['mavcolors'] is not None: + config['_ma_color_cycle'] = cycle(style['mavcolors']) + else: + config['_ma_color_cycle'] = None + if not external_axes_mode: fig = plt.figure() _adjust_figsize(fig,config) @@ -1142,10 +1155,7 @@ def _plot_mav(ax,config,xdates,prices,apmav=None,apwidth=None): 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 + mavc = config['_ma_color_cycle'] for idx,mav in enumerate(mavgs): mean = pd.Series(prices).rolling(mav).mean() @@ -1178,11 +1188,8 @@ def _plot_ema(ax,config,xdates,prices,apmav=None,apwidth=None): 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 + + mavc = config['_ma_color_cycle'] for idx,mav in enumerate(mavgs): # mean = pd.Series(prices).rolling(mav).mean() diff --git a/tests/reference_images/ema02.png b/tests/reference_images/ema02.png new file mode 100644 index 0000000000000000000000000000000000000000..5b8807f5614c4d471d3823203ef1245e32a0face GIT binary patch literal 73712 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 eHy0uH@@f^mZ?(XjHZfEl*@Av;_w2X7n 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 O9aq;mnt8 Date: Mon, 31 Oct 2022 16:37:38 -0400 Subject: [PATCH 17/22] fix xlabel functionality --- src/mplfinance/_panels.py | 9 ++++++++- src/mplfinance/plotting.py | 21 ++++++++++++++++----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/mplfinance/_panels.py b/src/mplfinance/_panels.py index 7d8524a2..c1b8bef1 100644 --- a/src/mplfinance/_panels.py +++ b/src/mplfinance/_panels.py @@ -218,15 +218,22 @@ def _build_panels( figure, config ): return panels -def _set_ticks_on_bottom_panel_only(panels,formatter,rotation=45): +def _set_ticks_on_bottom_panel_only(panels,formatter,rotation=45,xlabel=None): bot = panels.index.values[-1] ax = panels.at[bot,'axes'][0] ax.tick_params(axis='x',rotation=rotation) ax.xaxis.set_major_formatter(formatter) + if xlabel is not None: + ax.set_xlabel(xlabel) + if len(panels) == 1: return + # [::-1] reverses the order of the panel id's + # [1:] all but the first element, which, since the array + # is reversed, means we take all but the LAST panel id. + # Thus, only the last (bottom) panel id gets tick labels: for panid in panels.index.values[::-1][1:]: panels.at[panid,'axes'][0].tick_params(axis='x',labelbottom=False) diff --git a/src/mplfinance/plotting.py b/src/mplfinance/plotting.py index 073864a0..72468b74 100644 --- a/src/mplfinance/plotting.py +++ b/src/mplfinance/plotting.py @@ -185,8 +185,8 @@ def _valid_plot_kwargs(): 'Description' : 'Axes Title (subplot title)', 'Validator' : lambda value: isinstance(value,(str,dict)) }, - 'xlabel' : { 'Default' : 'Date', # x-axis label - 'Description' : 'label for x-axis of main plot', + 'xlabel' : { 'Default' : None, # x-axis label + 'Description' : 'label for x-axis of plot', 'Validator' : lambda value: isinstance(value,str) }, 'ylabel' : { 'Default' : 'Price', # y-axis label @@ -653,10 +653,12 @@ def plot( data, **kwargs ): xrotation = config['xrotation'] if not external_axes_mode: - _set_ticks_on_bottom_panel_only(panels,formatter,rotation=xrotation) + _set_ticks_on_bottom_panel_only(panels,formatter,rotation=xrotation, + xlabel=config['xlabel']) else: axA1.tick_params(axis='x',rotation=xrotation) axA1.xaxis.set_major_formatter(formatter) + axA1.set_xlabel(config['xlabel']) ysd = config['yscale'] if isinstance(ysd,dict): @@ -705,7 +707,13 @@ def plot( data, **kwargs ): 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'] - + if apdict['xlabel'] is not None: + apdict['xlabel'] = None # set to None so `_addplot_apply_supplements()` won't apply it. + warnings.warn('\n\n ================================================================= '+ + '\n\n WARNING: make_addplot `xlabel` IGNORED in Panels mode.'+ + '\n Use `mpf.plot(...,xlabel=)` instead.'+ + '\n\n ================================================================ ', + category=UserWarning) aptype = apdict['type'] if aptype == 'ohlc' or aptype == 'candle': ax = _addplot_collections(panid,panels,apdict,xdates,config) @@ -783,7 +791,6 @@ def plot( data, **kwargs ): # working in `addplot`). axA1.set_ylabel(config['ylabel']) - axA1.set_xlabel(config['xlabel']) if config['volume']: if external_axes_mode: @@ -1250,6 +1257,10 @@ def _valid_addplot_kwargs(): 'Description' : 'label for y-axis (for this addplot)', 'Validator' : lambda value: isinstance(value,str) }, + 'xlabel' : { 'Default' : None, # x-axis label + 'Description' : 'x-axis label (for addplot ONLY when in external axes mode)', + 'Validator' : lambda value: isinstance(value,str) }, + 'ylim' : {'Default' : None, 'Description' : 'Limits for addplot y-axis as tuple (min,max), i.e. (bottom,top)', 'Validator' : lambda value: isinstance(value, (list,tuple)) and len(value) == 2 From 5f039a814e32a779eb73481d3cbe6daa45585659 Mon Sep 17 00:00:00 2001 From: Daniel Goldfarb Date: Mon, 31 Oct 2022 16:48:06 -0400 Subject: [PATCH 18/22] no addplot xlabel support; bump vers; example --- examples/plot_customizations.ipynb | 23 ++++++++++++----------- src/mplfinance/_version.py | 2 +- src/mplfinance/plotting.py | 18 +++++------------- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/examples/plot_customizations.ipynb b/examples/plot_customizations.ipynb index dc1642c1..e51b45a8 100644 --- a/examples/plot_customizations.ipynb +++ b/examples/plot_customizations.ipynb @@ -223,7 +223,7 @@ { "data": { "text/plain": [ - "'0.12.9b0'" + "'0.12.9b3'" ] }, "execution_count": 4, @@ -359,9 +359,16 @@ "\n", "---\n", "\n", - "#### Setting the Figure Title and the Y-axis Label:" + "#### Setting the Figure Title, the Y-axis Label, and the X-axis Label:" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "code", "execution_count": 7, @@ -369,7 +376,7 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAsUAAAItCAYAAADR3Af3AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAA9hAAAPYQGoP6dpAAB/tElEQVR4nO3deXiMV+M+8Hsm+yoiC5KILIiKXexCUES1lnpbWxMltGpNtZailiryWvK1lDaKELSq9qJUxV5tlVIiVBYiIUFkXyczvz/ym3kzss5kliTP/bmuXJJ5zpznPHOSuOfkPOeIZDKZDEREREREAibWdwOIiIiIiPSNoZiIiIiIBI+hmIiIiIgEj6GYiIiIiASPoZiIiIiIBI+hmIiIiIgEj6GYiIiIiASPoZiIiIiIBI+hmIiIiIgEj6GYiIiIiASPoZiIiIiIBI+hmIiIiIgEj6GYiIiIiASPoZiIiIiIBI+hmIiIiIgEj6GYiIiIiASPoZiIiIiIBI+hmIiIiIgEj6GYiIiIiASPoZiIiIiIBI+hmIiIiIgEj6GYiIiIiASPoZiIiIiIBI+hmIiIiIgEj6GYiIiIiASPoZiIiIiIBI+hmIiIiIgEj6GYiIiIiASPoZiIiIiIBI+hmIiIiIgEj6GYiIiIiASPoZiIiIiIBI+hmIiIiIgEj6GYiIiIiASPoZiIiIiIBI+hmIiIiIgEj6GYiIiIiATPUN8NIKK6IysrCwcOHMC5c+fw4MEDpKeno6ioCJaWlnByckKnTp3w7rvvwsPDo8J6njx5gg0bNuDy5ctITU2Fvb092rZti+nTp1f43BYtWpR7TCwWw9raGp6enujXrx/effddWFhYVOm6+vbti8TExErLrVu3Dm+88YbSY48fP8a2bdvw22+/4cmTJzA1NcVrr72G9957D3379i2znoKCAvz44484dOgQHj16hLy8PDg7O8Pf3x9BQUEwNTWtUrsrcvDgQcyfP1/xtbOzM06ePAljY+MKy3bu3BkRERHVPr+2nD9/Ht9//z1u3bqFtLQ0mJiYoGnTpujbty8CAwNhZWVV6jnVeb1//fVXLF68GM+ePQMADB8+HKtWrSq3/NmzZ/HDDz/gn3/+QXp6OiwtLeHt7Y2xY8fCz8+v+i8AEalNJJPJZPpuBBHVfrdv38aHH36oCAflMTQ0xLJly/D222+Xefz58+cYNmxYmfWYmJhgz549aN26dZnPrSgUv8rZ2Rnbt2+Hq6trpWXVDcXR0dEICAhAenp6meVnzZqFKVOmKD1WUFCAqVOn4sKFC2U+57XXXsPu3burHOjL82ooBoD58+dj/PjxFZatyaF45cqVCA8PL/e4s7Mzdu/ejUaNGikeU/f1TktLwxdffIGffvpJ6fGKQvHnn3+Offv2ldu+yZMnY/bs2eUeJyLt4kgxEVVbbm4upk6dqgiyzs7OGDVqFNzc3CCTyXD37l3s3bsXL1++hEQiweeff46OHTuiadOmperavXu3op4RI0agf//++Ouvv7Bt2zbk5+dj8+bN2LJlS6VtevfddzFo0CDF1+np6fjrr7/w/fffo7CwEI8fP8a0adNw+PBhGBgYVPla586dCy8vrzKPNW/eXPG5TCbDJ598ogjEAwcOxJtvvomEhARs3LgROTk5WL9+PXr27KkU8nfu3KkIaE5OTpg6dSrMzMzw7bff4s6dO4iKikJoaCgWLlxY5TZX1ZYtW/D222+XOZpa0/36669Kgfidd95B7969kZycjM2bN+P58+d4/PgxPv/8c2zdulVRTp3X+9KlS5g7dy6eP38OoPivEFKptML2/fjjj0qBeNiwYejbty/+/vtv7NixAzKZDGFhYfDx8YGvr68mXhIiUhFDMRFV2/nz5/H06VMAxQFh7969cHR0VBx//fXXMXjwYAwbNgyFhYWQSCT48ccf8cknn5Sq686dOwCKg/WKFSsgEonQr18/XLp0Cffu3UNcXFyV2uTq6oru3bsrPebv7w8vLy8sWLAAAHD//n1cunQJvXv3rvK1tmrVCl26dKm0XGRkJP79918AgJubG0JDQxXhOy8vD+vXr4dMJkN4eDjWrl0LACgsLFQKdqtXr0bHjh0BAO3atUO/fv0glUqxb98+zJo1C5aWllVud1WkpaXh66+/xqeffqrRenVhz549is979+6NL774QvG1jY0NPv74YwDAxYsXkZqaCltbW7Vf759++gnPnz+HmZkZpk+fjujoaBw9erTC9u3cuVPxeZ8+fRASEgKg+M2SgYGBIqh//fXXDMVEesIb7Yio2h49eqT43NraWikQy3l6emLlypUIDQ3FgQMH8OGHH5ZZl3xOq7W1NUQiEQAgJydHMSpX8k/f6hg6dKjSyPCtW7eqVV95fv31V8Xnfn5+SucsOZf4woULKCoqAgDcvHlTcZ22traKgAYAjRs3VoxQFxQU4PLlyxpra+fOnRXti4iIwJMnT1R6vlQqxfHjxzF58mT06NED3t7e8PHxwdtvv42NGzfi5cuXSuXff/99tGjRAi1atMD7779fZp2HDx9WlGnVqhVevHhRYRtcXFzw5ptv4s0338R7772ndKzktBqZTIaEhAQA1Xu9u3fvjmPHjmHixImV/qUhJycH9+/fV3zt7++vdHz06NGKz69fv17ptRKRdjAUE1G12djYKD5PS0srd17nm2++icGDB8Pb27vcUU75lIqYmBhkZmbi0aNHCAoKUgSFsua8qsLIyEipvTk5OdWqrzx3795VfN6sWTOlYx4eHorAn5GRgcePHwMonoMs5+npWarOkjcZRkVFaaytNjY2ijne+fn5+L//+78qPzcrKwuTJk3Cxx9/jPPnz+P58+coLCxERkYGbt++jU2bNmHIkCFKbz5Kzrv+888/kZWVVareX375RfF5jx490KBBgwrbsXTpUqxZswZr1qxBr169lI7FxsYqfW1iYgJA/dd70qRJ2LFjB1xcXCpsk1xmZqbS169eS+PGjWFkZAQAiulGRKR7DMVEVG3t2rVThDyg+IangQMHYuXKlThz5oxiNK4q+vfvD6A4nI0fPx7+/v7466+/YGBggM8//1ylqQ5lycvLQ2pqquLrska1K/L333/jgw8+UMwF9vf3R2hoaKlgJ59OAii/aQCKg7m1tbXia/nIZckR2lefAyiHKXmQ1oTs7GxMmzYNZmZmAICjR48qBcaKLFy4EJcuXQJQHDanTJmCr7/+Gp999hns7e0BFN88+dFHHynmVw8YMEARAgsLC0vd5Jabm6uoEwDeeustta+tsLBQaQ66paUl3N3dAaj/ele2esqrXr1J79Wfh/z8fEgkEsXX8u8HItIthmIiqrbmzZvj3XffVXosPj4e4eHhmDp1Knr06IGBAwfiyy+/xL179yqsKycnRxGYbt++DYlEgs6dO+PHH3/E2LFjq93WPXv2oOSiO6+OKlZm3bp1OHfuHJ49e4aCggLExsbi66+/xjvvvKM0TSAjI0PxuTxsllRymS95oC45oljWMmBlPUcTCgsL4ejoiMDAQADF0yFWr15d6fPu3LmDkydPKr5etmwZZs2aBT8/PwQGBmLfvn2Ka3/27Bn27t0LoHhq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f+Dh4aFYYoyIiFTDOcVEREREJHicPkFEREREgsdQTERERESCx1BMRERERILHUExEREREgsdQTERERESCx1BMRERERILHUExEREREgsdQTERERESCx1BMRERERILHUExEREREgsdQTERERESCx1BMRERERILHUExEREREgsdQTERERESCx1BMRERERILHUExEREREgsdQTERERESCx1BMRERERILHUExEREREgsdQTERERESCx1BMRERERILHUExEREREgsdQTERERESCx1BMRERERILHUExEREREgsdQTERERESCx1BMRERERILHUExEREREgsdQTERERESCZ6jvBpBuSKVSPH/+HABgbm4OkUik5xYRERERaZdMJkNOTg4AwM7ODmJx+ePBDMUC8fz5czg6Ouq7GURERER6kZycDAcHh3KPc/oEEREREQkeR4oFwtzcXPH51Rv3lb6ubUQiwK2xDeKS0iCT6bs1pAvsc+FhnwsT+114tN3nOTk56Nq+OQBUmn0YigWi5Bxic3NzmJtb6LE11SMSARYWFjA3L+QvTYFgnwsP+1yY2O/Co8s+r+x+Kk6fICIiIiLBYygmIiIiIsFjKCYiIiIiwWMoJiIiIiLBYygmIiIiIsFjKCYiIiIiwWMoJiIiIiLBYygmIiIiIsFjKCYiIiIiwWMoJiIiIiLBYygmIiIiIsEz1HcDiIiIiKi0+LgYZGVllXksNfUFTp04ioGD34KtbYMyy1haWqKpm4c2m1inMBQTERER1TDxcTHo79up0nL79u6s8PiZC9cYjKuIoZiIiIiohpGPEO/ctQteXi1Vfn509F0EBgSUO9JMpTEUExEREdVQXl4t0aFDB303QxB4ox0RERERCR5DMREREREJHkMxEREREQkeQzERERERCR5DMREREREJHkMxEREREQkeQzERERERlZKS/BTr161CSvJTfTdFJxiKiYiIiKiUlJRkbAwNQUpKsr6bohMMxUREREQkeAzFRERERCR4DMVEREREJHgMxUREREQkeIb6bgARERER6Ud8XAyysrLKPBbz4L7Sv2WxtLREUzcPrbRN1xiKiYiIiAQoPi4G/X07VVpu9ozJFR4/c+FanQjGDMVEREREAiQfIba2rgcDA9UjYVGRBBkZ6eWONNc2DMVEREREAmZgYAgjIyN9N0PveKMdEREREQkeR4qJ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BYiLi8OtW7cwf/58tG3bFv369QMADBgwAC1btsScOXNw69YtxMbGYv78+SgsLERQUBAAwNHREWPGjMGGDRtw9uxZJCYmYuvWrYiMjCxzFz0iIiIiqhjnFGvQ9OnT8ccffyi+fvr0KX799VcAwMqVKzFixAjs3LkTISEhGDp0KExNTeHn54d58+ZBLC5+f2JoaIhvv/0WK1euxMSJE1FQUID27dsjIiICDRs2VNQ9f/58WFpaYsmSJUhNTYWbmxtCQ0MVo8lEREREVHUMxRoUERFRaZnWrVtj9+7dFZaxs7PD2rVrKyxjaGiI4OBgjgwTERERaQCnTxARERGR4DEUExEREZHgMRQTERERkeAxFBMRERGR4DEUExEREZHgMRQTERERkeAxFBMRERGR4DEUExEREZHgMRQTERERkeAxFBMRERGR4FVrm+e0tDQcOXIEgYGBAICCggIsX74cf/31Fxo0aIApU6agW7du5T7/zz//VPvcPj4+aj+XiIiIiKgktUPx8+fPMWbMGCQkJGDUqFEwMTHBjBkzcP78echkMsTExODGjRs4dOgQPD09y6zjvffeg0gkUvncIpEIUVFR6jadiIiIiEiJ2tMntm3bhkePHsHExARpaWm4d+8ezp07BwBo3749mjZtisLCQoSHh1dYj0wmU+uDiIiIiEhT1B4pPnv2LEQiEXbt2gVHR0ccOXIEAODi4oLvvvsOT58+RZ8+fXDt2rVy65g2bVqpx65fv44rV66gdevWaN++PerVq4eXL1/ir7/+wt27dzF48GB06dJF3WYTEREREZWidihOTk6Gubk52rRpA6A4zIpEIvTq1QsA0LBhQ1hYWCA5ObncOl4NxRcuXMCWLVuwaNEijB07tlT57du3Y926dRg1apS6zSYiIiIiKkXt6RNmZmYoLCwEUDwF4u+//wYAtGvXTlGmsLAQRkZGVa5zw4YNMDAwwOjRo8s8HhAQAJFIhA0bNqjbbCIiIiKiUtQeKXZ2dsbt27exefNmFBYWIi0tDSKRCJ07dwYAXL16FQUFBWjSpEmV6/z3339RWFiIxMREuLi4lDqemJiIwsJC3LlzR91mExERERGVonYoHjZsGP755x9s3LgRQPGKEJ06dYKjoyMePHiASZMmQSQSoU+fPlWu09raGs+fP8eECRMwceJEeHt7w9raGhkZGbh9+za+/fZbAICxsbG6zSYiIiIiKkXtUPyf//wH58+fx4ULFwAAjRs3xvLlywEAtra2KCwshKurKyZMmFDlOt944w2Eh4fj8ePHWLp0aZllRCIRBgwYoG6ziYiIiIhKUTsUGxsbIywsDLGxscjJyUGLFi0U84dtbW0xe/Zs/Oc//4GNjU2V65w1axYePnyIyMjIcst07doVc+fOVbfZRERERESlVGtHOwBwd3cv8/FJkyapXJepqSm2bNmCa9euITIyEnFxccjJyYG5uTlcXV3h6+tb4Q55JV27dg22traK9u3Zswd79+5FUlISnJycMHbs2HJv6CMiItKncd9E4kV2nlbP0cDCFLs/8NPqOYhqk2qH4pMnT+LYsWO4e/cuXr58qViFYvny5fjoo49ga2urcp2dOnVCp06dqtWuJUuWYOHChXB3d8eePXuwZs0aBAQEwN3dHXFxcVi7di2kUmmZS78RERHp04vsPBQYW2v5HBlarZ+otlE7FBcUFGDatGm4ePEigOJl2eRbNufk5GD37t24cuUK9u7dq9IUCqB4DeSTJ08qgnZYWBgA4JdffsHrr79epToePXqkWMFi//79+PzzzzF8+HDF8RYtWmD9+vUMxURERESk/jrFYWFhuHDhAmQyGdzd3WFo+L98nZWVBQMDA8TFxSlWjKiqrVu3on///ggJCcGRI0cUobugoADTp0/HjBkzqrTNs4WFBV6+fAkASElJQcuWLZWOt2rVCk+ePFGpbURERERUN6kdio8dOwaRSISQkBAcP34clpaWimMODg7Yvn07ZDIZfvnllyrXeeTIEaxduxaFhYWlgq884P7yyy/Yu3dvpXX17t0be/bsAQB06dIFJ0+eVDp+4sQJNG3atMptIyIiIqK6S+1QnJSUBBMTEwwdOrTM4126dIGZmVmF2zy/KiIiAiKRCKNGjcKVK1eUpl3Y29tj1qxZkMlkOHDgQKV1zZ49G3/++SfGjBmDhg0bIjw8HGPGjMGiRYswbtw4bNq0CR9//HGV20ZEREREdZfaodjc3Bz5+fl48eJFmcejo6ORm5sLExOTKtcZExMDQ0NDLFiwoNQNemKxGB988AFMTU3x6NGjSuuyt7fHoUOH0KlTJ8U0j1u3buHy5ctwdHTEd999h969e1e5bURERERUd6l9o127du1w4cIFTJ48GYGBgZBIJACA8+fP4969e4pR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EREREQkeQzERERERCR5DMREREREJHkMxEREREQkeQzERERERCR5DMREREREJHkMxEREREQkeQzERERERCR5DMREREREJHkMxEREREQkeQzERERERCR5DMREREREJHkMxEREREQkeQzERERERCR5DMREREREJnqG+G0C6IZPJFJ/n5OTosSVlkxbmQQpjrdYPgOfgOWrdOeTnERXk8Rw8B8+hx3MAtf/3ia7OkZOTXeXyIhGQnW2EnJxslIgqGlMy88gqOYFIVlkJqhNSUlLg6Oio72YQERER6UVycjIcHBzKPc7pE0REREQkeBwpFgipVIrnz58DAMzNzSESifTcIiIiIiLtkslkiikUdnZ2EIvLHw9mKCYiIiIiweP0CSIiIiISPIZiIiIiIhI8hmIiIiIiEjyGYiKqs3jLBFHdx59z0hSGYiKqs+SrrEilUj23hLQtJycHJ0+eREFBgb6bQnrCn3OqLu5oR3WWVCqFVCqFoSG/zYUkJycHhw8fRmJiIho0aIA33ngDjo6OkEqlFS7FQ7VXVlYW+vXrhxEjRsDf31/fzSEdyM3NxZ49e/D48WNYWlpi9OjRcHJy4s85VQuXZKM6KScnB3PmzMHw4cPh6+sLIyMjfTeJdCArKwtvv/027O3tkZKSguzsbDg6OmLr1q1o0KCBvptHWpCVlYW33noLrVu3xvr16/XdHNKBrKwsvPPOO2jQoAHS0tLw7NkzyGQy7N+/H02aNNF386gW49spqpN27dqFM2fOYO3atfj9999RVFSk7yaRlhUUFGDKlCnw9vbGN998gxMnTmDGjBl4+vQpbty4oSjHcYC6IysrC2+++Sa8vb0Vgbisn3X2ed1RWFiITz75BM2bN0dYWBgOHz6MFStWwMLCAmvXrkVhYaG+m0i1GEMx1UnJycno3LkzbGxsMG/ePPz222+QSCT6bhZpUVRUFNLS0vDhhx/CwsIChoaGGD58OOrVqweg+K8Hubm5EIlEDEl1QF5eHgYPHozWrVtjw4YNAIrfGBkYGAAAUlNTkZSUhKysLPZ5HfL06VM8fvwYb7/9NszMzGBgYIC+ffuiW7duiI6O5rxiqhaGYqqTXrx4gb59+2L16tVwdHTEZ599hqtXrzIY12FPnz7Fw4cPlUYKpVIpCgoK8O2332LIkCF49913cf36dYhEIv7nWcvdvXsXL1++hIGB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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAsUAAAJGCAYAAACz7/huAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAA9hAAAPYQGoP6dpAACDzklEQVR4nO3deXhN1+L/8ffJJDJJQxJzJNRQY5GaKoqWUr2G9raK0po6GZoOhmqLXi2uki9aWkrNHc1FtYoqOimlxlYEESSIkEFkOr8/8su5jgwynyT783oeT0/2Xnvttc9KTj9ZWXttk9lsNiMiIiIiYmB2tm6AiIiIiIitKRSLiIiIiOEpFIuIiIiI4SkUi4iIiIjhKRSLiIiIiOEpFIuIiIiI4SkUi4iIiIjhKRSLiIiIiOEpFIuIiIiI4SkUi4iIiIjhKRSLiIiIiOEpFIuIiIiI4SkUi4iIiIjhKRSLiIiIiOEpFIuIiIiI4SkUi4iIiIjhKRSLiIiIiOEpFIuIiIiI4SkUi4iIiIjhKRSLiIiIiOEpFIuIiIiI4SkUi4iIiIjhKRSLiIiIiOEpFIuIiIiI4SkUi4iIiIjhKRSLiIiIiOEpFIuIiIiI4SkUi4iIiIjhKRSLiIiIiOEpFIuIiIiI4SkUi4iIiIjhKRSLiIiIiOEpFIuIiIiI4SkUi4iIiIjhKRSLiIiIiOEpFIuIiIiI4SkUi4iIiIjhOdi6ASJSdsTFxbF69Wp27tzJyZMnuXbtGqmpqbi5uVGtWjVatmzJk08+Se3atXOs58KFC8yZM4c9e/YQHR2Nt7c3TZs2ZeTIkTkeW69evWz32dnZ4eHhQZ06dejcuTNPPvkkrq6uubquTp06ERERccdys2bN4pFHHrHadu7cORYtWsTPP//MhQsXcHZ25p577uHpp5+mU6dOWdaTlJTE119/zdq1azl79iyJiYlUr16dbt26MXToUJydnXPV7pysWbOG8ePHW76uXr06W7ZswcnJKcey9913H8uXLy/w+YvKjz/+yOeff86hQ4eIiYmhXLly1KpVi06dOjFo0CDc3d0zHVOQ9/uHH35g4sSJXLp0CYDevXszbdq0bMtv376dL7/8kr/++otr167h5uZGo0aN6N+/Px07diz4GyAi+WYym81mWzdCREq/w4cP8/zzz1vCQXYcHBx45513eOyxx7Lcf/nyZXr16pVlPeXKlWPlypU0btw4y2NzCsW3q169OosXL8bPz++OZfMbio8fP87AgQO5du1aluVffvllXnjhBattSUlJvPTSS+zatSvLY+655x5WrFiR60CfndtDMcD48eN55plncixbkkPx1KlTWbJkSbb7q1evzooVK6hSpYplW37f75iYGP7zn//wzTffWG3PKRS//fbbfPHFF9m2b/jw4bz66qvZ7heRoqWRYhEpsBs3bvDSSy9Zgmz16tXp27cv/v7+mM1mjh07xqpVq7h69SopKSm8/fbbtGjRglq1amWqa8WKFZZ6+vTpw4MPPsgff/zBokWLuHnzJvPmzWP+/Pl3bNOTTz7Jww8/bPn62rVr/PHHH3z++eckJydz7tw5RowYwbp167C3t8/1tY4dO5b69etnua9u3bqW12azmddee80SiLt27cqjjz5KeHg4c+fOJSEhgdmzZ3P//fdbhfylS5daAlq1atV46aWXKF++PJ988glHjhzh6NGjhISE8Oabb+a6zbk1f/58HnvssSxHU0u6H374wSoQP/HEE3To0IHIyEjmzZvH5cuXOXfuHG+//TYLFy60lMvP+717927Gjh3L5cuXgfS/QqSlpeXYvq+//toqEPfq1YtOnTrx559/8umnn2I2m1mwYAGBgYEEBQUVxlsiInmkUCwiBfbjjz9y8eJFID0grFq1Cl9fX8v+hx56iO7du9OrVy+Sk5NJSUnh66+/5rXXXstU15EjR4D0YP3ee+9hMpno3Lkzu3fv5sSJE4SFheWqTX5+frRt29ZqW7du3ahfvz4TJkwA4O+//2b37t106NAh19fasGFDWrVqdcdyO3bs4J9//gHA39+fkJAQS/hOTExk9uzZmM1mlixZwsyZMwFITk62CnYzZsygRYsWADRr1ozOnTuTlpbGF198wcsvv4ybm1uu250bMTExfPTRR7z++uuFWm9xWLlypeV1hw4d+M9//mP52tPTk1deeQWAn376iejoaLy8vPL9fn/zzTdcvnyZ8uXLM3LkSI4fP86GDRtybN/SpUstrx944AGmT58OpP+yZG9vbwnqH330kUKxiI3oRjsRKbCzZ89aXnt4eFgF4gx16tRh6tSphISEsHr1ap5//vks68qY0+rh4YHJZAIgISHBMip365++86Nnz55WI8OHDh0qUH3Z+eGHHyyvO3bsaHXOW+cS79q1i9TUVAAOHjxouU4vLy9LQAOoWrWqZYQ6KSmJPXv2FFpb77vvPkv7li9fzoULF/J0fFpaGps2bWL48OG0a9eORo0aERgYyGOPPcbcuXO5evWqVflnn32WevXqUa9ePZ599tks61y3bp2lTMOGDbly5UqObahRowaPPvoojz76KE8//bTVvlun1ZjNZsLDw4GCvd9t27Zl48aNDBky5I5/aUhISODvv/+2fN2tWzer/U899ZTl9f79++94rSJSNBSKRaTAPD09La9jYmKyndf56KOP0r17dxo1apTtKGfGlIrQ0FBiY2M5e/YsQ4cOtQSFrOa85oWjo6NVexMSEgpUX3aOHTtmeX333Xdb7atdu7Yl8F+/fp1z584B6XOQM9SpUydTnbfeZHj06NFCa6unp6dljvfNmzf5v//7v1wfGxcXx7Bhw3jllVf48ccfuXz5MsnJyVy/fp3Dhw/zwQcf0KNHD6tfPm6dd/37778TFxeXqd7vv//e8rpdu3ZUrFgxx3ZMnjyZ999/n/fff5/27dtb7Tt16pTV1+XKlQPy/34PGzaMTz/9lBo1auTYpgyxsbFWX99+LVWrVsXR0RHAMt1IRIqfQrGIFFizZs0sIQ/Sb3jq2rUrU6dOZdu2bZbRuNx48MEHgfRw9swzz9CtWzf++OMP7O3tefvtt/M01SEriYmJREdHW77OalQ7J3/++SfPPfecZS5wt27dCAkJyRTsMqaTgPUvDZAezD08PCxfZ4xc3jpCe/sxYB2mMoJ0YYiPj2fEiBGUL18egA0bNlgFxpy8+eab7N69G0gPmy+88AIfffQRb7zxBt7e3kD6zZMvvviiZX51ly5dLCEwOTk5001uN27csNQJ8K9//Svf15acnGw1B93NzY2AgAAg/+/3nVZPud3tN+nd/vNw8+ZNUlJSLF9nfD+ISPFSKBaRAqtbty5PPvmk1bbTp0+zZMkSXnrpJdq1a0fXrl159913OXHiRI51JSQkWALT4cOHSUlJ4b777uPrr7+mf//+BW7rypUruXXRndtHFe9k1qxZ7Ny5k0uXLpGUlMSpU6f46KOPeOKJJ6ymCVy/ft3yOiNs3urWZb4yAvWtI4pZLQOW1TGFITk5GV9fXwYNGgSkT4eYMWPGHY87cuQIW7ZssXz9zjvv8PLLL9OxY0cGDRrEF198Ybn2S5cusWrVKiB9asyt82ZvnWoC6fN+ExMTgfRAmfGLUl6lpaUxYcIEq1HeJ554wjJFp7jebzc3N6tR5dtXrPj222+tvicLs29FJPcUikWkUEyaNInXXnstyxE3SA/Jy5Yto2fPnvznP//JdLd+bGwsI0eOZPDgwSQnJ1u2Dx48mOXLl3PPPfcA6dMqnnjiCZYsWWI14nu7M2fOsHfvXsu/rVu3Wv7EnqFr1655HvWrVq0a77zzDnPnzqV3796W7aGhoVY3d9068mdnl/mj9tZ5qBkB8NbrzmqealbHFKZhw4bh5eUFpK+wsHfv3hzL3zrFwdPTk0cffdRqf7Vq1axG9n/88UfL6x49elhe79q1y+r9urXeLl265Gtd5pSUFMaNG8f69est2wICAhgxYoTl6+J8v3v16mV5vXv3bl577TW+++47PvzwQyZOnJip7SJS/LT6hIgUCpPJxLBhw3j66actQfT333/nn3/+sdxIBulzJlesWEHFihV58cUXgfRw8swzz3D48GEgfWTNwcGBmJgY1q1bx/Dhw7nrrrsA2LJlCwcPHuTgwYM0b97cEuJu98UXX+S4JmzLli159913c3Vto0aNsozedenSBR8fH8trk8nEmjVrLG17++238fT0xNnZmRs3bgBkuVzXrYEsI/TdGv5ufc9yOqYwubm58eKLLzJlyhQA/vvf/7J27dpsy588edLyuk6dOlkGy7vvvptvv/0WwGrlkE6dOuHi4kJCQgLXr1/n999/p02bNiQnJ7Nz505LufxMnbh58yajR49mx44dlm21atXi008/tZrKUJzv97Bhw9i5cyd//fUXABs3bmTjxo0AuLu7U7lyZU6fPg2Ai4tLgc4lIvmjkWIRKVTOzs506tSJN998k/Xr1/Pbb7/x0UcfZXpa1/Llyy1/Mv78888tgbhOnTps2bKFd955B4Do6GjL65SUFMvIn7+/P02aNMl1u+zs7KhUqRJt2rThvffeY/ny5blej7dXr14MGDCAAQMGWAJxhltHi9PS0ixzcW+9kTAjHN/q1pHHjPnFt7Ynq5HJW7cV1VrCffv2pWbNmkD6zYLr16/HwSHr8ZP4+HjL6+yC3K3bby3v7OxM586dLV9nTKH49ddfLVNPfH19ad26dZ7af+PGDYYOHWoViO+55x6WL19O5cqVrcoW5/tdrlw5li5dyvPPP0/NmjVxdHSkUqVK9OjRg6+++srySx9g9VpEio9CsYgUKTc3Nzp27MhHH31Ev379LNujo6Mt0x+2bdtm2T5mzBh8fHzo2rWrZZWCzZs3s3jxYr7++mvL8m+PP/54jucdM2YMJ06csPw7duwYe/bsYcmSJTz22GNZTmnIj9tXEshYzeLWJ+XdPs0jMTHRaj5rRtmMMJrVMYDVU/6yevBJYXB0dCQ4ONjy9Zw5c7INxbcG/1sD761unR97+4ojt06hyBgdvvV74ZFHHslTP6WkpDB69Gh+++03y7auXbuyatWqTL/MQPG/366urgQHB/P9999z+PBh9uzZw8yZM/H397d6YmJWK2GISNFTKBaRArl27RrLly/nrbfeom/fvuzfvz/bsrc/lCAjbN26UsOto3n/+c9/aNCgAQDvv/8+ISEhAPj4+BTKTXe5ER4ezo8//sjq1autbirLcGvbASpVqgSkP+Qjw63TDCB9/vGt5atWrQpAo0aNrMrcevPV7cdl96jrwtCtWzdL/REREWzevDnLcreGt5MnT2Y5BeHWVSxuX5quXbt2ljno4eHhnDlzhu3bt1v29+zZM0/t/u9//2s1b7l///7Mnj07yxsdwTbvd1xcXKZfIMLDw4mKigLS/2pw+/skIsVDoVhECsTJyYlZs2bx5ZdfcuDAAd5///1sb0q6NbBUq1aNChUqANYP5Lh1pNDV1ZWPPvoILy8vUlNTiYmJAeDFF1/MNugUtq1btzJ8+HDeeOMN3njjjUzLad06b7lChQqWEH/r1IAdO3ZYBcatW7daXnfu3NmynF39+vWpVq0aAFevXmXfvn2WcqdOnbI8Ic/d3Z02bdpY9o0bN87yoItbR1/zy2QyWT3V7vbVITJ06dLF8vratWts2rTJan9YWJjVcmtdu3a12u/o6Gi1bfHixURGRgLpK5pk9zjtrPz8888sW7bM8vVTTz3F22+/bbVU4O3y+37nx/Tp0wkKCqJFixbMnj3bat+nn35qed21a1fL6isiUrx0o52IFEj58uUZMWIE//3vfwH4448/6NmzJ3369KFWrVo4OTlx6dIltm3bZhWKhwwZYnn96KOP8vPPPwPw4YcfEhkZSbt27TCbzWzfvj3TE9EWLFhASkoKVatWxdXVNc/zTvOid+/ezJs3j/j4eBISEhg0aBDPPvssbm5ufPvtt1YB97nnnrMEmlatWtGsWTP+/PNPTp8+zauvvkqPHj04efKkJQQ5OjoybNgwq/MNHz7cshrBuHHjGDFiBI6OjsyfP98ykjl48GDLsmJFpVWrVjzwwAPs3LkzyxsFARo0aMAjjzxiCcNvv/02586do2HDhpw9e5YFCxZYblYLCAjIcspLjx49LL9YfP3115btebnBzmw2M336dMv74+LiwgMPPJDt6hl16tSxTKfIz/u9ceNGqzWOb31a3d9//82CBQssX3fo0IF69epRt25dFi9eDMCKFStwd3enYcOG7N692/KIahcXF1566aVcX7eIFC6T+fa/F4mI5MOsWbNYsGBBpj9B387e3p5hw4ZZzVtNS0tj7NixbNiwIdvjvL29qVu3bqbH7fbu3Ztp06YB1o/zHTNmjFXwLogdO3YwevRobt68mW2Zp556iokTJ1qNTIaHh9O3b98sH15iZ2fHu+++S58+fay2m81mgoODs5yqAXD//ffz8ccfW83zHTdunGWViLp161pWNcjJmjVrGD9+PJD+mOfly5dnKvP333/Ts2dPq1B8e9m4uDhGjRqV42Ona9WqxYIFC6zmWWdIS0vjgQcesIwQQ/p7s2PHjkw3xmUnIiLC6tHZdzJ16lTL+56f9/vpp5+2mrecm3OlpaXxwgsvWK2scStnZ2fmzJlT4IfTiEj+aaRYRArFK6+8Qs+ePVmzZg379u3jzJkzlpusXF1d8fPzo2XLlvTp0yfTjUR2dnbMmDGDhx56iDVr1nD48GGuXr2Ks7MztWvX5qGHHqJv3764u7uzdetWPv/8cw4dOkRKSorV1Iui0rFjRzZt2sTSpUvZs2cP58+fJzU1FS8vL5o1a0bfvn1p27ZtpuNq1KjBhg0bmDdvHjt27CAqKgp3d3eaNWvG0KFDadGiRaZjTCYTISEhtGnThq+//toyH7lWrVqWVTCyWvosQ8YjjAtD3bp16dWrl2XJuay4ubnxySefsGnTJtatW8fRo0e5fv06Li4u1KlTh65du/LEE09kuzqFnZ0d3bt3t5pCEBgYmOtADNzxF7GcFPT9zi07Ozs++OAD1q9fz+rVqzl79izXrl3D29ubtm3bMnz48Cx/aRCR4qORYhGRMiA0NJTu3btbVvoQEZG80Y12IiJlwO7duwFo1qyZbRsiIlJKKRSLiJRycXFxLFq0CEdHx0yPWhYRkdzRnGIRkVLu+PHj/Pvf/6Z27dqWJcZERCRvNKdYRERERAxP0ydERERExPAUikVERETE8BSKRURERMTwFIpFRERExPAUikVERETE8BSKRURERMTwFIpFRERExPAUikVERETE8BSKRURERMTwFIpFRERExPAUikVERETE8BSKRURERMTwFIpFRERExPAUikVERETE8BSKRURERMTwFIpFRERExPAUikVERETE8BSKRURERMTwFIpFRERExPAUikVERETE8BSKRURERMTwFIpFRERExPAUikVERETE8BSKRURERMTwFIpFRERExPAUikVERETE8BSKRURERMTwFIpFRERExPAUikVERETE8BSKRURERMTwHGzdACkeaWlpXL58GQAXFxdMJpONWyQiIiJStMxmMwkJCQBUqlQJO7vsx4MVig3i8uXL+Pr62roZIiIiIjYRGRmJj49Ptvs1fUJEREREDE8jxQbh4uJief3Lgb+tvi5tTCbwr+pJ2PkYzGZbt0aKg/rceNTnxqR+N56i7vOEhARa31sX4I7ZR6HYIG6dQ+zi4oKLi6sNW1MwJhO4urri4pKsD02DUJ8bj/rcmNTvxlOcfX6n+6k0fUJEREREDE+hWEREREQMT6FYRERERAxPoVhEREREDE+hWEREREQMT6FYRERERAxPoVhEREREDE+hWEREREQMT6FYRERERAxPoVhEREREDE+hWERERKSUiYq8yOxZ04iKvGjrppQZDrZugIiIiIhkdjoslLi4uCz3hZ78m7kh06nlX5vadepmWcbNzY1a/rWLsollikKxiIiISAlzOiyUB4Na3rHcq6OG57h/2659Csa5pFAsIiIiUsJkjBAvXbaM+vUb5Pn448ePMWjgwGxHmiUzhWIRERGREqp+/QY0b97c1s0wBN1oJyIiIiKGp1AsIiIiIoanUCwiIiIihqdQLCIiIiKGp1AsIiIiIoanUCwiIiIimRjtqXkKxSIiIiKSSVRUJHNDphMVFWnrphQLhWIRERERMTyFYhERERExPIViERERETE8hWIRERERMTyFYhERERExPAdbN0BEREREbON0WChxcXFZ7gs9+bfVf7Pi5uZGLf/aRdK24qZQLCIiImJAp8NCeTCo5R3LvTpqeI77t+3aVyaCsUKxiIiIiAFljBB7eFTA3j7vkTA1NYXr169lO9Jc2igUi4iIiBiYvb0Djo6Otm6G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8PDg+vXrHD58mE8++QQAJyen/DZbRERERCSTfIfif//73/z444/s2rULgKpVqzJlyhQAvLy8SE5Oxs/Pj8GDB+e6zkceeYQlS5Zw7tw5Jk+enGUZk8lEly5d8ttsEREREZFM8h2KnZycWLBgAadOnSIhIYF69epZ5g97eXnx6quv8u9//xtPT89c1/nyyy9z5swZduzYkW2Z1q1bM3bs2Pw2W0REREQkkwI90Q4gICAgy+3Dhg3Lc13Ozs7Mnz+fffv2sWPHDsLCwkhISMDFxQU/Pz+CgoJyfELerfbt24eXl5elfStXrmTVqlWcP3+eatWq0b9//2xv6BMREbGlAR/v4Ep8YpGeo6KrMyue61ik5xApTQocirds2cLGjRs5duwYV69etaxCMWXKFF588UW8vLzyXGfLli1p2bJlgdo1adIk3nzzTQICAli5ciXvv/8+AwcOJCAggLCwMGbOnElaWlqWS7+JiIjY0pX4RJKcPIr4HNeLtH6R0ibfoTgpKYkRI0bw008/AenLsmU8sjkhIYEVK1awd+9eVq1alacpFJC+BvKWLVssQXvBggUAfP/99zz00EO5quPs2bOWFSy++uor3n77bXr37m3ZX69ePWbPnq1QLCIiIiL5X6d4wYIF7Nq1C7PZTEBAAA4O/8vXcXFx2NvbExYWZlkxIrcWLlzIgw8+yPTp01m/fr0ldCclJTFy5EhGjRqVq8c8u7q6cvXqVQCioqJo0KCB1f6GDRty4cKFPLVNRERERMqmfIfijRs3YjKZmD59Ops2bcLNzc2yz8fHh8WLF2M2m/n+++9zXef69euZOXMmycnJmYJvRsD9/vvvWbVq1R3r6tChAytXrgSgVatWbNmyxWr/5s2bqVWrVq7bJiIiIiJlV75D8fnz5ylXrhw9e/bMcn+rVq0oX758jo95vt3y5csxmUz07duXvXv3Wk278Pb25uWXX8ZsNrN69eo71vXqq6/y+++/069fPypXrsySJUvo168fb731FgMGDOCDDz7glVdeyXXbRERERKTsyncodnFx4ebNm1y5ciXL/cePH+fGjRuUK1cu13WGhobi4ODAhAkTMt2gZ2dnx3PPPYezszNnz569Y13e3t6sXbuWli1bWqZ5HDp0iD179uDr68tnn31Ghw4dct02ERERESm78n2jXbNmzdi1axfDhw9n0KBBpKSkAPDjjz9y4sQJy6hv48aNc12nyWTKcb7w1atXuXnzpuWGvjtxd3fnlVde0YiwiIiIiOQo3yPFQ4cOxWQycfToUcaOHUt8fDwAzz//PCEhIVy6dAmTycSQIUNyXWfdunVJTU1l0qRJVtMubty4wZ9//sno0aMxm83UqVMnv80WEREREckk3yPFgYGBTJ06lcmTJ5OQkJBpf/ny5XnzzTdz/bANgMcff5w///yTNWvWsGbNGsv25s2bW16bTCYee+yx/DabxMRE/vzzT06fPs316+lrNHp4eFCrVi2aNWuGs7NzvusWERERkdKpQA/v6NmzJx07duT777/n77//Jj4+Hjc3N+rVq0enTp2oUKFCnup7/PHHOXToEF9++aVl2+1TKnr37k3fvn3z3NZr164REhLC+vXrSUzM+ilBzs7O9OrVi5dffjnPbRcRERGR0itfoTg1NZWNGzcC8OijjxZo5PZ277zzDg8//DDr16/PFLQfeeQR7r///jzXef36dZ566ilOnTpF+fLladu2LbVq1bIsIxcXF8fp06fZv38/n332Gb/++itffPEF7u7uhXZdIiIiIlJy5SsU29nZ8eabb+Lo6EivXr0KuUnQtm1b2rZtW2j1ffDBB5w6dYpnnnmGkSNH4urqmmW5+Ph45syZw9KlS/nwww8ZN25cobVBREREREqufN1oZzKZuOeee0hMTMzTOsS2sm3bNlq3bs24ceOyDcSQ/hS88ePHc9999/Hdd98VYwtFRERExJbyPad42rRpvP766wwbNoxXXnmFJk2aZFpb+E5uf/RybmWsepFbly5dokePHrku36xZMw4cOJCfpomIiIhIKZTvUPziiy+SnJzM+fPneeGFF7Itl1OAzWlN4sLk6elJWFhYrsuHhoZaPU1PRERERMq2fIfi06dP3/FhG3cSGBiYadv169c5ceIETk5OBAQEUKFCBa5evcqpU6dITU2lefPmVK5cOU/nuf/++1m3bh0rV66kf//+OZZdsWIF27dvp3fv3nk6h4iIiIiUXgVap7igli9fbvX16dOnGTBgAP/617+YMGGC1bJo0dHRTJ48mcOHDzNjxow8nefll1/mxx9/ZMqUKXz66ae0a9cOf39/q9UnwsLC2LNnDxEREVSsWJHRo0cX+PpEREREpHTIdyi+PdAWhqlTpxITE8PkyZMpX7681T4vLy/e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7hSKevXqZftvzZo1hXYeETEe3WgnIiK5kpqaanldrlw5y+uM0FupUiVatmxp2d6tWzeOHj3K77//TmRkJL6+vgVuQ06rT9zaJhGRvFIoFhGRXDl37pzldUbAjY2NZceOHQBcvnyZBg0aZDouLS2Nb775hiFDhhS4DVp9QkSKiqZPiIhIruzatcvy+r777gPS1yK+fSWKrGgVChEp6TRSLCIid/Tbb7/x448/AlC3bl0aN24M/C/sli9fnkWLFmFvb2913KpVq1i/fj3Hjx/nn3/+4e677y7ehouI5JJCsYiIZOvmzZts2bKFKVOmYDabcXBw4N133wXSl2b7/fffAbj//vtp0aJFpuOTkpIsT7rbsGEDr776avE1XkQkDxSKRUTEyosvvoiDQ/r/HmJjYy032Lm7uzNt2jSaNGkCpIdcs9kMYFmX+HYtW7akYsWKXLlyhW+++YZXXnnF6lHOeZXxpL3svP322zzyyCP5rl9EjEuhWERErMTFxVleu7u7U61aNTp16kTfvn2tVpDYuHEjAI6OjnTs2DHLuuzs7HjwwQf54osvOH/+PPv27SMwMDDfbYuJiclx/82bN/Ndt4gYm8mc8Wu+iIiIiIhBafUJERERETE8hWIRERERMTyFYhERERExPIViERERETE8hWIRERERMTyFYhERERExPIViERERETE8hWIRERERMTyFYhERERExPIViERERETE8hWIRERERMTyFYhERERExPIViERERETG8/wdai0pgFyjqLwAAAABJRU5ErkJggg==\n", "text/plain": [ "

" ] @@ -382,16 +389,10 @@ "mpf.plot(daily,type='candle',volume=True,\n", " title='\\nS&P 500, Nov 2019',\n", " ylabel='OHLC Candles',\n", - " ylabel_lower='Shares\\nTraded')" + " ylabel_lower='Shares\\nTraded',\n", + " xlabel='DATE')" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", "metadata": {}, diff --git a/src/mplfinance/_version.py b/src/mplfinance/_version.py index 44439d7d..ff6c7f0b 100644 --- a/src/mplfinance/_version.py +++ b/src/mplfinance/_version.py @@ -1,4 +1,4 @@ -version_info = (0, 12, 9, 'beta', 2) +version_info = (0, 12, 9, 'beta', 3) _specifier_ = {'alpha': 'a','beta': 'b','candidate': 'rc','final': ''} diff --git a/src/mplfinance/plotting.py b/src/mplfinance/plotting.py index 72468b74..17c97e7c 100644 --- a/src/mplfinance/plotting.py +++ b/src/mplfinance/plotting.py @@ -707,13 +707,6 @@ def plot( data, **kwargs ): 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'] - if apdict['xlabel'] is not None: - apdict['xlabel'] = None # set to None so `_addplot_apply_supplements()` won't apply it. - warnings.warn('\n\n ================================================================= '+ - '\n\n WARNING: make_addplot `xlabel` IGNORED in Panels mode.'+ - '\n Use `mpf.plot(...,xlabel=)` instead.'+ - '\n\n ================================================================ ', - category=UserWarning) aptype = apdict['type'] if aptype == 'ohlc' or aptype == 'candle': ax = _addplot_collections(panid,panels,apdict,xdates,config) @@ -1082,8 +1075,11 @@ def _addplot_columns(panid,panels,ydata,apdict,xdates,config): def _addplot_apply_supplements(ax,apdict,xdates): if (apdict['ylabel'] is not None): ax.set_ylabel(apdict['ylabel']) - if (apdict['xlabel'] is not None): - ax.set_xlabel(apdict['xlabel']) + # Note that xlabel is NOT supported for addplot. This is because + # in Panels Mode, there is only one xlabel (on the bottom axes) + # which is handled by the `xlabel` kwarg of `mpf.plot()`, + # whereas in External Axes Mode, users can call `Axes.set_xlabel()` on + # the axes object of their choice. if apdict['ylim'] is not None: ax.set_ylim(apdict['ylim'][0],apdict['ylim'][1]) if apdict['title'] is not None: @@ -1257,10 +1253,6 @@ def _valid_addplot_kwargs(): 'Description' : 'label for y-axis (for this addplot)', 'Validator' : lambda value: isinstance(value,str) }, - 'xlabel' : { 'Default' : None, # x-axis label - 'Description' : 'x-axis label (for addplot ONLY when in external axes mode)', - 'Validator' : lambda value: isinstance(value,str) }, - 'ylim' : {'Default' : None, 'Description' : 'Limits for addplot y-axis as tuple (min,max), i.e. (bottom,top)', 'Validator' : lambda value: isinstance(value, (list,tuple)) and len(value) == 2 From ac8aef25fce5f1bee385eea5ab660c9c78b1b56c Mon Sep 17 00:00:00 2001 From: Daniel Goldfarb Date: Mon, 31 Oct 2022 16:51:29 -0400 Subject: [PATCH 19/22] tweak version --- src/mplfinance/_version.py | 2 +- src/mplfinance/plotting.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mplfinance/_version.py b/src/mplfinance/_version.py index ff6c7f0b..e16ebafe 100644 --- a/src/mplfinance/_version.py +++ b/src/mplfinance/_version.py @@ -1,4 +1,4 @@ -version_info = (0, 12, 9, 'beta', 3) +version_info = (0, 12, 9, 'beta', 4) _specifier_ = {'alpha': 'a','beta': 'b','candidate': 'rc','final': ''} diff --git a/src/mplfinance/plotting.py b/src/mplfinance/plotting.py index 17c97e7c..1fa55ac1 100644 --- a/src/mplfinance/plotting.py +++ b/src/mplfinance/plotting.py @@ -708,6 +708,7 @@ def plot( data, **kwargs ): 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,xdates) From 2ea49e4fdc6dc017686f1b29190cefa7398a512e Mon Sep 17 00:00:00 2001 From: Daniel Goldfarb Date: Mon, 31 Oct 2022 20:41:27 -0400 Subject: [PATCH 20/22] check for whitespace when column not found; Issue #496 --- src/mplfinance/_arg_validators.py | 55 ++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/src/mplfinance/_arg_validators.py b/src/mplfinance/_arg_validators.py index d7398232..e3782fe9 100644 --- a/src/mplfinance/_arg_validators.py +++ b/src/mplfinance/_arg_validators.py @@ -30,21 +30,6 @@ def _check_and_prepare_data(data, config): if not isinstance(data.index,pd.core.indexes.datetimes.DatetimeIndex): raise TypeError('Expect data.index as DatetimeIndex') - if (len(data.index) > config['warn_too_much_data'] and - (config['type']=='candle' or config['type']=='ohlc' or config['type']=='hollow_and_filled') - ): - warnings.warn('\n\n ================================================================= '+ - '\n\n WARNING: YOU ARE PLOTTING SO MUCH DATA THAT IT MAY NOT BE'+ - '\n POSSIBLE TO SEE DETAILS (Candles, Ohlc-Bars, Etc.)'+ - '\n For more information see:'+ - '\n - https://github.com/matplotlib/mplfinance/wiki/Plotting-Too-Much-Data'+ - '\n '+ - '\n TO SILENCE THIS WARNING, set `type=\'line\'` in `mpf.plot()`'+ - '\n OR set kwarg `warn_too_much_data=N` where N is an integer '+ - '\n LARGER than the number of data points you want to plot.'+ - '\n\n ================================================================ ', - category=UserWarning) - # We will not be fully case-insensitive (since Pandas columns as NOT case-insensitive) # but because so many people have requested it, for the default column names we will # try both Capitalized and lower case: @@ -57,10 +42,22 @@ def _check_and_prepare_data(data, config): o, h, l, c, v = columns cols = [o, h, l, c] - if config['tz_localize']: - dates = mdates.date2num(data.index.tz_localize(None).to_pydatetime()) - else: # Just in case someone was depending on this bug (Issue 236) - dates = mdates.date2num(data.index.to_pydatetime()) + if config['volume'] != False: + expect_cols = columns + else: + expect_cols = cols + + for c in expect_cols: + if c not in data.columns: + for dc in data.columns: + if dc.strip() != dc: + warnings.warn('\n ================================================================= '+ + '\n Input DataFrame column name "'+dc+'" '+ + '\n contains leading and/or trailing whitespace.',category=UserWarning) + raise ValueError('Column "'+c+'" NOT FOUND in Input DataFrame!'+ + '\n CHECK that your column names are correct AND/OR'+ + '\n CHECK for leading or trailing blanks in your column names.') + opens = data[o].values highs = data[h].values lows = data[l].values @@ -75,6 +72,26 @@ def _check_and_prepare_data(data, config): if not all( isinstance(v,(float,int)) for v in data[col] ): raise ValueError('Data for column "'+str(col)+'" must be ALL float or int.') + if config['tz_localize']: + dates = mdates.date2num(data.index.tz_localize(None).to_pydatetime()) + else: # Just in case someone was depending on this bug (Issue 236) + dates = mdates.date2num(data.index.to_pydatetime()) + + if (len(data.index) > config['warn_too_much_data'] and + (config['type']=='candle' or config['type']=='ohlc' or config['type']=='hollow_and_filled') + ): + warnings.warn('\n\n ================================================================= '+ + '\n\n WARNING: YOU ARE PLOTTING SO MUCH DATA THAT IT MAY NOT BE'+ + '\n POSSIBLE TO SEE DETAILS (Candles, Ohlc-Bars, Etc.)'+ + '\n For more information see:'+ + '\n - https://github.com/matplotlib/mplfinance/wiki/Plotting-Too-Much-Data'+ + '\n '+ + '\n TO SILENCE THIS WARNING, set `type=\'line\'` in `mpf.plot()`'+ + '\n OR set kwarg `warn_too_much_data=N` where N is an integer '+ + '\n LARGER than the number of data points you want to plot.'+ + '\n\n ================================================================ ', + category=UserWarning) + return dates, opens, highs, lows, closes, volumes def _get_valid_plot_types(plottype=None): From d63f27f312783c9a695daf07593409fcc0e3582c Mon Sep 17 00:00:00 2001 From: Daniel Goldfarb Date: Mon, 31 Oct 2022 21:10:12 -0400 Subject: [PATCH 21/22] fix bug that was trashing "Close" values --- src/mplfinance/_arg_validators.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mplfinance/_arg_validators.py b/src/mplfinance/_arg_validators.py index e3782fe9..55c320b3 100644 --- a/src/mplfinance/_arg_validators.py +++ b/src/mplfinance/_arg_validators.py @@ -46,15 +46,15 @@ def _check_and_prepare_data(data, config): expect_cols = columns else: expect_cols = cols - - for c in expect_cols: - if c not in data.columns: + + for col in expect_cols: + if col not in data.columns: for dc in data.columns: if dc.strip() != dc: warnings.warn('\n ================================================================= '+ '\n Input DataFrame column name "'+dc+'" '+ '\n contains leading and/or trailing whitespace.',category=UserWarning) - raise ValueError('Column "'+c+'" NOT FOUND in Input DataFrame!'+ + raise ValueError('Column "'+col+'" NOT FOUND in Input DataFrame!'+ '\n CHECK that your column names are correct AND/OR'+ '\n CHECK for leading or trailing blanks in your column names.') From c7005ab66259df4341d4b8719b31a30fd79fb96f Mon Sep 17 00:00:00 2001 From: Daniel Goldfarb Date: Mon, 31 Oct 2022 21:11:54 -0400 Subject: [PATCH 22/22] bump version --- src/mplfinance/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mplfinance/_version.py b/src/mplfinance/_version.py index e16ebafe..52221d42 100644 --- a/src/mplfinance/_version.py +++ b/src/mplfinance/_version.py @@ -1,4 +1,4 @@ -version_info = (0, 12, 9, 'beta', 4) +version_info = (0, 12, 9, 'beta', 5) _specifier_ = {'alpha': 'a','beta': 'b','candidate': 'rc','final': ''}