Skip to content

Fill Between of Supblot #257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mcavas98 opened this issue Sep 5, 2020 · 16 comments
Closed

Fill Between of Supblot #257

mcavas98 opened this issue Sep 5, 2020 · 16 comments
Labels
question Further information is requested

Comments

@mcavas98
Copy link

mcavas98 commented Sep 5, 2020

Hi, I have 2 questions;

Screen Shot 2020-09-05 at 4 03 16 PM

You can see my graph attached.

1)Firstly I was wondering if there was a way to use the fill between commands in subplots. For example in my RSI(third graph from top) where the data is over 70 or below 30. Also is there a way to customize the values on the y axis? For example it would be nice to add 30 and 70 to the y-axis as tickers.

2)Additionally can I move my title (it is in the middle of a graph)?

My code is below:

def plot_graph(ticker, df):

mc = mpl.make_marketcolors(up='green', down='red', inherit=True)
s = mpl.make_mpf_style(base_mpl_style='ggplot',
marketcolors=mc, y_on_right=False, mavcolors=['#1f77b4', '#ff7f0e', '#2ca02c'], gridcolor='#b6b6b6',
gridstyle='--', figcolor='#eee9e9', edgecolor='#8b8585')

ap = [mpl.make_addplot(df['Bought'], type='scatter',markersize=200,marker='>', color='#29854f', panel=1),
      mpl.make_addplot(df['Sold'], type='scatter', marker='<', markersize=200, color='#720c06', panel=1),
      mpl.make_addplot(df['ATR'], type='line', panel=0, ylabel='ATR', color='#8774AB', secondary_y=False, ylim=(
          min(df['ATR']), max(df['ATR'])
      )),
      mpl.make_addplot(df['Lower B'], type='line', panel=1, color='#3838ea', alpha=0.50),
      mpl.make_addplot(df['Upper B'], type='line', panel=1, color='#3838ea', alpha=0.50),
      mpl.make_addplot(df['70'], panel=2, type='line', secondary_y=False, ylim=(0, 100), color='r', alpha=0.25),
      mpl.make_addplot(df['30'], panel=2, type='line', secondary_y=False, ylim=(0, 100), color='g', alpha=0.25),
      mpl.make_addplot(df['RSI'], panel=2, type='line', ylabel='RSI', secondary_y=False, ylim=(0, 100)),
      mpl.make_addplot(df['SMA_20'], panel=1, type='line', alpha=0.5, color='orange'),
      mpl.make_addplot(df['macdline'], type='line', color='purple', panel=3, secondary_y=False),
      mpl.make_addplot(df['signalline'], type ='line', color='orange', panel=3, secondary_y=False),
      mpl.make_addplot(df['hist'], type='bar',panel=3, ylabel='MACD',color='#9c9796'),
      mpl.make_addplot(df['0'],type='line',panel=3,color='k',secondary_y=False,
                       ylim=((min(df['signalline']-1), (max(df['signalline']+0.5)))))]
mpl.plot(df, title=ticker, type='candle', style=s, ylabel='Price'
    , addplot=ap, panel_ratios=(0.7, 2, 0.7, 0.8), figratio=(2, 1),
    figscale=1.1, datetime_format='%m-%Y', tight_layout=True, main_panel=1,
         ylim=(min(df['Adj Close']-5), max(df['Adj Close']+5)),
         fill_between=dict(y1=df['Lower B'].values, y2=df['Upper B'].values, color='#f2ad73', alpha=0.20))
plt.show()

Thanks in advance for any help.

@mcavas98 mcavas98 added the question Further information is requested label Sep 5, 2020
@mcavas98 mcavas98 changed the title Customization of Subplot, Problem with Bollinger Bands Fill Between of Supblot Sep 5, 2020
@DanielGoldfarb
Copy link
Collaborator

DanielGoldfarb commented Sep 6, 2020

Hi @mcavas98

