## Task

Include trading costs in an optimization.

## Preparation

This presumes that you can do a basic portfolio optimization. For example, that you have mastered “Passive, no benchmark (minimum variance)”.

- Portfolio Probe

You need to have the Portfolio Probe package loaded into your R session:

require(PortfolioProbe)

If you don’t have Portfolio Probe, see “Demo or Buy”.

### Doing the example

- The objects:
`priceVector`

,`grossVal`

,`curPortfol`

that are defined in several examples, such as in “Passive, no benchmark (minimum variance)” `xaLWvar06`

variance matrix from “Returns to variance matrix” example

## Doing it

We will do optimizations that:

- impose simplistic linear costs
- impose simplistic linear costs that differ for buying and selling
- impose non-linear costs

None of the examples are complete — see the “Further details” section for the missing ingredient.

### Simplistic linear costs

We perform an optimization that assumes that trading costs are 10 basis points:

opTC10bps <- trade.optimizer(priceVector, variance=xaLWvar06, existing=curPortfol, gross=grossVal, long.only=TRUE, utility="minimum variance", long.buy.cost=0.001 * priceVector)

This optimization is correct in the sense that it tells the optimizer that trading costs are 10 basis points. But the optimization is (almost surely) wrong in the sense of doing the right thing even if trading costs really are 10 basis points — see the “Further details” section below.

### Costs that differ for buying and selling

Real costs are different depending on whether you are buying or selling. In a long-only portfolio you merely do a sell. But when you buy, you are also committing yourself to a subsequent sell.

Here we impose 20 basis point costs for buying and 10 basis points for selling:

opTC2010bps <- trade.optimizer(priceVector, variance=xaLWvar06, existing=curPortfol, gross=grossVal, long.only=TRUE, utility="minimum variance", long.buy.cost=0.002 * priceVector, long.sell.cost=0.001 * priceVector)

### Non-linear costs

The cost of small trades is linear, but bigger trades have market impact and grow faster than linear. Suppose that we believe that market impact grows with an exponent of 0.6 — so slightly faster than the square root of the inventory model.

First we want to create a two-column matrix that holds the trading cost coefficients per asset. A quick stand-in is:

tcNonlin <- cbind(priceVector * .001, rep(1:5, length=350) * .005)

This looks like:

> head(tcNonlin) [,1] [,2] XA101 0.03356 0.005 XA103 0.07225 0.010 XA105 0.07439 0.015 XA107 0.19206 0.020 XA108 0.00591 0.025 XA111 0.01598 0.005

Now we are ready to do the optimization:

opTCnonlin <- trade.optimizer(priceVector, variance=xaLWvar06, existing=curPortfol, gross=grossVal, long.only=TRUE, utility="minimum variance", long.buy.cost=tcNonlin, cost.par=c(1, 1.6))

## Explanation

### Linear costs

The value given as `long.buy.cost`

(and mates) should be a vector with names that are the asset identifiers. All of the tradable assets must be included. A one-column matrix also works.

There is, of course, no reason that the costs need to be proportional to prices.

### Non-linear costs

The `cost.par`

argument is a vector of the exponents that go with each column of the cost arguments (`long.buy.cost`

and its mates). (But see the “Further details” section below.)

In the example we had:

long.buy.cost=tcNonlin, cost.par=c(1, 1.6)

with

> head(tcNonlin, 3) [,1] [,2] XA101 0.03356 0.005 XA103 0.07225 0.010 XA105 0.07439 0.015

If asset `XA101`

trades 99 shares (buy or sell), then the cost for it will be:

> 0.03356 * 99 + 0.005 * 99 ^ 1.6 [1] 11.1205

The numbers in the first column of `long.buy.cost`

go with the first element of `cost.par`

. The second column goes with the second element of `cost.par`

, and so on. You can have as many columns as you like. In particular, there can be an element of `cost.par`

that is zero, which means there is a fixed cost for trading the asset at all.

