Tax-smart withdrawals and partially ordered sets

Audience requirements: Some Java, but only for the last half.

Problem description

When a client makes a large enough withdrawal, we may need to suspend some of our normal restrictions, such as avoiding wash sales1, or avoiding selling of (highly-taxed) short-term capital gains2. If the client wants to withdraw money, we cannot just tell her “sorry, that would cause a wash sale”.

Solution

An architectural principle of ours is that, for almost everything that could be a hard-coded constant or a configuration file setting, we instead allow it to be dynamically passed into the system on a per-API-call basis3. That is, investing customer X’s portfolio could use one set of settings, and then one second later the system can be called with customer Y and his (possibly different) settings.

In keeping with that spirit, it is possible to specify a “withdrawal relaxation path” dynamically. The Java code reads like English:

taxAwareEigenRebalancerRelaxationPathInstructions(
    emptyTaxAwareEigenRebalancerSettingsRelaxationStepInstructions(),
    relaxTo(onlySellShortTermLosses(),        DISALLOW_WASH_SALES),
    relaxTo(doNotSellAnyGains(),              DISALLOW_WASH_SALES),
    relaxTo(doNotSellShortTermCapitalGains(), DISALLOW_WASH_SALES),
    relaxTo(canSellAnything(),                DISALLOW_WASH_SALES),
    relaxTo(canSellAnything(),                ALLOW_WASH_SALES));

The idea is that, if it is not possible to fulfill the withdrawal with the restrictions implied by the settings in step N, we try the settings in step N+1, and so on, until it is possible for the withdrawal to go through (i.e. the underlying linear optimization is feasible).

Our mechanism is very flexible. It is possible to specify separate restrictions for short-term and long-term tax lots. The following example can be passed as a 1st argument to relaxTo above, and will not sell any:

  • short-term capital gain above 20%
  • long-term capital gain above 50%
  • short-term capital gain that will go long-term (and be eligible for lower tax) in 15 or fewer days
  sellPermissionsBuilder()
      .forShortTerm(doNotSellAbove(onesBasedReturn(1.2))
      .forLongTerm(doNotSellAbove(onesBasedReturn(1.5))
      .dontSellThisManyDaysBeforeLongTermGain(15)
      .build();

What is partial comparison?

Partial comparison is (roughly speaking) when not all items in a set are comparable. As an example, let us use a pair of portfolios (N, R): N can be liquidated now, whereas R can only be liquidated in 20 years (at retirement).

It is hard to disagree that it is always better if one or both of N and R have more money in them:

  • (100, 301) > (100, 300)
  • (101, 300) > (100, 300)
  • (101, 301) > (100, 300)

However, how do you compare (101, 300) and (100, 302)?

  • (101, 300) has a bigger amount that can be used right now
  • (100, 302) has more total money in it

Any preference for one or another pair of portfolios is not objective, and will depend on liquidity needs.

Why partial comparison?

We want to confirm that the sequence of relaxation steps is indeed in increasing “relaxation order”.

There is nothing inherently wrong if this is not respected. We can tell the system to try trading first under some loose restrictions (step 1), and – after that fails – try trading with stricter restrictions (step 2). The stricter step will always fail if the looser step failed. However, the 2nd step is a waste of resources. Even more importantly, it may mean that the person who constructed this sequence of steps made an error, so it is good to get an alert.

Partial comparison is relevant here, because “relaxation steps” are partially comparable. Take the following steps:

  • Step 1: do not sell short-term capital gains of over 10%, or long-term gains over 20%
  • Step 2: do not sell short-term capital gains of over 17%, or long-term gains over 27%
  • Step 3: do not sell short-term capital gains of over 14%, or long-term gains over 15%

Step 2 is more relaxed than step 1, as it is less restrictive in selling both short- and long-term gains.

However, there is no partial comparison of step 1 and step 3; step 3 is more restrictive with long-term gains, but less restrictive with short-term gains.

Our sanity checks will only complain if step K is strictly less relaxed than step M (K < M), but not when they are not comparable. Still, this has the potential to catch bugs, so it increases system safety.

Our Java support for partial comparison

In order to build our sanity checks, we formalized the concept of partial comparison in our code. Java has Comparable<T> and Comparator<T>, but there is no support for partial comparison, even in commonly used libraries such as Google Guava or Apache Commons. Therefore, we wrote our own.

Without too much code, here is a summary of what we added:

public interface PartialComparator<T> {
  PartialComparisonResult partiallyCompare(T o1, T o2);
}
public interface PartiallyComparable<T> {
  PartialComparisonResult partiallyCompareTo(T o);
}

PartialComparisonResult (code not shown): can be less-than, equal, greater-than, undefined (4 cases total). This also abstracts away the less-intuitive outcomes of Comparable#compareTo, which use {negative, 0, positive} to denote {<, ==, >}.

The following method will look at the comparisons of all fields of two objects, and determine whether there is a well-defined ordering or not. This generalizes the idea that, for class A, if all fields of object A1 are >= those of object A2, and at least one is >, then A1 > A2. If any field is not comparable, then A1 and A2 are not comparable.

public static PartialComparisonResult partiallyCompareMultiple(
    PartialComparisonResult first, 
    PartialComparisonResult second,
    PartialComparisonResult...rest)

Finally, the following throws an exception if the items in a list do not respect a certain partial comparison, as per the PartialComparator<T> passed in. One actually has to compare every pair of items, not just consecutive ones.

public static <T> void checkDecreasingPerPartialComparison(
    PartialComparator<T> partialComparator, 
    List<T> list, 
    String format, 
    Object...args) {
  forEachUnequalPairInList(list, (item1, item2) ->
      partialComparator.partiallyCompare(item1, item2)
          .getRawResult()
          .ifPresent(comparisonResult -> Preconditions.checkArgument(
              comparisonResult > 0,
              format,
              args)));
}

All this infrastructure will now be reusable for other cases in the future, beyond this specific scenario of withdrawal relaxation instructions.

Summary

It is possible to specify dynamically – and in a very understandable, English-language-like format – how we want the system to behave when there is a withdrawal. We are very meticulous about system safety, so we added checks that the sequence of constraint relaxation steps does not get stricter. We could have done this with a one-off, localized check, but we instead added support in the code for generalized partial comparison. This makes for cleaner code; also, we can reuse that infrastructure later, if needed.


Notes

  1. Very roughly speaking, a wash sale is when we buy and sell the same stock within a month. The IRS disallows claiming a capital loss in certain cases. It is there to prevent a largely riskless realization of capital losses just for tax purposes. Wash sales add undesirable for several reasons (beyond the scope of this post).
  2. Our optimization logic already penalizes selling of capital gains. However, it takes in an additional parameter that results in adding hard constraints to avoid bad tax outcomes, such as selling short-term capital gains, selling any long-term gains above 50%, etc. The idea is that, even if in some cases selling a short-term capital gain may improve the portfolio in some way (e.g. better tracking to the target allocation), in practice it looks bad to a client if we sell short-term capital gains. Note that this is highly configurable, so “no extra constraints” is still an option.
  3. One obvious advantage is that it makes the system very flexible. However, another big advantage is that we can have test code that compares what happens with one setting vs. another. For example, we can assert that, over the duration of an 8-year backtest, we will realize fewer tax gains if we prevent the selling of gains than if we do not. This, in turn, helps us ascertain that our investing logic is correct.

Author: Rowboat Advisors

Rowboat Advisors builds software for sophisticated and fully automated portfolio management for the financial advisor industry.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s