Thanks for your interest in mplfinance, and thanks for including your code (easier for me to answer your questions). By the way, that's a great looking plot. I really enjoy seeing the creative things people are doing with mplfinance. Now, to answer your questions:


1. Yes and no. Presently mpf.plot() does not yet support fill_between other than for the main panel. However there is a realtively easy workaround. Set returnfig=True as follows:

fig, axlist = mpf.plot(data,...,returnfig=True)

axlist will contain two Axes objects per panel (the primary and secondary Axes for each panel) in order from the top panel to the bottom (primary followed by secondary). For example, axlist[0] and axlist[1] are the primary and secondary axes, respectively, for panel 0 (top). axlist[2] and axlist[3] are the primary and secondary axes for panel 1 (second from top), etc.

Choose the Axes object you want from the list and call axlist[choice].fill_between().
Regarding setting the ticks at 30 and 70, once you have the Axes objects you should be able to do this with ax.set_yticks()
When you are done, call mpf.show() (or plt.show()).


2. Regarding the title location, this is being pushed down into the graph because you have chosen tight_layout=True.
There are few different things you can try to change this:

  • Don't use tight_layout, instead you can control padding with kwarg scale_padding, which is described here.
  • Try using kwarg axtitle instead of title. I'm not sure if this will work, but give it a try and see.
  • Don't use mpf.plot() to set the title, but do it yourself, after setting returnfig=True using either of the following methods to gain full control over the title. Note that the fig method is the title for the whole figure, whereas the ax method can be used to title each individual axis (panel).

I hope that helps. Keep me posted. All the best. --Daniel

P.S. If you have any interest in writing the code for mplfinance to support fill_between for any and all panels, I can guide you through it. Please let me know. Thanks.

@mcavas98
Copy link
Author

Thank you for your reply. Unfortunately I am very new to coding, I don't think I would be able to do it. But thank you for this great library.

@luongjames8
Copy link

luongjames8 commented Nov 9, 2020

Hi @DanielGoldfarb, I have tried using the solution above to fill between two curves on an addplot.

However, I cannot just call axlist[10] for example, because it needs two parameters for x and y1. However, your solution seems to be parameterless? I get the following error:

TypeError: fill_between() missing 2 required positional arguments: 'x' and 'y1'

How do you access the values of the plots on the axes to feed that to the x and y1 of the fill between function.

@DanielGoldfarb
Copy link
Collaborator

DanielGoldfarb commented Nov 9, 2020

Hi @luongjames8 ... sorry for any confusion; let me clarify:

However, your solution seems to be parameterless?

How do you access the values of the plots on the axes to feed that to the x and y1 of the fill between function.

  • For y1, use the data you are providing to mpf.plot() (either directly or via .make_addplot())
    or possibly whatever y-data that you want.

  • For x, it depends whether you have set show_nontrading=True or not
    ("not" meaning either not set, in which case it defaults to False, or explicitly set to False, either one).

    • If show_nontrading=True, then for x pass in the dates from your dataframe:
        x = df.index.values()

    • Else for x pass in a list of integers from zero to the length of your data:
        x = [ ix for ix in range(len(df.index)) ]
      or alternatively
        x = np.arange(len(df.index)) where np is numpy.

Please let me know if these suggestions help. Thanks. --Daniel

@luongjames8
Copy link

Hi @DanielGoldfarb

Thanks for clarifying and for the mini-tutorial on matplotlib axes.

I actually had tried passing the index values directly for x -- which did not work. But after reading your comment, realise that I need to pass integers if show_nontrading is not set.

Thanks. This worked for me.

@viorell91
Copy link

Hi @DanielGoldfarb ,

I came across this issue which helped me figure out my questions with fill_between() on axes level. Implementing the suggested workaround (returnfig=True), I was able call fill_between() on the correct axes object. However, this seems to modify the format of values on the x axis and the plotted result doesn't make much sense therefore. Here is my code:

date_axis = df.index

s  = mpf.make_mpf_style(base_mpf_style='yahoo')
figure, axlist = mpf.plot(df,type='candle',style=s,returnfig=True)

