forked from hkhanna/mistbat
-
Notifications
You must be signed in to change notification settings - Fork 0
/
events.py
185 lines (157 loc) · 5.79 KB
/
events.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
# pylint: disable=E1101
import dateutil.parser
import datetime
import pytz
import hashlib
class Event:
def __init__(self, **kwargs):
self.time = None
for name, val in kwargs.items():
setattr(self, name, val)
# Make sure that if buy_fmv is provided, so is sell_fmv and vice versa
if kwargs.get("buy_fmv"):
assert kwargs.get("sell_fmv") is not None
if kwargs.get("sell_fmv"):
assert kwargs.get("buy_fmv") is not None
# Parse strings into datetime
if type(self.time) == str:
self.time = dateutil.parser.parse(self.time)
# Parse unix timestamps into datetime
elif type(self.time) == int:
self.time = datetime.datetime.fromtimestamp(self.time / 1e3, tz=pytz.utc)
# Assume UTC timezone if not specified
# Otherwise, convert to UTC
if self.time.tzinfo is None:
self.time = self.time.replace(tzinfo=pytz.utc)
else:
self.time = self.time.astimezone(pytz.utc)
# Generate unique ID based on available info
self.generate_id()
# Get rid of any sub-second resolution
self.time = self.time.replace(microsecond=0)
def generate_id(self):
id = self.location[:3]
# If there's an id provided by the exchange leverage that
if hasattr(self, "location_id"):
id += "-" + self.location_id[-5:]
# Otherwise, hash a few things we have to get the id
else:
hashstr = (
getattr(self, "location", "")
+ str(self.time)
+ self.__class__.__name__
+ getattr(self, "coin", "")
+ getattr(self, "buy_coin", "")
+ getattr(self, "sell_coin", "")
+ str(getattr(self, "amount", ""))
+ str(getattr(self, "buy_amount", ""))
+ str(getattr(self, "sell_amount", ""))
+ getattr(self, "txid", "")
)
id += "-" + hashlib.sha256(hashstr.encode()).hexdigest()[-5:]
self.id = id
class Exchange(Event):
def entries(self):
return (
(self.location, self.buy_coin, self.buy_amount),
(self.location, self.sell_coin, -self.sell_amount),
)
def __str__(self):
return "{} ({}) - EXCH {} {} -> {} {}".format(
self.time.strftime("%Y-%m-%d %H:%M:%S"),
self.id,
self.sell_coin,
self.sell_amount,
self.buy_coin,
self.buy_amount,
)
class FiatExchange(Exchange):
def __init__(self, **kwargs):
Exchange.__init__(self, **kwargs)
if self.sell_coin == "USD":
self.investing = True
self.redeeming = False
self.rate = round(self.sell_amount / self.buy_amount, 2)
else:
self.investing = False
self.redeeming = True
self.rate = round(self.buy_amount / self.sell_amount, 2)
def entries(self):
return (
(self.location, self.buy_coin, self.buy_amount),
(self.location, self.sell_coin, -self.sell_amount),
(self.location, self.fee_with, -self.fee_amount),
)
def __str__(self, alt_id=None):
if self.investing:
return "{} ({}) - FXCH {} {} (+ USD {} fee) -> {} {} (@ USD {} per {})".format(
self.time.strftime("%Y-%m-%d %H:%M:%S"),
alt_id or self.id,
self.sell_coin,
self.sell_amount,
self.fee_amount,
self.buy_coin,
self.buy_amount,
self.rate,
self.buy_coin,
)
else:
return "{} ({}) - FXCH {} {} -> {} {} (+ USD {} fee) (@ USD {} per {})".format(
self.time.strftime("%Y-%m-%d %H:%M:%S"),
alt_id or self.id,
self.sell_coin,
self.sell_amount,
self.buy_coin,
self.buy_amount,
self.fee_amount,
self.rate,
self.sell_coin,
)
class Send(Event):
def entries(self):
return (self.location, self.coin, -self.amount)
def __str__(self):
return "{} ({}) - SEND {} {} from {}".format(
self.time.strftime("%Y-%m-%d %H:%M:%S"),
self.id,
self.coin,
self.amount,
self.location,
)
class Receive(Event):
def entries(self):
return (self.location, self.coin, self.amount)
def __str__(self):
return "{} ({}) - RECV {} {} by {}".format(
self.time.strftime("%Y-%m-%d %H:%M:%S"),
self.id,
self.coin,
self.amount,
self.location,
)
def get_events(loaders, typ=None, remote_update=False):
"""Return events from exchange loaders.
Args:
loaders: A list of all the loader modules to be used.
typ: A filter for specific event types (e.g., 'Send')
remote_update: Poll the exchange APIs and update the transaction records.
Returns:
A list of events from all loaders, sorted by time.
"""
all_events = []
for loader in loaders:
if remote_update:
print("Remote update from {}".format(loader.__name__))
loader.update_from_remote()
all_events.extend(loader.parse_events())
# TODO: confirm all events have unique id attribute
# Sort all events by time
all_events.sort(key=lambda x: x.time)
# Filter by type if requested
if typ == None:
return all_events
else:
if type(typ) == list or type(typ) == tuple:
return [ev for ev in all_events if isinstance(ev, tuple(typ))]
else:
return [ev for ev in all_events if ev.__class__.__name__ == typ]