it's important to have a system in place to pare down the number of charts that are worth a closer look.
my search for trends is always for rising stocks.
this doesn't generate a list of recommended positions, but a list of tickers worth a closer look via charts, fundamentals, and macro/broader market conditions.
the basic idea is to apply conditional checks and filtering to your entire ticker universe of prices, returning only those tickers that have short-term simple moving averages (SMAs) that are higher than longer-term ones, across a specified range of days.
we also filter out any ticker that has had a gap in either direction greater than 10% (using absolute value) within the date range.
The specific conditions to be coded are:
- 20-period SMA > 50-period SMA for the last 100 days
- 50-period SMA > 200-period SMA for the last 100 days
- No gaps (up or down) for the last 100 days between a day's opening price and the prior day's close > 10% (abs value)
- Each day's trading volume >= 200,000 shares
1) a single csv file of the entire Quandl/Sharadar EOD Equity Prices data by importing it into my Python environment as a pandas data frame.
- import pandas as pd
- #read in the data set, drop the columns not needed, and make sure 'tradeDate' col
- #is datetime data type
- df = pd.read_csv('..\\appData\\dailyEquityData.csv')
- df = df[['ticker', 'tradeDate', 'open', 'close', 'volume']]
- df['tradeDate'] = pd.to_datetime(df['tradeDate'])
複製代碼 2)to get the latest date of the tradeDate column, and then filter the data to return rows where the trade date equals that value.- #get the tickers that have the maxDate in their tradeDate column
- latest_tickers = df[df['tradeDate']==maxDate].ticker
- df = df[df['ticker'].isin(latest_tickers)]
複製代碼 Now we set the ticker and tradeDate columns as a multi-level index.
- #set the multindex, without dropping the columns
- df.set_index(['ticker', 'tradeDate'], inplace=True, drop=False)
- df.sort_index(inplace=True)
複製代碼 3)to add the moving averages, and get a lagged value of the prior day's close, which we use to filter out price gaps.
- #add the moving average columns and the prior close to each row
- df['sma20'] = df.groupby(level=0).close.apply(lambda x: x.rolling(20).mean())
- df['sma50'] = df.groupby(level=0).close.apply(lambda x: x.rolling(50).mean())
- df['sma200'] = df.groupby(level=0).close.apply(lambda x: x.rolling(200).mean())
- df['priorClose'] = df.groupby(level=0).close.shift(1)
複製代碼 The moving average calculations need their respective number of data points before they can return values.
For this reason, there are a bunch of NaN values in the data frame.
These rows aren't needed anyway, so we'll remove them. 4)The next step after that is to get the latest 100 data points for each ticker. - #drop all na values, and get the latest 100 data points for each ticker
- df.dropna(inplace=True)
- df = df.groupby(level=0).tail(100)
複製代碼 use the tail function on the data frame to show that we're working with the latest data points.
And now the moment of truth, literally... 5)add a boolean column to the data frame for each condition we wish to check. The last one confirms that all of the conditions required are true. - #add a boolean column for each filter condition as well as a check that all = True
- df['notBigGapOpen'] = abs(((df['open'] / df['priorClose']) - 1) < 0.10)
- df['sma20gtsma50'] = df['sma20'] > df['sma50']
- df['sma50gtsma200'] = df['sma50'] > df['sma200']
- df['adequateVol'] = df['volume'] >= 200000
- df['rowPassed'] = (df['notBigGapOpen'] & df['sma20gtsma50'] & df['sma50gtsma200'] &
- df['adequateVol'])
複製代碼
At this point, our data set has the latest 100 data points for each ticker. The conditional checks are in place for each data point. All that is left to do is reduce the data to those tickers that have a rowPassed = True for every one of the 100 rows. We can do this by aggregating on the ticker, and calling the sum function on the rowPassed column. Since numerically, each False value equals 0, and each True value equals 1, each ticker that has a True in all of the rows will have a sum of 100. Those tickers are our watchlist of trending stocks. - #for each ticker, see if the rowPassed col is true for each data point. sum the boolean
- #values, and then filter for those tickers that have a sum = 100 (the count of the rows
- #for each ticker)
- df_sums = pd.DataFrame(df.groupby(level=0).rowPassed.sum())
- trending_tickers = df_sums[df_sums['rowPassed']==100].index
複製代碼
We started with over 6,000 tickers, and reduced it to a mere 34. Admittedly, this is a fairly strict definition of an uptrend. It's likely that there are many nice potential trade setups that have been filtered out; but 34 charts to review on a daily basis is more realistic than 6,000+, and the coded parameters can be adjusted and optimized.
While writing this, I used two data sets I knew to have strong trends, Apple (AAPL) and Google (GOOG).
Both made the cut, and are in the final list. The charts await:
|