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.

12
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).

7
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.