matplotlib / mplfinance

Financial Markets Data Visualization using Matplotlib
https://pypi.org/project/mplfinance/
Other
3.57k stars 618 forks source link

Add a number or a symbol above/below a candle chart with mplfinance? #97

Open manuelwithbmw opened 4 years ago

manuelwithbmw commented 4 years ago

Hi, is there a way to add on top of a day candle a number ('1', '2', .... , '9', '10', ... ) or other symbols like '+' all this on top of the HIGH or beneath the LOW of the candle? Or is there a way of also drawing on top/below an Arrow going down/up towards the body candle. Screenshot 2020-04-17 at 00 58 28

DanielGoldfarb commented 4 years ago

presently there is no way to do this. There is a similar request here, and this enhancement to allow users to pass in their own Figure and Axes should provide a good work around as well. (Expected completion approximately the end of May or beginning of June.

I'm not sure (haven't tried it yet), but you may be able to take advantage of this feature to be able to add the text as well.

Finally, now that I think of it, you should be able to do this with the existing addplot feature. Read through that example notebook. I think if you use a scatter plot, and set the marker to be the numbers and arrows, you can accomplish what you want, but you may have to call make_addplot() several times.

manuelwithbmw commented 4 years ago

Hi Daniel, thanks for the prompt answer. I had already built a scatter plot via make_addplot() and having triangles, or squares as marker signals (actually the overall overlapped chart to my candlestick is a signal chart basically). I had tried markers as numbers and been refused, I will play a little more with it. And will look at your other suggested features

DanielGoldfarb commented 4 years ago

For using markers, these references may help:

https://matplotlib.org/3.1.0/gallery/lines_bars_and_markers/marker_reference.html

https://matplotlib.org/3.2.1/tutorials/text/mathtext.html (note sections on numbers and arrows)

https://stackoverflow.com/questions/44726675/custom-markers-using-python-matplotlib

https://stackoverflow.com/questions/14324270/matplotlib-custom-marker-symbol

manuelwithbmw commented 4 years ago

Oh Daniel, that's a fantastic help, thank you!

fxhuhn commented 4 years ago

@manuelwithbmw it would be greate if you share your solution with us. :-)

manuelwithbmw commented 4 years ago

So, thanks to above marker reference links that Daniel gave (Thanks!), i managed to draw numbers and arrows on top and below certain candles, with the aim to represent a trading signal of some sort. I used the scatter method plot (not ideal for signals as all markers are equal per every candle) within a mpf.plot call with an addplot() in it and making use of make_addplot().

Valid examples: marker='$1$' or marker='$2$' or ... marker='$9$'

Valid examples: marker=r'$\Uparrow$' or marker=r'$\Downarrow$' or marker=r'$\uparrow$' or marker=r'$\downarrow$' where uppercase is for Bold and lowercase is for thin arrows.

As said, to plot all that, I created additional (to candle data frame df) plots for my Sell and Buy signals grouped in a single Dictionary (or whatever it is), like below:

apd = [mpf.make_addplot(sell_signal1, scatter=True, markersize=50, marker='$9$'), mpf.make_addplot(buy_signal1, scatter=True, markersize=50, marker='$9$'), mpf.make_addplot(buy_signal2, scatter=True, markersize=100, marker=r'$\Uparrow$'), mpf.make_addplot(sell_signal2, scatter=True, markersize=50, marker='$5$')]

I noticed Numbers and Arrows can also be used in charts labels if you like: Examples: ylabel=r'$\Uparrow$' or ylabel=r'$\Downarrow$' or ylabel=r'$\uparrow$' or ylabel=r'$\downarrow$'

Results shown in the attached pictures Arrows and numbers 1 Arrows and numbers 2

A scatter not ideal as Ideally I'd treat every candle separately with different numbers, while scatter markers are always the same for the overall chart. And I cannot have 20 subplots I suppose, I will try.

Hope it helps @fxhuhn !

DanielGoldfarb commented 4 years ago

