This repository has been archived by the owner on Jun 18, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 13
/
eval.py
358 lines (286 loc) · 12.9 KB
/
eval.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
"""
Script for evaluating Stock Trading Bot.
Usage:
eval.py <eval-stock> [--window-size=<window-size>] [--model-name=<model-name>] [--run-bot=<y/n] [--stock-name=<stock-ticker>] [--natural-lang] [--debug]
Options:
--window-size=<window-size> Size of the n-day window stock data representation used as the feature vector. [default: 10]
--model-name=<model-name> Name of the pretrained model to use (will eval all models in `models/` if unspecified).
--run-bot=<y/n> Whether you wish to run the trading bot
--stock-name=<stock-ticker> The name of the stock (eg. AMD, GE)
--natural-lang Specifies whether to use Google's Natural Language API or not
--debug Specifies whether to use verbose logs during eval operation.
"""
import os
import coloredlogs
from docopt import docopt
import logging
from yahoo_fin import stock_info as si
import numpy as np
from tqdm import tqdm
from time import process_time
from trading_bot.agent import Agent
from trading_bot.methods import evaluate_model
import alpaca_trade_api as tradeapi
import time
import datetime
from trading_bot.sentiment import runNewsAnalysis, decide_stock
from trading_bot.utils import (
get_stock_data,
format_currency,
format_position,
show_eval_result,
switch_k_backend_device,
format_sentiment)
from trading_bot.ops import get_state
# Method to run script for each minute
def decisions(agent, data, window_size, debug, stock, api):
# Initialize Variables
total_profit = 0
global orders
orders = []
history = []
agent.inventory = []
action = None
sentiments = runNewsAnalysis(stock, api, natural_lang)
state = get_state(data, 0, window_size + 1)
# decide_stock()
t = 0
# Main While Loop
while True:
data_length = len(data) - 1
is_open = True
# Checks for if the original 1000 data points were tested
if t == data_length - 1:
# Check for connection errors and retry 30 times
cnt = 0
while cnt <= 30:
try:
# Wait for market to open.
is_open = api.get_clock().is_open
break
except:
logging.warning("Error in checking market status, retrying in 30s (" + str(cnt) + "/30)")
time.sleep(30)
cnt += 1
continue
# Checks for if Market is open
while not is_open:
logging.info("Waiting for market to open...")
# Check for connection errors and retry 30 times
cnt = 0
while cnt <= 30:
try:
clock = api.get_clock()
opening_time = clock.next_open.replace(tzinfo=datetime.timezone.utc).timestamp()
curr_time = clock.timestamp.replace(tzinfo=datetime.timezone.utc).timestamp()
time_to_open = int((opening_time - curr_time) / 60)
logging.info("Last days profit: {}".format(format_currency(str(total_profit))))
# Countdown timer until market opens
while time_to_open > -1:
print(str(time_to_open) + " minutes til market open.", end='\r')
time.sleep(60)
time_to_open -= 1
# Alternative timer here
# time.sleep(time_to_open * 60)
is_open = api.get_clock().is_open
break
except:
logging.warning("Error in checking market status, retrying in 30s (" + str(cnt) + "/30)")
time.sleep(30)
cnt += 1
continue
# Initialization of new day, we only want this to happen once at the beginning of each day
if is_open:
logging.info("Market opened.")
# Runs Analysis on all new sources
try:
sentiments = runNewsAnalysis(stock, api, natural_lang)
except:
logging.info("Error Collecting Sentiment")
# Save last days data
if action is not None:
agent.memory.append((state, action, reward, next_state, True))
agent.soft_save()
# Reinitialize for new day
total_profit = 0
orders = []
history = []
q = 0
agent.inventory = []
# ****COMMENT THIS OUT IF YOU DON'T WANT TO SELL ALL OF THE STOCKS AT THE BEGINNING OF NEW DAY****
# Sell all stock using Alpaca API at the beginning of the new day
if t == data_length - 1:
try:
qty = api.get_position(stock).qty
except:
logging.warning("Error fetching stock position, may not exist.")
# Just checks to see if I'm trying to sell zero or a negative number of stocks
if int(qty) > 2:
submit_order_helper(int(qty) - 2, stock, 'sell', api)
# Checks for if the original 1000 data points were tested
if t == data_length - 1:
time.sleep(60)
# Check for connection errors and retry 30 times
cnt = 0
while cnt <= 30:
try:
date = api.get_barset(timeframe='minute', symbols=stock_name, limit=1, end=datetime.datetime.now())
break
except:
logging.warning("Unable to retrieve barset, retrying in 30s (" + str(cnt) + "/30)")
time.sleep(30)
cnt += 1
continue
data.append(date.get(stock)[0].c)
reward = 0
next_state = get_state(data, t + 1, window_size + 1)
# select an action
action = agent.act(state, is_eval=True)
# BUY
if action == 1 and sentiments >= 0:
# if action == 1:
agent.inventory.append(data[t])
# Buy using Alpaca API, only if it is realtime data
if t == data_length - 1:
file = open('data/' + stock + "_trading_data.csv", 'a')
file.write(str(datetime.datetime.now().strftime("%m/%d/%Y,%H:%M:%S")) + ',BUY,$' + str(
date.get(stock)[0].c) + '\n')
file.close()
global now
global current_time
global bbbpower
now = datetime.now()
current_time = now.strftime("%H")
account = api.get_account()
perstockpower = int(float(account.buying_power)/4)
bbbpower = int(perstockpower/float(si.get_live_price(stock)))
orders.append(submit_order_helper(bbbpower, stock, 'buy', api))
# Appends and logs
history.append((data[t], "BUY"))
if debug:
logging.debug(
"Buy at: {} | Sentiment: {} | Total Profit: {}".format(format_currency(data[t]),
format_sentiment(sentiments),
format_currency(total_profit)))
# "Buy at: {}".format(format_currency(data[t])))
# SELL
elif (action == 2 or sentiments < 0) and len(agent.inventory) > 0:
# elif action == 2 and len(agent.inventory) > 0:
bought_price = agent.inventory.pop(0)
reward = max(data[t] - bought_price, 0)
total_profit += data[t] - bought_price
# Sell all stock using Alpaca API
# if t == data_length - 1 and len(orders) != 0:
# try:
# qty = api.get_position(stock).qty
# submit_order_helper(qty, stock, 'sell', api)
# orders.pop()
# except:
# logging.info("No position!")
# Sell's one stock using Alpaca's API if it is in realtime
if t == data_length - 1:
file = open('data/' + stock + "_trading_data.csv", 'a')
file.write(str(datetime.datetime.now().strftime("%m/%d/%Y,%H:%M:%S")) + ',SELL,$' + str(
date.get(stock)[0].c) + '\n')
file.close()
position = api.get_position(stock)
submit_order_helper(int(position.qty), stock, 'sell', api)
history.append((data[t], "SELL"))
if debug:
logging.debug("Sell at: {} | Sentiment: {} | Position: {}".format(
format_currency(data[t]), format_sentiment(sentiments), format_position(data[t] - bought_price)))
# format_currency(data[t]), format_position(data[t] - bought_price)))
# HOLD
else:
history.append((data[t], "HOLD"))
if debug:
logging.debug("Hold at: {} | Sentiment: {} | Total Profit: {}".format(
format_currency(data[t]), format_sentiment(sentiments), format_currency(total_profit)))
# format_currency(data[t])))
if t == data_length - 1:
file = open('data/' + stock + "_trading_data.csv", 'a')
file.write(str(datetime.datetime.now().strftime("%m/%d/%Y,%H:%M:%S")) + ',HOLD,$' + str(
date.get(stock)[0].c) + '\n')
file.close()
agent.memory.append((state, action, reward, next_state, False))
if len(agent.memory) > 32:
agent.train_experience_replay(32)
state = next_state
t += 1
# Submit an order if quantity is above 0.
def submit_order_helper(qty, stock, side, api):
if int(qty) > 0:
try:
api.submit_order(stock, qty, side, "market", "day")
logging.info("Market order of | " + str(qty) + " " + stock + " " + side + " | completed.")
except:
logging.warning("Order of | " + str(qty) + " " + stock + " " + side + " | did not go through.")
else:
logging.info("Quantity is 0, order of | " + str(qty) + " " + stock + " " + side + " | not completed.")
# Method to actually run the script
def alpaca_trading_bot(stock_name, window_size=10, model_name='model_debug'):
# Alpaca API
api = tradeapi.REST()
# Create Agent Object
agent = Agent(window_size, pretrained=True, model_name=model_name)
# Get Ticker from last intraday times from Polygon
file = open('ticker.csv', 'w')
file.write('Adj Close\n')
# Check for connection errors and retry 30 times
cnt = 0
while cnt <= 30:
try:
date = api.get_barset(timeframe='minute', symbols=stock_name, limit=1000, end=datetime.datetime.now())
break
except:
logging.warning("Error retrieving initial 1000 prices, retrying in 30s (" + str(cnt) + "/30)")
time.sleep(30)
cnt += 1
continue
# Write ticker csv
for minutes in date.get(stock_name):
file.write(str(minutes.c))
file.write('\n')
file.close()
data = get_stock_data('ticker.csv')
# Call actual buy/sell/hold decisions and print result forever
decisions(agent, data, window_size, debug, stock_name, api)
def main(eval_stock, window_size, model_name, debug):
""" Evaluates the stock trading bot.
Please see https://arxiv.org/abs/1312.5602 for more details.
Args: [python eval.py --help]
"""
data = get_stock_data(eval_stock)
initial_offset = data[1] - data[0]
# Single Model Evaluation
if model_name is not None:
agent = Agent(window_size, pretrained=True, model_name=model_name)
profit, _ = evaluate_model(agent, data, window_size, debug)
show_eval_result(model_name, profit, initial_offset)
# Multiple Model Evaluation
else:
for model in os.listdir("models"):
if os.path.isfile(os.path.join("models", model)):
agent = Agent(window_size, pretrained=True, model_name=model)
profit = evaluate_model(agent, data, window_size, debug)
show_eval_result(model, profit, initial_offset)
del agent
if __name__ == "__main__":
args = docopt(__doc__)
# Arguments
eval_stock = 'data/' + str(args["<eval-stock>"]) + '.csv'
window_size = int(args["--window-size"])
model_name = args["--model-name"]
run_bot = str(args['--run-bot'])
stock_name = str(args['--stock-name'])
natural_lang = args['--natural-lang']
debug = args["--debug"]
coloredlogs.install(level="DEBUG")
switch_k_backend_device()
try:
main(eval_stock, window_size, model_name, debug)
except KeyboardInterrupt:
print("Aborted")
# Run the Actual bot
if run_bot == 'y' or run_bot == 'Y':
alpaca_trading_bot(stock_name, window_size, model_name)