# Inconsistency between simulation and the probability of a "stock" hitting take profit before stop loss

Let's assume a stock at time $$t$$ is worth $$X(t)$$. If the returns of $$X(t)$$ are i.i.d. and normally distributed,the probability of $$X(t)$$ hitting a value $$H>X(t)$$ before $$L is $$\frac{H-X(t)}{H-L}$$ (not considering exchange fees and the cost of shorting).

Now, I ran a python simulation on a stochastic process with normal i.i.d returns (price diffs) and obtained results that seem inconsistent with my assumptions about the probabilities mentioned above. I took the folling steps in the simulation:

1. obtain a simulated time series of prices using the "fake_stock" function.
2. upsample the price data to 5 minute bars.
3. obtain the high, low, and close of each 5 minute bar.
4. calculate the close to high percentage (ch) of each 5 minute bar. $$ch = \frac{high-close}{high-low}, 0 <=ch <= 1$$. In theory, $$ch$$ denotes the probability of $$X(t)$$ hitting the high after the low, correct? This is where the inconsistency lies.
5. run 'high_after_low' to see if $$H$$ was hit after $$L$$.
6. based on step five, I obtain a dataframe of close_to_highs and a boolean showing if they hit their respective highs before lows.
def fake_stock():
'''returns a time series of prices based on gaussian returns'''
dti = pd.date_range(start="2018-01-01 17:00", end="2019-01-01 16:00", freq="S")
returns = np.random.normal(0, 1, len(dti))
df = pd.DataFrame(index=dti, columns=["price"])
df["price"] = returns
df = df.between_time("17:00", "16:00")
df["price"] = df["price"].cumsum()
df["price"] = df["price"] - df["price"].min()
return df

def upsample(df, freq="5T"):
'''upsample second bars to minutes. obtain close, high, and low of each bar'''
upsampled_df = pd.DataFrame()
upsampled_df.loc[:, "high"] = df["price"].resample(freq, label="left", closed="left").max()
upsampled_df.loc[:, "low"] = df["price"].resample(freq, label="left", closed="left").min()
upsampled_df.loc[:, "close"] = df["price"].resample(freq, label="left", closed="left").last()
upsampled_df.loc[:, "close_to_high"] = (upsampled_df["high"]-upsampled_df["close"])/(upsampled_df["high"]-upsampled_df["low])
return upsampled

def high_after_low_apply(row, minute_df):
'''returns True if high was reached after low, False otherwise'''
minute_df_after = minute_df.loc[row.end_idx:]
first_highs = (minute_df_after.ge(row.high))
first_lows = (minute_df_after.le(row.low))
if ((len(first_highs) == 0) & (len(first_lows) == 0)):
return None
elif (len(first_highs) == 0):
return True
elif (len(first_lows) == 0):
return False
return first_highs.idxmax() > first_lows.idxmax()


Now, we are ready to run the simulation.

df = fake_stock()
upsampled_df = upsample(df)
upsampled_df.loc[:,"end_idx"]=upsampled_df.index+pd.Timedelta(minutes=5)
upsampled_df.loc[:,"high_after_low"] = upsampled_df.apply(high_after_low_apply, axis=1, args=(df,))
upsampled_df = pd.sort_values(upsampled_df, by="close_to_high")


Now we have the upsampled_df dataframe sorted by close_to_high values. Let's drop close_to_high values that are 0 or 1.

upsampled_df = upsampled_df.loc[(upsampled_df["high_after_low"]!=0)&(upsampled_df["high_after_low"]!=1)]


Now, I can calculate the historic probabilities of hitting high after low using a rolling window.

probs = (upsampled_df["close_to_high"]*upsampled_df["high_after_low]).rolling(1000).sum()/upsampled_df["close_to_high"].rolling(1000).sum()


when I compare probs to the average values of high_after_low, I get inconsistent results. Namely, for small close_to_high values the probability of hitting the high after the low is larger than theory suggests and vice versa. What is the explanation for this inconsistency?

• Are you sure your (H-X(t))/(H-L) assumption is correct? It would work by symmetry if X(t) were the average of H and L, but I don't see it working otherwise. At the very least, you'd need to apply the normal's pdf? Did you mean uniformly distributed? May 22, 2022 at 15:07
• What is your sample size ? May 22, 2022 at 15:13
• @barrycarter it's based on the assumption that E.V. for X(t) hitting any of the targets is zero. $EV(.) = (H-X(t))p + (X(t)-L)(1-p)$ May 22, 2022 at 15:22
• @HamishGibson 30,000,000 seconds. Since i'm using simulated data in this example, sample size is not a practical constraint. May 22, 2022 at 15:22
• Can you please clarify why the probability of $X$ reaching $H$ before it reaches $L$ is $(H-X)/(H-L)$? May 23, 2022 at 8:06