Risk of ruin in trading (1)

Using Python and a Monte Carlo approach to estimate it

Roberto Gomes, PhD
5 min readFeb 24, 2024
Photo by yeswanth M on Unsplash

Frequently neglected or entirely overlooked, the risk of ruin in trading is the probability that, after a certain number of trades, the balance of the trading account will fall below zero. Put differently, it signifies the likelihood that the capital allocated for financial speculation will be completely wiped out, thereby preventing the trader from continuing in trading.

Upon googling, you will come across formulas that, given their assumptions (as is usual in any model), allow for the estimation of the probability of ruin for a trading system. For sure, you can find articles about this right here on Medium.

In this article, however, I’m not going to delve into these formulas. Instead, I choose to introduce a simple Monte Carlo (MC) algorithm — emphasizing that I am not asserting its originality — that facilitates, through simulations, the estimation not only of the probability of ruin but also of other factors that may be pertinent to a trader’s analysis.

The implementation of the MC algorithm within a function, which I called calcRoR() , written in Python is as follows:

def calcRoR(npaths,ntrades,winratio,capital,profit,loss):
balance=[]
finalbalance=[]
consecutivewins=[]
consecutivelosses=[]
ruinindex=ruined=0
index=[j for j in range(ntrades)]

plt.title("Trading paths")
plt.xlabel("Trade index")
plt.ylabel("Account balance")
plt.xlim(left=0,right=ntrades)

for i in range(npaths):
nwins=0
maxconsecutivewins=0
nlosses=0
maxconsecutivelosses=0
isruined=False

for j in range(ntrades):
if j==0:
balance.append([capital])
else:
if isruined:
balance[i].append(balance[i][j-1])
else:
r=random.uniform(0.0,1.0)

if r<=winratio:
balance[i].append(balance[i][j-1]+profit)

nwins+=1
nlosses=0

if nwins>maxconsecutivewins:
maxconsecutivewins=nwins
else:
balance[i].append(balance[i][j-1]+loss)

nlosses+=1
nwins=0

if nlosses>maxconsecutivelosses:
maxconsecutivelosses=nlosses

if balance[i][j]<=0.0:
ruined+=1
ruinindex+=j
isruined=True

consecutivelosses.append(maxconsecutivelosses)
consecutivewins.append(maxconsecutivewins)
plt.plot(index,balance[i])
finalbalance.append(balance[i][-1])

plt.show()

if ruined>0:
ruinindex=ruinindex/ruined

print("SIMULATION OUTPUT:\n-------")
print("Probability of ruin: %.1f" % (ruined/npaths*100))
print("After how many trades, on average, the account will be ruined: %.1f" % ruinindex)
print("Expected number of consecutive wins: %.1f" % (mean(consecutivewins)))
print("Expected number of consecutive losses: %.1f" % (mean(consecutivelosses)))
print("Expected final balance of the trading account: %.2f" % (mean(finalbalance)))
print("Maximum final balance of the trading account: %.2f" % (max(finalbalance)))
print("Minimum final balance of the trading account: %.2f" % (min(finalbalance)))

Here’s a brief explanation of the algorithm: it randomly generates npaths independent paths — generating random paths is the basis of any MC simulation — where each path corresponds to the evolution of a trading account as up to ntrades trades are conducted.

The specifics of each trade in the MC simulation, which may involve the buying and/or selling of stocks, forex, options, or commodities, are not really important. What matters is that each opened trade can be closed either with a profit equal to profit or a loss equal to loss, where the probability of the former outcome is winratio times 100, and the probability of the latter is (1-winratio) times 100. Below, we'll explore a use case for the sake of illustration.

As winning and losing trades unfold along a Monte Carlo path, the trading account evolves with the amounts profit or loss being added to its balance, the initial value of which is capital.

The function calcRoR() displays on-screen a set of (presumably) valuable pieces of information. The first is the probability of ruin itself, expressed as a percentage. The second is the average (expected) number of trades after which the trading account will be wiped out, for the MC paths that lead to this unfortunate outcome.

In real-world scenarios of systematic trading, traders should not anticipate any alternating pattern of wins and losses. Instead, it is common for them to observe several consecutive wins or losses, which typically impacts the emotional state of many traders, either leaving them optimistic or inducing panic. The calcRoR() function also calculates the average (expected) value of consecutive wins and losses.

Upon concluding the Monte Carlo simulation, the expected final balance in the trading account is calculated by averaging the final balances of the generated paths. Additionally, the simulation returns the maximum and minimum final balances.

Last but not least, a graph depicting all paths generated by the Monte Carlo simulation is also plotted.

It is time to provide an example of using the Monte Carlo algorithm. In our first simulation, the initial balance of the trading account is $1000. We will generate 1000 independent paths randomly, each comprising up to 100 trades.

Winning trades in our trading strategy, whatever it may be, have a probability of occurrence equal to that of losing trades: 50%. However, each successful trade yields a return of $100, while unsuccessful ones result in a -$95 impact on the trading account. It’s essential to note that the allocation of funds in the trading account balance to each trade is not significant. It could be the entire amount of available capital (which you might otherwise use, for example, to buy stocks) or not a single cent (if the trade involves selling options). For the purposes of our analysis, only the outcomes of the trades matter: if they are winners, $100 will be added to the trading account; otherwise, $95 will be subtracted. If, after a trade, the account balance is equal to or less than zero, the game is over.

The scenario described above implies calling calcRoR() as we see next:

calcRoR(npaths=1000,ntrades=100,winratio=0.5,capital=1000,profit=100,loss=-95)

After the MC simulation ends, we see the plot of the trading paths:

Something quite evident in this plot (which may appear counterintuitive at first) is that, despite the return from winning trades being higher than that of losing trades, with both cases having the same probability of occurrence, there are still paths that end in ruin. In fact, the probability of ruin for this hypothetical strategy has proven to be quite high, nearly 21%!

Quite intriguing, isn’t it? In the upcoming article, I will run more simulations to discuss the implications of these simulations in more detail.

Always important to emphasize:

  • I am not a financial advisor, and the code and simulation presented in this article are purely for educational purposes.
  • Trading is a risky activity that should be approached with due diligence. I am not responsible for any losses incurred through the use of this code.

That being said, suggestions and corrections to the code are welcome and can be submitted as comments on this article. Feel free to connect with me on LinkedIn or on X.

--

--

Roberto Gomes, PhD

I am a Professor of Materials Engineering specialized in computational research in materials science, physics, and engineering. Also interested in finance.