-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathsatoshimars_ml.py
246 lines (197 loc) · 10.3 KB
/
satoshimars_ml.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
from datetime import timedelta, datetime, timezone
import time
import operator
import pandas as pd
from model import EnsembleModel
# Statistical Arbitrage
# Mark Price is Avg Price of : Bitmex, Coinbase and Bitfinex
# Last Price is simply Bitmex Spot Price
from bitmex import bitmex
ID = 'mGzrlIYqeVHtxIRUnV4-DcGc'
SECRET = 'yJIZ2bWCG-3S3cRUfCJPFHOQScxibfEE_14DsQe2TGNgkeWw'
# classes provide a means of bundling data and functionality together.
# creating a new class creates a new type of object, allowing new instances of that type to be made.
# each class instance can have attributes attached to it for maintaining its state.
# class instances can also have methods (defined by its class) for modifying its state.
class BitmexObject: # this is the class variable that will be shared by all instances
def __init__(self,attr_dict): # When a class defines an __init__() method, class instantiation automatically invokes __init__() for the newly-created class instance.
self.__dict__.update(attr_dict) # self.__dict__ is an instance variable unique to each instance, attr_dict is attribute dictionary that allows its elements to be accessed both as keys and as attributes
# now, we are utilizing our newly defined class BitmexObject for 3 instances: Being Order(BitmexObject), Position(BitmexObject), and InstrumentQuery(BitmexObject):
class Order(BitmexObject):
def __repr__(
self): # object.__repr__(self): called by the repr() built-in function and by string conversions (reverse quotes) to compute the "official" string representation of an object
return f'{self.side}({self.symbol}, {self.orderQty})' # define what we want our defined representation for our object to return
class Position(BitmexObject):
def __repr__(
self): # object.__repr__(self): called by the repr() built-in function and by string conversions (reverse quotes) to compute the "official" string representation of an object
return f'Position({self.symbol}, {self.currentQty})' # define what we want our defined representation for our object to return
class InstrumentQuery(BitmexObject):
def __repr__(
self): # object.__repr__(self): called by the repr() built-in function and by string conversions (reverse quotes) to compute the "official" string representation of an object
return f'InstrumentQuery({self.symbol}, {self.lastPrice}, {self.markPrice})' # define what we want our defined representation for our object to return
# class Bitmex, ***** note: no longer utilizing BitmexObject though on class Bitmex:
class Bitmex: # define new class called Bitmex , which defines algorithm framework and parameters with different methods
def __init__(self, api_key, api_secret,
test=True): # first, need to define our _init_ instance before our algo methods
self._client = bitmex( # where we define bitmex as client for algorithm
test=test,
api_key=api_key,
api_secret=api_secret
)
self._orders = {}
# NOTE: Only the first 3 methods (get_intrument_query, _enter_market, and get_positions required us to set up class Bitmex objects above before we defined class Bitmex.
# The last methods in class Bitmex (buy, sell place_stop, place_target, calc_targets_ and enter_bracket did not require this.
def get_instrument_query(self, symbol): # retrieve prices for mark and last price
json, adapter = self._client.Instrument.Instrument_get(symbol=symbol, reverse=True, count=1).result()
inst = InstrumentQuery(json[0]) # InstrumentQuery is class we have defined above
print(f'{symbol}:, Last: {inst.lastPrice}, Mark: {inst.markPrice}')
return inst
def get_dataframe(self, symbol, timeframe):
tf_deltas = {
'1m': timedelta(minutes=750),
'5m': timedelta(minutes=750 * 5),
'1h': timedelta(hours=750),
'1d': timedelta(days=750),
}
start = datetime.now(timezone.utc) - tf_deltas[timeframe]
json, adapter = self._client.Trade.Trade_getBucketed(
binSize=timeframe,
symbol=symbol,
count=750,
startTime=start,
columns='open, high, low, close'
).result()
return pd.DataFrame.from_records(
data=json,
columns=['timestamp', 'open', 'high', 'low', 'close'],
index='timestamp',
)
def _enter_market(self, symbol, size,
**kws): # **kwargs allows you to pass keyworded variable length of arguments to a function
order = self._client.Order.Order_new(symbol=symbol, orderQty=size, **kws)
json, adapter = order.result()
order = Order(json) # Order is class we have defined above
self._orders[order.orderID] = order
return order
def get_positions(self):
positions, adapter = self._client.Position.Position_get().result()
return [Position(p) for p in positions if p["currentQty"] != 0] # Position is class we have defined above
def buy(self, symbol, size, **kws):
return self._enter_market(symbol, abs(size),
**kws) # **kwargs allows you to pass keyworded variable length of arguments to a function
def sell(self, symbol, size, **kws):
return self._enter_market(symbol, -1 * abs(size),
**kws) # -1 for sell, **kwargs allows you to pass keyworded variable length of arguments to a function
def place_stop(self, symbol, size, price, *, side, link_id):
func = getattr(self, side)
return func(
symbol,
size,
stopPx=price, # stopPx = price for stop
clOrdLinkID=link_id,
contingencyType='OneCancelsTheOther'
)
def place_target(self, symbol, size, price, *, side, link_id):
func = getattr(self, side)
return func(
symbol,
size,
price=price, # standard price = price for limit
clOrdLinkID=link_id,
contingencyType='OneCancelsTheOther'
)
def _calc_targets(self, entry_price, side, target_offset=20,
stop_offset=40): # calculate targets for stops and limits
if side == 'buy':
stop_op = operator.sub
target_op = operator.add
else:
stop_op = operator.add
target_op = operator.sub
stop_price = stop_op(entry_price, stop_offset)
# stop_price = round(stop_price, 0.5)
target_price = target_op(entry_price, target_offset)
# target_price = round(target_price, 0.5)
return stop_price, target_price
def enter_bracket(self, symbol, size, side='buy', target_offset=20, stop_offset=40): # create bracket order
assert side in {'buy', 'sell'}, 'Side must be buy or sell'
exit_side_map = {'buy': 'sell', 'sell': 'buy'}
print(f'Submitting Market order for {symbol}')
entry = getattr(self, side)(symbol, size)
"""""""""
if entry.ordStatus == 'Filled':
link_id = entry.orderID
exit_side = exit_side_map[side]
stop_price, target_price = self._calc_targets(
entry.avgPx, side, target_offset, stop_offset)
stop = self.place_stop(symbol, size, stop_price,
side=exit_side, link_id=link_id)
target = self.place_target(symbol, size, target_price,
side=exit_side, link_id=link_id)
"""""""""
# Trader Class, ***** note: no longer utilizing class BitmexObject though on any methods
class Trader: # define another new class called Trader, where trading happens
"""""""""
Available Bitmex timeframe arguments:
tf_deltas = {
'1m': timedelta(minutes=750),
'5m': timedelta(minutes=750 * 5),
'1h': timedelta(hours=750),
'1d': timedelta(days=750),
}
"""""""""""
def __init__(self, symbol, model, size, timeframe='5m',
# again, first, need to define our _init_ instance before our algo methods
target_offset=20, stop_offset=40):
self.symbol = symbol
self.model = model
self.size = size
self.timeframe = timeframe
self.target_offset = target_offset
self.stop_offset = stop_offset
self.bit = Bitmex(ID, SECRET) # for live trading, change to self.bit = Bitmex(ID, SECRET, test=False)
def run(self):
while True:
try:
print('Checking on entry criteria...')
self._enter_if_flat_and_good_price()
except Exception as ex:
print('Error:', ex)
break
time.sleep(30)
def _trade_criteria(self):
positions = self.bit.get_positions()
return len(positions) == 0
def _predict(self):
df = self.bit.get_dataframe(self.symbol, self.timeframe)
print(f'Got data on {self.symbol} from {df.index[0]} to {df.index[-1]}')
predict = self.model.run(df)
print('Prediction: ', predict)
return predict
def _enter_if_flat_and_good_price(self):
if self._trade_criteria():
print('No positions, will run prediction and enter market')
signal = self._predict()
if signal == 1:
side = 'buy'
elif signal == -1:
side = 'sell'
elif signal == 0:
print('Signal is 0, staying flat')
return
else:
raise ValueError(f'Unexpected signal: {signal}, should have been 0, 1, or -1')
self.bit.enter_bracket(
self.symbol,
self.size,
side=side,
target_offset=self.target_offset,
stop_offset=self.stop_offset
)
else:
print('Already in a trade and not entering')
if __name__ == '__main__': # where we call our Trader class defined above, in main
model = EnsembleModel()
trader = Trader('XBTUSD', model, 450, '1d',
target_offset=30, stop_offset=0)
trader.run()