88from pytz import timezone
99
1010stocks_to_hold = 150 # Max 200
11- max_stock_price = 20
12- min_stock_price = 10
1311
12+ # Only stocks with prices in this range will be considered.
13+ max_stock_price = 26
14+ min_stock_price = 6
15+
16+ # API datetimes will match this format. (-04:00 represents the market's TZ.)
1417api_time_format = '%Y-%m-%dT%H:%M:%S.%f-04:00'
1518
1619# Rate stocks based on the volume's deviation from the previous 5 days and
1720# momentum. Returns a dataframe mapping stock symbols to ratings and prices.
21+ # Note: If algo_time is None, the API's default behavior of the current time
22+ # as `end` will be used. We use this for live trading.
1823def get_ratings (symbols , algo_time ):
1924 assets = api .list_assets ()
2025 assets = [asset for asset in assets if asset .tradable ]
2126 ratings = pd .DataFrame (columns = ['symbol' , 'rating' , 'price' ])
2227 index = 0
2328 batch_size = 200 # The maximum number of stocks to request data for
2429 window_size = 5 # The number of days of data to consider
25- # Convert the time to something compatable with the Alpaca API
26- formatted_time = algo_time .date ().strftime (api_time_format )
30+ formatted_time = None
31+ if algo_time is not None :
32+ # Convert the time to something compatable with the Alpaca API.
33+ formatted_time = algo_time .date ().strftime (api_time_format )
2734 while index < len (assets ):
2835 symbol_batch = [
2936 asset .symbol for asset in assets [index :index + batch_size ]
3037 ]
38+ # Retrieve data for this batch of symbols.
3139 barset = api .get_barset (
3240 symbols = symbol_batch ,
3341 timeframe = 'day' ,
@@ -38,23 +46,28 @@ def get_ratings(symbols, algo_time):
3846 for symbol in symbol_batch :
3947 bars = barset [symbol ]
4048 if len (bars ) == window_size :
41- # Make sure we aren't missing the most recent data
49+ # Make sure we aren't missing the most recent data.
4250 latest_bar = bars [- 1 ].t .to_pydatetime ().astimezone (
4351 timezone ('EST' )
4452 )
4553 gap_from_present = algo_time - latest_bar
4654 if gap_from_present .days > 1 :
4755 continue
56+
57+ # Now, if the stock is within our target range, rate it.
4858 price = bars [- 1 ].c
4959 if price <= max_stock_price and price >= min_stock_price :
5060 price_change = price - bars [0 ].c
5161 # Calculate standard deviation of previous volumes
5262 past_volumes = [bar .v for bar in bars [:- 1 ]]
5363 volume_stdev = statistics .stdev (past_volumes )
54- # Then, compare it to the change in volume since yesterday
64+ if volume_stdev == 0 :
65+ # The data for the stock might be low quality.
66+ continue
67+ # Then, compare it to the change in volume since yesterday.
5568 volume_change = bars [- 1 ].v - bars [- 2 ].v
5669 volume_factor = volume_change / volume_stdev
57- # Rating = Number of volume standard deviations * momentum
70+ # Rating = Number of volume standard deviations * momentum.
5871 rating = price_change / bars [0 ].c * volume_factor
5972 if rating > 0 :
6073 ratings = ratings .append ({
@@ -78,7 +91,7 @@ def get_shares_to_buy(ratings_df, portfolio):
7891 return shares
7992
8093
81- # Returns a string version of a timestamp compatible with the Alpaca API
94+ # Returns a string version of a timestamp compatible with the Alpaca API.
8295def api_format (dt ):
8396 return dt .strftime (api_time_format )
8497
@@ -108,11 +121,14 @@ def backtest(api, days_to_test, portfolio_amount):
108121 )
109122
110123 if cal_index == len (calendars ) - 1 :
124+ # We've reached the end of the backtesting window.
111125 break
112126
127+ # Get the ratings for a particular day
113128 ratings = get_ratings (symbols , timezone ('EST' ).localize (calendar .date ))
114129 shares = get_shares_to_buy (ratings , portfolio_amount )
115130 for _ , row in ratings .iterrows ():
131+ # "Buy" our shares on that day and subtract the cost.
116132 shares_to_buy = shares [row ['symbol' ]]
117133 cost = row ['price' ] * shares_to_buy
118134 portfolio_amount -= cost
@@ -140,7 +156,7 @@ def get_value_of_assets(api, shares_bought, on_date):
140156 return 0
141157
142158 total_value = 0
143- formatted_date = on_date . strftime ( "%Y-%m-%dT00:00:00-04:00" )
159+ formatted_date = api_format ( on_date )
144160 barset = api .get_barset (
145161 symbols = shares_bought .keys (),
146162 timeframe = 'day' ,
@@ -153,10 +169,10 @@ def get_value_of_assets(api, shares_bought, on_date):
153169
154170
155171def run_live (api ):
156- cycle = 0
172+ cycle = 0 # Only used to print a "waiting" message every few minutes.
157173
158- # See if we've already bought or sold positions today. Useful in case the
159- # script is restarted during market hours.
174+ # See if we've already bought or sold positions today. If so, we don't want to do it again.
175+ # Useful in case the script is restarted during market hours.
160176 bought_today = False
161177 sold_today = False
162178 try :
@@ -180,9 +196,6 @@ def run_live(api):
180196 # We don't have any orders, so we've obviously not done anything today.
181197 pass
182198
183- bought_today = False
184- sold_today = True
185-
186199 while True :
187200 # We'll wait until the market's open to do anything.
188201 clock = api .get_clock ()
@@ -195,7 +208,7 @@ def run_live(api):
195208 print ('Buying positions...' )
196209 portfolio_cash = float (api .get_account ().cash )
197210 ratings = get_ratings (
198- api , clock . timestamp . astimezone ( timezone ( 'EST' ))
211+ api , None
199212 )
200213 shares_to_buy = get_shares_to_buy (ratings , portfolio_cash )
201214 for symbol in shares_to_buy :
@@ -219,15 +232,15 @@ def run_live(api):
219232 else :
220233 bought_today = False
221234 sold_today = False
222- if cycle % 5 == 0 :
235+ if cycle % 10 == 0 :
223236 print ("Waiting for next market day..." )
224237 time .sleep (30 )
225238 cycle += 1
226239
227240
228241
229242if __name__ == '__main__' :
230- api = tradeapi .REST ()
243+ api = tradeapi .REST ('PKLF4UR7WCI5U9D6QKTU' , '0pEmERtAlypbsm/peYlFSacQljQs5AxuQt3yAeJE' , 'https://paper-api.alpaca.markets' )
231244
232245 if len (sys .argv ) < 2 :
233246 print ('Error: please specify a command; either "run" or "backtest".' )
0 commit comments