-
Notifications
You must be signed in to change notification settings - Fork 0
/
trader.py
391 lines (337 loc) · 13 KB
/
trader.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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
import os
import sys
from pygraph.classes.graph import graph
from pygraph.readwrite.dot import write
from pygraph.algorithms.cycles import find_cycle
sys.path.append("/Users/wojtek/Sources/btce-api/")
import btceapi
from decorators import debug
import sqlalchemy as sa
class Secrets:
key='3VHY49XV-915CL848-ZL4GZZRW-U0IMFDLY-CHSQTXK0'
secret='02bc45897f501efeb23c4fb0645e290175bad695536c0332ddc40da553276da1'
class KeyFileManager:
def __init__(self, key_file_path):
self.key_file = key_file_path
self.handler = btceapi.KeyHandler(self.key_file, resaveOnDeletion=True)
def get_handler(self):
return self.handler
class Database():
'''
- Database should manage DB migrations and abstraction
- Should return database object to interact with
'''
def __init__(self):
self.sqlite_db_path = "trader.db"
self.db = sa.create_engine('sqlite:///' + self.sqlite_db_path)
self.metadata = sa.MetaData(self.db)
self.db_migrate()
def db_migrate(self):
create_db = not os.path.isfile(self.sqlite_db_path)
if create_db:
print "Creating database"
print "...secrets table"
secrets = sa.Table('secrets', self.metadata,
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('slug', sa.String(20)),
sa.Column('value', sa.String(100))
)
secrets.create()
i = secrets.insert()
i.execute({'slug' : 'key', 'value' : Secrets.key},
{'slug' : 'secret', 'value' : Secrets.secret}
)
print "...currencies table"
currencies = sa.Table('currencies', self.metadata,
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('symbol', sa.String(16)),
sa.Column('name', sa.String(100))
)
currencies.create()
i = currencies.insert()
i.execute({'symbol' : 'USD', 'name' : 'United States Dollar'},
{'symbol' : 'BTC', 'name' : 'Bitcoin'},
{'symbol' : 'LTC', 'name' : 'Litecoin'},
{'symbol' : 'PPC', 'name' : 'Peercoin'},
{'symbol' : 'FTC', 'name' : 'Feathercoin'},
{'symbol' : 'NMC', 'name' : 'Namecoin'}
)
print "...done creating tables"
def get_db(self):
return self.db
class CurrencyPair:
''' Return currency pair cost of conversion with respect to fee '''
def __init__(self, currency_from, currency_to, cost):
self.pair = {'from' : currency_from,
'to' : currency_to,
'cost' : cost}
def refresh(self):
''' Tell api to refresh values '''
pass
def data(self):
return self.pair
class TradeGraph:
'''
- TradeGraph should keep the graph of currency pairs with all transition costs
- TradeGraph should propose profitable transaction chains
* example transaction triad : ['ltc_btc', 'btc_ftc', 'ftc_ltc']
- TradeGraph should be initialized with dictionary of pair costs
'''
def __init__(self, pairs_rates):
self.trade_fee = 0.02
self.graph_depth = 5
self.pairs_rates = pairs_rates
self.graph = graph()
self.trade_graph = self.init_graph()
self.path = self.paths_from_to(self.graph, "nvc", "trc")
print "Paths: %s" % str(self.path)
def __repr__(self):
return self.graph
def init_graph(self):
''' Returns graph where nodes are currencies and edges are exchange rates'''
self.add_nodes()
self.add_edges()
self.add_edges_attributes()
self.draw_graph()
def draw_graph(self):
dot = write(self.graph)
f = open('currencies.dot', 'a')
f.write(dot)
f.close()
command = '/usr/local/bin/dot -Tpng currencies.dot > currencies.png'
print "Generating graph with %s" % command
os.system(command)
def add_nodes(self):
''' adds nodes to trade graph '''
try:
self.graph.add_nodes(list(btceapi.all_currencies))
return True
except Exception, e:
print "Could not add nodes to tradegraph: %s" % e
raise
def all_edges(self):
''' returns tuples of currency pairs '''
return [tuple(i.split('_')) for i in btceapi.all_pairs]
def add_edges(self):
''' Adds edges to trade graph '''
try:
for edge in self.all_edges():
self.graph.add_edge(edge)
return True
except Exception, e:
print "Could not add edges to trade graph: %s" % e
raise
def add_edges_attributes(self):
''' Ads attributes to trade graph '''
try:
for rate in self.pairs_rates:
edge = tuple(rate[0].split("_"))
edge_attribute_1 = tuple([edge[0], rate[1]])
edge_attribute_2 = tuple([edge[1], 1/rate[1]])
self.graph.add_edge_attribute(edge,(edge_attribute_1))
self.graph.add_edge_attribute(edge,(edge_attribute_2))
self.graph.add_edge_attribute(edge,("label",rate[1]))
except Exception, e:
print "Could not add attributes to edges: %s" % e
raise
def refresh_graph(self):
''' Iterate through thru all CurrencyPairs and refresh pair cost'''
pass
def get_graph(self):
return self.graph
def adjlist_find_paths(self, a, n, m, path=[]):
'''Find paths from node index n to m using adjacency list a.'''
path = path + [n]
if n == m:
return [path]
paths = []
for child in a[n]:
if child not in path:
child_paths = self.adjlist_find_paths(a, child, m, path)
for child_path in child_paths:
paths.append(child_path)
return paths
def paths_from_to(self, graph, source, dest):
'''Find paths in graph from vertex source to vertex dest.'''
a = graph.node_neighbors
n = source
m = dest
return self.adjlist_find_paths(a, n, m)
def find_all_flows(self):
pass
def find_cycle_to_ancestor(self, spanning_tree, node, ancestor):
"""
Find a cycle containing both node and ancestor.
"""
path = []
while (node != ancestor):
if node is None:
return []
path.append(node)
node = spanning_tree[node]
path.append(node)
path.reverse()
return path
def find_all_cycles(self):
"""
Find all cycles in the given graph.
This function will return a list of lists of nodes, which form cycles in the
graph or an empty list if no cycle exists.
"""
def dfs(node):
"""
Depth-first search subfunction.
"""
visited.add(node)
# Explore recursively the connected component
for each in self.graph[node]:
if each not in visited:
spanning_tree[each] = node
dfs(each)
else:
if (spanning_tree[node] != each):
cycle = self.find_cycle_to_ancestor(spanning_tree, node, each)
if cycle:
cycles.append(cycle)
visited = set() # List for marking visited and non-visited nodes
spanning_tree = {} # Spanning tree
cycles = []
# Algorithm outer-loop
for each in self.graph:
# Select a non-visited node
if each not in visited:
spanning_tree[each] = None
# Explore node's connected component
dfs(each)
return cycles
class MarketKnowledge:
'''
- Knowledge should be able to access database but leave
all the lower level logic to Database class
- Knowledge should provide API abstraction
'''
def __init__(self, key_file_path):
self.handler = KeyFileManager(key_file_path).get_handler()
self.db = self.init_database()
self.trade_api = self.init_trade_api()
self.all_currencies = btceapi.all_currencies
self.pairs_rates = self.init_pairs_rates()
self.graph = TradeGraph(self.pairs_rates).get_graph()
self.all_cycles = TradeGraph(self.pairs_rates).find_all_cycles()
def __repr__(self):
return str(self.handler)
@debug
def init_pairs_rates(self):
''' Returns list of tuples containing (pair <string>, rate <float>)'''
try:
pairs_rates = []
for pair in btceapi.all_pairs:
rate = btceapi.getTicker(pair).avg
pairs_rates.append((pair, rate))
return pairs_rates
except Exception, e:
print "Could not produce pairs rates: %s" % e
raise
def init_database(self):
db = Database().get_db()
return db
def init_trade_api(self):
try:
self.api_connection = btceapi.BTCEConnection()
self.key = self.handler.getKeys()[0]
trade_api = btceapi.TradeAPI(self.key, self.handler)
return trade_api
except Exception, e:
print "Could not initialize API because %s" % e
raise
@debug
def get_pair_depth(self, pair):
''' Returns asks and bids '''
asks, bids = btceapi.getDepth(pair)
ask_prices, ask_volumes = zip(*asks)
bid_prices, bid_volumes = zip(*bids)
pair_depth = {pair: {'ask_prices': ask_prices,
'ask_volumes': ask_volumes,
'bid_prices': bid_prices,
'bid_volumes' : bid_volumes
}
}
return pair_depth
def get_all_depth(self):
all_pairs_depth = {}
for pair in btceapi.all_pairs:
pair_depth = self.get_pair_depth(pair)
all_pairs_depth[pair] = pair_depth
return all_pairs_depth
def get_ticker(self, pair):
''' Returns high, low, avg, etc '''
pass
def get_trade_fee(self, pair):
''' Returns exchange's trade fee '''
pass
def get_info(self):
''' Returns dictionary that contains all needed data '''
r = self.trade_api.getInfo(connection = self.api_connection)
balances = self.get_balances(r)
open_orders = self.get_open_orders()
transaction_history_count = self.get_transaction_history_count(r)
market_depth = self.get_all_depth()
info = {'balances' : balances,
'open_orders' : open_orders,
'transaction_history_count' : transaction_history_count,
'market_depth' : market_depth
}
return info
def get_transaction_history_count(self, r):
return r.transaction_count
def get_open_orders(self):
orders = self.trade_api.activeOrders(connection = self.api_connection)
orders_list = {}
for o in orders:
order = {}
order['id'] = o.order_id
order['type'] = o.type
order['pair'] = o.pair
order['rate'] = o.rate
order['amount'] = o.amount
order['timestamp_created'] = o.timestamp_created
order['status'] = o.status
orders_list[o.order_id] = order
return orders_list
def get_balances(self, r):
balances = {}
for currency in self.all_currencies:
balance = getattr(r, "balance_" + currency)
balances[currency.upper()] = balance
return balances
def get_graph(self):
return self.graph
def get_cycles(self):
return self.all_cycles
class Trader():
'''
- Trader should receive currencies to trade with from database
- Trader should compute transition costs/profits so trader must know:
* current commission/fee of exchange api
* last transactions (good to have)
* price of every cryptocurrency with respect to dollar
* trader should keep trace of all operations
- Trader should perform only synchronous trades (live trading)
- Trader should have some fallback-currency or safety strategy
'''
def __init__(self, key_file):
self.market = MarketKnowledge(key_file)
self.graph = self.market.get_graph()
self.cycles = self.market.get_cycles()
#self.trade_pairs = TradeGraph().trade_pairs
def trade(self):
print "Graph"
print self.graph
print "Cycles"
print self.cycles
#pass
if __name__ == "__main__":
if len(sys.argv) > 0:
print "Launching trader with key file %s" % sys.argv[1]
t = Trader(sys.argv[1])
t.trade()