ax = axlist[0]
ap = mpf.make_addplot(df[['senkou_span_a','senkou_span_b']],ax=ax)

#green cloud
ax.fill_between(date_axis,df['senkou_span_a'], df['senkou_span_b'], where=df['senkou_span_b'] > df['senkou_span_a'], facecolor='#008000', alpha=0.5)
#red cloud
ax.fill_between(date_axis,df['senkou_span_a'], df['senkou_span_b'], where=df['senkou_span_b'] < df['senkou_span_a'], facecolor='#ff0000', alpha=0.5)

mpf.plot(df,type='candle', ax=ax, addplot=ap)

and here the resulting plot:
grafik

If I try to subplot the axes separately, everything seems to work just fine:

grafik
grafik

It seems to me as if the axes objects don't share the same x axis (or at least the format doesn't seem to match). Do you happen to have an advise on that or maybe a hint on how to fix it? Thank you!

@DanielGoldfarb
Copy link
Collaborator

@viorell91
Viorel,
Yes, this is one of the reasons why I should really enhance mplfinance to handle multiple fill_betweens and to handle them correctly, so users don't have to worry about this. The issue, I suspect, is what I indicated in this note above.

Please try one of the following:

  1. Set kwarg show_nontrading=True in all calls to mpf.plot(),
    or
  2. Instead of date_axis = df.index, use:
    date_axis = [ x for x in range(len(df.index)) ]

The reason is this: When show_nontrading=False (the default value if unspecified) then, although the date x-axis is formatted as dates, under the hood it is really just the row number from the DataFrame containing the ohlc data.

Please let me know if either of the above solutions work for you. They should both work (Individually, but not together). It's just a matter of whether or not you want to see non-trading days on the x-axis.
All the best. --Daniel

@viorell91
Copy link

viorell91 commented Feb 1, 2021

Worked like a charm, @DanielGoldfarb. I should have read your explanation more carefully - it makes sense now. Thanks for your work and amazing support!

@leemidgley
Copy link

Hi

I have an issue where I get fill white gaps between the lines drawn over time, can anyone please see anything wrong with below? thanks.

<extra_plot = [
mpf.make_addplot(bollinger_up, panel=0,color='b',secondary_y=True, type='line'),
mpf.make_addplot(bollinger_down,panel=0,color='b',secondary_y=True, type='line')

]
fig, axlist = mpf.plot(\
    df.rename(columns={'o':'Open','h':'High','l':'Low','c':'Close','v':'Volume'}),\
    title=str1, scale_width_adjustment=dict(volume=0.8,candle=2.5), \
    style=style1, type='candle',volume=True, figsize =(1600/100,1024/100), figratio=(12,8), \
    ylabel_lower="Volume per " + vol_inters, returnfig=True, addplot=extra_plot,\
    fill_between=dict(y1=bollinger_down.values, y2=bollinger_up.values, color='b', alpha=0.05)
    )

image

@DanielGoldfarb
Copy link
Collaborator

@leemidgley
Can you please provide the data you are using in a csv file, as well as code where you read the csv and fill df and bollinger variables, so that we have a fully reproducible example. Just glancing at only the code posted so far, i don't see an obvious problem related to fill_between. It gives the appearance that the values passed in to fill_between are not identical to those passed into make_addplot; but could be another problem also, need to be able to reproduce.

Probably not related: you can use figscale and figratio together, but once you use figsize then both figscale and figratio are ignored (since figsize includes both).

Also, just curious, have you tried withOUT returnfig=True? Just curious to know if you get the same problem for that case. The mplfinance code path is slightly different when returnfig=True.

@leemidgley
Copy link

Sure, I've tried without the returnfig=True - on screen has same issue.

The first thing I tried was taking out all the scaling as it does look that way but still no joy!.

def get_sma(prices, rate):
return prices.rolling(rate).mean()

