2

TL;DR - The issue

I have an mplfinance plot based on a pandas dataframe in which the indices are in Georgian calendar format and I need to have them displayed as Jalali format.

My data and code

My data looks like this:

            open    high    low     close
date                                       
2021-03-15  67330.0 69200.0 66870.0 68720.0
2021-03-16  69190.0 71980.0 69000.0 71620.0
2021-03-17  72450.0 73170.0 71700.0 71820.0
2021-03-27  71970.0 73580.0 70000.0 73330.0
2021-03-28  73330.0 73570.0 71300.0 71850.0
...         ...     ...     ...     ...

The first column is both a date and the index. This is required by mplfinance plot the data correctly; Which I can plot with something like this:

import mplfinance as mpf
mpf.plot(chart_data.tail(7), figratio=(16,9), type="candle", style='yahoo', ylabel='', tight_layout=True, xrotation=90)

Where chart_data is the data above and the rest are pretty much formatting stuff.

What I have now

My chart looks like this:

enter image description here

However, the I need the dates to look like this: 1400-01-12. Here's a table of equivalence to further demonstrate my case.

2021-03-15  1399-12-25
2021-03-16  1399-12-26
2021-03-17  1399-12-27
2021-03-27  1400-01-07
2021-03-28  1400-01-08

What I've tried

Setting Jdates as my indices:

chart_data.index = history.jdate
mpf.plot(chart_data_j)

Throws this exception:

TypeError('Expect data.index as DatetimeIndex')

So I tried converting the jdates into datetimes:

chart_data_j.index = pd.to_datetime(history.jdate)

Which threw an out of bounds exception:

OutOfBoundsDatetime: Out of bounds nanosecond timestamp: 1398-03-18 00:00:00

So I though maybe changing the timezone/locale would be an option, so I tried changing the timezones, following the official docs:

pd.to_datetime(history.date).tz_localize(tz='US/Eastern')

But I got this exception:

raise TypeError(f"{ax_name} is not a valid DatetimeIndex or PeriodIndex")

And finally I tried using libraries such as PersianTools and pandas_jalali to no avail.

M.H. Tajaddini
  • 776
  • 4
  • 20

2 Answers2

2

Have you tried making these dates

1399-12-25
1399-12-26
1399-12-27
1400-01-07
1400-01-08

the index of the dataframe (maybe that's what you mean by "swapping the indices"?) and set kwarg datetime_format='%Y-%m-%d' ?

I think that should work.


UPDATE:

It appears to me that the problem is that

  • mplfinace requires a Pandas.DatetimeIndex as the index of your dataframe, and
  • Pandas.DatetimeIndex is made up of Pandas.Timestamp objects, and
  • Pandas.Timestamp has limits which preclude dates having years less than 1677:
In [1]: import pandas as pd

In [2]: pd.Timestamp.max
Out[2]: Timestamp('2262-04-11 23:47:16.854775807')

In [3]: pd.Timestamp.min
Out[3]: Timestamp('1677-09-21 00:12:43.145225')

I am going to poke around and see if I can come up with another solution.
Internally Matplotlib dates can go to year zero.

Daniel Goldfarb
  • 6,937
  • 5
  • 29
  • 61
2

You can get this to work by creating your own custom DateFormatter class, and using mpf.plot() kwarg returnfig=True to gain access to the Axes objects to be able to install your own custom DateFormatter.

I have written a custom DateFormatter (see code below) that is aware of the special way that MPLfinance handles the x-axis when show_nontrading=False (i.e. the default value).

import pandas as pd
import mplfinance as mpf
import jdatetime as jd
import matplotlib.dates as mdates

from matplotlib.ticker import Formatter
class JalaliDateTimeFormatter(Formatter):
    """
    Formatter for JalaliDate in mplfinance.
    Handles both `show_nontrading=False` and `show_nontrading=True`.
    When show_nonntrading=False, then the x-axis is indexed by an
    integer representing the row number in the dataframe, thus:
    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=None, fmt='%b %d, %H:%M', show_nontrading=False):
        self.dates = dates
        self.len   = len(dates) if dates is not None else 0
        self.fmt   = fmt
        self.snt   = show_nontrading

    def __call__(self, x, pos=0):
        '''
        Return label for time x at position pos
        '''
        if self.snt:
            jdate = jd.date.fromgregorian(date=mdates.num2date(x))
            formatted_date = jdate.strftime(self.fmt)
            return formatted_date

        ix = int(round(x,0))

        if ix >= self.len or ix < 0:
            date = None
            formatted_date = ''
        else:
            date = self.dates[ix]
            jdate = jd.date.fromgregorian(date=mdates.num2date(date))
            formatted_date = jdate.strftime(self.fmt)
        return formatted_date

#  ---------------------------------------------------

df = pd.read_csv('so_67001540.csv',index_col=0,parse_dates=True)

mpf.plot(df,figratio=(16,9),type="candle",style='yahoo',ylabel='',xrotation=90)

dates     = [mdates.date2num(d) for d in df.index]
formatter = JalaliDateTimeFormatter(dates=dates,fmt='%Y-%m-%d')

fig, axlist = mpf.plot(df,figratio=(16,9), 
                       type="candle",style='yahoo',
                       ylabel='',xrotation=90,
                       returnfig=True)

axlist[0].xaxis.set_major_formatter(formatter)
mpf.show()
  • The file 'so_67001540.csv' looks like this:
date,open,high,low,close,alt_date
2021-03-15,67330.0,69200.0,66870.0,68720.0,1399-12-25
2021-03-16,69190.0,71980.0,69000.0,71620.0,1399-12-26
2021-03-17,72450.0,73170.0,71700.0,71820.0,1399-12-27
2021-03-27,71970.0,73580.0,70000.0,73330.0,1400-01-07
2021-03-28,73330.0,73570.0,71300.0,71850.0,1400-01-08
  • When you run the above script, you should get the following two plots:

enter image description here

enter image description here

Daniel Goldfarb
  • 6,937
  • 5
  • 29
  • 61