The pilgrim climbs, the mountain steep,
His burden once light, now runs deep.
What seemed so fleeting in the plain,
Now drags him down with silent strain.
A voice appears, calm and clear,
“Only in trials do mistakes appear”
Aim : To automate a position sizing strategy using python for recurrent use.
Source/further reading : Masterclass With Super-Investors by Vishal Mittal and Saurabh Basrar
There are several questions that we typically need to answer when investing in stocks :
- What stock do we want to buy?
- Why do we want to buy that stock?
- When is the best time to buy the stock?
- How much of the stock should we buy?
‘Position sizing’ addresses the fourth question of how much to buy. Answering this seemingly simpler aspect of buying a stock with great caution will ultimately play a major role in our investment success.
Any investment is a strategic bet which we think is favourable by analysing the potential upside with the potential downside. All the outcomes of any investment/trade can broadly be classified into one of two categories – money made or money lost – the upside and downside. Mistakes on the upside lead to lost opportunities and poor returns and mistakes on the downside leads to capital erosion and excessive losses. There are two distinct aspects when we make investment calls that needs consideration :
- Whether we were right or wrong
- Given that we were right or wrong, whether we managed it well to our advantage
All the investors – both successful and unsuccessful ones lie around the 50% success rate for getting things right. Which means that as investors we’ll be right and wrong almost equally – thus making it essential to capitalise on the times when we are right and make good returns and prevent any untoward losses when we are wrong. This is what makes position sizing such an important aspect.
On one hand, allocating too big a position is going to cause problems on the downside if we are wrong. While a too small of a position is not going to give us meaningful returns even if we are right.
The following strategy helps us build large positions while risking the same capital at every stage.
The Strategy
Suppose we have a total capital of ₹2,00,000 and want to take up a position in a stock that is currently trading at ₹1000 per share.
Capital : ₹2,00,000
CMP : ₹1000
Now we need to decide a maximum % loss of our total capital that we are willing to risk in this particular trade. A safe value to go for would be 1% – we can go higher/lower depending on our risk appetite
Max loss % = 1 %
Now, the maximum money we can lose in this trade should be ₹2,000
Max loss amount = 1% of ₹2,00,000 = ₹2,000
Now, we will have to set a stop loss (SL) for the stock – a price to which if the stock drops, will make us question the investment idea.
SL = ₹900 and loss per share if SL triggered = ₹1000 – ₹900 = ₹100
Now to determine the number of share to buy initially, we just need to divide the Maximum loss amount by the per share loss if SL gets triggered :
No. of shares to buy initially : N = ₹2,000 / ₹100 = 20 shares
Note : This No of shares to buy : 20 shares quantity is the maximum number of shares that can be bought initially. We can buy a portion of this quantity but cannot exceed.
Now after we have bought, the stock price can take any course and the course of action is described in the following matrix :
Initial Position/Stock price from initial buy price | Bought the exact no of shares : N | Bought less than the number of shares : < N |
---|---|---|
Stock went up from buy price | Case 1 : If we would like to pyramid up, move new SL up from old SL and buy some more | Case 2 : If we would like to pyramid up, buy some more – we may or may not move SL. |
Stock went down from buy price but didn’t hit SL | Case 3 : Wait and watch | Case 4 : Buy some more if we would like to average down |
Stock went down from buy price and hit SL | Case 5 : Exit and realise the full 1% loss | Case 6 : Exit and realise the <1% loss |
Explanation using example :
Total Capital = ₹10,00,000 ; Max loss % = 1% ; Thus max loss amount = ₹10,000
Example :
- A stock is trading at ₹450
- We set SL at ₹400
- Per share loss is ₹50
- ₹10,000/₹50 = 200. Thus we can buy maximum of 200 shares initially
Case 1:
- We initially bought all the 200 shares
- Current price has risen to 525 from initial average buy price of ₹450
Now, if we still think the stock is undervalued at ₹525, we can “pyramid up” and increase our position size while risking the same ₹10,000 :
- Move the stop loss from initial 400 to, say 450 now (have to if we want to buy more)
- Buy x = 133 shares at this new price of 525 – Pyramiding Up
- To find this 133 value, we can use the simple python program below
- Or solve for $x$ in the equation:
- $\left( \frac{x \cdot \text{current price} + \text{old_avg_price} \cdot \text{old_qty}}{x + \text{old_qty}} – \text{new_SL} \right) \cdot (x + \text{old_qty}) = \text{Max loss amount}$
- $\left( \frac{x \cdot 525 + 450 \cdot 200}{x + 200} – 450 \right) \cdot (x + 200) = 10,000$
Metric | Initial | Action / Outcome | Final |
---|---|---|---|
Stock Price (₹) | 450 | Increase by 75 | 525 |
buy average (₹) | 450 | Increase by 30 | 480 |
Total quantity (shares) | 200 | Increase by 133 | 333 |
Stop loss (₹) | 400 | Increase by 50 | 450 |
Per share loss (₹) | 50 | Decrease by 20 | 30 |
Total loss (₹) | 50 x 200 = 10,000 | Remains same | 30 x 333 ~= 10,000 |
Capital allocation to stock | 90,000 (9%) | Increase by 7% | ~1,60,000 (~16%) |
Case 2:
- We initially bought only a portion of the 200 shares, say 100
- Current price has risen to 525 from initial average buy price of ₹450
Now if we still think that the stock is undervalued at ₹525, we may add more :
- Wemay or may not increase the SL from the original old SL – this is because we did not fully commit and buy the initial position
- To find the number of shares to buy, solve the same equation as Case 1 :
- If we choose to move the SL up to 450, x = 133 :
- $\left( \frac{x \cdot 525 + 450 \cdot 100}{x + 100} – 450 \right) \cdot (x + 100) = 10,000$
- If we don’t change the SL and keep it at 400, x = :
- $\left( \frac{x \cdot 525 + 400 \cdot 100}{x + 100} – 400 \right) \cdot (x + 100) = 10,000$
Metric | Initial | Action / Outcome | Final |
---|---|---|---|
Stock Price (₹) | 450 | Increase by 75 | 525 |
Buy average (₹) | 450 | Increase by 43 | 493 |
Total quantity (shares) | 100 | Increase by 133 | 233 |
Stop loss (₹) | 400 | Increase by 50 | 450 |
Per share loss (₹) | 50 | Decrease by 7 | 43 |
Total loss (₹) | 50 x 100 = 5,000 | Remains same | 43 x 233 ~= 10,000 |
Capital allocation to stock | 450 x 100 (4.5%) | Increase by 7% | ~1,14,869 (~11-12%) |
Case 3:
- We initially bought all the 200 shares
- Current Price has dropped to somewhere between our buy price and stop loss
- No action required
Case 4:
- We initially bought only a portion of the 200 shares, say 100
- Current Price has dropped to somewhere between our buy price and stop loss, say to 410
- Generally in this case, we might want to move the stop loss down too, otherwise our new position’s average ends up too close to the stop-loss and can cause an easy trigger : Let’s say we move it from initial SL of 400 to 360
- Buy x=20 more shares – Averaging down
- Use the equation / python script to find this 20
Metric | Initial | Action / Outcome | Final |
---|---|---|---|
Stock Price (₹) | 450 | Decreases by 40 | 410 |
Buy average (₹) | 450 | Decrease by 7 | 443 |
Total quantity (shares) | 100 | Increase by 20 | 120 |
Stop loss (₹) | 400 | Decrease by 40 | 360 |
Per share loss (₹) | 50 | Increase by 33 | 83 |
Total loss (₹) | 50 x 100 = 5,000 | Remains same | 83 x 120 ~= 10,000 |
Capital allocation to stock | 45,000 (4.5%) | Increase by 0.5% | ~53,160(~5.3%) |
Case 5:
The trade is a failed one but do not worry for we lost only a tiny fraction of our portfolio. If we take 1% as our per trade loss, even if every trade is a coin flip, even in an extreme case of 20 consecutive failed trades which has a probability of : $\frac{1}{2^{20}} = \frac{1}{1048576}$ , we only lose 20% of capital
Look into where it went wrong. Possible considerations :
- Too close a stop loss for such a volatile stock
- Too close a stop loss for a falling stock with no momentum
- General market downturn/bear market causing exit
- Fundamental reasons / unexpected events causing stock to tank
Case 6:
In this case, you’ll lose less than 1% of capital. Suppose we lost 0.6% since we did not allocate full 200 initial position and we still want to buy the stock, we can enter a position at this new lower price with 1-0.6 = 0.4% as the maximum loss % for the second trade
import math
def initial_position():
total_capital = float(input("Enter the total capital : "))
max_loss_percent = float(input("Enter the maximum % loss that you are willing to risk in this stock : "))
cmp = float((input("Enter the current market price of the stock : ")))
sl = float(input("Enter the Stop-Loss for the stock : "))
max_loss = max_loss_percent/100*total_capital
max_loss_pershare = cmp-sl
shares_to_buy = math.floor(max_loss/max_loss_pershare)
print("")
print("Number of shares to buy : "+str(shares_to_buy))
print("")
print("Position Overview : ")
print("Average holding price : ", cmp)
print("Total holding quantity : ", shares_to_buy)
print("Current stop loss : ", sl)
print("Per share loss : ", max_loss_pershare)
print("Max total loss : ", max_loss_pershare*shares_to_buy)
print("Capital Allocation to stock : ", cmp*shares_to_buy/total_capital*100,"%")
def existing_postition():
total_capital = float(input("Enter the total capital : "))
max_loss_percent = float(input("Enter the maximum % loss that you are willing to risk in this stock : "))
max_loss = max_loss_percent/100*total_capital
existing_avg = float(input("Enter the average purchase price of existing position in the stock : "))
existing_qty = float(input("Enter the quantity(no. of shares) that you already hold : "))
existing_sl = float(input("Enter the existing SL you have for the stock : "))
used_loss = existing_qty*(existing_avg-existing_sl)
if used_loss > max_loss:
print("You have exceeded the amount of shares you were supposed to purchase initially... System exited")
return 0
else:
cmp = float((input("Enter the current market price of the stock : ")))
if cmp <= existing_sl: #Case 5 & 6
print("Looks like the current price has already hit the stop loss... Exit your initial position")
elif (cmp >= existing_avg and used_loss<=max_loss): #Case 1 & 2
upper_move_verify = input("Do you think the stock is still attractive at this increased current price? Enter Y/y for yes and N/n for no : ")
if upper_move_verify=='Y' or 'y':
sl_up_move = float(input("Enter the new Stop-Loss for the stock : "))
new_shares_to_buy_up = (max_loss_percent / 100 * total_capital - (existing_avg * existing_qty - sl_up_move * existing_qty)) / (cmp - sl_up_move)
print("")
print("No of shares to further add : ",math.floor(new_shares_to_buy_up))
new_avg_up = (new_shares_to_buy_up*cmp+existing_avg*existing_qty)/(new_shares_to_buy_up+existing_qty)
print("")
#Print old position
print("")
print("Old Position Overview : ")
print("Old average holding price : ", existing_avg)
print("Old total quantity : ", existing_qty)
print("Old stop loss : ", existing_sl)
print("Old per share loss : ", existing_avg-existing_sl)
print("Old Max Total loss : ", (existing_avg-existing_sl)*(existing_qty))
print("Old Capital Allocation to stock : ", existing_avg*existing_qty/total_capital*100,"%")
print("")
#Print new position
print("New Position Overview : ")
print("")
print("New average holding price : ", round(new_avg_up))
print("New total quantity : ", math.floor(new_shares_to_buy_up+existing_qty))
print("New stop loss : ", sl_up_move )
print("New per share loss : ", round(new_avg_up-sl_up_move))
print("New Max Total loss : ", round((new_shares_to_buy_up+existing_qty)*(new_avg_up-sl_up_move)))
print("New Capital Allocation to stock : ", round(new_avg_up*(new_shares_to_buy_up+existing_qty)/total_capital*100),"%")
else :
print("Do not add to your existing position")
#Print old position
print("")
print("Old Position Overview : ")
print("Old average holding price : ", existing_avg)
print("Old total quantity : ", existing_qty)
print("Old stop loss : ", existing_sl)
print("Old per share loss : ", existing_avg-existing_sl)
print("Old Max Total loss : ", (existing_avg-existing_sl)*(existing_qty))
print("Old Capital Allocation to stock : ", existing_avg*existing_qty/total_capital*100,"%")
return 0
elif cmp < existing_avg and cmp > existing_sl and used_loss<max_loss: #Case 4
lower_move_verify = input("Do you think the stock is still attractive at this lower current price? Enter Y/y for yes and N/n for no : ")
if lower_move_verify=='Y' or 'y':
sl_low_move = float(input("Enter the new Stop-Loss for the stock ** Ensure that it is below the CMP : "))
new_shares_to_buy_low = (max_loss_percent / 100 * total_capital - (existing_avg * existing_qty - sl_low_move * existing_qty)) / (cmp - sl_low_move)
print("")
print("No of shares to further add : "+str(math.floor(new_shares_to_buy_low)))
new_avg_low = (new_shares_to_buy_low*cmp+existing_avg*existing_qty)/(new_shares_to_buy_low+existing_qty)
print("")
#Print old position
print("")
print("Old Position Overview : ")
print("Old average holding price : ", existing_avg)
print("Old total quantity : ", existing_qty)
print("Old stop loss : ", existing_sl)
print("Old per share loss : ", existing_avg-existing_sl)
print("Old Max Total loss : ", (existing_avg-existing_sl)*(existing_qty))
print("Old Capital Allocation to stock : ", existing_avg*existing_qty/total_capital*100,"%")
print("")
#Print new position
print("New Position Overview : ")
print("")
print("New average holding price : ", round(new_avg_low))
print("New total quantity : ", math.floor(new_shares_to_buy_low+existing_qty))
print("New stop loss : ", sl_low_move)
print("New per share loss : ", round(new_avg_low-sl_low_move))
print("New Max Total loss : ", round((new_shares_to_buy_low+existing_qty)*(new_avg_low-sl_low_move)))
print("New Capital Allocation to stock : ", round(new_avg_low*(new_shares_to_buy_low+existing_qty)/total_capital*100),"%")
else :
print("Do not add to your existing position")
#Print old position
print("")
print("Old Position Overview : ")
print("Old average holding price : ", existing_avg)
print("Old total quantity : ", existing_qty)
print("Old stop loss : ", existing_sl)
print("Old per share loss : ", existing_avg-existing_sl)
print("Old Max Total loss : ", (existing_avg-existing_sl)*(existing_qty))
print("Old Capital Allocation to stock : ", existing_avg*existing_qty/total_capital*100,"%")
return 0
else: #Case 3
print("")
print("You have already purchased the full initial position and the current price is between your average price and stop loss... Wait ")
print("For adding further positions, the price must move above your average purchase price")
#Print old position
print("")
print("Old Position Overview : ")
print("Old average holding price : ", existing_avg)
print("Old total quantity : ", existing_qty)
print("Old stop loss : ", existing_sl)
print("Old per share loss : ", existing_avg-existing_sl)
print("Old Max Total loss : ", (existing_avg-existing_sl)*(existing_qty))
print("Old Capital Allocation to stock : ", existing_avg*existing_qty/total_capital*100,"%")
menu_inp = int(input(("Are you entering a new position or adding to an existing position in the stock? Enter 1 for new position, 2 for existing one : ")))
if menu_inp == 1:
initial_position()
elif menu_inp == 2:
existing_postition()
else:
print("Invalid")