@manuelwithbmw - Thank you so much for sharing! (@fxhuhn Thanks for asking for it)

@manuelwithbmw - Can you please explain "And I cannot have 20 subplots I suppose, I will try." ... are you talking about "make_addplot()" (which are not actually subplots, so 20 should be fine ... tedious, but fine. they can all be put into a list and pass the list into mpf.plot().

Now, regarding where we might go from here. I realize calling mpf.make_addplot() for each different marker you want is tedious (let alone figuring their locations). Would you like to propose what you would ideally want the interface to be? I had originally been thinking regarding #49 to have the user pass in something like trades=list_of_trades where list_of_trades would be a list of mostly zeros with positive numbers for buys and negative numbers for sells. Thoughts?

manuelwithbmw commented 4 years ago

Oh yes I articulated improperly, they are not subplots, they are just additional plots to my df. Basically every number under/above a candle will be a call to make_addplot(), and it represents a reference to a signal. But I need a cluster of those signals, meaning that I need 10-20 candles with numbers above/below before actually having a real actionable signal. So yes i will put all of them into a list ("apd" in my example above) and will pass this to mpf.plot(). Not particularly tedious in itself, the only prob is I need to calc and handle an appropriate series on NaN (or zeros) plus my number/arrow on/below the correct candle for every one of them, every series. Yes figuring their candle location is the hard part and the fact that the number of elements of this Series has to correspond to len(df) in a different way for every one is driving me mad. Not sure I explained myself. To be clearer, in my case, the complexity of the indicator is that I need to plot 10-20 numbers/arrow according to certain rules, before actually having a real actionable trading signal, which would be the last of those designed numbers above/below a certain candle.

Yeah, passing in a list_of_trades as a list of mostly zeros (or NaN? Zeros probably better) with positive numbers for buys and negative numbers for sells would work OK.

In my example above signal1 (in "buy_signal = signal1 low 0.95) is exactly that, a series of 1s and NaNs like:

signal1= [nan nan nan nan nan nan nan nan 1 1 1 nan nan nan 1 1 1 1 1 nan nan nan nan nan nan nan nan nan nan nan nan 1 1 1 1 nan]

where 1s represent the position of a component of my buy trade. And

signal2 (in "sell_signal = signal2 high 1.05) is a series of 1s and NaNs like:

signal2= [nan 1 1 1 1 1 nan nan 1 1 1 1 nan nan 1 1 1 1 1 nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan]

where 1s represent the position of a component of my sell trade.

What I'd like is a function able to plot the numbers/arrows more easily. The real prob for now is that number of all markers have to correspond -obviously- to len(df) and this is hard when you have a math logic embedded to design where the 1s have to be and have not.

So ideally passing in the list_of_trades coupled with different markers for any trade would be the optimal for me, but probably too crazy to program, did I make it overly complicated? Thanks

DanielGoldfarb commented 4 years ago

@manuelwithbmw,

It all sounds reasonable to want to do. My only comment is you seem stuck on the idea that making your signal list the same lengh as the dataframe is somewhat of a challenge. I would suggest thinking of the signal generation somewhat differently.

It seems to me (please corrrect me if I am wrong) that to generate each of the signals, you need to examine every row (date) in your dataframe prices (even if for some of those rows you do nothing, or return zero or NaN or None). If so, you many consider using Pandas.Dataframe.apply() to do this, and place the result from the apply() call into a new column in the same data frame. Then every signal is automatically the correct length.

If it is not clear what I mean, then if I can see your code I may pehaps be able to show specifically for your use-case what I mean.

manuelwithbmw commented 4 years ago

It is exactly as you say. I didn't know about Pandas.Dataframe.apply(), this sounds like a great idea, let me see if I can leverage this for my calculation, if not I may show you the code no problem. Yes the signal is actioned after -say- nine subsequent closes < than close for days ago, and subsequent conditions comparing following closes to previous highs/lows...the results of all that will be a candle signalling exhaustion of current trend. Its a TD sequential. Thank you for your help, this is my first Python project

manuelwithbmw commented 4 years ago

Hi @DanielGoldfarb I have the feeling I cannot call make_addplot() more than 10 times? Is there a limitation? I am facing a lot of the below that prevent me from plotting, not sure this is related to the number of make_addplot():

_Traceback (most recent call last): mpf.plot(df, type='candle', ylabel='Price', ylabellower='Volume', File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/mplfinance/plotting.py", line 35, in decorator return func(*args, kwargs) File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/mplfinance/plotting.py", line 336, in plot ymhi = math.log(max(math.fabs(max(yd)),1e-7),10) ValueError: max() arg is an empty sequence**

Basically I have something like:

apd = [mpf.make_addplot(sell_signal1, scatter=True, markersize=50, marker='$1$', mpf.make_addplot(sell_signal2, scatter=True, markersize=50, marker='$2$', mpf.make_addplot(sell_signal3, scatter=True, markersize=50, marker='$3$', ... , mpf.make_addplot(sell_signal9, scatter=True, ... markersize=50, marker='$9$', .. .)]

but if I exceeds this list I have many ValueError: max() arg is an empty sequence_ ... or these errors refer to a different problem in my code and I am not seeing the root of it. I keep trying

Basically I am in a situation where my indicator works with a simplified unique marker, but the code gets very long and messy if I try and plot markers with all appropriate numbers correctly. Not a big deal for trading purposes but still not perfect

DanielGoldfarb commented 4 years ago

@manuelwithbmw , I am inclined to think this has nothing to do with the number of addplots in your list. Certainly there is nothing intentional in the code to limit the number.

The exception is being raised in a part of the code that uses logarithms to determine the "order of magnitude" of the addplot data, to decide whether or not it is the same order of magnitude as the original OHLC data. If it is not the same order of magnitude, then it will put the addplot data on a "secondary_y" axis. The log is determined for both the min and max of each set of addplot data.

The exception is complaining that the max function is being passed an empty list (sequence) of data. Just before the line of code that takes the max, there is a line of code that generates this list by stripping NaNs out of your addplot data: yd = [y for y in ydata if not math.isnan(y)] .

It seems to me, at a glance, that the only way for that list to be empty is if, for one of your addplot data sets, you passed in a sequence where the entire sequence was NaN.

Please check to see if this may be the case for any of your addplot data sets.

If it is not the case, then I may have a bug in the algorithm. If so, then if you can provide both your data and your code, I will attempt to debug it.

All the best. --Daniel

manuelwithbmw commented 4 years ago

Sure, let me have a look at these exceptions more deeply, it could well be a full list of NaNs. Also, it seems to me exceptions are df dependent (so yes it may depend on particular values, prices, calculations for that particular time series, while ok for others). In my code, I have a lot of placeholders to check the logic and the steps of the underlying algorithm so I am quite confident the prob is on the plotting side. Will get back later today! Thank you, very useful!

DanielGoldfarb commented 4 years ago

A couple points that may be relevant to this issue (once we figure out the cause for the exception):

manuelwithbmw commented 4 years ago
manuelwithbmw commented 4 years ago

@DanielGoldfarb To overcome the problem of passing in a sequence of all NaNs -maybe not the purest solution but for now- just forcing at the initialisation the first element to be zero in all those lists, I can plot OK all addplots. You just have not to look at that first candle on the left where all markers overlap wildly :-). I am happy with that while maybe looking for a cleaner solution.

signal_1 = np.full(len(signal1), np.nan) signal_1[0] = 1

fxhuhn commented 4 years ago

@manuelwithbmw

Hope it helps @fxhuhn !

Thanks, I'm still trying to add it to my charts.

fxhuhn commented 4 years ago

@manuelwithbmw

  • Interesting, I read the 'ignore empty' one. Yes from a first check 2 of the lists of data to plot where all NaNs (no signal triggered for that day), the other lists correctly had just one value (1 )(signal triggered for that day), so it seems it depended on this. Will check more instances later.

It seems we have a similar approach. I'm glad to see my idea helped you with your problem-solving.

fxhuhn commented 4 years ago

ads_de I'm playing with different colors and triangles. Besides the numbers, the idea with the arrows is very interesting for me.

manuelwithbmw commented 4 years ago

@DanielGoldfarb sorry to be a pain. All is good now, every charts plots OK except in one single case where it depends on the timeframe I choose, for which I almost often have the below messages and series are not all NaNs anymore. Is there any other thing I can check on the max() function functionality? Thanks

Again on:

mpf.plot(df, type='candle', ylabel='Price', ylabel_lower='Volume', File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/mplfinance/plotting.py", line 35, in decorator return func(*args, **kwargs) File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/mplfinance/plotting.py", line 336, in plot ymhi = math.log(max(math.fabs(max(yd)),1e-7),10) ValueError: max() arg is an empty sequence

DanielGoldfarb commented 4 years ago

@manuelwithbmw , I'm not clear on what you are asking. Unless and until we decide to modify the mpf.plot() code to accept an addplot argument that contains no data (or all NaNs) then you need to make sure you are not passing in a list or Series, etc. of all NaN's as the first argument to make_addplot()

You can test this before calling make addplot, as follows:

Suppose ydata is you signal list or Series (not a dataframe) and you are about to call mpf.make_addplot(ydata,**kwargs), then do something like this:

if 0 < len( [y for y in ydata if not math.isnan(y)] ):
    apd = mpf.make_addplot(ydata,...)
else:
    pass    # or, alternatively, print a warning here that you have all NaNs

(If ydata is a dataframe, then you just have to do the 0 < len([y for y in ydata if not math.isnan(y)]) check for every column in the dataframe.)

I hope that helps. Please let me know if this is not clear.

manuelwithbmw commented 4 years ago

@DanielGoldfarb sorry Daniel, I just simply realised I was having those errors, because for specific timeframes, I had no signals at all generated by my algorithm (hence all Series -yes they are not dataframes I'm my code- were NaNs). I wasn't expecting there could be such cases in my trading spectrum. I just need to expand the time horizon, the scope of calculations wide enough and the plots are OK. Apologies if you have wasted time

DanielGoldfarb commented 4 years ago

@manuelwithbmw no problem; as suggested above, you may want to put a check in your code just before calling make_addplot(). even if all time horizons are good now, it may change in the future with different data sets and/or if you modify the code someday.

manuelwithbmw commented 4 years ago

I have added your check code! Thank you

char101 commented 4 years ago

An example on how to add percentage on top/bottom of the candlebars

def _add_candlestick_labels(ax, ohlc):
    transform = ax.transData.inverted()
    # show the text 10 pixels above/below the bar
    text_pad = transform.transform((0, 10))[1] - transform.transform((0, 0))[1]
    percentages = 100. * (ohlc.close - ohlc.open) / ohlc.open
    kwargs = dict(horizontalalignment='center', color='#000000')
    for i, (idx, val) in enumerate(percentages.items()):
        if val != np.nan:
            row = ohlc.loc[idx]
            open = row.open
            close = row.close
            if open < close:
                ax.text(i, row.high + text_pad, np.round(val, 1), verticalalignment='bottom', **kwargs)
            elif open > close:
                ax.text(i, row.low - text_pad, np.round(val, 1), verticalalignment='top', **kwargs)

figure, axlist = mpf.plot(ohlc, ..., show_nontrading=False, returnfig=True)
_add_candlestick_labels(axlist[0], ohlc)
DanielGoldfarb commented 4 years ago

image


@char101

Charles, once again, very creative. Thanks. I may integrate this into mplfinance. Much appreciated. --Daniel

DanielGoldfarb commented 4 years ago

@manuelwithbmw

mpf.make_addplot() now supports a sequence of markers. See here for some examples. Please try it out and let me know if it works for you.

manuelwithbmw commented 4 years ago

Hi @DanielGoldfarb , this looks amazing thank you. You got such a good memory of everything.

PS: I had in mind to write you a generic question: I am following all your library enhancements with functions and kwargs etc. I am also updating mplfinance to your last release to get them working. In general, I still struggle to understand what is the best way to get all information about new 'functions', new active **kwargs, how to use them, where I can derive their main logic? I understand this is constantly ongoing/changing so examples in notebooks are the last thing you can think of. However what is the best to get new features' info, is there any /help, original code, where I can nosedive? Thank you for everything you are making this really great

Manuel

char101 commented 4 years ago

Mplfinance code is actually quite compact, you can look at the code directly, at least that is what I have done. It might be a little difficult if you are not used to matplotlib API but it will get easier the more you research into it.

Also a suggestion to @DanielGoldfarb , if you could format your code with black or yapf, it will help other people navigate the code.

manuelwithbmw commented 4 years ago

Mplfinance code is actually quite compact, you can look at the code directly, at least that is what I have done. It might be a little difficult if you are not used to matplotlib API but it will get easier the more you research into it.

Also a suggestion to @DanielGoldfarb , if you could format your code with black or yapf, it will help other people navigate the code.

Ok so I just go into Code tab and look for the latest commits. Thanks

DanielGoldfarb commented 4 years ago

@manuelwithbmw as Charles mentioned, looking at the code can help. Looking at latest commits may help, but more importantly I recommend looking at the various internal "valid kwargs" methods (for example, _valid_plot_kwargs()).

I also regularly post a brief summary of updates in the RELEASE NOTES.

Finally, almost everything mentioned in the RELEASE NOTES will have a specific example in one of the example notebooks in the Tutorials section (which can also be found here)

DanielGoldfarb commented 4 years ago

@char101

... if you could format your code with black or yapf ...

Thanks for the idea. I will look into it.

luongjames8 commented 3 years ago

Hi @manuelwithbmw, I am looking to implement the basic DeMark indicators. Your first chart looks like DeMark, but from the thread I gather you're doing some custom with it instead. Just wondering, have you implemented the basic DeMark indicator? If so, is that something you're willing to share? Otherwise, it is my next project. hah.

@DanielGoldfarb - Do you have any plans for a modular way of adding indicators to mplfinance? I would imagine that there is some overlap in terms of the technical studies people are working on implementing?

DanielGoldfarb commented 3 years ago

@luongjames8 -- I definitely have in mind to provide a modular or easy way to use third-party and/or user-provided indicators (that can be shared among various users) within mplfinance.

I have not yet given a lot of thought to how that would be implemented, because presently there remains a few more basic features (labels and annotations, for example) that probably should be implemented first. (In fact, another contributor is working on labels as we speak).

Here are my "not much yet" thoughts on the implementation: I am very reluctant to add any dependencies on other libraries/packages, unless it can be done in a way that is an optional dependency; perhaps a kwarg to implement the indicator, which first checks that the required package is installed and raises an exception if not. Alternatively we could just provide a simpler interface for passing in indicators, either as an indicator function that mplfinance calls, or as data (i.e. an interface somehow simpler than the existing make_addplot() interface; one that is specifically designed for indicators - I have in mind one possibly way to do this, where the user passes in a series of buy and sell signals, where the value of the signal indicates how much to buy (positive values) or sell (negative values)). This latter approach is the way I am currently leaning.

If you have any thoughts as to how you imagine it to be implemented -- especially in terms of the user interface, or calling convention, for "a modular way of adding indicators to mplfinance" -- please feel free to express your thoughts here. I am open to suggestions.

luongjames8 commented 3 years ago

Please give me some time to think about this.

On Fri, Nov 6, 2020 at 8:43 PM Daniel Goldfarb notifications@github.com wrote:

@luongjames8 https://github.com/luongjames8 -- I definitely have in mind to provide a modular or easy way to use third-party and/or user-provided indicators (that can be shared among various users) within mplfinance.

I have not yet given a lot of thought to how that would be implemented, because presently there remains a few more basic features (labels and annotations, for example) that probably should be implemented first. (In fact, another contributor is working on labels as we speak).

Here are my "not much yet" thoughts on the implementation: I am very reluctant to add any dependencies on other libraries/packages, unless it can be done in a way that is optional dependency; perhaps a kwarg to implement the indicator, which first checks for the installing of the required package and raises an exception if not installed. Alternatively we could just provide a simpler interface for passing in indicators, as either an indicator function, or as data (i.e. an interface somehow simpler than the existing make_addplot() interface; one that is specifically designed for indicators - I have in mind one possibly way to do this, where the user passes in a series of buy and sell signals, where the value of the signal indicates how much to buy (positive values) or sell (negative values)). This latter approach is the way I am currently leaning.

If you have any thoughts as to how you imagine it to be implemented -- especially in terms of the user interface, or calling convention, for "a modular way of adding indicators to mplfinance" -- please feel free to express your thoughts here. I am open to suggestions.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/matplotlib/mplfinance/issues/97#issuecomment-723061375, or unsubscribe https://github.com/notifications/unsubscribe-auth/AHFL7ZQK33ROXDDQOXNS3QLSOPVQXANCNFSM4MKKGU2Q .

char101 commented 3 years ago

AFAIK only set_size_inches has anything to do with the image size. That code above no matter how big the value you pass should not effect the image size. The default DPI is 100, so you'll just need to divide the output image size in pixels by 100 to get the inches.

manuelwithbmw commented 3 years ago

Hi @manuelwithbmw, I am looking to implement the basic DeMark indicators. Your first chart looks like DeMark, but from the thread I gather you're doing some custom with it instead. Just wondering, have you implemented the basic DeMark indicator? If so, is that something you're willing to share? Otherwise, it is my next project. hah.

Hi @luongjames8 , it is all done, customised and embedded in an whole trad system, sorry cannot share this. Sorry for late reply

zhang-andrew commented 3 years ago

AFAIK only set_size_inches has anything to do with the image size. That code above no matter how big the value you pass should not effect the image size. The default DPI is 100, so you'll just need to divide the output image size in pixels by 100 to get the inches.

Many thanks for the swift reply @char101. Are you saying I have to edit the image size that is outputted from "mpf.show()"? I've added code below that demonstrates how I'm trying to implement your code.

data = yf.download(tickers="FB")

fig = mpf.figure(figsize=(20,10)) 
ax1 = fig.add_subplot(2,1,1) 
ax2 = fig.add_subplot(2,1,2)

mpf.plot(data, 
         ax=ax1, 
         volume=ax2
         )

_add_candlestick_labels(ax1,  data)

mpf.show()
char101 commented 3 years ago

AFAIK only set_size_inches has anything to do with the image size. That code above no matter how big the value you pass should not effect the image size. The default DPI is 100, so you'll just need to divide the output image size in pixels by 100 to get the inches.

Many thanks for the swift reply @char101. Are you saying I have to edit the image size that is outputted from "mpf.show()"? I've added code below that demonstrates how I'm trying to implement your code.

data = yf.download(tickers="FB")

fig = mpf.figure(figsize=(1,1)) 
ax1 = fig.add_subplot(2,1,1) 
ax2 = fig.add_subplot(2,1,2)

mpf.plot(data, 
         ax=ax1, 
         volume=ax2
         )

_add_candlestick_labels(ax1,  data)

mpf.show()

Maybe you could try adding fig.set_sizes_inches(3, 4) before mpf.show().

char101 commented 3 years ago

Hi @manuelwithbmw, I am looking to implement the basic DeMark indicators. Your first chart looks like DeMark, but from the thread I gather you're doing some custom with it instead. Just wondering, have you implemented the basic DeMark indicator? If so, is that something you're willing to share? Otherwise, it is my next project. hah.

Hi @luongjames8 , it is all done, customised and embedded in an whole trad system, sorry cannot share this. Sorry for late reply

IMO you can use use a scatter plot while adjusting the y points to display those markers.