Back to Community
Probability State Indicator (PSI)

Just wanted to share a POC for the Probability State Indicator (PSI). Simon linked to it a while back

http://www.priceactionlab.com/Blog/2013/09/psi-the-probability-state-indicator/

Turns out it was fairly easy to reverse from the screenshot. It seems a little too simple but I'd be interested if anyone has a strategy for it. Basically, the closer the price is to the high of n periods, then the closer it is to 1. The inverse is true for the lows



delta = highest_high - lowest_low

delta_diff = highest_high - current_price

def calculate_psi(delta,delta_diff):  
    psi = (delta - delta_diff)/delta  
    return psi  
13 responses

when would the current_px / (max(n)-min(n)) ever be < 1.0?

you're right, it needs to be adjusted first.. fixing it now.

fixed

ok. See what you mean now...test it. Shouldn't be too hard..Of course you can boil what this is saying to the following:

1) are we near the n day high? 1 = yes, 0 = nope, 0> x <1 = sorta

Not sure why this would be predictive of tomorrow outside of the usual measures of momentum/reversion

I would have thought this indicator was more like fitting a Brownian motion to the recent past and then returning the probability of the indicator function for touching the barrier (of the high).

I can see how it could be applied to identify trends or ranges. If one takes a sampling of PSI results for a given timeframe and there are more values over .90 (for example) than there are values below (.10) then an uptrend can be presumed. Or if most of the values are in the middle, then a sideways market could be identified

This looks to be the only difference :

(Current Close - Lowest Low) is replaced with (Highest High - Current Close)

and its not multiplied by 100. And no MA.

Delta= HH-LL
Delta_Diff=HH-C
PSI=(Delta-Delta_Diff)/Delta

PSI= ((HH-LL)-(HH-C))/(HH-LL)

PSI= (C-LL)/(HH-LL)

If multiplied by 100 PSI will be exactly the same as 65 years old George C. Lane Stochastic Oscillator %K (talib.STOCHF).

I'll bet that's why the formula wasn't shared on the blog

You guys are still talking about your own version of the PSI, which you deduced by eyeballing an image on a web page, right?

You can compare the results from this to the screenshot of the blog and you'll see its exactly the same. Don't know what the chance of the output being the same with a different formula would be, but I'm sure its minuscule.

Clone Algorithm
12
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
import talib
from datetime import datetime
import numpy as np

def initialize(context):
    context.max_notional = 100000
    #context.stock = sid(8084)
    #set_benchmark(sid(8084))
    context.stock = symbol('spy')
    set_benchmark(symbol('spy'))
    context.lookback = 500

    
    context.charttype = '1d'

    
    context.psi_period = 30



def calculate_PSI(prices_low,prices_high,prices,context,prices_close):
    delta = max(prices_high[-context.psi_period:]) - min(prices_low[-context.psi_period:])
    delta_diff = max(prices_high[-context.psi_period:]) - prices_close[-1]
    psi = (delta - delta_diff)/delta 
    return psi
    
    
    
    
# Will be called on every trade event for the securities you specify. 
def handle_data(context, data):
    
    
    prices_open = history(context.lookback, context.charttype, 'open_price')
    prices_open = list(prices_open.values.flatten())    
    prices_high = history(context.lookback, context.charttype, 'high')       
    prices_high = list(prices_high.values.flatten())    
    prices_low = history(context.lookback, context.charttype, 'low')
    prices_low = list(prices_low.values.flatten())    
    prices_close = history(context.lookback, context.charttype, 'close_price')
    prices_close = list(prices_close.values.flatten())   

    prices = history(context.lookback, context.charttype, 'price')[context.stock]


    openprice = prices_open[-1]
    highprice = prices_high[-1]
    lowprice = prices_low[-1]
    closeprice = prices_close[-1]
    
    psi = calculate_PSI(prices_low,prices_high,prices,context,prices_close)
    
    record(psi_value = psi)

        
        
        
There was a runtime error.

As I mention above
If multiplied by 100 PSI will be exactly the same as 65 years old George C. Lane Stochastic Oscillator %K (talib.STOCHF).

Clone Algorithm
7
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# plot_psi_fastk

# If multiplied by 100 PSI will be exactly the same as 65 years old George C. Lane Stochastic Oscillator %K (talib.STOCHF). 

import talib

def initialize(context):
    schedule_function(plot_psi_fastk, date_rules.every_day(), time_rules.market_close(minutes=1))
 
def plot_psi_fastk(context, data):
    
    stk = symbol('SPY')
    period=30
    
    H = data.history(stk, 'high',period, '1d')
    L = data.history(stk, 'low', period, '1d')
    C = data.history(stk, 'price', period, '1d')

    fastk, fastd = talib.STOCHF(H,L,C,period,1)
    
    delta = max(H[-period:]) - min(L[-period:])
    delta_diff = max(H[-period:]) - C[-1]
    psi_1 = (delta - delta_diff)/delta 

    HH = max(H[-period:])
    LL = min(L[-period:])
    psi_2 = (C[-1]- LL)/(HH-LL)

    record(fastk=fastk[-1], psi_1=psi_1*100, psi_2=psi_2*100)   
There was a runtime error.