def get_bollinger_bands(prices, rate=20):
sma = get_sma(prices, rate)
std = prices.rolling(rate).std()
bollinger_up = sma + std * 2 # Calculate top band
bollinger_down = sma - std * 2 # Calculate bottom band
return bollinger_up, bollinger_down

closing_prices = df['c']

bollinger_up, bollinger_down = get_bollinger_bands(closing_prices)

The data is more tricky to post as it's not straight forward (taken from a live exchange binance), the thing is the bands are been generated correctly and plot correctly, it's just the fill between the bands that is the issue yet same values are been used. (weird one)

example data:-

                       o        h        l        c        v          close_time      quote_av  trades  tb_base_av   tb_quote_av  ignore

ts
2022-03-08 08:08:00 38577.3 38585.9 38537.7 38552.6 120.828 2022-03-08 08:08:00 4.658962e+06 1587 41.319 1.593284e+06 0
2022-03-08 08:09:00 38552.7 38552.7 38451.0 38462.2 273.451 2022-03-08 08:09:00 1.052620e+07 2754 64.996 2.502151e+06 0
2022-03-08 08:10:00 38462.3 38526.9 38450.0 38505.1 261.949 2022-03-08 08:10:00 1.008000e+07 2908 160.878 6.190658e+06 0
2022-03-08 08:11:00 38505.2 38614.7 38505.2 38580.7 296.668 2022-03-08 08:11:00 1.144544e+07 3073 221.938 8.562420e+06 0
2022-03-08 08:12:00 38582.6 38659.9 38582.6 38659.8 463.294 2022-03-08 08:12:00 1.789668e+07 3990 368.799 1.424700e+07 0
... ... ... ... ... ... ... ... ... ... ... ...
2022-03-08 12:03:00 39068.3 39109.6 39060.0 39082.9 405.765 2022-03-08 12:03:00 1.585944e+07 4144 290.745 1.136441e+07 0
2022-03-08 12:04:00 39081.5 39111.0 39045.3 39110.1 334.903 2022-03-08 12:04:00 1.308619e+07 3280 113.584 4.439806e+06 0
2022-03-08 12:05:00 39111.0 39134.9 39087.9 39100.0 392.539 2022-03-08 12:05:00 1.535318e+07 4252 251.923 9.853588e+06 0
2022-03-08 12:06:00 39100.1 39113.5 39041.7 39056.3 216.417 2022-03-08 12:06:00 8.454958e+06 2888 55.473 2.167251e+06 0
2022-03-08 12:07:00 39056.4 39086.5 39052.0 39069.8 136.277 2022-03-08 12:07:00 5.323716e+06 1736 75.614 2.953831e+06 0

[240 rows x 11 columns]

Process of elimination (Simplified):-

