aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2018-02-24 19:53:58 +0100
committerIsmaël Bouya <ismael.bouya@normalesup.org>2018-02-24 19:53:58 +0100
commit3d0247f944d7510943dfaa64eeb0e15a43b6c989 (patch)
tree2695181c77e444d52bca5de47b6c99b9b6c1ed32
parentc31df868c655612b8387a25111e69882f0fe6344 (diff)
downloadTrader-3d0247f944d7510943dfaa64eeb0e15a43b6c989.tar.gz
Trader-3d0247f944d7510943dfaa64eeb0e15a43b6c989.tar.zst
Trader-3d0247f944d7510943dfaa64eeb0e15a43b6c989.zip
Add report store to store messages and logs
-rw-r--r--helper.py81
-rw-r--r--portfolio.py107
-rw-r--r--store.py224
-rw-r--r--test.py850
4 files changed, 1041 insertions, 221 deletions
diff --git a/helper.py b/helper.py
index 23b2d0b..87539f1 100644
--- a/helper.py
+++ b/helper.py
@@ -4,6 +4,8 @@ from store import *
4 4
5def move_balances(market, debug=False): 5def move_balances(market, debug=False):
6 needed_in_margin = {} 6 needed_in_margin = {}
7 moving_to_margin = {}
8
7 for currency in BalanceStore.all: 9 for currency in BalanceStore.all:
8 if BalanceStore.all[currency].margin_free != 0: 10 if BalanceStore.all[currency].margin_free != 0:
9 needed_in_margin[currency] = 0 11 needed_in_margin[currency] = 0
@@ -14,18 +16,16 @@ def move_balances(market, debug=False):
14 needed_in_margin[trade.value_to.currency] += abs(trade.value_to) 16 needed_in_margin[trade.value_to.currency] += abs(trade.value_to)
15 for currency, needed in needed_in_margin.items(): 17 for currency, needed in needed_in_margin.items():
16 current_balance = BalanceStore.all[currency].margin_free 18 current_balance = BalanceStore.all[currency].margin_free
17 delta = (needed - current_balance).value 19 moving_to_margin[currency] = (needed - current_balance)
18 # FIXME: don't remove too much if there are open margin position 20 delta = moving_to_margin[currency].value
21 if debug:
22 ReportStore.log_debug_action("Moving {} from exchange to margin".format(moving_to_margin[currency]))
23 continue
19 if delta > 0: 24 if delta > 0:
20 if debug: 25 market.transfer_balance(currency, delta, "exchange", "margin")
21 print("market.transfer_balance({}, {}, 'exchange', 'margin')".format(currency, delta))
22 else:
23 market.transfer_balance(currency, delta, "exchange", "margin")
24 elif delta < 0: 26 elif delta < 0:
25 if debug: 27 market.transfer_balance(currency, -delta, "margin", "exchange")
26 print("market.transfer_balance({}, {}, 'margin', 'exchange')".format(currency, -delta)) 28 ReportStore.log_move_balances(needed_in_margin, moving_to_margin, debug)
27 else:
28 market.transfer_balance(currency, -delta, "margin", "exchange")
29 29
30 BalanceStore.fetch_balances(market) 30 BalanceStore.fetch_balances(market)
31 31
@@ -73,6 +73,7 @@ def fetch_fees(market):
73 return fees_cache[market.__class__] 73 return fees_cache[market.__class__]
74 74
75def prepare_trades(market, base_currency="BTC", liquidity="medium", compute_value="average", debug=False): 75def prepare_trades(market, base_currency="BTC", liquidity="medium", compute_value="average", debug=False):
76 ReportStore.log_stage("prepare_trades")
76 BalanceStore.fetch_balances(market) 77 BalanceStore.fetch_balances(market)
77 values_in_base = BalanceStore.in_currency(base_currency, market, compute_value=compute_value) 78 values_in_base = BalanceStore.in_currency(base_currency, market, compute_value=compute_value)
78 total_base_value = sum(values_in_base.values()) 79 total_base_value = sum(values_in_base.values())
@@ -82,6 +83,7 @@ def prepare_trades(market, base_currency="BTC", liquidity="medium", compute_valu
82 TradeStore.compute_trades(values_in_base, new_repartition, market=market, debug=debug) 83 TradeStore.compute_trades(values_in_base, new_repartition, market=market, debug=debug)
83 84
84def update_trades(market, base_currency="BTC", liquidity="medium", compute_value="average", only=None, debug=False): 85def update_trades(market, base_currency="BTC", liquidity="medium", compute_value="average", only=None, debug=False):
86 ReportStore.log_stage("update_trades")
85 BalanceStore.fetch_balances(market) 87 BalanceStore.fetch_balances(market)
86 values_in_base = BalanceStore.in_currency(base_currency, market, compute_value=compute_value) 88 values_in_base = BalanceStore.in_currency(base_currency, market, compute_value=compute_value)
87 total_base_value = sum(values_in_base.values()) 89 total_base_value = sum(values_in_base.values())
@@ -89,91 +91,76 @@ def update_trades(market, base_currency="BTC", liquidity="medium", compute_value
89 TradeStore.compute_trades(values_in_base, new_repartition, only=only, market=market, debug=debug) 91 TradeStore.compute_trades(values_in_base, new_repartition, only=only, market=market, debug=debug)
90 92
91def prepare_trades_to_sell_all(market, base_currency="BTC", compute_value="average", debug=False): 93def prepare_trades_to_sell_all(market, base_currency="BTC", compute_value="average", debug=False):
94 ReportStore.log_stage("prepare_trades_to_sell_all")
92 BalanceStore.fetch_balances(market) 95 BalanceStore.fetch_balances(market)
93 values_in_base = BalanceStore.in_currency(base_currency, market, compute_value=compute_value) 96 values_in_base = BalanceStore.in_currency(base_currency, market, compute_value=compute_value)
94 total_base_value = sum(values_in_base.values()) 97 total_base_value = sum(values_in_base.values())
95 new_repartition = BalanceStore.dispatch_assets(total_base_value, repartition={ base_currency: (1, "long") }) 98 new_repartition = BalanceStore.dispatch_assets(total_base_value, repartition={ base_currency: (1, "long") })
96 TradeStore.compute_trades(values_in_base, new_repartition, market=market, debug=debug) 99 TradeStore.compute_trades(values_in_base, new_repartition, market=market, debug=debug)
97 100
98def follow_orders(verbose=True, sleep=None): 101def follow_orders(sleep=None):
99 if sleep is None: 102 if sleep is None:
100 sleep = 7 if TradeStore.debug else 30 103 sleep = 7 if TradeStore.debug else 30
104 if TradeStore.debug:
105 ReportStore.log_debug_action("Set follow_orders tick to {}s".format(sleep))
101 tick = 0 106 tick = 0
107 ReportStore.log_stage("follow_orders_begin")
102 while len(TradeStore.all_orders(state="open")) > 0: 108 while len(TradeStore.all_orders(state="open")) > 0:
103 time.sleep(sleep) 109 time.sleep(sleep)
104 tick += 1 110 tick += 1
105 for order in TradeStore.all_orders(state="open"): 111 open_orders = TradeStore.all_orders(state="open")
112 ReportStore.log_stage("follow_orders_tick_{}".format(tick))
113 ReportStore.log_orders(open_orders, tick=tick)
114 for order in open_orders:
106 if order.get_status() != "open": 115 if order.get_status() != "open":
107 if verbose: 116 ReportStore.log_order(order, tick, finished=True)
108 print("finished {}".format(order))
109 else: 117 else:
110 order.trade.update_order(order, tick) 118 order.trade.update_order(order, tick)
111 if verbose: 119 ReportStore.log_stage("follow_orders_end")
112 print("All orders finished")
113 120
114def print_orders(market, base_currency="BTC"): 121def print_orders(market, base_currency="BTC"):
122 ReportStore.log_stage("print_orders")
115 prepare_trades(market, base_currency=base_currency, compute_value="average", debug=True) 123 prepare_trades(market, base_currency=base_currency, compute_value="average", debug=True)
116 TradeStore.prepare_orders(compute_value="average") 124 TradeStore.prepare_orders(compute_value="average")
117 for currency, balance in BalanceStore.all.items():
118 print(balance)
119 TradeStore.print_all_with_order()
120 125
121def print_balances(market, base_currency="BTC"): 126def print_balances(market, base_currency="BTC"):
122 BalanceStore.fetch_balances(market) 127 BalanceStore.fetch_balances(market)
123 for currency, balance in BalanceStore.all.items():
124 print(balance)
125 if base_currency is not None: 128 if base_currency is not None:
126 print("total:") 129 ReportStore.print_log("total:")
127 print(sum(BalanceStore.in_currency(base_currency, market).values())) 130 ReportStore.print_log(sum(BalanceStore.in_currency(base_currency, market).values()))
128 131
129def process_sell_needed__1_sell(market, liquidity="medium", base_currency="BTC", debug=False): 132def process_sell_needed__1_sell(market, liquidity="medium", base_currency="BTC", debug=False):
133 ReportStore.log_stage("process_sell_needed__1_sell_begin")
130 prepare_trades(market, liquidity=liquidity, base_currency=base_currency, debug=debug) 134 prepare_trades(market, liquidity=liquidity, base_currency=base_currency, debug=debug)
131 TradeStore.prepare_orders(compute_value="average", only="dispose") 135 TradeStore.prepare_orders(compute_value="average", only="dispose")
132 print("------------------")
133 for currency, balance in BalanceStore.all.items():
134 print(balance)
135 print("------------------")
136 TradeStore.print_all_with_order()
137 print("------------------")
138 TradeStore.run_orders() 136 TradeStore.run_orders()
139 follow_orders() 137 follow_orders()
138 ReportStore.log_stage("process_sell_needed__1_sell_end")
140 139
141def process_sell_needed__2_buy(market, liquidity="medium", base_currency="BTC", debug=False): 140def process_sell_needed__2_buy(market, liquidity="medium", base_currency="BTC", debug=False):
141 ReportStore.log_stage("process_sell_needed__2_buy_begin")
142 update_trades(market, base_currency=base_currency, liquidity=liquidity, debug=debug, only="acquire") 142 update_trades(market, base_currency=base_currency, liquidity=liquidity, debug=debug, only="acquire")
143 TradeStore.prepare_orders(compute_value="average", only="acquire") 143 TradeStore.prepare_orders(compute_value="average", only="acquire")
144 print("------------------")
145 for currency, balance in BalanceStore.all.items():
146 print(balance)
147 print("------------------")
148 TradeStore.print_all_with_order()
149 print("------------------")
150 move_balances(market, debug=debug) 144 move_balances(market, debug=debug)
151 TradeStore.run_orders() 145 TradeStore.run_orders()
152 follow_orders() 146 follow_orders()
147 ReportStore.log_stage("process_sell_needed__2_buy_end")
153 148
154def process_sell_all__1_all_sell(market, base_currency="BTC", debug=False, liquidity="medium"): 149def process_sell_all__1_all_sell(market, base_currency="BTC", debug=False, liquidity="medium"):
150 ReportStore.log_stage("process_sell_all__1_all_sell_begin")
155 prepare_trades_to_sell_all(market, base_currency=base_currency, debug=debug) 151 prepare_trades_to_sell_all(market, base_currency=base_currency, debug=debug)
156 TradeStore.prepare_orders(compute_value="average") 152 TradeStore.prepare_orders(compute_value="average")
157 print("------------------")
158 for currency, balance in BalanceStore.all.items():
159 print(balance)
160 print("------------------")
161 TradeStore.print_all_with_order()
162 print("------------------")
163 TradeStore.run_orders() 153 TradeStore.run_orders()
164 follow_orders() 154 follow_orders()
155 ReportStore.log_stage("process_sell_all__1_all_sell_end")
165 156
166def process_sell_all__2_all_buy(market, base_currency="BTC", debug=False, liquidity="medium"): 157def process_sell_all__2_all_buy(market, base_currency="BTC", debug=False, liquidity="medium"):
158 ReportStore.log_stage("process_sell_all__2_all_buy_begin")
167 prepare_trades(market, liquidity=liquidity, base_currency=base_currency, debug=debug) 159 prepare_trades(market, liquidity=liquidity, base_currency=base_currency, debug=debug)
168 TradeStore.prepare_orders(compute_value="average") 160 TradeStore.prepare_orders(compute_value="average")
169 print("------------------")
170 for currency, balance in BalanceStore.all.items():
171 print(balance)
172 print("------------------")
173 TradeStore.print_all_with_order()
174 print("------------------")
175 move_balances(market, debug=debug) 161 move_balances(market, debug=debug)
176 TradeStore.run_orders() 162 TradeStore.run_orders()
177 follow_orders() 163 follow_orders()
164 ReportStore.log_stage("process_sell_all__2_all_buy_end")
178 165
179 166
diff --git a/portfolio.py b/portfolio.py
index c3809f0..f9423b9 100644
--- a/portfolio.py
+++ b/portfolio.py
@@ -3,6 +3,7 @@ from datetime import datetime, timedelta
3from decimal import Decimal as D, ROUND_DOWN 3from decimal import Decimal as D, ROUND_DOWN
4# Put your poloniex api key in market.py 4# Put your poloniex api key in market.py
5from json import JSONDecodeError 5from json import JSONDecodeError
6from simplejson.errors import JSONDecodeError as SimpleJSONDecodeError
6from ccxt import ExchangeError, ExchangeNotAvailable 7from ccxt import ExchangeError, ExchangeNotAvailable
7import requests 8import requests
8import helper as h 9import helper as h
@@ -33,11 +34,14 @@ class Portfolio:
33 def get_cryptoportfolio(cls): 34 def get_cryptoportfolio(cls):
34 try: 35 try:
35 r = requests.get(cls.URL) 36 r = requests.get(cls.URL)
36 except Exception: 37 ReportStore.log_http_request(r.request.method,
38 r.request.url, r.request.body, r.request.headers, r)
39 except Exception as e:
40 ReportStore.log_error("get_cryptoportfolio", exception=e)
37 return 41 return
38 try: 42 try:
39 cls.data = r.json(parse_int=D, parse_float=D) 43 cls.data = r.json(parse_int=D, parse_float=D)
40 except JSONDecodeError: 44 except (JSONDecodeError, SimpleJSONDecodeError):
41 cls.data = None 45 cls.data = None
42 46
43 @classmethod 47 @classmethod
@@ -126,6 +130,12 @@ class Amount:
126 else: 130 else:
127 raise Exception("This asset is not available in the chosen market") 131 raise Exception("This asset is not available in the chosen market")
128 132
133 def as_json(self):
134 return {
135 "currency": self.currency,
136 "value": round(self).value.normalize(),
137 }
138
129 def __round__(self, n=8): 139 def __round__(self, n=8):
130 return Amount(self.currency, self.value.quantize(D(1)/D(10**n), rounding=ROUND_DOWN)) 140 return Amount(self.currency, self.value.quantize(D(1)/D(10**n), rounding=ROUND_DOWN))
131 141
@@ -216,12 +226,13 @@ class Amount:
216 return "Amount({:.8f} {} -> {})".format(self.value, self.currency, repr(self.linked_to)) 226 return "Amount({:.8f} {} -> {})".format(self.value, self.currency, repr(self.linked_to))
217 227
218class Balance: 228class Balance:
229 base_keys = ["total", "exchange_total", "exchange_used",
230 "exchange_free", "margin_total", "margin_borrowed",
231 "margin_free"]
219 232
220 def __init__(self, currency, hash_): 233 def __init__(self, currency, hash_):
221 self.currency = currency 234 self.currency = currency
222 for key in ["total", 235 for key in self.base_keys:
223 "exchange_total", "exchange_used", "exchange_free",
224 "margin_total", "margin_borrowed", "margin_free"]:
225 setattr(self, key, Amount(currency, hash_.get(key, 0))) 236 setattr(self, key, Amount(currency, hash_.get(key, 0)))
226 237
227 self.margin_position_type = hash_.get("margin_position_type") 238 self.margin_position_type = hash_.get("margin_position_type")
@@ -236,6 +247,9 @@ class Balance:
236 ]: 247 ]:
237 setattr(self, key, Amount(base_currency, hash_.get(key, 0))) 248 setattr(self, key, Amount(base_currency, hash_.get(key, 0)))
238 249
250 def as_json(self):
251 return dict(map(lambda x: (x, getattr(self, x).as_json()["value"]), self.base_keys))
252
239 def __repr__(self): 253 def __repr__(self):
240 if self.exchange_total > 0: 254 if self.exchange_total > 0:
241 if self.exchange_free > 0 and self.exchange_used > 0: 255 if self.exchange_free > 0 and self.exchange_used > 0:
@@ -318,23 +332,34 @@ class Trade:
318 def update_order(self, order, tick): 332 def update_order(self, order, tick):
319 new_order = None 333 new_order = None
320 if tick in [0, 1, 3, 4, 6]: 334 if tick in [0, 1, 3, 4, 6]:
321 print("{}, tick {}, waiting".format(order, tick)) 335 update = "waiting"
336 compute_value = None
322 elif tick == 2: 337 elif tick == 2:
338 update = "adjusting"
339 compute_value = 'lambda x, y: (x[y] + x["average"]) / 2'
323 new_order = self.prepare_order(compute_value=lambda x, y: (x[y] + x["average"]) / 2) 340 new_order = self.prepare_order(compute_value=lambda x, y: (x[y] + x["average"]) / 2)
324 print("{}, tick {}, cancelling and adjusting to {}".format(order, tick, new_order))
325 elif tick ==5: 341 elif tick ==5:
342 update = "adjusting"
343 compute_value = 'lambda x, y: (x[y]*2 + x["average"]) / 3'
326 new_order = self.prepare_order(compute_value=lambda x, y: (x[y]*2 + x["average"]) / 3) 344 new_order = self.prepare_order(compute_value=lambda x, y: (x[y]*2 + x["average"]) / 3)
327 print("{}, tick {}, cancelling and adjusting to {}".format(order, tick, new_order))
328 elif tick >= 7: 345 elif tick >= 7:
329 if tick == 7:
330 print("{}, tick {}, fallbacking to market value".format(order, tick))
331 if (tick - 7) % 3 == 0: 346 if (tick - 7) % 3 == 0:
332 new_order = self.prepare_order(compute_value="default") 347 new_order = self.prepare_order(compute_value="default")
333 print("{}, tick {}, market value, cancelling and adjusting to {}".format(order, tick, new_order)) 348 update = "market_adjust"
349 compute_value = "default"
350 else:
351 update = "waiting"
352 compute_value = None
353 if tick == 7:
354 update = "market_fallback"
355
356 ReportStore.log_order(order, tick, update=update,
357 compute_value=compute_value, new_order=new_order)
334 358
335 if new_order is not None: 359 if new_order is not None:
336 order.cancel() 360 order.cancel()
337 new_order.run() 361 new_order.run()
362 ReportStore.log_order(order, tick, new_order=new_order)
338 363
339 def prepare_order(self, compute_value="default"): 364 def prepare_order(self, compute_value="default"):
340 if self.action is None: 365 if self.action is None:
@@ -405,7 +430,7 @@ class Trade:
405 close_if_possible = (self.value_to == 0) 430 close_if_possible = (self.value_to == 0)
406 431
407 if delta <= 0: 432 if delta <= 0:
408 print("Less to do than already filled: {}".format(delta)) 433 ReportStore.log_error("prepare_order", message="Less to do than already filled: {}".format(delta))
409 return None 434 return None
410 435
411 order = Order(self.order_action(inverted), 436 order = Order(self.order_action(inverted),
@@ -414,6 +439,15 @@ class Trade:
414 self.orders.append(order) 439 self.orders.append(order)
415 return order 440 return order
416 441
442 def as_json(self):
443 return {
444 "action": self.action,
445 "from": self.value_from.as_json()["value"],
446 "to": self.value_to.as_json()["value"],
447 "currency": self.currency,
448 "base_currency": self.base_currency,
449 }
450
417 def __repr__(self): 451 def __repr__(self):
418 return "Trade({} -> {} in {}, {})".format( 452 return "Trade({} -> {} in {}, {})".format(
419 self.value_from, 453 self.value_from,
@@ -421,12 +455,12 @@ class Trade:
421 self.currency, 455 self.currency,
422 self.action) 456 self.action)
423 457
424 def print_with_order(self): 458 def print_with_order(self, ind=""):
425 print(self) 459 ReportStore.print_log("{}{}".format(ind, self))
426 for order in self.orders: 460 for order in self.orders:
427 print("\t", order, sep="") 461 ReportStore.print_log("{}\t{}".format(ind, order))
428 for mouvement in order.mouvements: 462 for mouvement in order.mouvements:
429 print("\t\t", mouvement, sep="") 463 ReportStore.print_log("{}\t\t{}".format(ind, mouvement))
430 464
431class Order: 465class Order:
432 def __init__(self, action, amount, rate, base_currency, trade_type, market, 466 def __init__(self, action, amount, rate, base_currency, trade_type, market,
@@ -445,6 +479,20 @@ class Order:
445 self.id = None 479 self.id = None
446 self.fetch_cache_timestamp = None 480 self.fetch_cache_timestamp = None
447 481
482 def as_json(self):
483 return {
484 "action": self.action,
485 "trade_type": self.trade_type,
486 "amount": self.amount.as_json()["value"],
487 "currency": self.amount.as_json()["currency"],
488 "base_currency": self.base_currency,
489 "rate": self.rate,
490 "status": self.status,
491 "close_if_possible": self.close_if_possible,
492 "id": self.id,
493 "mouvements": list(map(lambda x: x.as_json(), self.mouvements))
494 }
495
448 def __repr__(self): 496 def __repr__(self):
449 return "Order({} {} {} at {} {} [{}]{})".format( 497 return "Order({} {} {} at {} {} [{}]{})".format(
450 self.action, 498 self.action,
@@ -480,7 +528,7 @@ class Order:
480 amount = round(self.amount, self.market.order_precision(symbol)).value 528 amount = round(self.amount, self.market.order_precision(symbol)).value
481 529
482 if TradeStore.debug: 530 if TradeStore.debug:
483 print("market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format( 531 ReportStore.log_debug_action("market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format(
484 symbol, self.action, amount, self.rate, self.account)) 532 symbol, self.action, amount, self.rate, self.account))
485 self.results.append({"debug": True, "id": -1}) 533 self.results.append({"debug": True, "id": -1})
486 else: 534 else:
@@ -493,16 +541,15 @@ class Order:
493 return 541 return
494 except Exception as e: 542 except Exception as e:
495 self.status = "error" 543 self.status = "error"
496 print("error when running market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format( 544 action = "market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format(symbol, self.action, amount, self.rate, self.account)
497 symbol, self.action, amount, self.rate, self.account)) 545 ReportStore.log_error(action, exception=e)
498 self.error_message = str("{}: {}".format(e.__class__.__name__, e))
499 print(self.error_message)
500 return 546 return
501 self.id = self.results[0]["id"] 547 self.id = self.results[0]["id"]
502 self.status = "open" 548 self.status = "open"
503 549
504 def get_status(self): 550 def get_status(self):
505 if TradeStore.debug: 551 if TradeStore.debug:
552 ReportStore.log_debug_action("Getting {} status".format(self))
506 return self.status 553 return self.status
507 # other states are "closed" and "canceled" 554 # other states are "closed" and "canceled"
508 if not self.finished: 555 if not self.finished:
@@ -513,13 +560,17 @@ class Order:
513 560
514 def mark_finished_order(self): 561 def mark_finished_order(self):
515 if TradeStore.debug: 562 if TradeStore.debug:
563 ReportStore.log_debug_action("Mark {} as finished".format(self))
516 return 564 return
517 if self.status == "closed": 565 if self.status == "closed":
518 if self.trade_type == "short" and self.action == "buy" and self.close_if_possible: 566 if self.trade_type == "short" and self.action == "buy" and self.close_if_possible:
519 self.market.close_margin_position(self.amount.currency, self.base_currency) 567 self.market.close_margin_position(self.amount.currency, self.base_currency)
520 568
521 def fetch(self, force=False): 569 def fetch(self, force=False):
522 if TradeStore.debug or (not force and self.fetch_cache_timestamp is not None 570 if TradeStore.debug:
571 ReportStore.log_debug_action("Fetching {}".format(self))
572 return
573 if (not force and self.fetch_cache_timestamp is not None
523 and time.time() - self.fetch_cache_timestamp < 10): 574 and time.time() - self.fetch_cache_timestamp < 10):
524 return 575 return
525 self.fetch_cache_timestamp = time.time() 576 self.fetch_cache_timestamp = time.time()
@@ -566,6 +617,7 @@ class Order:
566 617
567 def cancel(self): 618 def cancel(self):
568 if TradeStore.debug: 619 if TradeStore.debug:
620 ReportStore.log_debug_action("Mark {} as cancelled".format(self))
569 self.status = "canceled" 621 self.status = "canceled"
570 return 622 return
571 self.market.cancel_order(self.id) 623 self.market.cancel_order(self.id)
@@ -587,6 +639,17 @@ class Mouvement:
587 # rate * total = total_in_base 639 # rate * total = total_in_base
588 self.total_in_base = Amount(base_currency, hash_.get("total", 0)) 640 self.total_in_base = Amount(base_currency, hash_.get("total", 0))
589 641
642 def as_json(self):
643 return {
644 "fee_rate": self.fee_rate,
645 "date": self.date,
646 "action": self.action,
647 "total": self.total.value,
648 "currency": self.currency,
649 "total_in_base": self.total_in_base.value,
650 "base_currency": self.base_currency
651 }
652
590 def __repr__(self): 653 def __repr__(self):
591 if self.fee_rate > 0: 654 if self.fee_rate > 0:
592 fee_rate = " fee: {}%".format(self.fee_rate * 100) 655 fee_rate = " fee: {}%".format(self.fee_rate * 100)
diff --git a/store.py b/store.py
index abbe5ee..dfadef2 100644
--- a/store.py
+++ b/store.py
@@ -1,6 +1,181 @@
1import portfolio 1import portfolio
2import simplejson as json
3from decimal import Decimal as D, ROUND_DOWN
4from datetime import date, datetime
2 5
3__all__ = ["BalanceStore", "TradeStore"] 6__all__ = ["BalanceStore", "ReportStore", "TradeStore"]
7
8class ReportStore:
9 logs = []
10 verbose_print = True
11
12 @classmethod
13 def print_log(cls, message):
14 message = str(message)
15 if cls.verbose_print:
16 print(message)
17
18 @classmethod
19 def add_log(cls, hash_):
20 hash_["date"] = datetime.now()
21 cls.logs.append(hash_)
22
23 @classmethod
24 def to_json(cls):
25 def default_json_serial(obj):
26 if isinstance(obj, (datetime, date)):
27 return obj.isoformat()
28 raise TypeError ("Type %s not serializable" % type(obj))
29 return json.dumps(cls.logs, default=default_json_serial)
30
31 @classmethod
32 def set_verbose(cls, verbose_print):
33 cls.verbose_print = verbose_print
34
35 @classmethod
36 def log_stage(cls, stage):
37 cls.print_log("-" * (len(stage) + 8))
38 cls.print_log("[Stage] {}".format(stage))
39
40 cls.add_log({
41 "type": "stage",
42 "stage": stage,
43 })
44
45 @classmethod
46 def log_balances(cls, market):
47 cls.print_log("[Balance]")
48 for currency, balance in BalanceStore.all.items():
49 cls.print_log("\t{}".format(balance))
50
51 cls.add_log({
52 "type": "balance",
53 "balances": BalanceStore.as_json()
54 })
55
56 @classmethod
57 def log_tickers(cls, market, amounts, other_currency,
58 compute_value, type):
59 values = {}
60 rates = {}
61 for currency, amount in amounts.items():
62 values[currency] = amount.as_json()["value"]
63 rates[currency] = amount.rate
64 cls.add_log({
65 "type": "tickers",
66 "compute_value": compute_value,
67 "balance_type": type,
68 "currency": other_currency,
69 "balances": values,
70 "rates": rates,
71 "total": sum(amounts.values()).as_json()["value"]
72 })
73
74 @classmethod
75 def log_dispatch(cls, amount, amounts, liquidity, repartition):
76 cls.add_log({
77 "type": "dispatch",
78 "liquidity": liquidity,
79 "repartition_ratio": repartition,
80 "total_amount": amount.as_json(),
81 "repartition": { k: v.as_json()["value"] for k, v in amounts.items() }
82 })
83
84 @classmethod
85 def log_trades(cls, matching_and_trades, only, debug):
86 trades = []
87 for matching, trade in matching_and_trades:
88 trade_json = trade.as_json()
89 trade_json["skipped"] = not matching
90 trades.append(trade_json)
91
92 cls.add_log({
93 "type": "trades",
94 "only": only,
95 "debug": debug,
96 "trades": trades
97 })
98
99 @classmethod
100 def log_orders(cls, orders, tick=None, only=None, compute_value=None):
101 cls.print_log("[Orders]")
102 TradeStore.print_all_with_order(ind="\t")
103 cls.add_log({
104 "type": "orders",
105 "only": only,
106 "compute_value": compute_value,
107 "tick": tick,
108 "orders": [order.as_json() for order in orders if order is not None]
109 })
110
111 @classmethod
112 def log_order(cls, order, tick, finished=False, update=None,
113 new_order=None, compute_value=None):
114 if finished:
115 cls.print_log("[Order] Finished {}".format(order))
116 elif update == "waiting":
117 cls.print_log("[Order] {}, tick {}, waiting".format(order, tick))
118 elif update == "adjusting":
119 cls.print_log("[Order] {}, tick {}, cancelling and adjusting to {}".format(order, tick, new_order))
120 elif update == "market_fallback":
121 cls.print_log("[Order] {}, tick {}, fallbacking to market value".format(order, tick))
122 elif update == "market_adjust":
123 cls.print_log("[Order] {}, tick {}, market value, cancelling and adjusting to {}".format(order, tick, new_order))
124
125 cls.add_log({
126 "type": "order",
127 "tick": tick,
128 "update": update,
129 "order": order.as_json(),
130 "compute_value": compute_value,
131 "new_order": new_order.as_json() if new_order is not None else None
132 })
133
134 @classmethod
135 def log_move_balances(cls, needed, moving, debug):
136 cls.add_log({
137 "type": "move_balances",
138 "debug": debug,
139 "needed": { k: v.as_json()["value"] if isinstance(v, portfolio.Amount) else v for k, v in needed.items() },
140 "moving": { k: v.as_json()["value"] if isinstance(v, portfolio.Amount) else v for k, v in moving.items() },
141 })
142
143 @classmethod
144 def log_http_request(cls, method, url, body, headers, response):
145 cls.add_log({
146 "type": "http_request",
147 "method": method,
148 "url": url,
149 "body": body,
150 "headers": headers,
151 "status": response.status_code,
152 "response": response.text
153 })
154
155 @classmethod
156 def log_error(cls, action, message=None, exception=None):
157 cls.print_log("[Error] {}".format(action))
158 if exception is not None:
159 cls.print_log(str("\t{}: {}".format(exception.__class__.__name__, exception)))
160 if message is not None:
161 cls.print_log("\t{}".format(message))
162
163 cls.add_log({
164 "type": "error",
165 "action": action,
166 "exception_class": exception.__class__.__name__ if exception is not None else None,
167 "exception_message": str(exception) if exception is not None else None,
168 "message": message,
169 })
170
171 @classmethod
172 def log_debug_action(cls, action):
173 cls.print_log("[Debug] {}".format(action))
174
175 cls.add_log({
176 "type": "debug_action",
177 "action": action,
178 })
4 179
5class BalanceStore: 180class BalanceStore:
6 all = {} 181 all = {}
@@ -16,6 +191,8 @@ class BalanceStore:
16 other_currency_amount = getattr(balance, type)\ 191 other_currency_amount = getattr(balance, type)\
17 .in_currency(other_currency, market, compute_value=compute_value) 192 .in_currency(other_currency, market, compute_value=compute_value)
18 amounts[currency] = other_currency_amount 193 amounts[currency] = other_currency_amount
194 ReportStore.log_tickers(market, amounts, other_currency,
195 compute_value, type)
19 return amounts 196 return amounts
20 197
21 @classmethod 198 @classmethod
@@ -25,6 +202,7 @@ class BalanceStore:
25 if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \ 202 if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \
26 currency in cls.all: 203 currency in cls.all:
27 cls.all[currency] = portfolio.Balance(currency, balance) 204 cls.all[currency] = portfolio.Balance(currency, balance)
205 ReportStore.log_balances(market)
28 206
29 @classmethod 207 @classmethod
30 def dispatch_assets(cls, amount, liquidity="medium", repartition=None): 208 def dispatch_assets(cls, amount, liquidity="medium", repartition=None):
@@ -38,14 +216,20 @@ class BalanceStore:
38 amounts[currency] = - amounts[currency] 216 amounts[currency] = - amounts[currency]
39 if currency not in BalanceStore.all: 217 if currency not in BalanceStore.all:
40 cls.all[currency] = portfolio.Balance(currency, {}) 218 cls.all[currency] = portfolio.Balance(currency, {})
219 ReportStore.log_dispatch(amount, amounts, liquidity, repartition)
41 return amounts 220 return amounts
42 221
222 @classmethod
223 def as_json(cls):
224 return { k: v.as_json() for k, v in cls.all.items() }
225
43class TradeStore: 226class TradeStore:
44 all = [] 227 all = []
45 debug = False 228 debug = False
46 229
47 @classmethod 230 @classmethod
48 def compute_trades(cls, values_in_base, new_repartition, only=None, market=None, debug=False): 231 def compute_trades(cls, values_in_base, new_repartition, only=None, market=None, debug=False):
232 computed_trades = []
49 cls.debug = cls.debug or debug 233 cls.debug = cls.debug or debug
50 base_currency = sum(values_in_base.values()).currency 234 base_currency = sum(values_in_base.values()).currency
51 for currency in BalanceStore.currencies(): 235 for currency in BalanceStore.currencies():
@@ -55,41 +239,49 @@ class TradeStore:
55 value_to = new_repartition.get(currency, portfolio.Amount(base_currency, 0)) 239 value_to = new_repartition.get(currency, portfolio.Amount(base_currency, 0))
56 240
57 if value_from.value * value_to.value < 0: 241 if value_from.value * value_to.value < 0:
58 cls.add_trade_if_matching( 242 computed_trades.append(cls.trade_if_matching(
59 value_from, portfolio.Amount(base_currency, 0), 243 value_from, portfolio.Amount(base_currency, 0),
60 currency, only=only, market=market) 244 currency, only=only, market=market))
61 cls.add_trade_if_matching( 245 computed_trades.append(cls.trade_if_matching(
62 portfolio.Amount(base_currency, 0), value_to, 246 portfolio.Amount(base_currency, 0), value_to,
63 currency, only=only, market=market) 247 currency, only=only, market=market))
64 else: 248 else:
65 cls.add_trade_if_matching(value_from, value_to, 249 computed_trades.append(cls.trade_if_matching(
66 currency, only=only, market=market) 250 value_from, value_to,
251 currency, only=only, market=market))
252 for matching, trade in computed_trades:
253 if matching:
254 cls.all.append(trade)
255 ReportStore.log_trades(computed_trades, only, cls.debug)
67 256
68 @classmethod 257 @classmethod
69 def add_trade_if_matching(cls, value_from, value_to, currency, 258 def trade_if_matching(cls, value_from, value_to, currency,
70 only=None, market=None): 259 only=None, market=None):
71 trade = portfolio.Trade(value_from, value_to, currency, 260 trade = portfolio.Trade(value_from, value_to, currency,
72 market=market) 261 market=market)
73 if only is None or trade.action == only: 262 matching = only is None or trade.action == only
74 cls.all.append(trade) 263 return [matching, trade]
75 return True
76 return False
77 264
78 @classmethod 265 @classmethod
79 def prepare_orders(cls, only=None, compute_value="default"): 266 def prepare_orders(cls, only=None, compute_value="default"):
267 orders = []
80 for trade in cls.all: 268 for trade in cls.all:
81 if only is None or trade.action == only: 269 if only is None or trade.action == only:
82 trade.prepare_order(compute_value=compute_value) 270 orders.append(trade.prepare_order(compute_value=compute_value))
271 ReportStore.log_orders(orders, only, compute_value)
83 272
84 @classmethod 273 @classmethod
85 def print_all_with_order(cls): 274 def print_all_with_order(cls, ind=""):
86 for trade in cls.all: 275 for trade in cls.all:
87 trade.print_with_order() 276 trade.print_with_order(ind=ind)
88 277
89 @classmethod 278 @classmethod
90 def run_orders(cls): 279 def run_orders(cls):
91 for order in cls.all_orders(state="pending"): 280 orders = cls.all_orders(state="pending")
281 for order in orders:
92 order.run() 282 order.run()
283 ReportStore.log_stage("run_orders")
284 ReportStore.log_orders(orders)
93 285
94 @classmethod 286 @classmethod
95 def all_orders(cls, state=None): 287 def all_orders(cls, state=None):
diff --git a/test.py b/test.py
index 9fd058a..0f1f130 100644
--- a/test.py
+++ b/test.py
@@ -27,6 +27,8 @@ class WebMockTestCase(unittest.TestCase):
27 self.wm.start() 27 self.wm.start()
28 28
29 self.patchers = [ 29 self.patchers = [
30 mock.patch.multiple(portfolio.ReportStore,
31 logs=[], verbose_print=True),
30 mock.patch.multiple(portfolio.BalanceStore, 32 mock.patch.multiple(portfolio.BalanceStore,
31 all={},), 33 all={},),
32 mock.patch.multiple(portfolio.TradeStore, 34 mock.patch.multiple(portfolio.TradeStore,
@@ -63,7 +65,8 @@ class PortfolioTest(WebMockTestCase):
63 65
64 self.wm.get(portfolio.Portfolio.URL, text=self.json_response) 66 self.wm.get(portfolio.Portfolio.URL, text=self.json_response)
65 67
66 def test_get_cryptoportfolio(self): 68 @mock.patch("portfolio.ReportStore")
69 def test_get_cryptoportfolio(self, report_store):
67 self.wm.get(portfolio.Portfolio.URL, [ 70 self.wm.get(portfolio.Portfolio.URL, [
68 {"text":'{ "foo": "bar" }', "status_code": 200}, 71 {"text":'{ "foo": "bar" }', "status_code": 200},
69 {"text": "System Error", "status_code": 500}, 72 {"text": "System Error", "status_code": 500},
@@ -74,17 +77,28 @@ class PortfolioTest(WebMockTestCase):
74 self.assertEqual("bar", portfolio.Portfolio.data["foo"]) 77 self.assertEqual("bar", portfolio.Portfolio.data["foo"])
75 self.assertTrue(self.wm.called) 78 self.assertTrue(self.wm.called)
76 self.assertEqual(1, self.wm.call_count) 79 self.assertEqual(1, self.wm.call_count)
80 report_store.log_error.assert_not_called()
81 report_store.log_http_request.assert_called_once()
82 report_store.log_http_request.reset_mock()
77 83
78 portfolio.Portfolio.get_cryptoportfolio() 84 portfolio.Portfolio.get_cryptoportfolio()
79 self.assertIsNone(portfolio.Portfolio.data) 85 self.assertIsNone(portfolio.Portfolio.data)
80 self.assertEqual(2, self.wm.call_count) 86 self.assertEqual(2, self.wm.call_count)
87 report_store.log_error.assert_not_called()
88 report_store.log_http_request.assert_called_once()
89 report_store.log_http_request.reset_mock()
90
81 91
82 portfolio.Portfolio.data = "Foo" 92 portfolio.Portfolio.data = "Foo"
83 portfolio.Portfolio.get_cryptoportfolio() 93 portfolio.Portfolio.get_cryptoportfolio()
84 self.assertEqual("Foo", portfolio.Portfolio.data) 94 self.assertEqual("Foo", portfolio.Portfolio.data)
85 self.assertEqual(3, self.wm.call_count) 95 self.assertEqual(3, self.wm.call_count)
96 report_store.log_error.assert_called_once_with("get_cryptoportfolio",
97 exception=mock.ANY)
98 report_store.log_http_request.assert_not_called()
86 99
87 def test_parse_cryptoportfolio(self): 100 @mock.patch("portfolio.ReportStore")
101 def test_parse_cryptoportfolio(self, report_store):
88 portfolio.Portfolio.parse_cryptoportfolio() 102 portfolio.Portfolio.parse_cryptoportfolio()
89 103
90 self.assertListEqual( 104 self.assertListEqual(
@@ -121,15 +135,22 @@ class PortfolioTest(WebMockTestCase):
121 self.assertDictEqual(expected, liquidities["medium"][date]) 135 self.assertDictEqual(expected, liquidities["medium"][date])
122 self.assertEqual(portfolio.datetime(2018, 1, 15), portfolio.Portfolio.last_date) 136 self.assertEqual(portfolio.datetime(2018, 1, 15), portfolio.Portfolio.last_date)
123 137
138 report_store.log_http_request.assert_called_once_with("GET",
139 portfolio.Portfolio.URL, None, mock.ANY, mock.ANY)
140 report_store.log_http_request.reset_mock()
141
124 # It doesn't refetch the data when available 142 # It doesn't refetch the data when available
125 portfolio.Portfolio.parse_cryptoportfolio() 143 portfolio.Portfolio.parse_cryptoportfolio()
144 report_store.log_http_request.assert_not_called()
126 145
127 self.assertEqual(1, self.wm.call_count) 146 self.assertEqual(1, self.wm.call_count)
128 147
129 portfolio.Portfolio.parse_cryptoportfolio(refetch=True) 148 portfolio.Portfolio.parse_cryptoportfolio(refetch=True)
130 self.assertEqual(2, self.wm.call_count) 149 self.assertEqual(2, self.wm.call_count)
150 report_store.log_http_request.assert_called_once()
131 151
132 def test_repartition(self): 152 @mock.patch("portfolio.ReportStore")
153 def test_repartition(self, report_store):
133 expected_medium = { 154 expected_medium = {
134 'BTC': (D("1.1102e-16"), "long"), 155 'BTC': (D("1.1102e-16"), "long"),
135 'USDT': (D("0.1"), "long"), 156 'USDT': (D("0.1"), "long"),
@@ -163,6 +184,8 @@ class PortfolioTest(WebMockTestCase):
163 184
164 portfolio.Portfolio.repartition(refetch=True) 185 portfolio.Portfolio.repartition(refetch=True)
165 self.assertEqual(2, self.wm.call_count) 186 self.assertEqual(2, self.wm.call_count)
187 report_store.log_http_request.assert_called()
188 self.assertEqual(2, report_store.log_http_request.call_count)
166 189
167 @mock.patch.object(portfolio.time, "sleep") 190 @mock.patch.object(portfolio.time, "sleep")
168 @mock.patch.object(portfolio.Portfolio, "repartition") 191 @mock.patch.object(portfolio.Portfolio, "repartition")
@@ -434,6 +457,17 @@ class AmountTest(WebMockTestCase):
434 amount2.linked_to = amount3 457 amount2.linked_to = amount3
435 self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1)) 458 self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1))
436 459
460 def test_as_json(self):
461 amount = portfolio.Amount("BTX", 32)
462 self.assertEqual({"currency": "BTX", "value": D("32")}, amount.as_json())
463
464 amount = portfolio.Amount("BTX", "1E-10")
465 self.assertEqual({"currency": "BTX", "value": D("0")}, amount.as_json())
466
467 amount = portfolio.Amount("BTX", "1E-5")
468 self.assertEqual({"currency": "BTX", "value": D("0.00001")}, amount.as_json())
469 self.assertEqual("0.00001", str(amount.as_json()["value"]))
470
437@unittest.skipUnless("unit" in limits, "Unit skipped") 471@unittest.skipUnless("unit" in limits, "Unit skipped")
438class BalanceTest(WebMockTestCase): 472class BalanceTest(WebMockTestCase):
439 def test_values(self): 473 def test_values(self):
@@ -497,6 +531,18 @@ class BalanceTest(WebMockTestCase):
497 "margin_borrowed": 1, "exchange_free": 2, "exchange_total": 2}) 531 "margin_borrowed": 1, "exchange_free": 2, "exchange_total": 2})
498 self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX] Margin: [borrowed 1.00000000 BTX] Total: [0.00000000 BTX])", repr(balance)) 532 self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX] Margin: [borrowed 1.00000000 BTX] Total: [0.00000000 BTX])", repr(balance))
499 533
534 def test_as_json(self):
535 balance = portfolio.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 })
536 as_json = balance.as_json()
537 self.assertEqual(set(portfolio.Balance.base_keys), set(as_json.keys()))
538 self.assertEqual(D(0), as_json["total"])
539 self.assertEqual(D(2), as_json["exchange_total"])
540 self.assertEqual(D(2), as_json["exchange_free"])
541 self.assertEqual(D(0), as_json["exchange_used"])
542 self.assertEqual(D(0), as_json["margin_total"])
543 self.assertEqual(D(0), as_json["margin_free"])
544 self.assertEqual(D(0), as_json["margin_borrowed"])
545
500@unittest.skipUnless("unit" in limits, "Unit skipped") 546@unittest.skipUnless("unit" in limits, "Unit skipped")
501class HelperTest(WebMockTestCase): 547class HelperTest(WebMockTestCase):
502 def test_get_ticker(self): 548 def test_get_ticker(self):
@@ -575,7 +621,9 @@ class HelperTest(WebMockTestCase):
575 @mock.patch.object(portfolio.Portfolio, "repartition") 621 @mock.patch.object(portfolio.Portfolio, "repartition")
576 @mock.patch.object(helper, "get_ticker") 622 @mock.patch.object(helper, "get_ticker")
577 @mock.patch.object(portfolio.TradeStore, "compute_trades") 623 @mock.patch.object(portfolio.TradeStore, "compute_trades")
578 def test_prepare_trades(self, compute_trades, get_ticker, repartition): 624 @mock.patch("store.ReportStore")
625 @mock.patch("helper.ReportStore")
626 def test_prepare_trades(self, report_store_h, report_store, compute_trades, get_ticker, repartition):
579 repartition.return_value = { 627 repartition.return_value = {
580 "XEM": (D("0.75"), "long"), 628 "XEM": (D("0.75"), "long"),
581 "BTC": (D("0.25"), "long"), 629 "BTC": (D("0.25"), "long"),
@@ -614,11 +662,15 @@ class HelperTest(WebMockTestCase):
614 self.assertEqual(D("0.01"), call[0][0]["XVG"].value) 662 self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
615 self.assertEqual(D("0.2525"), call[0][1]["BTC"].value) 663 self.assertEqual(D("0.2525"), call[0][1]["BTC"].value)
616 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value) 664 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value)
665 report_store_h.log_stage.assert_called_once_with("prepare_trades")
666 report_store.log_balances.assert_called_once_with(market)
617 667
618 @mock.patch.object(portfolio.Portfolio, "repartition") 668 @mock.patch.object(portfolio.Portfolio, "repartition")
619 @mock.patch.object(helper, "get_ticker") 669 @mock.patch.object(helper, "get_ticker")
620 @mock.patch.object(portfolio.TradeStore, "compute_trades") 670 @mock.patch.object(portfolio.TradeStore, "compute_trades")
621 def test_update_trades(self, compute_trades, get_ticker, repartition): 671 @mock.patch("store.ReportStore")
672 @mock.patch("helper.ReportStore")
673 def test_update_trades(self, report_store_h, report_store, compute_trades, get_ticker, repartition):
622 repartition.return_value = { 674 repartition.return_value = {
623 "XEM": (D("0.75"), "long"), 675 "XEM": (D("0.75"), "long"),
624 "BTC": (D("0.25"), "long"), 676 "BTC": (D("0.25"), "long"),
@@ -657,11 +709,15 @@ class HelperTest(WebMockTestCase):
657 self.assertEqual(D("0.01"), call[0][0]["XVG"].value) 709 self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
658 self.assertEqual(D("0.2525"), call[0][1]["BTC"].value) 710 self.assertEqual(D("0.2525"), call[0][1]["BTC"].value)
659 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value) 711 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value)
712 report_store_h.log_stage.assert_called_once_with("update_trades")
713 report_store.log_balances.assert_called_once_with(market)
660 714
661 @mock.patch.object(portfolio.Portfolio, "repartition") 715 @mock.patch.object(portfolio.Portfolio, "repartition")
662 @mock.patch.object(helper, "get_ticker") 716 @mock.patch.object(helper, "get_ticker")
663 @mock.patch.object(portfolio.TradeStore, "compute_trades") 717 @mock.patch.object(portfolio.TradeStore, "compute_trades")
664 def test_prepare_trades_to_sell_all(self, compute_trades, get_ticker, repartition): 718 @mock.patch("store.ReportStore")
719 @mock.patch("helper.ReportStore")
720 def test_prepare_trades_to_sell_all(self, report_store_h, report_store, compute_trades, get_ticker, repartition):
665 def _get_ticker(c1, c2, market): 721 def _get_ticker(c1, c2, market):
666 if c1 == "USDT" and c2 == "BTC": 722 if c1 == "USDT" and c2 == "BTC":
667 return { "average": D("0.0001") } 723 return { "average": D("0.0001") }
@@ -694,16 +750,17 @@ class HelperTest(WebMockTestCase):
694 self.assertEqual(1, call[0][0]["USDT"].value) 750 self.assertEqual(1, call[0][0]["USDT"].value)
695 self.assertEqual(D("0.01"), call[0][0]["XVG"].value) 751 self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
696 self.assertEqual(D("1.01"), call[0][1]["BTC"].value) 752 self.assertEqual(D("1.01"), call[0][1]["BTC"].value)
753 report_store_h.log_stage.assert_called_once_with("prepare_trades_to_sell_all")
754 report_store.log_balances.assert_called_once_with(market)
697 755
698 @mock.patch.object(portfolio.time, "sleep") 756 @mock.patch.object(portfolio.time, "sleep")
699 @mock.patch.object(portfolio.TradeStore, "all_orders") 757 @mock.patch.object(portfolio.TradeStore, "all_orders")
700 def test_follow_orders(self, all_orders, time_mock): 758 def test_follow_orders(self, all_orders, time_mock):
701 for verbose, debug, sleep in [ 759 for debug, sleep in [
702 (True, False, None), (False, False, None), 760 (False, None), (True, None),
703 (True, True, None), (True, False, 12), 761 (False, 12), (True, 12)]:
704 (True, True, 12)]: 762 with self.subTest(sleep=sleep, debug=debug), \
705 with self.subTest(sleep=sleep, debug=debug, verbose=verbose), \ 763 mock.patch("helper.ReportStore") as report_store:
706 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
707 portfolio.TradeStore.debug = debug 764 portfolio.TradeStore.debug = debug
708 order_mock1 = mock.Mock() 765 order_mock1 = mock.Mock()
709 order_mock2 = mock.Mock() 766 order_mock2 = mock.Mock()
@@ -729,7 +786,7 @@ class HelperTest(WebMockTestCase):
729 order_mock2.trade = mock.Mock() 786 order_mock2.trade = mock.Mock()
730 order_mock3.trade = mock.Mock() 787 order_mock3.trade = mock.Mock()
731 788
732 helper.follow_orders(verbose=verbose, sleep=sleep) 789 helper.follow_orders(sleep=sleep)
733 790
734 order_mock1.trade.update_order.assert_any_call(order_mock1, 1) 791 order_mock1.trade.update_order.assert_any_call(order_mock1, 1)
735 order_mock1.trade.update_order.assert_any_call(order_mock1, 2) 792 order_mock1.trade.update_order.assert_any_call(order_mock1, 2)
@@ -743,25 +800,43 @@ class HelperTest(WebMockTestCase):
743 order_mock3.trade.update_order.assert_any_call(order_mock3, 2) 800 order_mock3.trade.update_order.assert_any_call(order_mock3, 2)
744 self.assertEqual(1, order_mock3.trade.update_order.call_count) 801 self.assertEqual(1, order_mock3.trade.update_order.call_count)
745 self.assertEqual(2, order_mock3.get_status.call_count) 802 self.assertEqual(2, order_mock3.get_status.call_count)
803 report_store.log_stage.assert_called()
804 calls = [
805 mock.call("follow_orders_begin"),
806 mock.call("follow_orders_tick_1"),
807 mock.call("follow_orders_tick_2"),
808 mock.call("follow_orders_tick_3"),
809 mock.call("follow_orders_end"),
810 ]
811 report_store.log_stage.assert_has_calls(calls)
812 report_store.log_orders.assert_called()
813 self.assertEqual(3, report_store.log_orders.call_count)
814 calls = [
815 mock.call([order_mock1, order_mock2], tick=1),
816 mock.call([order_mock1, order_mock3], tick=2),
817 mock.call([order_mock1, order_mock3], tick=3),
818 ]
819 report_store.log_orders.assert_has_calls(calls)
820 calls = [
821 mock.call(order_mock1, 3, finished=True),
822 mock.call(order_mock3, 3, finished=True),
823 ]
824 report_store.log_order.assert_has_calls(calls)
746 825
747 if sleep is None: 826 if sleep is None:
748 if debug: 827 if debug:
828 report_store.log_debug_action.assert_called_with("Set follow_orders tick to 7s")
749 time_mock.assert_called_with(7) 829 time_mock.assert_called_with(7)
750 else: 830 else:
751 time_mock.assert_called_with(30) 831 time_mock.assert_called_with(30)
752 else: 832 else:
753 time_mock.assert_called_with(sleep) 833 time_mock.assert_called_with(sleep)
754 834
755 if verbose:
756 self.assertNotEqual("", stdout_mock.getvalue())
757 else:
758 self.assertEqual("", stdout_mock.getvalue())
759
760 @mock.patch.object(portfolio.BalanceStore, "fetch_balances") 835 @mock.patch.object(portfolio.BalanceStore, "fetch_balances")
761 def test_move_balance(self, fetch_balances): 836 def test_move_balance(self, fetch_balances):
762 for debug in [True, False]: 837 for debug in [True, False]:
763 with self.subTest(debug=debug),\ 838 with self.subTest(debug=debug),\
764 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: 839 mock.patch("helper.ReportStore") as report_store:
765 value_from = portfolio.Amount("BTC", "1.0") 840 value_from = portfolio.Amount("BTC", "1.0")
766 value_from.linked_to = portfolio.Amount("ETH", "10.0") 841 value_from.linked_to = portfolio.Amount("ETH", "10.0")
767 value_to = portfolio.Amount("BTC", "10.0") 842 value_to = portfolio.Amount("BTC", "10.0")
@@ -788,8 +863,11 @@ class HelperTest(WebMockTestCase):
788 helper.move_balances(market, debug=debug) 863 helper.move_balances(market, debug=debug)
789 864
790 fetch_balances.assert_called_with(market) 865 fetch_balances.assert_called_with(market)
866 report_store.log_move_balances.assert_called_once()
867
791 if debug: 868 if debug:
792 self.assertRegex(stdout_mock.getvalue(), "market.transfer_balance") 869 report_store.log_debug_action.assert_called()
870 self.assertEqual(3, report_store.log_debug_action.call_count)
793 else: 871 else:
794 market.transfer_balance.assert_any_call("BTC", 3, "exchange", "margin") 872 market.transfer_balance.assert_any_call("BTC", 3, "exchange", "margin")
795 market.transfer_balance.assert_any_call("USDT", 50, "margin", "exchange") 873 market.transfer_balance.assert_any_call("USDT", 50, "margin", "exchange")
@@ -797,9 +875,8 @@ class HelperTest(WebMockTestCase):
797 875
798 @mock.patch.object(helper, "prepare_trades") 876 @mock.patch.object(helper, "prepare_trades")
799 @mock.patch.object(portfolio.TradeStore, "prepare_orders") 877 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
800 @mock.patch.object(portfolio.TradeStore, "print_all_with_order") 878 @mock.patch.object(portfolio.ReportStore, "log_stage")
801 @mock.patch('sys.stdout', new_callable=StringIO) 879 def test_print_orders(self, log_stage, prepare_orders, prepare_trades):
802 def test_print_orders(self, stdout_mock, print_all_with_order, prepare_orders, prepare_trades):
803 market = mock.Mock() 880 market = mock.Mock()
804 portfolio.BalanceStore.all = { 881 portfolio.BalanceStore.all = {
805 "BTC": portfolio.Balance("BTC", { 882 "BTC": portfolio.Balance("BTC", {
@@ -817,13 +894,12 @@ class HelperTest(WebMockTestCase):
817 prepare_trades.assert_called_with(market, base_currency="BTC", 894 prepare_trades.assert_called_with(market, base_currency="BTC",
818 compute_value="average", debug=True) 895 compute_value="average", debug=True)
819 prepare_orders.assert_called_with(compute_value="average") 896 prepare_orders.assert_called_with(compute_value="average")
820 print_all_with_order.assert_called() 897 log_stage.assert_called_with("print_orders")
821 self.assertRegex(stdout_mock.getvalue(), "Balance")
822 898
823 @mock.patch.object(portfolio.BalanceStore, "fetch_balances") 899 @mock.patch.object(portfolio.BalanceStore, "fetch_balances")
824 @mock.patch.object(portfolio.BalanceStore, "in_currency") 900 @mock.patch.object(portfolio.BalanceStore, "in_currency")
825 @mock.patch('sys.stdout', new_callable=StringIO) 901 @mock.patch.object(helper.ReportStore, "print_log")
826 def test_print_balances(self, stdout_mock, in_currency, fetch_balances): 902 def test_print_balances(self, print_log, in_currency, fetch_balances):
827 market = mock.Mock() 903 market = mock.Mock()
828 portfolio.BalanceStore.all = { 904 portfolio.BalanceStore.all = {
829 "BTC": portfolio.Balance("BTC", { 905 "BTC": portfolio.Balance("BTC", {
@@ -843,18 +919,18 @@ class HelperTest(WebMockTestCase):
843 } 919 }
844 helper.print_balances(market) 920 helper.print_balances(market)
845 fetch_balances.assert_called_with(market) 921 fetch_balances.assert_called_with(market)
846 self.assertRegex(stdout_mock.getvalue(), "Balance") 922 print_log.assert_has_calls([
847 self.assertRegex(stdout_mock.getvalue(), "0.95000000 BTC") 923 mock.call("total:"),
924 mock.call(portfolio.Amount("BTC", "0.95")),
925 ])
848 926
849 @mock.patch.object(helper, "prepare_trades") 927 @mock.patch.object(helper, "prepare_trades")
850 @mock.patch.object(helper, "follow_orders") 928 @mock.patch.object(helper, "follow_orders")
851 @mock.patch.object(portfolio.TradeStore, "prepare_orders") 929 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
852 @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
853 @mock.patch.object(portfolio.TradeStore, "run_orders") 930 @mock.patch.object(portfolio.TradeStore, "run_orders")
854 @mock.patch('sys.stdout', new_callable=StringIO) 931 @mock.patch.object(portfolio.ReportStore, "log_stage")
855 def test_process_sell_needed__1_sell(self, stdout_mock, run_orders, 932 def test_process_sell_needed__1_sell(self, log_stage, run_orders,
856 print_all_with_order, prepare_orders, follow_orders, 933 prepare_orders, follow_orders, prepare_trades):
857 prepare_trades):
858 market = mock.Mock() 934 market = mock.Mock()
859 portfolio.BalanceStore.all = { 935 portfolio.BalanceStore.all = {
860 "BTC": portfolio.Balance("BTC", { 936 "BTC": portfolio.Balance("BTC", {
@@ -873,20 +949,18 @@ class HelperTest(WebMockTestCase):
873 liquidity="medium", debug=False) 949 liquidity="medium", debug=False)
874 prepare_orders.assert_called_with(compute_value="average", 950 prepare_orders.assert_called_with(compute_value="average",
875 only="dispose") 951 only="dispose")
876 print_all_with_order.assert_called()
877 run_orders.assert_called() 952 run_orders.assert_called()
878 follow_orders.assert_called() 953 follow_orders.assert_called()
879 self.assertRegex(stdout_mock.getvalue(), "Balance") 954 log_stage.assert_called_with("process_sell_needed__1_sell_end")
880 955
881 @mock.patch.object(helper, "update_trades") 956 @mock.patch.object(helper, "update_trades")
882 @mock.patch.object(helper, "follow_orders") 957 @mock.patch.object(helper, "follow_orders")
883 @mock.patch.object(helper, "move_balances") 958 @mock.patch.object(helper, "move_balances")
884 @mock.patch.object(portfolio.TradeStore, "prepare_orders") 959 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
885 @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
886 @mock.patch.object(portfolio.TradeStore, "run_orders") 960 @mock.patch.object(portfolio.TradeStore, "run_orders")
887 @mock.patch('sys.stdout', new_callable=StringIO) 961 @mock.patch.object(portfolio.ReportStore, "log_stage")
888 def test_process_sell_needed__2_buy(self, stdout_mock, run_orders, 962 def test_process_sell_needed__2_buy(self, log_stage, run_orders,
889 print_all_with_order, prepare_orders, move_balances, 963 prepare_orders, move_balances,
890 follow_orders, update_trades): 964 follow_orders, update_trades):
891 market = mock.Mock() 965 market = mock.Mock()
892 portfolio.BalanceStore.all = { 966 portfolio.BalanceStore.all = {
@@ -906,21 +980,18 @@ class HelperTest(WebMockTestCase):
906 debug=False, liquidity="medium", only="acquire") 980 debug=False, liquidity="medium", only="acquire")
907 prepare_orders.assert_called_with(compute_value="average", 981 prepare_orders.assert_called_with(compute_value="average",
908 only="acquire") 982 only="acquire")
909 print_all_with_order.assert_called()
910 move_balances.assert_called_with(market, debug=False) 983 move_balances.assert_called_with(market, debug=False)
911 run_orders.assert_called() 984 run_orders.assert_called()
912 follow_orders.assert_called() 985 follow_orders.assert_called()
913 self.assertRegex(stdout_mock.getvalue(), "Balance") 986 log_stage.assert_called_with("process_sell_needed__2_buy_end")
914 987
915 @mock.patch.object(helper, "prepare_trades_to_sell_all") 988 @mock.patch.object(helper, "prepare_trades_to_sell_all")
916 @mock.patch.object(helper, "follow_orders") 989 @mock.patch.object(helper, "follow_orders")
917 @mock.patch.object(portfolio.TradeStore, "prepare_orders") 990 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
918 @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
919 @mock.patch.object(portfolio.TradeStore, "run_orders") 991 @mock.patch.object(portfolio.TradeStore, "run_orders")
920 @mock.patch('sys.stdout', new_callable=StringIO) 992 @mock.patch.object(portfolio.ReportStore, "log_stage")
921 def test_process_sell_all__1_sell(self, stdout_mock, run_orders, 993 def test_process_sell_all__1_sell(self, log_stage, run_orders,
922 print_all_with_order, prepare_orders, follow_orders, 994 prepare_orders, follow_orders, prepare_trades_to_sell_all):
923 prepare_trades_to_sell_all):
924 market = mock.Mock() 995 market = mock.Mock()
925 portfolio.BalanceStore.all = { 996 portfolio.BalanceStore.all = {
926 "BTC": portfolio.Balance("BTC", { 997 "BTC": portfolio.Balance("BTC", {
@@ -938,21 +1009,19 @@ class HelperTest(WebMockTestCase):
938 prepare_trades_to_sell_all.assert_called_with(market, base_currency="BTC", 1009 prepare_trades_to_sell_all.assert_called_with(market, base_currency="BTC",
939 debug=False) 1010 debug=False)
940 prepare_orders.assert_called_with(compute_value="average") 1011 prepare_orders.assert_called_with(compute_value="average")
941 print_all_with_order.assert_called()
942 run_orders.assert_called() 1012 run_orders.assert_called()
943 follow_orders.assert_called() 1013 follow_orders.assert_called()
944 self.assertRegex(stdout_mock.getvalue(), "Balance") 1014 log_stage.assert_called_with("process_sell_all__1_all_sell_end")
945 1015
946 @mock.patch.object(helper, "prepare_trades") 1016 @mock.patch.object(helper, "prepare_trades")
947 @mock.patch.object(helper, "follow_orders") 1017 @mock.patch.object(helper, "follow_orders")
948 @mock.patch.object(helper, "move_balances") 1018 @mock.patch.object(helper, "move_balances")
949 @mock.patch.object(portfolio.TradeStore, "prepare_orders") 1019 @mock.patch.object(portfolio.TradeStore, "prepare_orders")
950 @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
951 @mock.patch.object(portfolio.TradeStore, "run_orders") 1020 @mock.patch.object(portfolio.TradeStore, "run_orders")
952 @mock.patch('sys.stdout', new_callable=StringIO) 1021 @mock.patch.object(portfolio.ReportStore, "log_stage")
953 def test_process_sell_all__2_all_buy(self, stdout_mock, run_orders, 1022 def test_process_sell_all__2_all_buy(self, log_stage, run_orders,
954 print_all_with_order, prepare_orders, move_balances, 1023 prepare_orders, move_balances, follow_orders,
955 follow_orders, prepare_trades): 1024 prepare_trades):
956 market = mock.Mock() 1025 market = mock.Mock()
957 portfolio.BalanceStore.all = { 1026 portfolio.BalanceStore.all = {
958 "BTC": portfolio.Balance("BTC", { 1027 "BTC": portfolio.Balance("BTC", {
@@ -970,18 +1039,18 @@ class HelperTest(WebMockTestCase):
970 prepare_trades.assert_called_with(market, base_currency="BTC", 1039 prepare_trades.assert_called_with(market, base_currency="BTC",
971 liquidity="medium", debug=False) 1040 liquidity="medium", debug=False)
972 prepare_orders.assert_called_with(compute_value="average") 1041 prepare_orders.assert_called_with(compute_value="average")
973 print_all_with_order.assert_called()
974 move_balances.assert_called_with(market, debug=False) 1042 move_balances.assert_called_with(market, debug=False)
975 run_orders.assert_called() 1043 run_orders.assert_called()
976 follow_orders.assert_called() 1044 follow_orders.assert_called()
977 self.assertRegex(stdout_mock.getvalue(), "Balance") 1045 log_stage.assert_called_with("process_sell_all__2_all_buy_end")
978 1046
979 1047
980@unittest.skipUnless("unit" in limits, "Unit skipped") 1048@unittest.skipUnless("unit" in limits, "Unit skipped")
981class TradeStoreTest(WebMockTestCase): 1049class TradeStoreTest(WebMockTestCase):
982 @mock.patch.object(portfolio.BalanceStore, "currencies") 1050 @mock.patch.object(portfolio.BalanceStore, "currencies")
983 @mock.patch.object(portfolio.TradeStore, "add_trade_if_matching") 1051 @mock.patch.object(portfolio.TradeStore, "trade_if_matching")
984 def test_compute_trades(self, add_trade_if_matching, currencies): 1052 @mock.patch.object(portfolio.ReportStore, "log_trades")
1053 def test_compute_trades(self, log_trades, trade_if_matching, currencies):
985 currencies.return_value = ["XMR", "DASH", "XVG", "BTC", "ETH"] 1054 currencies.return_value = ["XMR", "DASH", "XVG", "BTC", "ETH"]
986 1055
987 values_in_base = { 1056 values_in_base = {
@@ -996,96 +1065,89 @@ class TradeStoreTest(WebMockTestCase):
996 "BTC": portfolio.Amount("BTC", D("0.4")), 1065 "BTC": portfolio.Amount("BTC", D("0.4")),
997 "ETH": portfolio.Amount("BTC", D("0.3")), 1066 "ETH": portfolio.Amount("BTC", D("0.3")),
998 } 1067 }
1068 side_effect = [
1069 (True, 1),
1070 (False, 2),
1071 (False, 3),
1072 (True, 4),
1073 (True, 5)
1074 ]
1075 trade_if_matching.side_effect = side_effect
999 1076
1000 portfolio.TradeStore.compute_trades(values_in_base, 1077 portfolio.TradeStore.compute_trades(values_in_base,
1001 new_repartition, only="only", market="market") 1078 new_repartition, only="only", market="market")
1002 1079
1003 self.assertEqual(5, add_trade_if_matching.call_count) 1080 self.assertEqual(5, trade_if_matching.call_count)
1004 add_trade_if_matching.assert_any_call( 1081 self.assertEqual(3, len(portfolio.TradeStore.all))
1005 portfolio.Amount("BTC", D("0.9")), 1082 self.assertEqual([1, 4, 5], portfolio.TradeStore.all)
1006 portfolio.Amount("BTC", 0), 1083 log_trades.assert_called_with(side_effect, "only", False)
1007 "XMR", only="only", market="market"
1008 )
1009 add_trade_if_matching.assert_any_call(
1010 portfolio.Amount("BTC", D("0.4")),
1011 portfolio.Amount("BTC", D("0.5")),
1012 "DASH", only="only", market="market"
1013 )
1014 add_trade_if_matching.assert_any_call(
1015 portfolio.Amount("BTC", D("-0.5")),
1016 portfolio.Amount("BTC", D("0")),
1017 "XVG", only="only", market="market"
1018 )
1019 add_trade_if_matching.assert_any_call(
1020 portfolio.Amount("BTC", D("0")),
1021 portfolio.Amount("BTC", D("0.1")),
1022 "XVG", only="only", market="market"
1023 )
1024 add_trade_if_matching.assert_any_call(
1025 portfolio.Amount("BTC", D("0")),
1026 portfolio.Amount("BTC", D("0.3")),
1027 "ETH", only="only", market="market"
1028 )
1029 1084
1030 def test_add_trade_if_matching(self): 1085 def test_trade_if_matching(self):
1031 result = portfolio.TradeStore.add_trade_if_matching( 1086 result = portfolio.TradeStore.trade_if_matching(
1032 portfolio.Amount("BTC", D("0")), 1087 portfolio.Amount("BTC", D("0")),
1033 portfolio.Amount("BTC", D("0.3")), 1088 portfolio.Amount("BTC", D("0.3")),
1034 "ETH", only="nope", market="market" 1089 "ETH", only="nope", market="market"
1035 ) 1090 )
1036 self.assertEqual(0, len(portfolio.TradeStore.all)) 1091 self.assertEqual(False, result[0])
1037 self.assertEqual(False, result) 1092 self.assertIsInstance(result[1], portfolio.Trade)
1038 1093
1039 portfolio.TradeStore.all = [] 1094 portfolio.TradeStore.all = []
1040 result = portfolio.TradeStore.add_trade_if_matching( 1095 result = portfolio.TradeStore.trade_if_matching(
1041 portfolio.Amount("BTC", D("0")), 1096 portfolio.Amount("BTC", D("0")),
1042 portfolio.Amount("BTC", D("0.3")), 1097 portfolio.Amount("BTC", D("0.3")),
1043 "ETH", only=None, market="market" 1098 "ETH", only=None, market="market"
1044 ) 1099 )
1045 self.assertEqual(1, len(portfolio.TradeStore.all)) 1100 self.assertEqual(True, result[0])
1046 self.assertEqual(True, result)
1047 1101
1048 portfolio.TradeStore.all = [] 1102 portfolio.TradeStore.all = []
1049 result = portfolio.TradeStore.add_trade_if_matching( 1103 result = portfolio.TradeStore.trade_if_matching(
1050 portfolio.Amount("BTC", D("0")), 1104 portfolio.Amount("BTC", D("0")),
1051 portfolio.Amount("BTC", D("0.3")), 1105 portfolio.Amount("BTC", D("0.3")),
1052 "ETH", only="acquire", market="market" 1106 "ETH", only="acquire", market="market"
1053 ) 1107 )
1054 self.assertEqual(1, len(portfolio.TradeStore.all)) 1108 self.assertEqual(True, result[0])
1055 self.assertEqual(True, result)
1056 1109
1057 portfolio.TradeStore.all = [] 1110 portfolio.TradeStore.all = []
1058 result = portfolio.TradeStore.add_trade_if_matching( 1111 result = portfolio.TradeStore.trade_if_matching(
1059 portfolio.Amount("BTC", D("0")), 1112 portfolio.Amount("BTC", D("0")),
1060 portfolio.Amount("BTC", D("0.3")), 1113 portfolio.Amount("BTC", D("0.3")),
1061 "ETH", only="dispose", market="market" 1114 "ETH", only="dispose", market="market"
1062 ) 1115 )
1063 self.assertEqual(0, len(portfolio.TradeStore.all)) 1116 self.assertEqual(False, result[0])
1064 self.assertEqual(False, result)
1065 1117
1066 def test_prepare_orders(self): 1118 @mock.patch.object(portfolio.ReportStore, "log_orders")
1119 def test_prepare_orders(self, log_orders):
1067 trade_mock1 = mock.Mock() 1120 trade_mock1 = mock.Mock()
1068 trade_mock2 = mock.Mock() 1121 trade_mock2 = mock.Mock()
1069 1122
1123 trade_mock1.prepare_order.return_value = 1
1124 trade_mock2.prepare_order.return_value = 2
1125
1070 portfolio.TradeStore.all.append(trade_mock1) 1126 portfolio.TradeStore.all.append(trade_mock1)
1071 portfolio.TradeStore.all.append(trade_mock2) 1127 portfolio.TradeStore.all.append(trade_mock2)
1072 1128
1073 portfolio.TradeStore.prepare_orders() 1129 portfolio.TradeStore.prepare_orders()
1074 trade_mock1.prepare_order.assert_called_with(compute_value="default") 1130 trade_mock1.prepare_order.assert_called_with(compute_value="default")
1075 trade_mock2.prepare_order.assert_called_with(compute_value="default") 1131 trade_mock2.prepare_order.assert_called_with(compute_value="default")
1132 log_orders.assert_called_once_with([1, 2], None, "default")
1133
1134 log_orders.reset_mock()
1076 1135
1077 portfolio.TradeStore.prepare_orders(compute_value="bla") 1136 portfolio.TradeStore.prepare_orders(compute_value="bla")
1078 trade_mock1.prepare_order.assert_called_with(compute_value="bla") 1137 trade_mock1.prepare_order.assert_called_with(compute_value="bla")
1079 trade_mock2.prepare_order.assert_called_with(compute_value="bla") 1138 trade_mock2.prepare_order.assert_called_with(compute_value="bla")
1139 log_orders.assert_called_once_with([1, 2], None, "bla")
1080 1140
1081 trade_mock1.prepare_order.reset_mock() 1141 trade_mock1.prepare_order.reset_mock()
1082 trade_mock2.prepare_order.reset_mock() 1142 trade_mock2.prepare_order.reset_mock()
1143 log_orders.reset_mock()
1083 1144
1084 trade_mock1.action = "foo" 1145 trade_mock1.action = "foo"
1085 trade_mock2.action = "bar" 1146 trade_mock2.action = "bar"
1086 portfolio.TradeStore.prepare_orders(only="bar") 1147 portfolio.TradeStore.prepare_orders(only="bar")
1087 trade_mock1.prepare_order.assert_not_called() 1148 trade_mock1.prepare_order.assert_not_called()
1088 trade_mock2.prepare_order.assert_called_with(compute_value="default") 1149 trade_mock2.prepare_order.assert_called_with(compute_value="default")
1150 log_orders.assert_called_once_with([2], "bar", "default")
1089 1151
1090 def test_print_all_with_order(self): 1152 def test_print_all_with_order(self):
1091 trade_mock1 = mock.Mock() 1153 trade_mock1 = mock.Mock()
@@ -1099,8 +1161,10 @@ class TradeStoreTest(WebMockTestCase):
1099 trade_mock2.print_with_order.assert_called() 1161 trade_mock2.print_with_order.assert_called()
1100 trade_mock3.print_with_order.assert_called() 1162 trade_mock3.print_with_order.assert_called()
1101 1163
1164 @mock.patch.object(portfolio.ReportStore, "log_stage")
1165 @mock.patch.object(portfolio.ReportStore, "log_orders")
1102 @mock.patch.object(portfolio.TradeStore, "all_orders") 1166 @mock.patch.object(portfolio.TradeStore, "all_orders")
1103 def test_run_orders(self, all_orders): 1167 def test_run_orders(self, all_orders, log_orders, log_stage):
1104 order_mock1 = mock.Mock() 1168 order_mock1 = mock.Mock()
1105 order_mock2 = mock.Mock() 1169 order_mock2 = mock.Mock()
1106 order_mock3 = mock.Mock() 1170 order_mock3 = mock.Mock()
@@ -1112,6 +1176,10 @@ class TradeStoreTest(WebMockTestCase):
1112 order_mock2.run.assert_called() 1176 order_mock2.run.assert_called()
1113 order_mock3.run.assert_called() 1177 order_mock3.run.assert_called()
1114 1178
1179 log_stage.assert_called_with("run_orders")
1180 log_orders.assert_called_with([order_mock1, order_mock2,
1181 order_mock3])
1182
1115 def test_all_orders(self): 1183 def test_all_orders(self):
1116 trade_mock1 = mock.Mock() 1184 trade_mock1 = mock.Mock()
1117 trade_mock2 = mock.Mock() 1185 trade_mock2 = mock.Mock()
@@ -1150,6 +1218,7 @@ class TradeStoreTest(WebMockTestCase):
1150 order_mock2.get_status.assert_called() 1218 order_mock2.get_status.assert_called()
1151 order_mock3.get_status.assert_called() 1219 order_mock3.get_status.assert_called()
1152 1220
1221
1153@unittest.skipUnless("unit" in limits, "Unit skipped") 1222@unittest.skipUnless("unit" in limits, "Unit skipped")
1154class BalanceStoreTest(WebMockTestCase): 1223class BalanceStoreTest(WebMockTestCase):
1155 def setUp(self): 1224 def setUp(self):
@@ -1184,7 +1253,8 @@ class BalanceStoreTest(WebMockTestCase):
1184 } 1253 }
1185 1254
1186 @mock.patch.object(helper, "get_ticker") 1255 @mock.patch.object(helper, "get_ticker")
1187 def test_in_currency(self, get_ticker): 1256 @mock.patch("portfolio.ReportStore.log_tickers")
1257 def test_in_currency(self, log_tickers, get_ticker):
1188 portfolio.BalanceStore.all = { 1258 portfolio.BalanceStore.all = {
1189 "BTC": portfolio.Balance("BTC", { 1259 "BTC": portfolio.Balance("BTC", {
1190 "total": "0.65", 1260 "total": "0.65",
@@ -1208,16 +1278,26 @@ class BalanceStoreTest(WebMockTestCase):
1208 self.assertEqual("BTC", amounts["ETH"].currency) 1278 self.assertEqual("BTC", amounts["ETH"].currency)
1209 self.assertEqual(D("0.65"), amounts["BTC"].value) 1279 self.assertEqual(D("0.65"), amounts["BTC"].value)
1210 self.assertEqual(D("0.30"), amounts["ETH"].value) 1280 self.assertEqual(D("0.30"), amounts["ETH"].value)
1281 log_tickers.assert_called_once_with(market, amounts, "BTC",
1282 "average", "total")
1283 log_tickers.reset_mock()
1211 1284
1212 amounts = portfolio.BalanceStore.in_currency("BTC", market, compute_value="bid") 1285 amounts = portfolio.BalanceStore.in_currency("BTC", market, compute_value="bid")
1213 self.assertEqual(D("0.65"), amounts["BTC"].value) 1286 self.assertEqual(D("0.65"), amounts["BTC"].value)
1214 self.assertEqual(D("0.27"), amounts["ETH"].value) 1287 self.assertEqual(D("0.27"), amounts["ETH"].value)
1288 log_tickers.assert_called_once_with(market, amounts, "BTC",
1289 "bid", "total")
1290 log_tickers.reset_mock()
1215 1291
1216 amounts = portfolio.BalanceStore.in_currency("BTC", market, compute_value="bid", type="exchange_used") 1292 amounts = portfolio.BalanceStore.in_currency("BTC", market, compute_value="bid", type="exchange_used")
1217 self.assertEqual(D("0.30"), amounts["BTC"].value) 1293 self.assertEqual(D("0.30"), amounts["BTC"].value)
1218 self.assertEqual(0, amounts["ETH"].value) 1294 self.assertEqual(0, amounts["ETH"].value)
1295 log_tickers.assert_called_once_with(market, amounts, "BTC",
1296 "bid", "exchange_used")
1297 log_tickers.reset_mock()
1219 1298
1220 def test_fetch_balances(self): 1299 @mock.patch.object(portfolio.ReportStore, "log_balances")
1300 def test_fetch_balances(self, log_balances):
1221 market = mock.Mock() 1301 market = mock.Mock()
1222 market.fetch_all_balances.return_value = self.fetch_balance 1302 market.fetch_all_balances.return_value = self.fetch_balance
1223 1303
@@ -1231,20 +1311,24 @@ class BalanceStoreTest(WebMockTestCase):
1231 portfolio.BalanceStore.fetch_balances(market) 1311 portfolio.BalanceStore.fetch_balances(market)
1232 self.assertEqual(0, portfolio.BalanceStore.all["ETC"].total) 1312 self.assertEqual(0, portfolio.BalanceStore.all["ETC"].total)
1233 self.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(portfolio.BalanceStore.currencies())) 1313 self.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(portfolio.BalanceStore.currencies()))
1314 log_balances.assert_called_with(market)
1234 1315
1235 @mock.patch.object(portfolio.Portfolio, "repartition") 1316 @mock.patch.object(portfolio.Portfolio, "repartition")
1236 def test_dispatch_assets(self, repartition): 1317 @mock.patch.object(portfolio.ReportStore, "log_balances")
1318 @mock.patch("store.ReportStore.log_dispatch")
1319 def test_dispatch_assets(self, log_dispatch, log_balances, repartition):
1237 market = mock.Mock() 1320 market = mock.Mock()
1238 market.fetch_all_balances.return_value = self.fetch_balance 1321 market.fetch_all_balances.return_value = self.fetch_balance
1239 portfolio.BalanceStore.fetch_balances(market) 1322 portfolio.BalanceStore.fetch_balances(market)
1240 1323
1241 self.assertNotIn("XEM", portfolio.BalanceStore.currencies()) 1324 self.assertNotIn("XEM", portfolio.BalanceStore.currencies())
1242 1325
1243 repartition.return_value = { 1326 repartition_hash = {
1244 "XEM": (D("0.75"), "long"), 1327 "XEM": (D("0.75"), "long"),
1245 "BTC": (D("0.26"), "long"), 1328 "BTC": (D("0.26"), "long"),
1246 "DASH": (D("0.10"), "short"), 1329 "DASH": (D("0.10"), "short"),
1247 } 1330 }
1331 repartition.return_value = repartition_hash
1248 1332
1249 amounts = portfolio.BalanceStore.dispatch_assets(portfolio.Amount("BTC", "11.1")) 1333 amounts = portfolio.BalanceStore.dispatch_assets(portfolio.Amount("BTC", "11.1"))
1250 repartition.assert_called_with(liquidity="medium") 1334 repartition.assert_called_with(liquidity="medium")
@@ -1252,6 +1336,9 @@ class BalanceStoreTest(WebMockTestCase):
1252 self.assertEqual(D("2.6"), amounts["BTC"].value) 1336 self.assertEqual(D("2.6"), amounts["BTC"].value)
1253 self.assertEqual(D("7.5"), amounts["XEM"].value) 1337 self.assertEqual(D("7.5"), amounts["XEM"].value)
1254 self.assertEqual(D("-1.0"), amounts["DASH"].value) 1338 self.assertEqual(D("-1.0"), amounts["DASH"].value)
1339 log_balances.assert_called_with(market)
1340 log_dispatch.assert_called_once_with(portfolio.Amount("BTC",
1341 "11.1"), amounts, "medium", repartition_hash)
1255 1342
1256 def test_currencies(self): 1343 def test_currencies(self):
1257 portfolio.BalanceStore.all = { 1344 portfolio.BalanceStore.all = {
@@ -1268,6 +1355,23 @@ class BalanceStoreTest(WebMockTestCase):
1268 } 1355 }
1269 self.assertListEqual(["BTC", "ETH"], list(portfolio.BalanceStore.currencies())) 1356 self.assertListEqual(["BTC", "ETH"], list(portfolio.BalanceStore.currencies()))
1270 1357
1358 def test_as_json(self):
1359 balance_mock1 = mock.Mock()
1360 balance_mock1.as_json.return_value = 1
1361
1362 balance_mock2 = mock.Mock()
1363 balance_mock2.as_json.return_value = 2
1364
1365 portfolio.BalanceStore.all = {
1366 "BTC": balance_mock1,
1367 "ETH": balance_mock2,
1368 }
1369
1370 as_json = portfolio.BalanceStore.as_json()
1371 self.assertEqual(1, as_json["BTC"])
1372 self.assertEqual(2, as_json["ETH"])
1373
1374
1271@unittest.skipUnless("unit" in limits, "Unit skipped") 1375@unittest.skipUnless("unit" in limits, "Unit skipped")
1272class ComputationTest(WebMockTestCase): 1376class ComputationTest(WebMockTestCase):
1273 def test_compute_value(self): 1377 def test_compute_value(self):
@@ -1426,7 +1530,8 @@ class TradeTest(WebMockTestCase):
1426 Order.assert_not_called() 1530 Order.assert_not_called()
1427 1531
1428 get_ticker.return_value = { "inverted": False } 1532 get_ticker.return_value = { "inverted": False }
1429 with self.subTest(desc="Already filled"), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: 1533 with self.subTest(desc="Already filled"),\
1534 mock.patch("portfolio.ReportStore") as report_store:
1430 filled_amount.return_value = portfolio.Amount("FOO", "100") 1535 filled_amount.return_value = portfolio.Amount("FOO", "100")
1431 compute_value.return_value = D("0.125") 1536 compute_value.return_value = D("0.125")
1432 1537
@@ -1441,7 +1546,7 @@ class TradeTest(WebMockTestCase):
1441 filled_amount.assert_called_with(in_base_currency=False) 1546 filled_amount.assert_called_with(in_base_currency=False)
1442 compute_value.assert_called_with(get_ticker.return_value, "sell", compute_value="default") 1547 compute_value.assert_called_with(get_ticker.return_value, "sell", compute_value="default")
1443 self.assertEqual(0, len(trade.orders)) 1548 self.assertEqual(0, len(trade.orders))
1444 self.assertRegex(stdout_mock.getvalue(), "Less to do than already filled: ") 1549 report_store.log_error.assert_called_with("prepare_order", message=mock.ANY)
1445 Order.assert_not_called() 1550 Order.assert_not_called()
1446 1551
1447 with self.subTest(action="dispose", inverted=False): 1552 with self.subTest(action="dispose", inverted=False):
@@ -1554,77 +1659,110 @@ class TradeTest(WebMockTestCase):
1554 prepare_order.return_value = new_order_mock 1659 prepare_order.return_value = new_order_mock
1555 1660
1556 for i in [0, 1, 3, 4, 6]: 1661 for i in [0, 1, 3, 4, 6]:
1557 with self.subTest(tick=i), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: 1662 with self.subTest(tick=i),\
1663 mock.patch.object(portfolio.ReportStore, "log_order") as log_order:
1558 trade.update_order(order_mock, i) 1664 trade.update_order(order_mock, i)
1559 order_mock.cancel.assert_not_called() 1665 order_mock.cancel.assert_not_called()
1560 new_order_mock.run.assert_not_called() 1666 new_order_mock.run.assert_not_called()
1561 self.assertRegex(stdout_mock.getvalue(), "tick {}, waiting".format(i)) 1667 log_order.assert_called_once_with(order_mock, i,
1668 update="waiting", compute_value=None, new_order=None)
1562 1669
1563 order_mock.reset_mock() 1670 order_mock.reset_mock()
1564 new_order_mock.reset_mock() 1671 new_order_mock.reset_mock()
1565 trade.orders = [] 1672 trade.orders = []
1566 1673
1567 with mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: 1674 with mock.patch.object(portfolio.ReportStore, "log_order") as log_order:
1568 trade.update_order(order_mock, 2) 1675 trade.update_order(order_mock, 2)
1569 order_mock.cancel.assert_called() 1676 order_mock.cancel.assert_called()
1570 new_order_mock.run.assert_called() 1677 new_order_mock.run.assert_called()
1571 prepare_order.assert_called() 1678 prepare_order.assert_called()
1572 self.assertRegex(stdout_mock.getvalue(), "tick 2, cancelling and adjusting") 1679 log_order.assert_called()
1680 self.assertEqual(2, log_order.call_count)
1681 calls = [
1682 mock.call(order_mock, 2, update="adjusting",
1683 compute_value='lambda x, y: (x[y] + x["average"]) / 2',
1684 new_order=new_order_mock),
1685 mock.call(order_mock, 2, new_order=new_order_mock),
1686 ]
1687 log_order.assert_has_calls(calls)
1573 1688
1574 order_mock.reset_mock() 1689 order_mock.reset_mock()
1575 new_order_mock.reset_mock() 1690 new_order_mock.reset_mock()
1576 trade.orders = [] 1691 trade.orders = []
1577 1692
1578 with mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: 1693 with mock.patch.object(portfolio.ReportStore, "log_order") as log_order:
1579 trade.update_order(order_mock, 5) 1694 trade.update_order(order_mock, 5)
1580 order_mock.cancel.assert_called() 1695 order_mock.cancel.assert_called()
1581 new_order_mock.run.assert_called() 1696 new_order_mock.run.assert_called()
1582 prepare_order.assert_called() 1697 prepare_order.assert_called()
1583 self.assertRegex(stdout_mock.getvalue(), "tick 5, cancelling and adjusting") 1698 self.assertEqual(2, log_order.call_count)
1699 log_order.assert_called()
1700 calls = [
1701 mock.call(order_mock, 5, update="adjusting",
1702 compute_value='lambda x, y: (x[y]*2 + x["average"]) / 3',
1703 new_order=new_order_mock),
1704 mock.call(order_mock, 5, new_order=new_order_mock),
1705 ]
1706 log_order.assert_has_calls(calls)
1584 1707
1585 order_mock.reset_mock() 1708 order_mock.reset_mock()
1586 new_order_mock.reset_mock() 1709 new_order_mock.reset_mock()
1587 trade.orders = [] 1710 trade.orders = []
1588 1711
1589 with mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: 1712 with mock.patch.object(portfolio.ReportStore, "log_order") as log_order:
1590 trade.update_order(order_mock, 7) 1713 trade.update_order(order_mock, 7)
1591 order_mock.cancel.assert_called() 1714 order_mock.cancel.assert_called()
1592 new_order_mock.run.assert_called() 1715 new_order_mock.run.assert_called()
1593 prepare_order.assert_called_with(compute_value="default") 1716 prepare_order.assert_called_with(compute_value="default")
1594 self.assertRegex(stdout_mock.getvalue(), "tick 7, fallbacking to market value") 1717 log_order.assert_called()
1595 self.assertRegex(stdout_mock.getvalue(), "tick 7, market value, cancelling and adjusting to") 1718 self.assertEqual(2, log_order.call_count)
1719 calls = [
1720 mock.call(order_mock, 7, update="market_fallback",
1721 compute_value='default',
1722 new_order=new_order_mock),
1723 mock.call(order_mock, 7, new_order=new_order_mock),
1724 ]
1725 log_order.assert_has_calls(calls)
1596 1726
1597 order_mock.reset_mock() 1727 order_mock.reset_mock()
1598 new_order_mock.reset_mock() 1728 new_order_mock.reset_mock()
1599 trade.orders = [] 1729 trade.orders = []
1600 1730
1601 for i in [10, 13, 16]: 1731 for i in [10, 13, 16]:
1602 with self.subTest(tick=i), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: 1732 with self.subTest(tick=i), mock.patch.object(portfolio.ReportStore, "log_order") as log_order:
1603 trade.update_order(order_mock, i) 1733 trade.update_order(order_mock, i)
1604 order_mock.cancel.assert_called() 1734 order_mock.cancel.assert_called()
1605 new_order_mock.run.assert_called() 1735 new_order_mock.run.assert_called()
1606 prepare_order.assert_called_with(compute_value="default") 1736 prepare_order.assert_called_with(compute_value="default")
1607 self.assertNotRegex(stdout_mock.getvalue(), "tick {}, fallbacking to market value".format(i)) 1737 log_order.assert_called()
1608 self.assertRegex(stdout_mock.getvalue(), "tick {}, market value, cancelling and adjusting to".format(i)) 1738 self.assertEqual(2, log_order.call_count)
1739 calls = [
1740 mock.call(order_mock, i, update="market_adjust",
1741 compute_value='default',
1742 new_order=new_order_mock),
1743 mock.call(order_mock, i, new_order=new_order_mock),
1744 ]
1745 log_order.assert_has_calls(calls)
1609 1746
1610 order_mock.reset_mock() 1747 order_mock.reset_mock()
1611 new_order_mock.reset_mock() 1748 new_order_mock.reset_mock()
1612 trade.orders = [] 1749 trade.orders = []
1613 1750
1614 for i in [8, 9, 11, 12]: 1751 for i in [8, 9, 11, 12]:
1615 with self.subTest(tick=i), mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: 1752 with self.subTest(tick=i), mock.patch.object(portfolio.ReportStore, "log_order") as log_order:
1616 trade.update_order(order_mock, i) 1753 trade.update_order(order_mock, i)
1617 order_mock.cancel.assert_not_called() 1754 order_mock.cancel.assert_not_called()
1618 new_order_mock.run.assert_not_called() 1755 new_order_mock.run.assert_not_called()
1619 self.assertEqual("", stdout_mock.getvalue()) 1756 log_order.assert_called_once_with(order_mock, i, update="waiting",
1757 compute_value=None, new_order=None)
1620 1758
1621 order_mock.reset_mock() 1759 order_mock.reset_mock()
1622 new_order_mock.reset_mock() 1760 new_order_mock.reset_mock()
1623 trade.orders = [] 1761 trade.orders = []
1624 1762
1625 1763
1626 @mock.patch('sys.stdout', new_callable=StringIO) 1764 @mock.patch.object(portfolio.ReportStore, "print_log")
1627 def test_print_with_order(self, mock_stdout): 1765 def test_print_with_order(self, print_log):
1628 value_from = portfolio.Amount("BTC", "0.5") 1766 value_from = portfolio.Amount("BTC", "0.5")
1629 value_from.linked_to = portfolio.Amount("ETH", "10.0") 1767 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1630 value_to = portfolio.Amount("BTC", "1.0") 1768 value_to = portfolio.Amount("BTC", "1.0")
@@ -1651,12 +1789,13 @@ class TradeTest(WebMockTestCase):
1651 1789
1652 trade.print_with_order() 1790 trade.print_with_order()
1653 1791
1654 out = mock_stdout.getvalue().split("\n") 1792 print_log.assert_called()
1655 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", out[0]) 1793 calls = print_log.mock_calls
1656 self.assertEqual("\tMock 1", out[1]) 1794 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(calls[0][1][0]))
1657 self.assertEqual("\tMock 2", out[2]) 1795 self.assertEqual("\tMock 1", str(calls[1][1][0]))
1658 self.assertEqual("\t\tMouvement 1", out[3]) 1796 self.assertEqual("\tMock 2", str(calls[2][1][0]))
1659 self.assertEqual("\t\tMouvement 2", out[4]) 1797 self.assertEqual("\t\tMouvement 1", str(calls[3][1][0]))
1798 self.assertEqual("\t\tMouvement 2", str(calls[4][1][0]))
1660 1799
1661 def test__repr(self): 1800 def test__repr(self):
1662 value_from = portfolio.Amount("BTC", "0.5") 1801 value_from = portfolio.Amount("BTC", "0.5")
@@ -1666,6 +1805,19 @@ class TradeTest(WebMockTestCase):
1666 1805
1667 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade)) 1806 self.assertEqual("Trade(0.50000000 BTC [10.00000000 ETH] -> 1.00000000 BTC in ETH, acquire)", str(trade))
1668 1807
1808 def test_as_json(self):
1809 value_from = portfolio.Amount("BTC", "0.5")
1810 value_from.linked_to = portfolio.Amount("ETH", "10.0")
1811 value_to = portfolio.Amount("BTC", "1.0")
1812 trade = portfolio.Trade(value_from, value_to, "ETH")
1813
1814 as_json = trade.as_json()
1815 self.assertEqual("acquire", as_json["action"])
1816 self.assertEqual(D("0.5"), as_json["from"])
1817 self.assertEqual(D("1.0"), as_json["to"])
1818 self.assertEqual("ETH", as_json["currency"])
1819 self.assertEqual("BTC", as_json["base_currency"])
1820
1669@unittest.skipUnless("unit" in limits, "Unit skipped") 1821@unittest.skipUnless("unit" in limits, "Unit skipped")
1670class OrderTest(WebMockTestCase): 1822class OrderTest(WebMockTestCase):
1671 def test_values(self): 1823 def test_values(self):
@@ -1693,6 +1845,27 @@ class OrderTest(WebMockTestCase):
1693 close_if_possible=True) 1845 close_if_possible=True)
1694 self.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending] ✂)", repr(order)) 1846 self.assertEqual("Order(buy long 10.00000000 ETH at 0.1 BTC [pending] ✂)", repr(order))
1695 1847
1848 def test_as_json(self):
1849 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1850 D("0.1"), "BTC", "long", "market", "trade")
1851 mouvement_mock1 = mock.Mock()
1852 mouvement_mock1.as_json.return_value = 1
1853 mouvement_mock2 = mock.Mock()
1854 mouvement_mock2.as_json.return_value = 2
1855
1856 order.mouvements = [mouvement_mock1, mouvement_mock2]
1857 as_json = order.as_json()
1858 self.assertEqual("buy", as_json["action"])
1859 self.assertEqual("long", as_json["trade_type"])
1860 self.assertEqual(10, as_json["amount"])
1861 self.assertEqual("ETH", as_json["currency"])
1862 self.assertEqual("BTC", as_json["base_currency"])
1863 self.assertEqual(D("0.1"), as_json["rate"])
1864 self.assertEqual("pending", as_json["status"])
1865 self.assertEqual(False, as_json["close_if_possible"])
1866 self.assertIsNone(as_json["id"])
1867 self.assertEqual([1, 2], as_json["mouvements"])
1868
1696 def test_account(self): 1869 def test_account(self):
1697 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 1870 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1698 D("0.1"), "BTC", "long", "market", "trade") 1871 D("0.1"), "BTC", "long", "market", "trade")
@@ -1728,7 +1901,8 @@ class OrderTest(WebMockTestCase):
1728 self.assertTrue(order.finished) 1901 self.assertTrue(order.finished)
1729 1902
1730 @mock.patch.object(portfolio.Order, "fetch") 1903 @mock.patch.object(portfolio.Order, "fetch")
1731 def test_cancel(self, fetch): 1904 @mock.patch("portfolio.ReportStore")
1905 def test_cancel(self, report_store, fetch):
1732 market = mock.Mock() 1906 market = mock.Mock()
1733 portfolio.TradeStore.debug = True 1907 portfolio.TradeStore.debug = True
1734 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 1908 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
@@ -1737,6 +1911,8 @@ class OrderTest(WebMockTestCase):
1737 1911
1738 order.cancel() 1912 order.cancel()
1739 market.cancel_order.assert_not_called() 1913 market.cancel_order.assert_not_called()
1914 report_store.log_debug_action.assert_called_once()
1915 report_store.log_debug_action.reset_mock()
1740 self.assertEqual("canceled", order.status) 1916 self.assertEqual("canceled", order.status)
1741 1917
1742 portfolio.TradeStore.debug = False 1918 portfolio.TradeStore.debug = False
@@ -1748,6 +1924,7 @@ class OrderTest(WebMockTestCase):
1748 order.cancel() 1924 order.cancel()
1749 market.cancel_order.assert_called_with(42) 1925 market.cancel_order.assert_called_with(42)
1750 fetch.assert_called_once() 1926 fetch.assert_called_once()
1927 report_store.log_debug_action.assert_not_called()
1751 1928
1752 def test_dust_amount_remaining(self): 1929 def test_dust_amount_remaining(self):
1753 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 1930 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
@@ -1824,7 +2001,8 @@ class OrderTest(WebMockTestCase):
1824 order.fetch_mouvements() 2001 order.fetch_mouvements()
1825 self.assertEqual(0, len(order.mouvements)) 2002 self.assertEqual(0, len(order.mouvements))
1826 2003
1827 def test_mark_finished_order(self): 2004 @mock.patch("portfolio.ReportStore")
2005 def test_mark_finished_order(self, report_store):
1828 market = mock.Mock() 2006 market = mock.Mock()
1829 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 2007 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1830 D("0.1"), "BTC", "short", market, "trade", 2008 D("0.1"), "BTC", "short", market, "trade",
@@ -1870,9 +2048,11 @@ class OrderTest(WebMockTestCase):
1870 2048
1871 order.mark_finished_order() 2049 order.mark_finished_order()
1872 market.close_margin_position.assert_not_called() 2050 market.close_margin_position.assert_not_called()
2051 report_store.log_debug_action.assert_called_once()
1873 2052
1874 @mock.patch.object(portfolio.Order, "fetch_mouvements") 2053 @mock.patch.object(portfolio.Order, "fetch_mouvements")
1875 def test_fetch(self, fetch_mouvements): 2054 @mock.patch("portfolio.ReportStore")
2055 def test_fetch(self, report_store, fetch_mouvements):
1876 time = self.time.time() 2056 time = self.time.time()
1877 with mock.patch.object(portfolio.time, "time") as time_mock: 2057 with mock.patch.object(portfolio.time, "time") as time_mock:
1878 market = mock.Mock() 2058 market = mock.Mock()
@@ -1883,10 +2063,14 @@ class OrderTest(WebMockTestCase):
1883 portfolio.TradeStore.debug = True 2063 portfolio.TradeStore.debug = True
1884 order.fetch() 2064 order.fetch()
1885 time_mock.assert_not_called() 2065 time_mock.assert_not_called()
2066 report_store.log_debug_action.assert_called_once()
2067 report_store.log_debug_action.reset_mock()
1886 order.fetch(force=True) 2068 order.fetch(force=True)
1887 time_mock.assert_not_called() 2069 time_mock.assert_not_called()
1888 market.fetch_order.assert_not_called() 2070 market.fetch_order.assert_not_called()
1889 fetch_mouvements.assert_not_called() 2071 fetch_mouvements.assert_not_called()
2072 report_store.log_debug_action.assert_called_once()
2073 report_store.log_debug_action.reset_mock()
1890 self.assertIsNone(order.fetch_cache_timestamp) 2074 self.assertIsNone(order.fetch_cache_timestamp)
1891 2075
1892 with self.subTest(debug=False): 2076 with self.subTest(debug=False):
@@ -1924,16 +2108,19 @@ class OrderTest(WebMockTestCase):
1924 order.fetch() 2108 order.fetch()
1925 market.fetch_order.assert_called_once() 2109 market.fetch_order.assert_called_once()
1926 fetch_mouvements.assert_called_once() 2110 fetch_mouvements.assert_called_once()
2111 report_store.log_debug_action.assert_not_called()
1927 2112
1928 @mock.patch.object(portfolio.Order, "fetch") 2113 @mock.patch.object(portfolio.Order, "fetch")
1929 @mock.patch.object(portfolio.Order, "mark_finished_order") 2114 @mock.patch.object(portfolio.Order, "mark_finished_order")
1930 def test_get_status(self, mark_finished_order, fetch): 2115 @mock.patch("portfolio.ReportStore")
2116 def test_get_status(self, report_store, mark_finished_order, fetch):
1931 with self.subTest(debug=True): 2117 with self.subTest(debug=True):
1932 portfolio.TradeStore.debug = True 2118 portfolio.TradeStore.debug = True
1933 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 2119 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1934 D("0.1"), "BTC", "long", "market", "trade") 2120 D("0.1"), "BTC", "long", "market", "trade")
1935 self.assertEqual("pending", order.get_status()) 2121 self.assertEqual("pending", order.get_status())
1936 fetch.assert_not_called() 2122 fetch.assert_not_called()
2123 report_store.log_debug_action.assert_called_once()
1937 2124
1938 with self.subTest(debug=False, finished=False): 2125 with self.subTest(debug=False, finished=False):
1939 portfolio.TradeStore.debug = False 2126 portfolio.TradeStore.debug = False
@@ -1968,13 +2155,13 @@ class OrderTest(WebMockTestCase):
1968 2155
1969 market.order_precision.return_value = 4 2156 market.order_precision.return_value = 4
1970 with self.subTest(debug=True),\ 2157 with self.subTest(debug=True),\
1971 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: 2158 mock.patch('portfolio.ReportStore') as report_store:
1972 portfolio.TradeStore.debug = True 2159 portfolio.TradeStore.debug = True
1973 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 2160 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1974 D("0.1"), "BTC", "long", market, "trade") 2161 D("0.1"), "BTC", "long", market, "trade")
1975 order.run() 2162 order.run()
1976 market.create_order.assert_not_called() 2163 market.create_order.assert_not_called()
1977 self.assertEqual("market.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)\n", stdout_mock.getvalue()) 2164 report_store.log_debug_action.assert_called_with("market.create_order('ETH/BTC', 'limit', 'buy', 10.0000, price=0.1, account=exchange)")
1978 self.assertEqual("open", order.status) 2165 self.assertEqual("open", order.status)
1979 self.assertEqual(1, len(order.results)) 2166 self.assertEqual(1, len(order.results))
1980 self.assertEqual(-1, order.id) 2167 self.assertEqual(-1, order.id)
@@ -1992,7 +2179,7 @@ class OrderTest(WebMockTestCase):
1992 2179
1993 market.create_order.reset_mock() 2180 market.create_order.reset_mock()
1994 with self.subTest(exception=True),\ 2181 with self.subTest(exception=True),\
1995 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: 2182 mock.patch('portfolio.ReportStore') as report_store:
1996 order = portfolio.Order("buy", portfolio.Amount("ETH", 10), 2183 order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
1997 D("0.1"), "BTC", "long", market, "trade") 2184 D("0.1"), "BTC", "long", market, "trade")
1998 market.create_order.side_effect = Exception("bouh") 2185 market.create_order.side_effect = Exception("bouh")
@@ -2000,8 +2187,7 @@ class OrderTest(WebMockTestCase):
2000 market.create_order.assert_called_once() 2187 market.create_order.assert_called_once()
2001 self.assertEqual(0, len(order.results)) 2188 self.assertEqual(0, len(order.results))
2002 self.assertEqual("error", order.status) 2189 self.assertEqual("error", order.status)
2003 self.assertRegex(stdout_mock.getvalue(), "error when running market.create_order") 2190 report_store.log_error.assert_called_once()
2004 self.assertRegex(stdout_mock.getvalue(), "Exception: bouh")
2005 2191
2006 market.create_order.reset_mock() 2192 market.create_order.reset_mock()
2007 with self.subTest(dust_amount_exception=True),\ 2193 with self.subTest(dust_amount_exception=True),\
@@ -2050,6 +2236,7 @@ class MouvementTest(WebMockTestCase):
2050 "amount": "10", "total": "1" 2236 "amount": "10", "total": "1"
2051 }) 2237 })
2052 self.assertEqual("Mouvement(2017-12-30 12:00:12 ; buy 10.00000000 ETH (1.00000000 BTC) fee: 0.1500%)", repr(mouvement)) 2238 self.assertEqual("Mouvement(2017-12-30 12:00:12 ; buy 10.00000000 ETH (1.00000000 BTC) fee: 0.1500%)", repr(mouvement))
2239
2053 mouvement = portfolio.Mouvement("ETH", "BTC", { 2240 mouvement = portfolio.Mouvement("ETH", "BTC", {
2054 "tradeID": 42, "type": "buy", 2241 "tradeID": 42, "type": "buy",
2055 "date": "garbage", "rate": "0.1", 2242 "date": "garbage", "rate": "0.1",
@@ -2057,10 +2244,401 @@ class MouvementTest(WebMockTestCase):
2057 }) 2244 })
2058 self.assertEqual("Mouvement(No date ; buy 10.00000000 ETH (1.00000000 BTC))", repr(mouvement)) 2245 self.assertEqual("Mouvement(No date ; buy 10.00000000 ETH (1.00000000 BTC))", repr(mouvement))
2059 2246
2247 def test_as_json(self):
2248 mouvement = portfolio.Mouvement("ETH", "BTC", {
2249 "tradeID": 42, "type": "buy", "fee": "0.0015",
2250 "date": "2017-12-30 12:00:12", "rate": "0.1",
2251 "amount": "10", "total": "1"
2252 })
2253 as_json = mouvement.as_json()
2254
2255 self.assertEqual(D("0.0015"), as_json["fee_rate"])
2256 self.assertEqual(portfolio.datetime(2017, 12, 30, 12, 0, 12), as_json["date"])
2257 self.assertEqual("buy", as_json["action"])
2258 self.assertEqual(D("10"), as_json["total"])
2259 self.assertEqual(D("1"), as_json["total_in_base"])
2260 self.assertEqual("BTC", as_json["base_currency"])
2261 self.assertEqual("ETH", as_json["currency"])
2262
2263@unittest.skipUnless("unit" in limits, "Unit skipped")
2264class ReportStoreTest(WebMockTestCase):
2265 def test_add_log(self):
2266 portfolio.ReportStore.add_log({"foo": "bar"})
2267
2268 self.assertEqual({"foo": "bar", "date": mock.ANY}, portfolio.ReportStore.logs[0])
2269
2270 def test_set_verbose(self):
2271 with self.subTest(verbose=True):
2272 portfolio.ReportStore.set_verbose(True)
2273 self.assertTrue(portfolio.ReportStore.verbose_print)
2274
2275 with self.subTest(verbose=False):
2276 portfolio.ReportStore.set_verbose(False)
2277 self.assertFalse(portfolio.ReportStore.verbose_print)
2278
2279 def test_print_log(self):
2280 with self.subTest(verbose=True),\
2281 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
2282 portfolio.ReportStore.set_verbose(True)
2283 portfolio.ReportStore.print_log("Coucou")
2284 portfolio.ReportStore.print_log(portfolio.Amount("BTC", 1))
2285 self.assertEqual(stdout_mock.getvalue(), "Coucou\n1.00000000 BTC\n")
2286
2287 with self.subTest(verbose=False),\
2288 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
2289 portfolio.ReportStore.set_verbose(False)
2290 portfolio.ReportStore.print_log("Coucou")
2291 portfolio.ReportStore.print_log(portfolio.Amount("BTC", 1))
2292 self.assertEqual(stdout_mock.getvalue(), "")
2293
2294 def test_to_json(self):
2295 portfolio.ReportStore.logs.append({"foo": "bar"})
2296 self.assertEqual('[{"foo": "bar"}]', portfolio.ReportStore.to_json())
2297 portfolio.ReportStore.logs.append({"date": portfolio.datetime(2018, 2, 24)})
2298 self.assertEqual('[{"foo": "bar"}, {"date": "2018-02-24T00:00:00"}]', portfolio.ReportStore.to_json())
2299 portfolio.ReportStore.logs.append({"amount": portfolio.Amount("BTC", 1)})
2300 with self.assertRaises(TypeError):
2301 portfolio.ReportStore.to_json()
2302
2303 @mock.patch.object(portfolio.ReportStore, "print_log")
2304 @mock.patch.object(portfolio.ReportStore, "add_log")
2305 def test_log_stage(self, add_log, print_log):
2306 portfolio.ReportStore.log_stage("foo")
2307 print_log.assert_has_calls([
2308 mock.call("-----------"),
2309 mock.call("[Stage] foo"),
2310 ])
2311 add_log.assert_called_once_with({'type': 'stage', 'stage': 'foo'})
2312
2313 @mock.patch.object(portfolio.ReportStore, "print_log")
2314 @mock.patch.object(portfolio.ReportStore, "add_log")
2315 @mock.patch("store.BalanceStore")
2316 def test_log_balances(self, balance_store, add_log, print_log):
2317 balance_store.as_json.return_value = "json"
2318 balance_store.all = { "FOO": "bar", "BAR": "baz" }
2319
2320 portfolio.ReportStore.log_balances("market")
2321 print_log.assert_has_calls([
2322 mock.call("[Balance]"),
2323 mock.call("\tbar"),
2324 mock.call("\tbaz"),
2325 ])
2326 add_log.assert_called_once_with({'type': 'balance', 'balances': 'json'})
2327
2328 @mock.patch.object(portfolio.ReportStore, "print_log")
2329 @mock.patch.object(portfolio.ReportStore, "add_log")
2330 def test_log_tickers(self, add_log, print_log):
2331 market = mock.Mock()
2332 amounts = {
2333 "BTC": portfolio.Amount("BTC", 10),
2334 "ETH": portfolio.Amount("BTC", D("0.3"))
2335 }
2336 amounts["ETH"].rate = D("0.1")
2337
2338 portfolio.ReportStore.log_tickers(market, amounts, "BTC", "default", "total")
2339 print_log.assert_not_called()
2340 add_log.assert_called_once_with({
2341 'type': 'tickers',
2342 'compute_value': 'default',
2343 'balance_type': 'total',
2344 'currency': 'BTC',
2345 'balances': {
2346 'BTC': D('10'),
2347 'ETH': D('0.3')
2348 },
2349 'rates': {
2350 'BTC': None,
2351 'ETH': D('0.1')
2352 },
2353 'total': D('10.3')
2354 })
2355
2356 @mock.patch.object(portfolio.ReportStore, "print_log")
2357 @mock.patch.object(portfolio.ReportStore, "add_log")
2358 def test_log_dispatch(self, add_log, print_log):
2359 amount = portfolio.Amount("BTC", "10.3")
2360 amounts = {
2361 "BTC": portfolio.Amount("BTC", 10),
2362 "ETH": portfolio.Amount("BTC", D("0.3"))
2363 }
2364 portfolio.ReportStore.log_dispatch(amount, amounts, "medium", "repartition")
2365 print_log.assert_not_called()
2366 add_log.assert_called_once_with({
2367 'type': 'dispatch',
2368 'liquidity': 'medium',
2369 'repartition_ratio': 'repartition',
2370 'total_amount': {
2371 'currency': 'BTC',
2372 'value': D('10.3')
2373 },
2374 'repartition': {
2375 'BTC': D('10'),
2376 'ETH': D('0.3')
2377 }
2378 })
2379
2380 @mock.patch.object(portfolio.ReportStore, "print_log")
2381 @mock.patch.object(portfolio.ReportStore, "add_log")
2382 def test_log_trades(self, add_log, print_log):
2383 trade_mock1 = mock.Mock()
2384 trade_mock2 = mock.Mock()
2385 trade_mock1.as_json.return_value = { "trade": "1" }
2386 trade_mock2.as_json.return_value = { "trade": "2" }
2387
2388 matching_and_trades = [
2389 (True, trade_mock1),
2390 (False, trade_mock2),
2391 ]
2392 portfolio.ReportStore.log_trades(matching_and_trades, "only", "debug")
2393
2394 print_log.assert_not_called()
2395 add_log.assert_called_with({
2396 'type': 'trades',
2397 'only': 'only',
2398 'debug': 'debug',
2399 'trades': [
2400 {'trade': '1', 'skipped': False},
2401 {'trade': '2', 'skipped': True}
2402 ]
2403 })
2404
2405 @mock.patch.object(portfolio.ReportStore, "print_log")
2406 @mock.patch.object(portfolio.ReportStore, "add_log")
2407 @mock.patch.object(portfolio.TradeStore, "print_all_with_order")
2408 def test_log_orders(self, print_all_with_order, add_log, print_log):
2409 order_mock1 = mock.Mock()
2410 order_mock2 = mock.Mock()
2411
2412 order_mock1.as_json.return_value = "order1"
2413 order_mock2.as_json.return_value = "order2"
2414
2415 orders = [order_mock1, order_mock2]
2416
2417 portfolio.ReportStore.log_orders(orders, tick="tick",
2418 only="only", compute_value="compute_value")
2419
2420 print_log.assert_called_once_with("[Orders]")
2421 print_all_with_order.assert_called_once_with(ind="\t")
2422
2423 add_log.assert_called_with({
2424 'type': 'orders',
2425 'only': 'only',
2426 'compute_value': 'compute_value',
2427 'tick': 'tick',
2428 'orders': ['order1', 'order2']
2429 })
2430
2431 @mock.patch.object(portfolio.ReportStore, "print_log")
2432 @mock.patch.object(portfolio.ReportStore, "add_log")
2433 def test_log_order(self, add_log, print_log):
2434 order_mock = mock.Mock()
2435 order_mock.as_json.return_value = "order"
2436 new_order_mock = mock.Mock()
2437 new_order_mock.as_json.return_value = "new_order"
2438 order_mock.__repr__ = mock.Mock()
2439 order_mock.__repr__.return_value = "Order Mock"
2440 new_order_mock.__repr__ = mock.Mock()
2441 new_order_mock.__repr__.return_value = "New order Mock"
2442
2443 with self.subTest(finished=True):
2444 portfolio.ReportStore.log_order(order_mock, 1, finished=True)
2445 print_log.assert_called_once_with("[Order] Finished Order Mock")
2446 add_log.assert_called_once_with({
2447 'type': 'order',
2448 'tick': 1,
2449 'update': None,
2450 'order': 'order',
2451 'compute_value': None,
2452 'new_order': None
2453 })
2454
2455 add_log.reset_mock()
2456 print_log.reset_mock()
2457
2458 with self.subTest(update="waiting"):
2459 portfolio.ReportStore.log_order(order_mock, 1, update="waiting")
2460 print_log.assert_called_once_with("[Order] Order Mock, tick 1, waiting")
2461 add_log.assert_called_once_with({
2462 'type': 'order',
2463 'tick': 1,
2464 'update': 'waiting',
2465 'order': 'order',
2466 'compute_value': None,
2467 'new_order': None
2468 })
2469
2470 add_log.reset_mock()
2471 print_log.reset_mock()
2472 with self.subTest(update="adjusting"):
2473 portfolio.ReportStore.log_order(order_mock, 3,
2474 update="adjusting", new_order=new_order_mock,
2475 compute_value="default")
2476 print_log.assert_called_once_with("[Order] Order Mock, tick 3, cancelling and adjusting to New order Mock")
2477 add_log.assert_called_once_with({
2478 'type': 'order',
2479 'tick': 3,
2480 'update': 'adjusting',
2481 'order': 'order',
2482 'compute_value': "default",
2483 'new_order': 'new_order'
2484 })
2485
2486 add_log.reset_mock()
2487 print_log.reset_mock()
2488 with self.subTest(update="market_fallback"):
2489 portfolio.ReportStore.log_order(order_mock, 7,
2490 update="market_fallback", new_order=new_order_mock)
2491 print_log.assert_called_once_with("[Order] Order Mock, tick 7, fallbacking to market value")
2492 add_log.assert_called_once_with({
2493 'type': 'order',
2494 'tick': 7,
2495 'update': 'market_fallback',
2496 'order': 'order',
2497 'compute_value': None,
2498 'new_order': 'new_order'
2499 })
2500
2501 add_log.reset_mock()
2502 print_log.reset_mock()
2503 with self.subTest(update="market_adjusting"):
2504 portfolio.ReportStore.log_order(order_mock, 17,
2505 update="market_adjust", new_order=new_order_mock)
2506 print_log.assert_called_once_with("[Order] Order Mock, tick 17, market value, cancelling and adjusting to New order Mock")
2507 add_log.assert_called_once_with({
2508 'type': 'order',
2509 'tick': 17,
2510 'update': 'market_adjust',
2511 'order': 'order',
2512 'compute_value': None,
2513 'new_order': 'new_order'
2514 })
2515
2516 @mock.patch.object(portfolio.ReportStore, "print_log")
2517 @mock.patch.object(portfolio.ReportStore, "add_log")
2518 def test_log_move_balances(self, add_log, print_log):
2519 needed = {
2520 "BTC": portfolio.Amount("BTC", 10),
2521 "USDT": 1
2522 }
2523 moving = {
2524 "BTC": portfolio.Amount("BTC", 3),
2525 "USDT": -2
2526 }
2527 portfolio.ReportStore.log_move_balances(needed, moving, True)
2528 print_log.assert_not_called()
2529 add_log.assert_called_once_with({
2530 'type': 'move_balances',
2531 'debug': True,
2532 'needed': {
2533 'BTC': D('10'),
2534 'USDT': 1
2535 },
2536 'moving': {
2537 'BTC': D('3'),
2538 'USDT': -2
2539 }
2540 })
2541
2542 @mock.patch.object(portfolio.ReportStore, "print_log")
2543 @mock.patch.object(portfolio.ReportStore, "add_log")
2544 def test_log_http_request(self, add_log, print_log):
2545 response = mock.Mock()
2546 response.status_code = 200
2547 response.text = "Hey"
2548
2549 portfolio.ReportStore.log_http_request("method", "url", "body",
2550 "headers", response)
2551 print_log.assert_not_called()
2552 add_log.assert_called_once_with({
2553 'type': 'http_request',
2554 'method': 'method',
2555 'url': 'url',
2556 'body': 'body',
2557 'headers': 'headers',
2558 'status': 200,
2559 'response': 'Hey'
2560 })
2561
2562 @mock.patch.object(portfolio.ReportStore, "print_log")
2563 @mock.patch.object(portfolio.ReportStore, "add_log")
2564 def test_log_error(self, add_log, print_log):
2565 with self.subTest(message=None, exception=None):
2566 portfolio.ReportStore.log_error("action")
2567 print_log.assert_called_once_with("[Error] action")
2568 add_log.assert_called_once_with({
2569 'type': 'error',
2570 'action': 'action',
2571 'exception_class': None,
2572 'exception_message': None,
2573 'message': None
2574 })
2575
2576 print_log.reset_mock()
2577 add_log.reset_mock()
2578 with self.subTest(message="Hey", exception=None):
2579 portfolio.ReportStore.log_error("action", message="Hey")
2580 print_log.assert_has_calls([
2581 mock.call("[Error] action"),
2582 mock.call("\tHey")
2583 ])
2584 add_log.assert_called_once_with({
2585 'type': 'error',
2586 'action': 'action',
2587 'exception_class': None,
2588 'exception_message': None,
2589 'message': "Hey"
2590 })
2591
2592 print_log.reset_mock()
2593 add_log.reset_mock()
2594 with self.subTest(message=None, exception=Exception("bouh")):
2595 portfolio.ReportStore.log_error("action", exception=Exception("bouh"))
2596 print_log.assert_has_calls([
2597 mock.call("[Error] action"),
2598 mock.call("\tException: bouh")
2599 ])
2600 add_log.assert_called_once_with({
2601 'type': 'error',
2602 'action': 'action',
2603 'exception_class': "Exception",
2604 'exception_message': "bouh",
2605 'message': None
2606 })
2607
2608 print_log.reset_mock()
2609 add_log.reset_mock()
2610 with self.subTest(message="Hey", exception=Exception("bouh")):
2611 portfolio.ReportStore.log_error("action", message="Hey", exception=Exception("bouh"))
2612 print_log.assert_has_calls([
2613 mock.call("[Error] action"),
2614 mock.call("\tException: bouh"),
2615 mock.call("\tHey")
2616 ])
2617 add_log.assert_called_once_with({
2618 'type': 'error',
2619 'action': 'action',
2620 'exception_class': "Exception",
2621 'exception_message': "bouh",
2622 'message': "Hey"
2623 })
2624
2625 @mock.patch.object(portfolio.ReportStore, "print_log")
2626 @mock.patch.object(portfolio.ReportStore, "add_log")
2627 def test_log_debug_action(self, add_log, print_log):
2628 portfolio.ReportStore.log_debug_action("Hey")
2629
2630 print_log.assert_called_once_with("[Debug] Hey")
2631 add_log.assert_called_once_with({
2632 'type': 'debug_action',
2633 'action': 'Hey'
2634 })
2635
2060@unittest.skipUnless("acceptance" in limits, "Acceptance skipped") 2636@unittest.skipUnless("acceptance" in limits, "Acceptance skipped")
2061class AcceptanceTest(WebMockTestCase): 2637class AcceptanceTest(WebMockTestCase):
2062 @unittest.expectedFailure 2638 @unittest.expectedFailure
2063 def test_success_sell_only_necessary(self): 2639 def test_success_sell_only_necessary(self):
2640 # FIXME: catch stdout
2641 portfolio.ReportStore.verbose_print = False
2064 fetch_balance = { 2642 fetch_balance = {
2065 "ETH": { 2643 "ETH": {
2066 "exchange_free": D("1.0"), 2644 "exchange_free": D("1.0"),