### Buys versus sells

The full set of cost coefficient arguments is:

`long.buy.cost`

`long.sell.cost`

`short.buy.cost`

`short.sell.cost`

Ones that are not given will default to the value of `long.buy.cost`

(hence if you are giving costs, you always want to give this argument).

If `cost.par`

is given, then all four of these arguments need to have the same number of columns. The number of columns, of course, has to be the length of `cost.par`

.

## Further details

### Costs relative to utility

In the examples we are minimizing variance. In this case it is easy to see that we must be missing something even if we exactly reproduce our trading costs.

The cost (divided — by default — by the gross value of the portfolio) is added to the utility. So we are adding dollars to something to do with squared returns. Without costs we get the same thing whether the variance is for daily returns or for returns in percent and annualized. But we get an entirely different effect if we add the same costs in these two cases.

Portfolio Probe has the `ucost`

argument (that defaults to 1) which scales the cost before it is put into the utility. So you might include something like:

ucost = 0.002

as an argument in an optimization.

We can do some exploration using the first optimization. The default value of `ucost`

is 1 so that is its value with that first optimization. We can redo the optimization setting `ucost`

to zero (that is, no trading costs):

opTC0 <- update(opTC10bps, ucost=0)

Now we can get the turnover for optimizations that have intermediate values of `ucost`

:

turnTC <- numeric(5) for(i in 1:5) { turnTC[i] <- valuation(update(opTC10bps, ucost=2^-i), trade=TRUE, collapse=TRUE) } names(turnTC) <- 2^-(1:5)

Now we can add the turnover for the two portfolios that we already have:

turnTCall <- c("1"=unname(valuation(opTC10bps, trade=TRUE, collapse=TRUE)), turnTC, "0"=unname(valuation(opTC0, trade=TRUE, collapse=TRUE)))

Figure 1 is a cleaned up version of:

plot(as.numeric(names(turnTCall)), turnTCall, type="l")

Figure 1: Turnover versus `ucost`

value for the simplistic linear costs.

The drop in the amount of trading stops at `ucost=.25`

. So it appears in this example that `ucost`

should be less than .25 (and larger than zero). Remember that if we change the scaling of the variance, then `ucost`

needs to change as well.

An element of melding costs into the utility is that the costs need to be amortized. That is, the expected holding period makes a difference. You need to get a higher rate of value out of an asset that you expect to hold for a day as opposed to an asset you expect to hold for a decade.

### Non-linear costs

#### asset-specific exponents

Above it was stated that `cost.par`

was to be a vector. Actually it can be a matrix with rows that correspond to assets and as many columns as `long.buy.cost`

. For example, we might give a matrix like the following to the `cost.par`

argument:

> head(tcCostpar) [,1] [,2] XA101 1 1.57 XA103 1 1.75 XA105 1 1.47 XA107 1 1.59 XA108 1 1.75 XA111 1 1.72

If all of the rows were equal, then it would be just the same as giving `cost.par`

a vector equal to one of the rows.

#### per trade versus per share

The exponent that you want to use depends on what coefficients you have. The calculation done in the optimizer is on the trade as a whole — it sees the number of shares traded.

If your coefficients are from a model of trade size with an exponent of 0.6, then put 0.6 into `cost.par`

. (Hint: I don’t think so, the exponent should be greater than 1.)

However, if the coefficients you have is for the cost per share, then you need to do a little elementary calculus to get the cost per trade size and the exponent is going to be 1.6 (the original exponent plus one).

## Troubleshooting

- Is a reasonable value of
`ucost`

given to match the trading costs to the utility?

- If you have non-linear costs, are the coefficients and exponents correctly matched in regards to per share versus total shares?

## See also

- Control turnover for an easier way to control the amount of trading
- Example data
- Some hints for the R beginner
- the Portfolio Probe User’s Manual

## Navigate

- Back to “Optimize Trades”
- Back to the top level of “Portfolio Probe Cookbook”