I recently wrote an algorithm that uses an extremely dumb method to detect horizontal support and resistance levels within a short (90 minute) window, which confirmed that this is a very reliable signal when used with medium-volatility, low-volume stocks (penny stocks). Encouraged by those results, I tried to build on the concept with a new script that on the fly keeps track of multiple levels of horizontal support while not limiting the time window, hoping it would work for stocks with slower momentum. Unfortunately, that version of the script seemed to almost exclusively enter losing trades so I gave up on it. :(
What I would do to find non-horizontal support and resistance trendlines I would loop through the history window you want to take into consideration, keeping track of each peak and valley and their corresponding time offset keys (so you collect sets of x-y pairs for each). To identify the peaks I simply compare -- is the current price greater than most recent entry for the peak? if so update it. If it dips below some threshold, add a new entry and start keeping track of a new max value. Visa versa for the valleys. Then run those x-y pairs through numpy's linear regression library (np.linalg.lstsq) and you've got your trendlines.
The problem with trendlines is that they're super open to interpretation. Looking at that image you posted, you could draw those lines in a dozen different ways that would make equally as much sense (and probably all be equally as unhelpful).
I took a quick shot at writing a method to detect a very simple head and shoulders pattern, but probably I made some mistake in my logic or wasn't using a large enough window or correct thresholds since none of the stocks I'm throwing at it get through. Let me know if you get this to produce anything useful. Perhaps it would be better to use this logic in a pipeline screen... I just don't know my way around pipeline well enough yet.
def head_n_shoulders(hist):
support_threshold = 0.004
shoulder_threshold = 0.01
head_threshold = 0.004
support = hist[-1]
shoulder1 = 0
support2 = 0
head = 0
support3 = 0
shoulder2 = 0
support4 = 0
# Work backwards to identify head and shoulders pattern
for d in range(0,len(hist)):
price = hist[-1-d]
# Breaks pattern if price dips below theoretical support
if price < support * (1-support_threshold):
return False
# Start by finding first shoulder level,
# must exceed specified threshold above support level
elif not support2 \
and price > support * (1+shoulder_threshold) \
and price > shoulder1:
shoulder1 = price
# Descending from first shoulder, retests support level
# (record lowest level reached within threshold)
elif shoulder1 and not head \
and price < support * (1+support_threshold) \
and price > support * (1-support_threshold) \
and (not support2 or price < support2):
support2 = price
# Ascending again past height of previous shoulder,
# record highest head level
elif support2 and not support3 \
and price > shoulder1 * (1+head_threshold) \
and price > head:
head = price
# Retest support level again
elif head and not shoulder2 \
and price < support * (1+support_threshold) \
and price > support * (1-support_threshold) \
and (not support3 or price < support3):
support3 = price
# Breaks pattern if next shoulder exceeds head
elif support3 and not support4 \
and price > head:
return False
# Instead it just needs to exceed threshold above support level again
elif support3 and not support4 \
and price > support * (1+shoulder_threshold) \
and price > shoulder2:
shoulder2 = price
# Pattern started by entering in from below support level
elif shoulder2 \
and price < support * (1-head_threshold):
return support - (head - price) # Price target?
# End of the line, nothing found...
elif d == len(hist):
return False