Single-Index Portfolio Selection and Return-Scale Sensitivity
Summary
The document presents an equity portfolio-selection problem based on monthly returns for a large stock universe and a market index. Its code estimates each stock's average return, beta, residual risk, and Treynor ratio, then applies a single-index cutoff procedure to select securities and derive portfolio weights. The author reports that scaling returns by 100 changes the number of selected stocks, raising a question about consistent units in the optimization.
The example indicates that unit consistency matters: expected returns, the risk-free rate, variances, and other quantities in the selection formulas must use compatible scales. The code supplies an implementation but no supporting derivation, independent validation, or out-of-sample performance evidence. It also does not resolve whether the reported difference arises solely from return scaling or from another implementation issue, so readers should check the formulas and units before relying on the selections.
Key ideas
- The code uses a single-index framework to rank stocks and select portfolio holdings.
- It estimates market betas and residual risk from historical monthly returns.
- Return scaling should be consistent across returns, the risk-free rate, and risk measures.
- The document reports different selections under two return scales but does not diagnose the cause.
Tags
Full text
# Correct way of making sharpe optimized portfolio?
# Correct way of making sharpe optimized portfolio?
I have monthly returns of about 977 securities of past 10 years.
If I keep the returns as it is i.e. I do not multiply by 100 and keep the returns as 0.1, 0.2 , -0.3, 1.2
then I get different results in the securities shortlisted if I have used returns that are multiplied by 100 i.e. 10, 20, -30, 120
If I used 100x multiplied returns I get more than 290 securities out of 977 shortlisted and if I use without the 100x multiple I get only 4 securities.
Not sure what is the correct method to apply sharpe optimization model. I was expecting not to concentrated and not to diversified portfolio.
Code:
```
library(RCurl)
library(readxl)
library(lubridate)
library(quantmod)
library(dplyr)
library(reshape2)
library(zoo)
library(data.table)
library(xlsx)
library(plotly)
stocksDataPath <- "Monthly_All_Stocks"
totalPer = 100
#Rf = 100 * 0.00027 #Daily
#Rf = 100 * 0.001302 #Weekly
#Rf = 100 * 0.07 #Yearly
Rf =100 * 0.005654 #Monthly
stocksList <- read.csv("topLiquidScrips.csv")
stocksList[nrow(stocksList)+1,1] = "^NSEI"
#market index (nifty 50)
n50 <- read.csv(paste(stocksDataPath, "\\^NSEI.csv", sep = "" ))
n50$Ret <- 100 *n50$Ret
inds <- 1
stocksRetData <- list()
# Read Stocks Return Data
for (i in 1:nrow(stocksList)){
d <- read.csv(paste(stocksDataPath, "\\", stocksList[i,1] , ".csv", sep="") )
d$Ret <- 100 * d$Ret
if (nrow(d)>=totalPer){
d$Name = stocksList[i,1]
d <- tail(d,totalPer)
stocksRetData[[inds]] <- d
inds <- inds + 1
}
}
RM <- tail(n50$Ret, totalPer)
CALCData <- data.frame(
StockName = as.character(),
AriMean = as.numeric(),
Beta = as.numeric(),
Alpha = as.numeric(),
Variance = as.numeric(),
StdDev = as.numeric(),
SysRisk = as.numeric(),
UnSysRisk = as.numeric(),
stringsAsFactors = FALSE
)
for (i in 1:length(stocksRetData)){
betaVal = cov(RM,stocksRetData[[i]]$Ret)/var(RM)
Ri = mean(stocksRetData[[i]]$Ret)
CALCData <- rbind(CALCData,
data.frame(
StockName = stocksRetData[[i]]$Name[1],
AriMean = Ri,
Beta = betaVal,
Alpha = Ri - (mean(RM)*betaVal),
Variance = var(stocksRetData[[i]]$Ret),
StdDev =StdDev(stocksRetData[[i]]$Ret),
SysRisk = StdDev(RM) * betaVal,
UnSysRisk = StdDev(stocksRetData[[i]]$Ret) - (StdDev(RM) * betaVal)
)
)
}
#---------------------------------------------------------------------------
CALCData$MarketRiskPremium = CALCData$AriMean - Rf
CALCData$TreynorRatio = CALCData$MarketRiskPremium/CALCData$Beta
# Sort by Tryenor ratio in descending order
CALCData <-CALCData[order(-CALCData$TreynorRatio),]
CALCData$CVal = (CALCData$MarketRiskPremium * CALCData$Beta) / CALCData$UnSysRisk
CALCData$CUMSUMCVal <- cumsum(CALCData$CVal)
CALCData$BetaSqrVar = (CALCData$Beta ^2) / CALCData$UnSysRisk
CALCData$CUMSUMBetaSqrVal = cumsum(CALCData$BetaSqrVar)
CALCData$Cutoff = ( var(RM)*CALCData$CUMSUMCVal ) / (1 + var(RM) + CALCData$CUMSUMBetaSqrVal )
CALCData$Diff =CALCData$Cutoff- lag(CALCData$Cutoff,1)
CALCData$Diff[is.na(CALCData$Diff)] <- 0
CALCData <- subset(CALCData,CALCData$Diff > 0)
CVal = tail(CALCData$Cutoff, 1)
#Z=(C5/D5)*((E5/C5)-$K$1)
CALCData$ZVal = (CALCData$Beta / CALCData$UnSysRisk)/ ( (CALCData$MarketRiskPremium / CALCData$Beta) - CVal)
sumofZVal = sum(CALCData$ZVal)
CALCData$InvProportion = 100 * round(CALCData$ZVal /sum(CALCData$ZVal),3)
#write.csv(CALCData,paste("CALCData_", stocksDataPath ,"_", totalPer, "_Period_", "_", gsub(":","", Sys.time()),".csv", sep=""), row.names = FALSE)
```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.