Skip to content
All library documents

Calculating Returns for Long-Short Portfolios With Cash

Article Quant Q&A · Author: Python Developer

Summary

The document explains how to calculate portfolio returns when security weights may be negative or may sum to less than the full portfolio value. It assumes the positions are not rebalanced and starts with a total portfolio value of one. Returns are converted into price paths beginning at one, then each period’s portfolio value is calculated as the weighted sum of those prices plus cash equal to one minus the weights’ sum. Period returns follow from consecutive portfolio values.

The worked example applies this method to fully invested, net-long, and market-neutral weights. Its calculations reproduce the supplied benchmark series, including the later-period results where changing asset values alter the portfolio’s exposure. The treatment depends on the stated assumptions: cash is included in total portfolio value, and weights are held as initial quantities rather than reset each period. Different cash-return assumptions, financing costs, or rebalancing rules could produce different returns; those are not addressed.

Key ideas

  • When weights do not sum to one, the remaining portfolio value is treated as cash under the stated setup.
  • Convert each security’s returns into a price series starting from one.
  • Calculate portfolio value by combining weighted security prices with the cash balance.
  • Compute each period’s return from the ratio of consecutive portfolio values.
  • The example assumes no rebalancing and does not model cash interest or short-borrow costs.

Tags

Full text
# How to Normalize Weights When Weights don't sum to 100%


# How to Normalize Weights When Weights don't sum to 100%












I'm working on a take-home assignment for a company. They want me to calculate the return of a portfolio of securities over time, given the returns of the securities over time and the initial weighting of the securities. I can do this for the normal case of no shorting, i.e. no negative weights. However, for the cases of 20% net long and 0% net long, I get different answers from their test cases after the first period, i.e. after the weights diverge from their initial values. I believe this is because I am not normalizing the weights properly. For the 100% long case, I just divide each weight by the sum of all weights to ensure they sum to 100%. What's the equivalent procedure for the other cases?

Here are the security returns:

```
SAMPLE_RETURN_ARRAY = [
    [0.02, 0.01, 0.08, 0.04, -0.05],  # MSFT
    [-0.02, 0.03, 0.06, 0.01, 0.05],  # AAPL
    [0.01, -0.04, -0.03, -0.05, -0.03],  # GOOG
    [0.04, 0.02, 0.01, -0.02, -0.01],  # TSLA
    [-0.01, -0.01, -0.06, -0.03, 0.02],  # JPM
]
```

And here are the initial weights:

`SAMPLE_WEIGHTS = [0.2, 0.3, 0.1, 0.05, 0.35]`

these are the results for the 100% long case:

```
correct_result = [-0.0025, 0.00440602, 0.01166979, -0.00329201, 0.00761087]
r1 = np.all(np.isclose(correct_result, result, atol=1e-5, rtol=0))
```

I do indeed get their results correct to 5 decimal places.

Here are there results for 20% long and 0% long (i.e. market neutral):

```
20%:     correct_result = [0.0005, 0.00924038, 0.05129543, 0.01710086, -0.00349387]
0%:      correct_result = [0.0015, -0.01729905, -0.05853928, -0.02974261, -0.00176708]
```

In the 20% case, the weights are `[0.2, 0.3, 0.1, -0.05, -0.35]` and in the 0% case the weights are `[-0.2, -0.3, 0.1, 0.05, 0.35]`.

Can someone help me understand how the 0.00924038 and -0.01729905 (the second-period portfolio returns) are calculated? I don't need code (although you're welcome to provide), just the math behind the numbers. I've tried a bunch of things and get close numbers, but never exact or to 5 decimal places.

Let me know if I can provide more info.

Many thanks in advance!

## Answer by Enrico Schumann (score 2, accepted)

https://quant.stackexchange.com/a/76358

Whenever you are not fully invested, you'll need to make assumptions. Simple example: You have USD 100 and invest 20% of it. Should you compute returns with a notional of only USD 20 (the actual notional invested), or on the total USD 100? In practice, these assumptions are rarely made explicit, but neither are they contentious: Since we stress that we invested 20%, we typically compute returns on the full notional.

In your example, the assumptions seem to be: you never rebalance; and you should not do any rescaling, but assume an initial notional of 1 (i.e. 100%).

One way to compute returns in this case is as follows:

- Create artificial prices series of your returns series, starting with 1 at time 0, and collect them in a matrix $P$, each stock in one column.

- Compute the portfolio values $v = Pw + (1 - \Sigma(w))$, with the remaining cash (if any) included in the portfolio value.

- Compute simple returns of $v$.

As a check, I do these computations in R, and compare with the results of function `returns` of package PMwR (which I maintain):

```
library("PMwR")

SAMPLE_RETURN_ARRAY <- cbind(
    MSFT = c( 0.02,  0.01 , 0.08,  0.04, -0.05),
    AAPL = c(-0.02,  0.03,  0.06,  0.01,  0.05),
    GOOG = c( 0.01, -0.04, -0.03, -0.05, -0.03),
    TSLA = c( 0.04,  0.02,  0.01, -0.02, -0.01),
    JPM  = c(-0.01, -0.01, -0.06, -0.03,  0.02)
)
```

First, create artificial price series:

```
P <- apply(rbind(0, SAMPLE_RETURN_ARRAY) + 1, 2, cumprod)
##          MSFT     AAPL      GOOG     TSLA       JPM
## [1,] 1.000000 1.000000 1.0000000 1.000000 1.0000000
## [2,] 1.020000 0.980000 1.0100000 1.040000 0.9900000
## [3,] 1.030200 1.009400 0.9696000 1.060800 0.9801000
## [4,] 1.112616 1.069964 0.9405120 1.071408 0.9212940
## [5,] 1.157121 1.080664 0.8934864 1.049980 0.8936552
## [6,] 1.099265 1.134697 0.8666818 1.039480 0.9115283
```

Now the computations:

```
## invest net 100%
w <- c(0.2, 0.3, 0.1, 0.05, 0.35)
## compute manually
v <- P %*% w + (1 - sum(w))
v[-1]/v[-length(v)] - 1 
## check with PMwR::returns
c(returns(P, weights = w, rebalance.when = 1))
## [1] -0.002500000  0.004406015  0.011669786 -0.003292007  0.007610873
## [1] -0.002500000  0.004406015  0.011669786 -0.003292007  0.007610873

## invest net 20%
w20 <- c(0.2, 0.3, 0.1, -0.05, -0.35)
## compute manually
v <- P %*% w20 + (1 - sum(w20))
v[-1]/v[-length(v)] - 1 
## check with PMwR::returns
c(returns(P, weights = w20, rebalance.when = 1))
## [1]  0.000500000  0.009240380  0.051295426  0.017100863 -0.003493869
## [1]  0.000500000  0.009240380  0.051295426  0.017100863 -0.003493869
```

Finally, net-zero investment:

```
## invest net 0%
w0 <- c(-0.2, -0.3, 0.1, 0.05, 0.35)
## compute manually
v <- P %*% w0 + (1 - sum(w0))
v[-1]/v[-length(v)] - 1 
## check with PMwR::returns
c(returns(P, weights = w0, rebalance.when = 1))
## [1]  0.00150000 -0.01729905 -0.05853928 -0.02974261 -0.00176708
## [1]  0.00150000 -0.01729905 -0.05853928 -0.02974261 -0.00176708
```

Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.