-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrpimarkets.py
executable file
·189 lines (169 loc) · 6.47 KB
/
rpimarkets.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
import getpass
import time
import json
import sys
import urllib2
BASE_URL = 'http://simla.cs.rpi.edu/api/'
# Number of times to retry a trade before giving up
TRADE_RETRY_LIMIT = 6
def prompt_password():
#user = raw_input('RPI Markets Username: ')
#password = getpass.getpass()
# You can hard code your username/password here if the prompt
# is too annoying (and comment out the above two lines).
return "danhakimi", "hakimibot"
def range_decision_callback(lowest_price_per_share,
highest_price_per_share):
"""Return a function which decides whether to execute a trade
based on a cost per share quote. In this case we simply check
that the price is in the given range, but this logic can be
as complex as you'd like in general."""
def decision_callback(buysell, quantity, cost_per_share):
decision = (lowest_price_per_share < cost_per_share
and cost_per_share < highest_price_per_share)
if decision:
print '%s %d shares at %1.2f per share' % (
buysell, quantity, cost_per_share)
else:
print ('canceled an offer to %s for %d shares at %1.2f'
' per share') % (buysell, quantity, cost_per_share)
# Return a boolean: True to execute, False to cancel
return decision
return decision_callback
class Experiment(object):
"""A wrapper for accessing an experiment."""
def __init__(self, experiment, account_num, base_url=BASE_URL):
"""Create a new Experiment object.
experiment: The name of the experiment to access.
account_num: The number of the account to use. A unique number
should be assigned to each bot you are testing simultaneously.
base_url: The URL of the API to access."""
self.experiment = experiment
self.account_num = account_num
self.base_url = base_url
self.username = None
self.password = None
def poll_for_data(self, new_data_callback):
"""Continuously call new_data_callback with new animation data
until it returns False. new_data_callback will be passed a list,
which for the one-dimensional bouncing ball will be of the form:
[(Position, Left count, Right count, Timestamp), ...]"""
url = self.base_url + 'experiments/%s/%d/data/' % (
self.experiment, self.account_num)
last_timestamp = 0
while True:
self.authenticate()
try:
result = json.load(urllib2.urlopen(url))
except urllib2.HTTPError, e:
if e.code == 404:
print 'No data from experiment yet'
else:
print 'HTTP error %d getting experiment data: %s' % (
e.code, e.msg)
time.sleep(2)
continue
new_data = []
for point in result:
if point[-1] > last_timestamp:
new_data.append(point)
last_timestamp = point[-1]
if new_data:
ret = new_data_callback(new_data)
if ret is not None and not ret:
return
time.sleep(2)
def trade(self, buysell, quantity, decision_callback,
retries=TRADE_RETRY_LIMIT):
"""Get a price quote, then call decision_callback with that quote.
If decision_callback returns True, then attempt to execute the
trade. If decision_callback returns False, the trade is canceled.
The return value is a tuple of two boolean values:
(Trade executed, decision_callback's return)
If there is an error getting a quote, the return will be (False,
None)
If we get a quote and decision_callback returns True but there is
an error executing the trade (insufficient funds, for example),
then the return value would be (False, True).
If decision_callback returns True and the trade is executed, the
return value is (True, True)
If the price of the stock changes, we'll retry the trade up to
TRADE_RETRY_LIMIT (default 6) times total (calling
decision_callback with the new quote each time and acting
accordingly).
"""
assert buysell in ['buy', 'sell']
url = self.base_url + 'experiments/%s/%d/trade/%s/%d/' % (
self.experiment, self.account_num, buysell, int(quantity))
auth_handler = self.authenticate()
try:
result = json.load(urllib2.urlopen(url))
except Exception, e:
time.sleep(.01)
return self.trade(buysell, quantity, decision_callback, retries)
if result['code'] == 0:
decision = decision_callback(buysell, int(quantity),
float(result['per_share']))
if decision:
try:
opener = urllib2.build_opener(auth_handler)
request = urllib2.Request('%s%s/' % (
url, result['per_share']))
request.get_method = lambda: 'POST'
result = opener.open(request)
except urllib2.HTTPError, e:
reason = e.read().split('\n')
if ('price has changed' in reason
and retries > 1):
print 'Price changed. Retrying trade...'
return self.trade(
buysell, quantity, decision_callback,
retries=retries-1)
else:
print ('HTTP error %d when trying to trade: %s '
) % (e.code, reason)
return (False, True)
return (True, True)
else:
opener = urllib2.build_opener(auth_handler)
request = urllib2.Request(url)
request.get_method = lambda: 'DELETE'
opener.open(request)
return (False, False)
else:
print 'trade error: %s' % (str(result),)
return (False, None)
def tradeWrapper(self, buyOrSell, amount, low, high):
return self.trade(buyOrSell, amount, range_decision_callback(low, high) )
def history(self):
"""Get the history of the stock for a given experiment.
Returns a list of dictionary entries describing each trade."""
try :
url = self.base_url + 'experiments/%s/%d/history/' % (
self.experiment, self.account_num)
self.authenticate()
result = json.load(urllib2.urlopen(url))
return result
except Exception, e :
return self.history()
def account(self):
"""Get your account information: holdings and cash."""
url = self.base_url + 'experiments/%s/%d/account/' % (
self.experiment, self.account_num)
self.authenticate()
result = json.load(urllib2.urlopen(url))
return result
def authenticate(self):
"""Prompt once for a username/password, but otherwise make sure
credentials are refreshed to avoid authorization errors. Using
basic HTTP authentication."""
if self.username is None or self.password is None:
self.username, self.password = prompt_password()
password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
password_mgr.add_password(None, uri=self.base_url,
user=self.username,
passwd=self.password)
auth_handler = urllib2.HTTPBasicAuthHandler(password_mgr)
opener = urllib2.build_opener(auth_handler)
urllib2.install_opener(opener)
return auth_handler