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<X(t)$ 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[:,"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?

  • $\begingroup$ 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? $\endgroup$ May 22, 2022 at 15:07
  • $\begingroup$ What is your sample size ? $\endgroup$ May 22, 2022 at 15:13
  • $\begingroup$ @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)$ $\endgroup$ May 22, 2022 at 15:22
  • $\begingroup$ @HamishGibson 30,000,000 seconds. Since i'm using simulated data in this example, sample size is not a practical constraint. $\endgroup$ May 22, 2022 at 15:22
  • $\begingroup$ Can you please clarify why the probability of $X$ reaching $H$ before it reaches $L$ is $(H-X)/(H-L)$? $\endgroup$ May 23, 2022 at 8:06


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.