diff options
Diffstat (limited to 'store.py')
-rw-r--r-- | store.py | 230 |
1 files changed, 104 insertions, 126 deletions
@@ -6,63 +6,59 @@ from datetime import date, datetime | |||
6 | __all__ = ["BalanceStore", "ReportStore", "TradeStore"] | 6 | __all__ = ["BalanceStore", "ReportStore", "TradeStore"] |
7 | 7 | ||
8 | class ReportStore: | 8 | class ReportStore: |
9 | logs = [] | 9 | def __init__(self, market, verbose_print=True): |
10 | verbose_print = True | 10 | self.market = market |
11 | self.verbose_print = verbose_print | ||
11 | 12 | ||
12 | @classmethod | 13 | self.logs = [] |
13 | def print_log(cls, message): | 14 | |
15 | def print_log(self, message): | ||
14 | message = str(message) | 16 | message = str(message) |
15 | if cls.verbose_print: | 17 | if self.verbose_print: |
16 | print(message) | 18 | print(message) |
17 | 19 | ||
18 | @classmethod | 20 | def add_log(self, hash_): |
19 | def add_log(cls, hash_): | ||
20 | hash_["date"] = datetime.now() | 21 | hash_["date"] = datetime.now() |
21 | cls.logs.append(hash_) | 22 | self.logs.append(hash_) |
22 | 23 | ||
23 | @classmethod | 24 | def to_json(self): |
24 | def to_json(cls): | ||
25 | def default_json_serial(obj): | 25 | def default_json_serial(obj): |
26 | if isinstance(obj, (datetime, date)): | 26 | if isinstance(obj, (datetime, date)): |
27 | return obj.isoformat() | 27 | return obj.isoformat() |
28 | raise TypeError ("Type %s not serializable" % type(obj)) | 28 | raise TypeError ("Type %s not serializable" % type(obj)) |
29 | return json.dumps(cls.logs, default=default_json_serial) | 29 | return json.dumps(self.logs, default=default_json_serial) |
30 | 30 | ||
31 | @classmethod | 31 | def set_verbose(self, verbose_print): |
32 | def set_verbose(cls, verbose_print): | 32 | self.verbose_print = verbose_print |
33 | cls.verbose_print = verbose_print | ||
34 | 33 | ||
35 | @classmethod | 34 | def log_stage(self, stage): |
36 | def log_stage(cls, stage): | 35 | self.print_log("-" * (len(stage) + 8)) |
37 | cls.print_log("-" * (len(stage) + 8)) | 36 | self.print_log("[Stage] {}".format(stage)) |
38 | cls.print_log("[Stage] {}".format(stage)) | ||
39 | 37 | ||
40 | cls.add_log({ | 38 | self.add_log({ |
41 | "type": "stage", | 39 | "type": "stage", |
42 | "stage": stage, | 40 | "stage": stage, |
43 | }) | 41 | }) |
44 | 42 | ||
45 | @classmethod | 43 | def log_balances(self, tag=None): |
46 | def log_balances(cls, market, tag=None): | 44 | self.print_log("[Balance]") |
47 | cls.print_log("[Balance]") | 45 | for currency, balance in self.market.balances.all.items(): |
48 | for currency, balance in BalanceStore.all.items(): | 46 | self.print_log("\t{}".format(balance)) |
49 | cls.print_log("\t{}".format(balance)) | ||
50 | 47 | ||
51 | cls.add_log({ | 48 | self.add_log({ |
52 | "type": "balance", | 49 | "type": "balance", |
53 | "tag": tag, | 50 | "tag": tag, |
54 | "balances": BalanceStore.as_json() | 51 | "balances": self.market.balances.as_json() |
55 | }) | 52 | }) |
56 | 53 | ||
57 | @classmethod | 54 | def log_tickers(self, amounts, other_currency, |
58 | def log_tickers(cls, market, amounts, other_currency, | ||
59 | compute_value, type): | 55 | compute_value, type): |
60 | values = {} | 56 | values = {} |
61 | rates = {} | 57 | rates = {} |
62 | for currency, amount in amounts.items(): | 58 | for currency, amount in amounts.items(): |
63 | values[currency] = amount.as_json()["value"] | 59 | values[currency] = amount.as_json()["value"] |
64 | rates[currency] = amount.rate | 60 | rates[currency] = amount.rate |
65 | cls.add_log({ | 61 | self.add_log({ |
66 | "type": "tickers", | 62 | "type": "tickers", |
67 | "compute_value": compute_value, | 63 | "compute_value": compute_value, |
68 | "balance_type": type, | 64 | "balance_type": type, |
@@ -72,9 +68,8 @@ class ReportStore: | |||
72 | "total": sum(amounts.values()).as_json()["value"] | 68 | "total": sum(amounts.values()).as_json()["value"] |
73 | }) | 69 | }) |
74 | 70 | ||
75 | @classmethod | 71 | def log_dispatch(self, amount, amounts, liquidity, repartition): |
76 | def log_dispatch(cls, amount, amounts, liquidity, repartition): | 72 | self.add_log({ |
77 | cls.add_log({ | ||
78 | "type": "dispatch", | 73 | "type": "dispatch", |
79 | "liquidity": liquidity, | 74 | "liquidity": liquidity, |
80 | "repartition_ratio": repartition, | 75 | "repartition_ratio": repartition, |
@@ -82,26 +77,24 @@ class ReportStore: | |||
82 | "repartition": { k: v.as_json()["value"] for k, v in amounts.items() } | 77 | "repartition": { k: v.as_json()["value"] for k, v in amounts.items() } |
83 | }) | 78 | }) |
84 | 79 | ||
85 | @classmethod | 80 | def log_trades(self, matching_and_trades, only): |
86 | def log_trades(cls, matching_and_trades, only, debug): | ||
87 | trades = [] | 81 | trades = [] |
88 | for matching, trade in matching_and_trades: | 82 | for matching, trade in matching_and_trades: |
89 | trade_json = trade.as_json() | 83 | trade_json = trade.as_json() |
90 | trade_json["skipped"] = not matching | 84 | trade_json["skipped"] = not matching |
91 | trades.append(trade_json) | 85 | trades.append(trade_json) |
92 | 86 | ||
93 | cls.add_log({ | 87 | self.add_log({ |
94 | "type": "trades", | 88 | "type": "trades", |
95 | "only": only, | 89 | "only": only, |
96 | "debug": debug, | 90 | "debug": self.market.debug, |
97 | "trades": trades | 91 | "trades": trades |
98 | }) | 92 | }) |
99 | 93 | ||
100 | @classmethod | 94 | def log_orders(self, orders, tick=None, only=None, compute_value=None): |
101 | def log_orders(cls, orders, tick=None, only=None, compute_value=None): | 95 | self.print_log("[Orders]") |
102 | cls.print_log("[Orders]") | 96 | self.market.trades.print_all_with_order(ind="\t") |
103 | TradeStore.print_all_with_order(ind="\t") | 97 | self.add_log({ |
104 | cls.add_log({ | ||
105 | "type": "orders", | 98 | "type": "orders", |
106 | "only": only, | 99 | "only": only, |
107 | "compute_value": compute_value, | 100 | "compute_value": compute_value, |
@@ -109,21 +102,20 @@ class ReportStore: | |||
109 | "orders": [order.as_json() for order in orders if order is not None] | 102 | "orders": [order.as_json() for order in orders if order is not None] |
110 | }) | 103 | }) |
111 | 104 | ||
112 | @classmethod | 105 | def log_order(self, order, tick, finished=False, update=None, |
113 | def log_order(cls, order, tick, finished=False, update=None, | ||
114 | new_order=None, compute_value=None): | 106 | new_order=None, compute_value=None): |
115 | if finished: | 107 | if finished: |
116 | cls.print_log("[Order] Finished {}".format(order)) | 108 | self.print_log("[Order] Finished {}".format(order)) |
117 | elif update == "waiting": | 109 | elif update == "waiting": |
118 | cls.print_log("[Order] {}, tick {}, waiting".format(order, tick)) | 110 | self.print_log("[Order] {}, tick {}, waiting".format(order, tick)) |
119 | elif update == "adjusting": | 111 | elif update == "adjusting": |
120 | cls.print_log("[Order] {}, tick {}, cancelling and adjusting to {}".format(order, tick, new_order)) | 112 | self.print_log("[Order] {}, tick {}, cancelling and adjusting to {}".format(order, tick, new_order)) |
121 | elif update == "market_fallback": | 113 | elif update == "market_fallback": |
122 | cls.print_log("[Order] {}, tick {}, fallbacking to market value".format(order, tick)) | 114 | self.print_log("[Order] {}, tick {}, fallbacking to market value".format(order, tick)) |
123 | elif update == "market_adjust": | 115 | elif update == "market_adjust": |
124 | cls.print_log("[Order] {}, tick {}, market value, cancelling and adjusting to {}".format(order, tick, new_order)) | 116 | self.print_log("[Order] {}, tick {}, market value, cancelling and adjusting to {}".format(order, tick, new_order)) |
125 | 117 | ||
126 | cls.add_log({ | 118 | self.add_log({ |
127 | "type": "order", | 119 | "type": "order", |
128 | "tick": tick, | 120 | "tick": tick, |
129 | "update": update, | 121 | "update": update, |
@@ -132,18 +124,16 @@ class ReportStore: | |||
132 | "new_order": new_order.as_json() if new_order is not None else None | 124 | "new_order": new_order.as_json() if new_order is not None else None |
133 | }) | 125 | }) |
134 | 126 | ||
135 | @classmethod | 127 | def log_move_balances(self, needed, moving): |
136 | def log_move_balances(cls, needed, moving, debug): | 128 | self.add_log({ |
137 | cls.add_log({ | ||
138 | "type": "move_balances", | 129 | "type": "move_balances", |
139 | "debug": debug, | 130 | "debug": self.market.debug, |
140 | "needed": { k: v.as_json()["value"] if isinstance(v, portfolio.Amount) else v for k, v in needed.items() }, | 131 | "needed": { k: v.as_json()["value"] if isinstance(v, portfolio.Amount) else v for k, v in needed.items() }, |
141 | "moving": { k: v.as_json()["value"] if isinstance(v, portfolio.Amount) else v for k, v in moving.items() }, | 132 | "moving": { k: v.as_json()["value"] if isinstance(v, portfolio.Amount) else v for k, v in moving.items() }, |
142 | }) | 133 | }) |
143 | 134 | ||
144 | @classmethod | 135 | def log_http_request(self, method, url, body, headers, response): |
145 | def log_http_request(cls, method, url, body, headers, response): | 136 | self.add_log({ |
146 | cls.add_log({ | ||
147 | "type": "http_request", | 137 | "type": "http_request", |
148 | "method": method, | 138 | "method": method, |
149 | "url": url, | 139 | "url": url, |
@@ -153,15 +143,14 @@ class ReportStore: | |||
153 | "response": response.text | 143 | "response": response.text |
154 | }) | 144 | }) |
155 | 145 | ||
156 | @classmethod | 146 | def log_error(self, action, message=None, exception=None): |
157 | def log_error(cls, action, message=None, exception=None): | 147 | self.print_log("[Error] {}".format(action)) |
158 | cls.print_log("[Error] {}".format(action)) | ||
159 | if exception is not None: | 148 | if exception is not None: |
160 | cls.print_log(str("\t{}: {}".format(exception.__class__.__name__, exception))) | 149 | self.print_log(str("\t{}: {}".format(exception.__class__.__name__, exception))) |
161 | if message is not None: | 150 | if message is not None: |
162 | cls.print_log("\t{}".format(message)) | 151 | self.print_log("\t{}".format(message)) |
163 | 152 | ||
164 | cls.add_log({ | 153 | self.add_log({ |
165 | "type": "error", | 154 | "type": "error", |
166 | "action": action, | 155 | "action": action, |
167 | "exception_class": exception.__class__.__name__ if exception is not None else None, | 156 | "exception_class": exception.__class__.__name__ if exception is not None else None, |
@@ -169,132 +158,121 @@ class ReportStore: | |||
169 | "message": message, | 158 | "message": message, |
170 | }) | 159 | }) |
171 | 160 | ||
172 | @classmethod | 161 | def log_debug_action(self, action): |
173 | def log_debug_action(cls, action): | 162 | self.print_log("[Debug] {}".format(action)) |
174 | cls.print_log("[Debug] {}".format(action)) | ||
175 | 163 | ||
176 | cls.add_log({ | 164 | self.add_log({ |
177 | "type": "debug_action", | 165 | "type": "debug_action", |
178 | "action": action, | 166 | "action": action, |
179 | }) | 167 | }) |
180 | 168 | ||
181 | class BalanceStore: | 169 | class BalanceStore: |
182 | all = {} | 170 | def __init__(self, market): |
171 | self.market = market | ||
172 | self.all = {} | ||
183 | 173 | ||
184 | @classmethod | 174 | def currencies(self): |
185 | def currencies(cls): | 175 | return self.all.keys() |
186 | return cls.all.keys() | ||
187 | 176 | ||
188 | @classmethod | 177 | def in_currency(self, other_currency, compute_value="average", type="total"): |
189 | def in_currency(cls, other_currency, market, compute_value="average", type="total"): | ||
190 | amounts = {} | 178 | amounts = {} |
191 | for currency, balance in cls.all.items(): | 179 | for currency, balance in self.all.items(): |
192 | other_currency_amount = getattr(balance, type)\ | 180 | other_currency_amount = getattr(balance, type)\ |
193 | .in_currency(other_currency, market, compute_value=compute_value) | 181 | .in_currency(other_currency, self.market, compute_value=compute_value) |
194 | amounts[currency] = other_currency_amount | 182 | amounts[currency] = other_currency_amount |
195 | ReportStore.log_tickers(market, amounts, other_currency, | 183 | self.market.report.log_tickers(amounts, other_currency, |
196 | compute_value, type) | 184 | compute_value, type) |
197 | return amounts | 185 | return amounts |
198 | 186 | ||
199 | @classmethod | 187 | def fetch_balances(self, tag=None): |
200 | def fetch_balances(cls, market, tag=None): | 188 | all_balances = self.market.ccxt.fetch_all_balances() |
201 | all_balances = market.fetch_all_balances() | ||
202 | for currency, balance in all_balances.items(): | 189 | for currency, balance in all_balances.items(): |
203 | if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \ | 190 | if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \ |
204 | currency in cls.all: | 191 | currency in self.all: |
205 | cls.all[currency] = portfolio.Balance(currency, balance) | 192 | self.all[currency] = portfolio.Balance(currency, balance) |
206 | ReportStore.log_balances(market, tag=tag) | 193 | self.market.report.log_balances(tag=tag) |
207 | 194 | ||
208 | @classmethod | 195 | def dispatch_assets(self, amount, liquidity="medium", repartition=None): |
209 | def dispatch_assets(cls, amount, liquidity="medium", repartition=None): | ||
210 | if repartition is None: | 196 | if repartition is None: |
211 | repartition = portfolio.Portfolio.repartition(liquidity=liquidity) | 197 | repartition = portfolio.Portfolio.repartition(self.market, liquidity=liquidity) |
212 | sum_ratio = sum([v[0] for k, v in repartition.items()]) | 198 | sum_ratio = sum([v[0] for k, v in repartition.items()]) |
213 | amounts = {} | 199 | amounts = {} |
214 | for currency, (ptt, trade_type) in repartition.items(): | 200 | for currency, (ptt, trade_type) in repartition.items(): |
215 | amounts[currency] = ptt * amount / sum_ratio | 201 | amounts[currency] = ptt * amount / sum_ratio |
216 | if trade_type == "short": | 202 | if trade_type == "short": |
217 | amounts[currency] = - amounts[currency] | 203 | amounts[currency] = - amounts[currency] |
218 | if currency not in BalanceStore.all: | 204 | if currency not in self.all: |
219 | cls.all[currency] = portfolio.Balance(currency, {}) | 205 | self.all[currency] = portfolio.Balance(currency, {}) |
220 | ReportStore.log_dispatch(amount, amounts, liquidity, repartition) | 206 | self.market.report.log_dispatch(amount, amounts, liquidity, repartition) |
221 | return amounts | 207 | return amounts |
222 | 208 | ||
223 | @classmethod | 209 | def as_json(self): |
224 | def as_json(cls): | 210 | return { k: v.as_json() for k, v in self.all.items() } |
225 | return { k: v.as_json() for k, v in cls.all.items() } | ||
226 | 211 | ||
227 | class TradeStore: | 212 | class TradeStore: |
228 | all = [] | 213 | def __init__(self, market): |
229 | debug = False | 214 | self.market = market |
215 | self.all = [] | ||
230 | 216 | ||
231 | @classmethod | 217 | def compute_trades(self, values_in_base, new_repartition, only=None): |
232 | def compute_trades(cls, values_in_base, new_repartition, only=None, market=None, debug=False): | ||
233 | computed_trades = [] | 218 | computed_trades = [] |
234 | cls.debug = cls.debug or debug | ||
235 | base_currency = sum(values_in_base.values()).currency | 219 | base_currency = sum(values_in_base.values()).currency |
236 | for currency in BalanceStore.currencies(): | 220 | for currency in self.market.balances.currencies(): |
237 | if currency == base_currency: | 221 | if currency == base_currency: |
238 | continue | 222 | continue |
239 | value_from = values_in_base.get(currency, portfolio.Amount(base_currency, 0)) | 223 | value_from = values_in_base.get(currency, portfolio.Amount(base_currency, 0)) |
240 | value_to = new_repartition.get(currency, portfolio.Amount(base_currency, 0)) | 224 | value_to = new_repartition.get(currency, portfolio.Amount(base_currency, 0)) |
241 | 225 | ||
242 | if value_from.value * value_to.value < 0: | 226 | if value_from.value * value_to.value < 0: |
243 | computed_trades.append(cls.trade_if_matching( | 227 | computed_trades.append(self.trade_if_matching( |
244 | value_from, portfolio.Amount(base_currency, 0), | 228 | value_from, portfolio.Amount(base_currency, 0), |
245 | currency, only=only, market=market)) | 229 | currency, only=only)) |
246 | computed_trades.append(cls.trade_if_matching( | 230 | computed_trades.append(self.trade_if_matching( |
247 | portfolio.Amount(base_currency, 0), value_to, | 231 | portfolio.Amount(base_currency, 0), value_to, |
248 | currency, only=only, market=market)) | 232 | currency, only=only)) |
249 | else: | 233 | else: |
250 | computed_trades.append(cls.trade_if_matching( | 234 | computed_trades.append(self.trade_if_matching( |
251 | value_from, value_to, | 235 | value_from, value_to, |
252 | currency, only=only, market=market)) | 236 | currency, only=only)) |
253 | for matching, trade in computed_trades: | 237 | for matching, trade in computed_trades: |
254 | if matching: | 238 | if matching: |
255 | cls.all.append(trade) | 239 | self.all.append(trade) |
256 | ReportStore.log_trades(computed_trades, only, cls.debug) | 240 | self.market.report.log_trades(computed_trades, only) |
257 | 241 | ||
258 | @classmethod | 242 | def trade_if_matching(self, value_from, value_to, currency, |
259 | def trade_if_matching(cls, value_from, value_to, currency, | 243 | only=None): |
260 | only=None, market=None): | ||
261 | trade = portfolio.Trade(value_from, value_to, currency, | 244 | trade = portfolio.Trade(value_from, value_to, currency, |
262 | market=market) | 245 | self.market) |
263 | matching = only is None or trade.action == only | 246 | matching = only is None or trade.action == only |
264 | return [matching, trade] | 247 | return [matching, trade] |
265 | 248 | ||
266 | @classmethod | 249 | def prepare_orders(self, only=None, compute_value="default"): |
267 | def prepare_orders(cls, only=None, compute_value="default"): | ||
268 | orders = [] | 250 | orders = [] |
269 | for trade in cls.all: | 251 | for trade in self.all: |
270 | if only is None or trade.action == only: | 252 | if only is None or trade.action == only: |
271 | orders.append(trade.prepare_order(compute_value=compute_value)) | 253 | orders.append(trade.prepare_order(compute_value=compute_value)) |
272 | ReportStore.log_orders(orders, only, compute_value) | 254 | self.market.report.log_orders(orders, only, compute_value) |
273 | 255 | ||
274 | @classmethod | 256 | def print_all_with_order(self, ind=""): |
275 | def print_all_with_order(cls, ind=""): | 257 | for trade in self.all: |
276 | for trade in cls.all: | ||
277 | trade.print_with_order(ind=ind) | 258 | trade.print_with_order(ind=ind) |
278 | 259 | ||
279 | @classmethod | 260 | def run_orders(self): |
280 | def run_orders(cls): | 261 | orders = self.all_orders(state="pending") |
281 | orders = cls.all_orders(state="pending") | ||
282 | for order in orders: | 262 | for order in orders: |
283 | order.run() | 263 | order.run() |
284 | ReportStore.log_stage("run_orders") | 264 | self.market.report.log_stage("run_orders") |
285 | ReportStore.log_orders(orders) | 265 | self.market.report.log_orders(orders) |
286 | 266 | ||
287 | @classmethod | 267 | def all_orders(self, state=None): |
288 | def all_orders(cls, state=None): | 268 | all_orders = sum(map(lambda v: v.orders, self.all), []) |
289 | all_orders = sum(map(lambda v: v.orders, cls.all), []) | ||
290 | if state is None: | 269 | if state is None: |
291 | return all_orders | 270 | return all_orders |
292 | else: | 271 | else: |
293 | return list(filter(lambda o: o.status == state, all_orders)) | 272 | return list(filter(lambda o: o.status == state, all_orders)) |
294 | 273 | ||
295 | @classmethod | 274 | def update_all_orders_status(self): |
296 | def update_all_orders_status(cls): | 275 | for order in self.all_orders(state="open"): |
297 | for order in cls.all_orders(state="open"): | ||
298 | order.get_status() | 276 | order.get_status() |
299 | 277 | ||
300 | 278 | ||