Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Modified following" business days in Pandas

How can I efficiently implement a vectorized version of the "Modified Following" business day payment rule in Pandas?

Background

In finance it is often the case that fixed income coupons are paid on a "modified following" basis, that is, if the coupon date falls on a weekend or holiday, it is paid on the following business day, unless said following business day falls into the next calendar month, in which case we go backwards to the nearest previous business day.

     April 2017       
Su Mo Tu We Th Fr Sa  
                   1  
 2  3  4  5  6  7  8  
 9 10 11 12 13 14 15  
16 17 18 19 20 21 22  
23 24 25 26 27 28 29  
30 

April 2017 offers a good example. If a payment falls on say, Saturday 15th, it will be moved forward to Monday 17th. However if it falls on Saturday 29th, two weeks later, it will be moved backwards to Friday 28th (since the following Monday will be in May).

I need to do this for thousands of dates at a time, hence I'd like it to be vectorised and pandas efficient if possible (that is, I'd like to avoid just running each date through an if-then-else). So given a vector of dates, it would check which fall on Sat/Sun and automatically apply the rule, outputting a vector of "modified following" dates.

like image 333
Thomas Browne Avatar asked Oct 26 '25 10:10

Thomas Browne


1 Answers

If you're interested in a pandas solution this is a start:

import pandas as pd
from pandas.tseries.holiday import USFederalHolidayCalendar
from pandas.tseries.offsets import CustomBusinessDay

BDayUS = CustomBusinessDay(calendar=USFederalHolidayCalendar())       

def is_weekday(dates):
    return ~dates.weekday_name.isin(['Saturday', 'Sunday'])

def in_next_month(dates1, dates2):
    return dates1.month == dates2.month - 1

def next_bus_day(dates):
    fwd = dates + BDayUS(1)
    return fwd.where(~in_next_month(dates, fwd), dates - BDayUS(1))

def payment_day(dates):
    return dates.where(is_weekday(dates), next_bus_day(dates))

For April 2017:

dates = pd.date_range('4/1/2017', '4/30/2017')

payment_day(dates)
# DatetimeIndex(['2017-04-03', '2017-04-03', '2017-04-03', '2017-04-04',
#                '2017-04-05', '2017-04-06', '2017-04-07', '2017-04-10',
#                '2017-04-10', '2017-04-10', '2017-04-11', '2017-04-12',
#                '2017-04-13', '2017-04-14', '2017-04-17', '2017-04-17',
#                '2017-04-17', '2017-04-18', '2017-04-19', '2017-04-20',
#                '2017-04-21', '2017-04-24', '2017-04-24', '2017-04-24',
#                '2017-04-25', '2017-04-26', '2017-04-27', '2017-04-28',
#                '2017-04-28', '2017-04-28'],
#               dtype='datetime64[ns]', freq='D')

payment_day(dates).weekday_name
# Index(['Monday', 'Monday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday',
#        'Friday', 'Monday', 'Monday', 'Monday', 'Tuesday', 'Wednesday',
#        'Thursday', 'Friday', 'Monday', 'Monday', 'Monday', 'Tuesday',
#        'Wednesday', 'Thursday', 'Friday', 'Monday', 'Monday', 'Monday',
#        'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Friday', 'Friday'],
#       dtype='object')

See also: Custom Business Day from the docs. Please stress-test this; I have mostly just worked with default BDay and am not sure if Federal Holidays align perfectly with NYSE market closings.

like image 86
Brad Solomon Avatar answered Oct 28 '25 22:10

Brad Solomon



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!