How to calculate the explicit price matching various Bollinger band standard deviation levels?

Question:

Utilizing 1min data I want to know the exact price at which the various standard deviation levels equal that price.

Bollinger Bands commonly use 2 standard deviations and a rolling period of 20 to calculate the current level for these standard deviations. What I’m trying to do is take the previous 19 1min candles to get the 20th candle at which the close_price equals the specified standard deviation level. I want to do this to potentially determine exit/entry levels ahead of time.

Here’s what I’ve done so far in Sympy:

from sympy import Symbol, solve, sqrt, Eq
import numpy as np

def get_equal_bollinger_bands(df, stdev_list = [0, 0.5, 1, 1.5, 2, 2.5, 3,3.5,4,4.5,5,5.5]):
    matching_prices_dict = {}
    for stdev_ in stdev_list:
        stddev_level_list = [stdev_, -stdev_]

        if (stdev_ == 0):
            stddev_level_list = [0]

        for stddev_level in stddev_level_list:
            matching_price = Symbol("matching_price")
            twenty_sma = Symbol("twenty_sma")
            stdev = Symbol("stdev")

            temp_list = df[-20:-1]["close"].to_list()

            temp_list.append(matching_price)

            stdev_list = []
            for close_price in temp_list:
                stdev_list.append((close_price - twenty_sma)**2)

            eq1 = Eq(np.array(temp_list).sum()/20, twenty_sma)
            eq2 = Eq(sqrt(np.array(stdev_list).sum()/20), stdev)

            eq3 = Eq(twenty_sma + (stddev_level * stdev), matching_price)

            result = solve((eq1.simplify(), eq2.simplify(), eq3.simplify()), (matching_price, twenty_sma, stdev), dict=True)

            if (result):
                matching_prices_dict[stddev_level] = result[0][matching_price]

    return matching_prices_dict

Calling it with these values produces these standard deviation levels where the price matches:

temp_minute_list = [3923.0,
 3922.5,
 3922.5,
 3922.75,
 3922.5,
 3922.5,
 3922.25,
 3922.0,
 3922.25,
 3922.25,
 3923.0,
 3923.75,
 3923.5,
 3923.5,
 3923.0,
 3923.5,
 3923.0,
 3923.25,
 3923.25,
 3923.0]

import pandas as pd
df = pd.DataFrame(temp_minute_list, columns=["close"])

temp = get_equal_bollinger_bands(df)
temp

{0: 3922.85526315789,
0.5: 3923.11453266651,
-0.5: 3922.59599364928,
1: 3923.38449482621,
-1: 3922.32603148958,
1.5: 3923.67819890843,
-1.5: 3922.03232740736,
2: 3924.01475164942,
-2: 3921.69577466636,
2.5: 3924.42731546604,
-2.5: 3921.28321084975,
3: 3924.98537953305,
-3: 3920.72514678273,
3.5: 3925.88007409178,
-3.5: 3919.83045222400,
4: 3928.04065333026,
-4: 3917.66987298550,
4.5: 3922.85526315793 – 9.03731840154405I,
5: 3922.8552631579 – 4.58328069248354
I,
5.5: 3922.8552631579 – 3.68187045988833*I}

I want to do this but I don’t want to get imaginary/complex numbers. How can I change this so that happens?

I’ve tried using,

matching_price = Symbol("matching_price", real=True)

, but that just removes the complex results.

Asked By: monsieur40a

||

Answers:

Your system of three equations is infeasible for real-valued prices above abs(stddev_level) > 4.36, so the solver returns complex solutions.

You can see this just by trying a range of prices:

for p in range(3900,3950,2):
    test_price = temp_minute_list
    test_price[19] = p
    x = np.array(test_price)
    sma = np.sum(x)/len(x)
    stdev = sqrt(np.sum((x - sma)**2)/20)
    print(p, sma, stdev, abs((p - sma) / stdev))

This prints:

3900 3921.7125 5.005169202934103 4.338015183836724
3902 3921.8125 4.571566334419747 4.333853771480871
3904 3921.9125 4.138444001070934 4.328317598441483
3906 3922.0125 3.7059706893066493 4.3207303409611155
3908 3922.1125 3.274403571644766 4.309945213293098
3910 3922.2125 2.8441551909134635 4.293893680280426
3912 3922.3125 2.4159302038759316 4.268542188617627
3914 3922.4125 1.9910345928687425 4.225190275513459
3916 3922.5125 1.5721700766774567 4.142363537259913
3918 3922.6125 1.1658553726770744 3.9563226349498324
3920 3922.7125 0.7916557016784506 3.426363246357109
3922 3922.8125 0.5236590016413353 1.551582227085438
3924 3922.9125 0.5492893135679958 1.9798309800277423
3926 3923.0125 0.8421512631350736 3.547462469958931
3928 3923.1125 1.223404573311707 3.994998961602336
3930 3923.2125 1.6322434714220793 4.158386980152142
3932 3923.3125 2.052247243876819 4.233164413264743
3934 3923.4125 2.4777446902374747 4.273039123730441
3936 3923.5125 2.906323923791015 4.296664902964932
3938 3923.6125 3.3367976789131224 4.311768762883515
3940 3923.7125 3.768516783828885 4.321992161449656
3942 3923.8125 4.201097326889726 4.329226053295242
3944 3923.9125 4.634298086010437 4.334529119013354
3946 3924.0125 5.06796001858736 4.338530674937914
3948 3924.1125 5.501974077547076 4.3416235088205815

So you can see that abs((p - sma) / stdev) is starting to asymptote.

My guess is the asymptotic value is sqrt(n-1). Intuitively, you can’t modify one element of a n-vector and make the std deviation scale by more than a factor of sqrt(n-1). Disclaimer: these last two sentences are quick guesses, not rigorously proven.

So you should limit your stddev_level to be less than sqrt(n-1). Alternatively, modify eq3 so that you just use stddev_level as an absolute stddev, ie: without multiplying by stdev.

Answered By: craigb
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.