Triple-witching day

Yesterday evening I was on a conference call with some colleagues. We are all trying to complete the UAT testing for a major project deployment next month.

Today is a triple-witching day (TL;DR: triple-witching day happens 4 times a year when many bank staff go home late from work). Unfortunately the test-managers want to use today to test the operations on a triple-witching day – trouble is I didn’t realise until now the implications for the system we are contributing. We’ll need to write and apply a fix to support triple-witching day.

So please join me for this special-edition ‘live’ blogging (on the train down to London) where I think about what we can do.

We have a scheduler in the system that schedules a particular job for 1730 in New York. We’ll have to disable that job today, and write up a procedure guide for what our operations staff should do every triple-witching day. But this project is designed to deliver cost-savings to the bank through automation, so it seems counter to the aims of the project to expand the number of manual processes.

Fortunately our scheduler (which we call ‘chime’) is written in Clojure. It is built on clj-time which in turn brings in the excellent Joda-Time library.

Our scheduler is designed to exploit Clojure’s lazy sequences. Our functions return sequences of infinitely re-occuring events. We can then use sequence operators such as map, filter and take. Then we build schedules by attaching these events to functions that get called at the right time.

We already have a function that returns all the days on which banking business happens in a given city (everyday excluding weekends and holidays). That function calls out to a C++ library which embeds official market information about holidays so we won’t cover that here.

For New York, we call it like this :-

(business-days "NY")

This returns a sequence of all the business days from today (which is implied).

We can override ‘today’ to be something else, with our as-of macro, which binds a dynamic var which usually defaults to the current time.

(def ^:dynamic ^DateTime *now* nil)

(defmacro as-of [^Object from & body]
  `(with-bindings {(var *now*) (DateTime. ~from)}
     ~@body))

(as-of "2010-10-20"
  (business-days "NY"))

What I want to do is build a function that returns a sequence of business days that are triple-witching-days, so I can build a special schedule for days like today.

Triple witching days are the third Friday of months of March, June, September and December (with the exception of Good Friday, which is a holiday, so the triple-witching day occurs on the prior Thursday).

Joda-Time doesn’t have the notion of ‘third Friday in the month’. Let’s first build a predicate in clj-time for what that means.

In London, our Clojure Dojos happen on the last Tuesday of the month.

(defn dojo-day? [day]
  (and (= DateTimeConstants/TUESDAY (.getDayOfWeek day))
       (not= (.getMonthOfYear day) (.getMonthOfYear (.plusWeeks day 1)))))

The first constraint checks that it’s a Tuesday. The second term tests whether the following Tuesday is in the same month. If not, then it must be the last Tuesday in this month, so it’s a Dojo-day!

We can easily generate the sequence of Dojo days from today. We start by generating a list of all the days from today :-

(defn ^LocalDate today []
  (LocalDate. *now*))

(defn days-from-today []
  (iterate #(.plusDays ^LocalDate % 1) (today)))

Now we just add a filter.

(take 6 (filter dojo-day? (days-from-today)))

which returns :-

(#<LocalDate 2012-09-25> 
 #<LocalDate 2012-10-30> 
 #<LocalDate 2012-11-27> 
 #<LocalDate 2012-12-25> 
 #<LocalDate 2013-01-29> 
 #<LocalDate 2013-02-26>)

I’m not sure we’ll have a Clojure Dojo on Xmas day this year but who you never know with those guys…

So back to work. With the predicate that tests for the Dojo day we can write a variation that tests for the last Friday in a month :-

(defn third-friday? [day]
  (and (= DateTimeConstants/FRIDAY (.getDayOfWeek day))
       (= (.getMonthOfYear day) (.getMonthOfYear (.minusWeeks day 2)))
       (not= (.getMonthOfYear day) (.getMonthOfYear (.minusWeeks day 3)))))

This is based on a similar principle. If it’s the third Friday of a month than the Friday 2 weeks before must be in the same month, but the Friday three weeks before must be in the previous month.

We can now build in the months in which triple-witching days occur.

(defn triple-witching-day? [day]
  (and
   (third-friday? day)
   (#{3 6 9 12} (.getMonthOfYear day))))

We not quite done yet because we haven’t considered what happens on Good Friday. We mustn’t fall into the trap of considering the third Thursday of the month – it still has to be the day before the third Friday.

(defn triple-witching-day? [day]
  (or (and
       (third-friday? day)
       (#{3 6 9 12} (.getMonthOfYear day))
       (business-day? "NY" day))
      (let [fri (.plusDays day 1)]
        (and
         (not (business-day? "NY" fri))
         (third-friday? fri)
         (#{3 6 9 12} (.getMonthOfYear fri))))))

Now our 2 schedules can be built as follows :-

(filter triple-witching-day? (business-days))
(filter (comp not triple-witching-day?) (business-days))

Note I prefer the (comp not f) style to writing #(not (f %)).

Now I’m ready to patch our UAT system when I get in. Still a few minutes left on the train for a few observations…

  1. In an ideal world you’d never have these awkward situations when you have to think how to patch a system with new functionality as soon as possible. But banks are not ideal worlds and these things do crop up. The Agile manifesto values ‘responding to change, over working to a plan’ for days like today.

  2. I’ve found hacking many fixes into a system usually degrades the architecture quickly. In Java systems these fixes can create dependency cycles. In Clojure systems the architecture is protected from cycles occurring because a Clojure system won’t even start if you have them between namespaces, and inside namespaces as long as you avoid using declare – this is a topic for a whole new blog article!

Well, my train is now slowing down so I have to post this.

(If you spot any flaws with my algorithms, please tell me soon!!!!)

Wish us luck!