fig, axlist = mpf.plot(\
    df.rename(columns={'o':'Open','h':'High','l':'Low','c':'Close','v':'Volume'}),\
    addplot=extra_plot, \
    fill_between=dict(y1=bollinger_down.values, y2=bollinger_up.values, color='b', alpha=0.05) 

produces image:-

image

@leemidgley
Copy link

you can see from below, all the data times match up:-

bollinger_up
ts
2022-03-08 08:32:00 NaN
2022-03-08 08:33:00 NaN
2022-03-08 08:34:00 NaN
2022-03-08 08:35:00 NaN
2022-03-08 08:36:00 NaN
...
2022-03-08 12:27:00 39155.137538
2022-03-08 12:28:00 39134.202935
2022-03-08 12:29:00 39099.633266
2022-03-08 12:30:00 39075.008310
2022-03-08 12:31:00 39047.971835
Name: c, Length: 240, dtype: float64
bollinger_down
ts
2022-03-08 08:32:00 NaN
2022-03-08 08:33:00 NaN
2022-03-08 08:34:00 NaN
2022-03-08 08:35:00 NaN
2022-03-08 08:36:00 NaN
...
2022-03-08 12:27:00 38753.342462
2022-03-08 12:28:00 38745.287065
2022-03-08 12:29:00 38739.866734
2022-03-08 12:30:00 38738.281690
2022-03-08 12:31:00 38739.048165
Name: c, Length: 240, dtype: float64
o h l c v close_time quote_av trades tb_base_av tb_quote_av ignore
ts
2022-03-08 08:32:00 38851.9 39030.5 38816.9 38972.1 2156.187 2022-03-08 08:32:00 8.394733e+07 17797 1559.689 6.072753e+07 0
2022-03-08 08:33:00 38974.3 39107.3 38950.0 39058.0 2048.019 2022-03-08 08:33:00 7.995707e+07 17616 1354.074 5.287040e+07 0
2022-03-08 08:34:00 39058.9 39100.0 38982.4 39004.8 1209.357 2022-03-08 08:34:00 4.721919e+07 10625 528.149 2.062663e+07 0
2022-03-08 08:35:00 39004.8 39018.3 38950.5 38960.2 422.402 2022-03-08 08:35:00 1.646619e+07 6243 158.945 6.196029e+06 0
2022-03-08 08:36:00 38963.1 38975.0 38907.4 38955.1 393.763 2022-03-08 08:36:00 1.533349e+07 5360 181.923 7.084899e+06 0
... ... ... ... ... ... ... ... ... ... ... ...
2022-03-08 12:27:00 38848.1 38860.9 38819.0 38819.0 144.812 2022-03-08 12:27:00 5.625041e+06 2263 75.501 2.933083e+06 0
2022-03-08 12:28:00 38819.1 38855.0 38810.0 38822.9 345.533 2022-03-08 12:28:00 1.341556e+07 2595 205.252 7.969046e+06 0
2022-03-08 12:29:00 38822.8 38835.7 38750.0 38762.2 524.848 2022-03-08 12:29:00 2.036028e+07 4680 125.623 4.874439e+06 0
2022-03-08 12:30:00 38762.1 38832.6 38733.0 38818.5 481.554 2022-03-08 12:30:00 1.867612e+07 5211 250.423 9.714336e+06 0
2022-03-08 12:31:00 38818.4 38823.1 38751.6 38809.3 278.437 2022-03-08 12:31:00 1.080117e+07 2491 176.827 6.859814e+06 0

@leemidgley
Copy link

worked it out, the secondary_y=True in the make_addplot causes it. I guess that is some kind of bug with the fill command.

bingo!
image

@DanielGoldfarb
Copy link
Collaborator

DanielGoldfarb commented Mar 8, 2022

@leemidgley
That totally makes sense. I should have noticed the secondary_y=True before.

The problem (or really limitation) is that, as mplfinance is currently written, fill_between always fills only on the primary Axes.

By putting the Bollinger bands on the secondary Axes you actually slight shift their position. You can see this in the image here if you look at the price labels on the left of right of the plot, you can see those on the right are slightly higher. Thus your Bollinger bands are really slightly out of place (and it is actually the fill_between that is correct).

The reason this can happen is matplotlib uses the data being plotted to determine the min and max of each Axes. When you put the Bollinger bands on a separate Axes, then matplotlib gives them their own min/max calculation. The result is that the Bollinger bands Axes gets a slightly different min and max that that for the candles (because the values themselves are indeed slightly different).

This means that if, for some reason, you wanted to keep secondary_y=True, then you could fix the problem by manually setting the min and max of the y-axis yourself. This can be done with kwarg ylim=(min,max) in both mpf.make_addplot() and mpf.plot(). This would force the left and right Axes to line up evenly, so that the fill_between would match the Bollinger bands. However, I do think the solution of not setting secondary_y is cleaner and makes more sense.

All the best. --Daniel

@leemidgley
Copy link

Thanks Daniel, one quick question - is it possible to show a circle with no fill. i.e. just the o outline?

mpf.make_addplot(df['circle'],type='scatter',marker='o',markersize=2400

Lee.

@leemidgley
Copy link

Ignore above, marker='$◯$' works fine for me. for anyone else I found this useful site for symbols: www.symbolcopy.com/circle-symbol.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

5 participants