aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2018-02-10 13:52:46 +0100
committerIsmaël Bouya <ismael.bouya@normalesup.org>2018-02-10 15:25:27 +0100
commit6ca5a1ec669593fa915a2824efca068c975f9caa (patch)
tree375b3f21b4f228eafea7fae364caa2e7bfb8422b
parentc51687d2b0cbad5460d8424f550014502d84696e (diff)
downloadTrader-6ca5a1ec669593fa915a2824efca068c975f9caa.tar.gz
Trader-6ca5a1ec669593fa915a2824efca068c975f9caa.tar.zst
Trader-6ca5a1ec669593fa915a2824efca068c975f9caa.zip
Separate store and add helper
-rw-r--r--helper.py151
-rw-r--r--portfolio.py309
-rw-r--r--store.py102
-rw-r--r--test.py668
4 files changed, 621 insertions, 609 deletions
diff --git a/helper.py b/helper.py
new file mode 100644
index 0000000..8a29f40
--- /dev/null
+++ b/helper.py
@@ -0,0 +1,151 @@
1import time
2from ccxt import ExchangeError
3from store import *
4
5def move_balances(market, debug=False):
6 needed_in_margin = {}
7 for trade in TradeStore.all:
8 if trade.trade_type == "short":
9 if trade.value_to.currency not in needed_in_margin:
10 needed_in_margin[trade.value_to.currency] = 0
11 needed_in_margin[trade.value_to.currency] += abs(trade.value_to)
12 for currency, needed in needed_in_margin.items():
13 current_balance = BalanceStore.all[currency].margin_free
14 delta = (needed - current_balance).value
15 # FIXME: don't remove too much if there are open margin position
16 if delta > 0:
17 if debug:
18 print("market.transfer_balance({}, {}, 'exchange', 'margin')".format(currency, delta))
19 else:
20 market.transfer_balance(currency, delta, "exchange", "margin")
21 elif delta < 0:
22 if debug:
23 print("market.transfer_balance({}, {}, 'margin', 'exchange')".format(currency, -delta))
24 else:
25 market.transfer_balance(currency, -delta, "margin", "exchange")
26
27 BalanceStore.fetch_balances(market)
28
29ticker_cache = {}
30ticker_cache_timestamp = time.time()
31def get_ticker(c1, c2, market, refresh=False):
32 global ticker_cache, ticker_cache_timestamp
33 def invert(ticker):
34 return {
35 "inverted": True,
36 "average": (1/ticker["bid"] + 1/ticker["ask"]) / 2,
37 "original": ticker,
38 }
39 def augment_ticker(ticker):
40 ticker.update({
41 "inverted": False,
42 "average": (ticker["bid"] + ticker["ask"] ) / 2,
43 })
44
45 if time.time() - ticker_cache_timestamp > 5:
46 ticker_cache = {}
47 ticker_cache_timestamp = time.time()
48 elif not refresh:
49 if (c1, c2, market.__class__) in ticker_cache:
50 return ticker_cache[(c1, c2, market.__class__)]
51 if (c2, c1, market.__class__) in ticker_cache:
52 return invert(ticker_cache[(c2, c1, market.__class__)])
53
54 try:
55 ticker_cache[(c1, c2, market.__class__)] = market.fetch_ticker("{}/{}".format(c1, c2))
56 augment_ticker(ticker_cache[(c1, c2, market.__class__)])
57 except ExchangeError:
58 try:
59 ticker_cache[(c2, c1, market.__class__)] = market.fetch_ticker("{}/{}".format(c2, c1))
60 augment_ticker(ticker_cache[(c2, c1, market.__class__)])
61 except ExchangeError:
62 ticker_cache[(c1, c2, market.__class__)] = None
63 return get_ticker(c1, c2, market)
64
65fees_cache = {}
66def fetch_fees(market):
67 global fees_cache
68 if market.__class__ not in fees_cache:
69 fees_cache[market.__class__] = market.fetch_fees()
70 return fees_cache[market.__class__]
71
72def prepare_trades(market, base_currency="BTC", compute_value="average", debug=False):
73 BalanceStore.fetch_balances(market)
74 values_in_base = BalanceStore.in_currency(base_currency, market, compute_value=compute_value)
75 total_base_value = sum(values_in_base.values())
76 new_repartition = BalanceStore.dispatch_assets(total_base_value)
77 # Recompute it in case we have new currencies
78 values_in_base = BalanceStore.in_currency(base_currency, market, compute_value=compute_value)
79 TradeStore.compute_trades(values_in_base, new_repartition, market=market, debug=debug)
80
81def update_trades(market, base_currency="BTC", compute_value="average", only=None, debug=False):
82 BalanceStore.fetch_balances(market)
83 values_in_base = BalanceStore.in_currency(base_currency, market, compute_value=compute_value)
84 total_base_value = sum(values_in_base.values())
85 new_repartition = BalanceStore.dispatch_assets(total_base_value)
86 TradeStore.compute_trades(values_in_base, new_repartition, only=only, market=market, debug=debug)
87
88def prepare_trades_to_sell_all(market, base_currency="BTC", compute_value="average", debug=False):
89 BalanceStore.fetch_balances(market)
90 values_in_base = BalanceStore.in_currency(base_currency, market, compute_value=compute_value)
91 total_base_value = sum(values_in_base.values())
92 new_repartition = BalanceStore.dispatch_assets(total_base_value, repartition={ base_currency: (1, "long") })
93 TradeStore.compute_trades(values_in_base, new_repartition, market=market, debug=debug)
94
95def follow_orders(verbose=True, sleep=None):
96 if sleep is None:
97 sleep = 7 if TradeStore.debug else 30
98 tick = 0
99 while len(TradeStore.all_orders(state="open")) > 0:
100 time.sleep(sleep)
101 tick += 1
102 for order in TradeStore.all_orders(state="open"):
103 if order.get_status() != "open":
104 if verbose:
105 print("finished {}".format(order))
106 else:
107 order.trade.update_order(order, tick)
108 if verbose:
109 print("All orders finished")
110
111def print_orders(market, base_currency="BTC"):
112 prepare_trades(market, base_currency=base_currency, compute_value="average")
113 TradeStore.prepare_orders(compute_value="average")
114 for currency, balance in BalanceStore.all.items():
115 print(balance)
116 TradeStore.print_all_with_order()
117
118def make_orders(market, base_currency="BTC"):
119 prepare_trades(market, base_currency=base_currency)
120 for trade in TradeStore.all:
121 print(trade)
122 for order in trade.orders:
123 print("\t", order, sep="")
124 order.run()
125
126def process_sell_all_sell(market, base_currency="BTC", debug=False):
127 prepare_trades_to_sell_all(market, debug=debug)
128 TradeStore.prepare_orders(compute_value="average")
129 print("------------------")
130 for currency, balance in BalanceStore.all.items():
131 print(balance)
132 print("------------------")
133 TradeStore.print_all_with_order()
134 print("------------------")
135 TradeStore.run_orders()
136 follow_orders()
137
138def process_sell_all_buy(market, base_currency="BTC", debug=False):
139 prepare_trades(market, debug=debug)
140 TradeStore.prepare_orders()
141 print("------------------")
142 for currency, balance in BalanceStore.all.items():
143 print(balance)
144 print("------------------")
145 TradeStore.print_all_with_order()
146 print("------------------")
147 move_balances(market, debug=debug)
148 TradeStore.run_orders()
149 follow_orders()
150
151
diff --git a/portfolio.py b/portfolio.py
index 45fbef9..efd9b84 100644
--- a/portfolio.py
+++ b/portfolio.py
@@ -1,14 +1,13 @@
1from ccxt import ExchangeError
2import time 1import time
3from decimal import Decimal as D, ROUND_DOWN 2from decimal import Decimal as D, ROUND_DOWN
4# Put your poloniex api key in market.py 3# Put your poloniex api key in market.py
5from market import market
6from json import JSONDecodeError 4from json import JSONDecodeError
7import requests 5import requests
6import helper as h
7from store import *
8 8
9# FIXME: correctly handle web call timeouts 9# FIXME: correctly handle web call timeouts
10 10
11
12class Portfolio: 11class Portfolio:
13 URL = "https://cryptoportfolio.io/wp-content/uploads/portfolio/json/cryptoportfolio.json" 12 URL = "https://cryptoportfolio.io/wp-content/uploads/portfolio/json/cryptoportfolio.json"
14 liquidities = {} 13 liquidities = {}
@@ -69,6 +68,24 @@ class Portfolio:
69 "high": high_liquidity, 68 "high": high_liquidity,
70 } 69 }
71 70
71class Computation:
72 computations = {
73 "default": lambda x, y: x[y],
74 "average": lambda x, y: x["average"],
75 "bid": lambda x, y: x["bid"],
76 "ask": lambda x, y: x["ask"],
77 }
78
79 @classmethod
80 def compute_value(cls, ticker, action, compute_value="default"):
81 if action == "buy":
82 action = "ask"
83 if action == "sell":
84 action = "bid"
85 if isinstance(compute_value, str):
86 compute_value = cls.computations[compute_value]
87 return compute_value(ticker, action)
88
72class Amount: 89class Amount:
73 def __init__(self, currency, value, linked_to=None, ticker=None, rate=None): 90 def __init__(self, currency, value, linked_to=None, ticker=None, rate=None):
74 self.currency = currency 91 self.currency = currency
@@ -86,9 +103,9 @@ class Amount:
86 self.value * rate, 103 self.value * rate,
87 linked_to=self, 104 linked_to=self,
88 rate=rate) 105 rate=rate)
89 asset_ticker = Trade.get_ticker(self.currency, other_currency, market) 106 asset_ticker = h.get_ticker(self.currency, other_currency, market)
90 if asset_ticker is not None: 107 if asset_ticker is not None:
91 rate = Trade.compute_value(asset_ticker, action, compute_value=compute_value) 108 rate = Computation.compute_value(asset_ticker, action, compute_value=compute_value)
92 return Amount( 109 return Amount(
93 other_currency, 110 other_currency,
94 self.value * rate, 111 self.value * rate,
@@ -180,7 +197,6 @@ class Amount:
180 return "Amount({:.8f} {} -> {})".format(self.value, self.currency, repr(self.linked_to)) 197 return "Amount({:.8f} {} -> {})".format(self.value, self.currency, repr(self.linked_to))
181 198
182class Balance: 199class Balance:
183 known_balances = {}
184 200
185 def __init__(self, currency, hash_): 201 def __init__(self, currency, hash_):
186 self.currency = currency 202 self.currency = currency
@@ -201,69 +217,6 @@ class Balance:
201 ]: 217 ]:
202 setattr(self, key, Amount(base_currency, hash_.get(key, 0))) 218 setattr(self, key, Amount(base_currency, hash_.get(key, 0)))
203 219
204 @classmethod
205 def in_currency(cls, other_currency, market, compute_value="average", type="total"):
206 amounts = {}
207 for currency in cls.known_balances:
208 balance = cls.known_balances[currency]
209 other_currency_amount = getattr(balance, type)\
210 .in_currency(other_currency, market, compute_value=compute_value)
211 amounts[currency] = other_currency_amount
212 return amounts
213
214 @classmethod
215 def currencies(cls):
216 return cls.known_balances.keys()
217
218 @classmethod
219 def fetch_balances(cls, market):
220 all_balances = market.fetch_all_balances()
221 for currency, balance in all_balances.items():
222 if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \
223 currency in cls.known_balances:
224 cls.known_balances[currency] = cls(currency, balance)
225 return cls.known_balances
226
227 @classmethod
228 def dispatch_assets(cls, amount, repartition=None):
229 if repartition is None:
230 repartition = Portfolio.repartition()
231 sum_ratio = sum([v[0] for k, v in repartition.items()])
232 amounts = {}
233 for currency, (ptt, trade_type) in repartition.items():
234 amounts[currency] = ptt * amount / sum_ratio
235 if trade_type == "short":
236 amounts[currency] = - amounts[currency]
237 if currency not in cls.known_balances:
238 cls.known_balances[currency] = cls(currency, {})
239 return amounts
240
241 @classmethod
242 def prepare_trades(cls, market, base_currency="BTC", compute_value="average", debug=False):
243 cls.fetch_balances(market)
244 values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value)
245 total_base_value = sum(values_in_base.values())
246 new_repartition = cls.dispatch_assets(total_base_value)
247 # Recompute it in case we have new currencies
248 values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value)
249 Trade.compute_trades(values_in_base, new_repartition, market=market, debug=debug)
250
251 @classmethod
252 def update_trades(cls, market, base_currency="BTC", compute_value="average", only=None, debug=False):
253 cls.fetch_balances(market)
254 values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value)
255 total_base_value = sum(values_in_base.values())
256 new_repartition = cls.dispatch_assets(total_base_value)
257 Trade.compute_trades(values_in_base, new_repartition, only=only, market=market, debug=debug)
258
259 @classmethod
260 def prepare_trades_to_sell_all(cls, market, base_currency="BTC", compute_value="average", debug=False):
261 cls.fetch_balances(market)
262 values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value)
263 total_base_value = sum(values_in_base.values())
264 new_repartition = cls.dispatch_assets(total_base_value, repartition={ base_currency: (1, "long") })
265 Trade.compute_trades(values_in_base, new_repartition, market=market, debug=debug)
266
267 def __repr__(self): 220 def __repr__(self):
268 if self.exchange_total > 0: 221 if self.exchange_total > 0:
269 if self.exchange_free > 0 and self.exchange_used > 0: 222 if self.exchange_free > 0 and self.exchange_used > 0:
@@ -296,18 +249,7 @@ class Balance:
296 249
297 return "Balance({}".format(self.currency) + "".join([exchange, margin, total]) + ")" 250 return "Balance({}".format(self.currency) + "".join([exchange, margin, total]) + ")"
298 251
299class Computation:
300 computations = {
301 "default": lambda x, y: x[y],
302 "average": lambda x, y: x["average"],
303 "bid": lambda x, y: x["bid"],
304 "ask": lambda x, y: x["ask"],
305 }
306
307class Trade: 252class Trade:
308 debug = False
309 trades = []
310
311 def __init__(self, value_from, value_to, currency, market=None): 253 def __init__(self, value_from, value_to, currency, market=None):
312 # We have value_from of currency, and want to finish with value_to of 254 # We have value_from of currency, and want to finish with value_to of
313 # that currency. value_* may not be in currency's terms 255 # that currency. value_* may not be in currency's terms
@@ -323,105 +265,6 @@ class Trade:
323 self.value_from.linked_to = Amount(self.currency, 0) 265 self.value_from.linked_to = Amount(self.currency, 0)
324 self.base_currency = self.value_from.currency 266 self.base_currency = self.value_from.currency
325 267
326 fees_cache = {}
327 @classmethod
328 def fetch_fees(cls, market):
329 if market.__class__ not in cls.fees_cache:
330 cls.fees_cache[market.__class__] = market.fetch_fees()
331 return cls.fees_cache[market.__class__]
332
333 ticker_cache = {}
334 ticker_cache_timestamp = time.time()
335 @classmethod
336 def get_ticker(cls, c1, c2, market, refresh=False):
337 def invert(ticker):
338 return {
339 "inverted": True,
340 "average": (1/ticker["bid"] + 1/ticker["ask"]) / 2,
341 "original": ticker,
342 }
343 def augment_ticker(ticker):
344 ticker.update({
345 "inverted": False,
346 "average": (ticker["bid"] + ticker["ask"] ) / 2,
347 })
348
349 if time.time() - cls.ticker_cache_timestamp > 5:
350 cls.ticker_cache = {}
351 cls.ticker_cache_timestamp = time.time()
352 elif not refresh:
353 if (c1, c2, market.__class__) in cls.ticker_cache:
354 return cls.ticker_cache[(c1, c2, market.__class__)]
355 if (c2, c1, market.__class__) in cls.ticker_cache:
356 return invert(cls.ticker_cache[(c2, c1, market.__class__)])
357
358 try:
359 cls.ticker_cache[(c1, c2, market.__class__)] = market.fetch_ticker("{}/{}".format(c1, c2))
360 augment_ticker(cls.ticker_cache[(c1, c2, market.__class__)])
361 except ExchangeError:
362 try:
363 cls.ticker_cache[(c2, c1, market.__class__)] = market.fetch_ticker("{}/{}".format(c2, c1))
364 augment_ticker(cls.ticker_cache[(c2, c1, market.__class__)])
365 except ExchangeError:
366 cls.ticker_cache[(c1, c2, market.__class__)] = None
367 return cls.get_ticker(c1, c2, market)
368
369 @classmethod
370 def compute_trades(cls, values_in_base, new_repartition, only=None, market=None, debug=False):
371 cls.debug = cls.debug or debug
372 base_currency = sum(values_in_base.values()).currency
373 for currency in Balance.currencies():
374 if currency == base_currency:
375 continue
376 value_from = values_in_base.get(currency, Amount(base_currency, 0))
377 value_to = new_repartition.get(currency, Amount(base_currency, 0))
378 if value_from.value * value_to.value < 0:
379 trade_1 = cls(value_from, Amount(base_currency, 0), currency, market=market)
380 if only is None or trade_1.action == only:
381 cls.trades.append(trade_1)
382 trade_2 = cls(Amount(base_currency, 0), value_to, currency, market=market)
383 if only is None or trade_2.action == only:
384 cls.trades.append(trade_2)
385 else:
386 trade = cls(
387 value_from,
388 value_to,
389 currency,
390 market=market
391 )
392 if only is None or trade.action == only:
393 cls.trades.append(trade)
394 return cls.trades
395
396 @classmethod
397 def prepare_orders(cls, only=None, compute_value="default"):
398 for trade in cls.trades:
399 if only is None or trade.action == only:
400 trade.prepare_order(compute_value=compute_value)
401
402 @classmethod
403 def move_balances(cls, market):
404 needed_in_margin = {}
405 for trade in cls.trades:
406 if trade.trade_type == "short":
407 if trade.value_to.currency not in needed_in_margin:
408 needed_in_margin[trade.value_to.currency] = 0
409 needed_in_margin[trade.value_to.currency] += abs(trade.value_to)
410 for currency, needed in needed_in_margin.items():
411 current_balance = Balance.known_balances[currency].margin_free
412 delta = (needed - current_balance).value
413 # FIXME: don't remove too much if there are open margin position
414 if delta > 0:
415 if cls.debug:
416 print("market.transfer_balance({}, {}, 'exchange', 'margin')".format(currency, delta))
417 else:
418 market.transfer_balance(currency, delta, "exchange", "margin")
419 elif delta < 0:
420 if cls.debug:
421 print("market.transfer_balance({}, {}, 'margin', 'exchange')".format(currency, -delta))
422 else:
423 market.transfer_balance(currency, -delta, "margin", "exchange")
424
425 @property 268 @property
426 def action(self): 269 def action(self):
427 if self.value_from == self.value_to: 270 if self.value_from == self.value_to:
@@ -481,11 +324,11 @@ class Trade:
481 def prepare_order(self, compute_value="default"): 324 def prepare_order(self, compute_value="default"):
482 if self.action is None: 325 if self.action is None:
483 return 326 return
484 ticker = Trade.get_ticker(self.currency, self.base_currency, self.market) 327 ticker = h.get_ticker(self.currency, self.base_currency, self.market)
485 inverted = ticker["inverted"] 328 inverted = ticker["inverted"]
486 if inverted: 329 if inverted:
487 ticker = ticker["original"] 330 ticker = ticker["original"]
488 rate = Trade.compute_value(ticker, self.order_action(inverted), compute_value=compute_value) 331 rate = Computation.compute_value(ticker, self.order_action(inverted), compute_value=compute_value)
489 # 0.1 332 # 0.1
490 333
491 delta_in_base = abs(self.value_from - self.value_to) 334 delta_in_base = abs(self.value_from - self.value_to)
@@ -536,51 +379,6 @@ class Trade:
536 delta - self.filled_amount, rate, currency, self.trade_type, 379 delta - self.filled_amount, rate, currency, self.trade_type,
537 self.market, self, close_if_possible=close_if_possible)) 380 self.market, self, close_if_possible=close_if_possible))
538 381
539 @classmethod
540 def compute_value(cls, ticker, action, compute_value="default"):
541 if action == "buy":
542 action = "ask"
543 if action == "sell":
544 action = "bid"
545 if isinstance(compute_value, str):
546 compute_value = Computation.computations[compute_value]
547 return compute_value(ticker, action)
548
549 @classmethod
550 def all_orders(cls, state=None):
551 all_orders = sum(map(lambda v: v.orders, cls.trades), [])
552 if state is None:
553 return all_orders
554 else:
555 return list(filter(lambda o: o.status == state, all_orders))
556
557 @classmethod
558 def run_orders(cls):
559 for order in cls.all_orders(state="pending"):
560 order.run()
561
562 @classmethod
563 def follow_orders(cls, verbose=True, sleep=None):
564 if sleep is None:
565 sleep = 7 if cls.debug else 30
566 tick = 0
567 while len(cls.all_orders(state="open")) > 0:
568 time.sleep(sleep)
569 tick += 1
570 for order in cls.all_orders(state="open"):
571 if order.get_status() != "open":
572 if verbose:
573 print("finished {}".format(order))
574 else:
575 order.trade.update_order(order, tick)
576 if verbose:
577 print("All orders finished")
578
579 @classmethod
580 def update_all_orders_status(cls):
581 for order in cls.all_orders(state="open"):
582 order.get_status()
583
584 def __repr__(self): 382 def __repr__(self):
585 return "Trade({} -> {} in {}, {})".format( 383 return "Trade({} -> {} in {}, {})".format(
586 self.value_from, 384 self.value_from,
@@ -588,11 +386,6 @@ class Trade:
588 self.currency, 386 self.currency,
589 self.action) 387 self.action)
590 388
591 @classmethod
592 def print_all_with_order(cls):
593 for trade in cls.trades:
594 trade.print_with_order()
595
596 def print_with_order(self): 389 def print_with_order(self):
597 print(self) 390 print(self)
598 for order in self.orders: 391 for order in self.orders:
@@ -612,7 +405,6 @@ class Order:
612 self.status = "pending" 405 self.status = "pending"
613 self.trade = trade 406 self.trade = trade
614 self.close_if_possible = close_if_possible 407 self.close_if_possible = close_if_possible
615 self.debug = trade.debug
616 408
617 def __repr__(self): 409 def __repr__(self):
618 return "Order({} {} {} at {} {} [{}]{})".format( 410 return "Order({} {} {} at {} {} [{}]{})".format(
@@ -648,7 +440,7 @@ class Order:
648 symbol = "{}/{}".format(self.amount.currency, self.base_currency) 440 symbol = "{}/{}".format(self.amount.currency, self.base_currency)
649 amount = round(self.amount, self.market.order_precision(symbol)).value 441 amount = round(self.amount, self.market.order_precision(symbol)).value
650 442
651 if self.debug: 443 if TradeStore.debug:
652 print("market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format( 444 print("market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format(
653 symbol, self.action, amount, self.rate, self.account)) 445 symbol, self.action, amount, self.rate, self.account))
654 self.status = "open" 446 self.status = "open"
@@ -665,7 +457,7 @@ class Order:
665 print(self.error_message) 457 print(self.error_message)
666 458
667 def get_status(self): 459 def get_status(self):
668 if self.debug: 460 if TradeStore.debug:
669 return self.status 461 return self.status
670 # other states are "closed" and "canceled" 462 # other states are "closed" and "canceled"
671 if self.status == "open": 463 if self.status == "open":
@@ -675,7 +467,7 @@ class Order:
675 return self.status 467 return self.status
676 468
677 def mark_finished_order(self): 469 def mark_finished_order(self):
678 if self.debug: 470 if TradeStore.debug:
679 return 471 return
680 if self.status == "closed": 472 if self.status == "closed":
681 if self.trade_type == "short" and self.action == "buy" and self.close_if_possible: 473 if self.trade_type == "short" and self.action == "buy" and self.close_if_possible:
@@ -683,7 +475,7 @@ class Order:
683 475
684 fetch_cache_timestamp = None 476 fetch_cache_timestamp = None
685 def fetch(self, force=False): 477 def fetch(self, force=False):
686 if self.debug or (not force and self.fetch_cache_timestamp is not None 478 if TradeStore.debug or (not force and self.fetch_cache_timestamp is not None
687 and time.time() - self.fetch_cache_timestamp < 10): 479 and time.time() - self.fetch_cache_timestamp < 10):
688 return 480 return
689 self.fetch_cache_timestamp = time.time() 481 self.fetch_cache_timestamp = time.time()
@@ -725,7 +517,7 @@ class Order:
725 self.base_currency, mouvement_hash)) 517 self.base_currency, mouvement_hash))
726 518
727 def cancel(self): 519 def cancel(self):
728 if self.debug: 520 if TradeStore.debug:
729 self.status = "canceled" 521 self.status = "canceled"
730 return 522 return
731 self.market.cancel_order(self.result['id']) 523 self.market.cancel_order(self.result['id'])
@@ -744,45 +536,6 @@ class Mouvement:
744 # rate * total = total_in_base 536 # rate * total = total_in_base
745 self.total_in_base = Amount(base_currency, hash_["total"]) 537 self.total_in_base = Amount(base_currency, hash_["total"])
746 538
747def print_orders(market, base_currency="BTC"):
748 Balance.prepare_trades(market, base_currency=base_currency, compute_value="average")
749 Trade.prepare_orders(compute_value="average")
750 for currency, balance in Balance.known_balances.items():
751 print(balance)
752 Trade.print_all_with_order()
753
754def make_orders(market, base_currency="BTC"):
755 Balance.prepare_trades(market, base_currency=base_currency)
756 for trade in Trade.trades:
757 print(trade)
758 for order in trade.orders:
759 print("\t", order, sep="")
760 order.run()
761
762def process_sell_all_sell(market, base_currency="BTC", debug=False):
763 Balance.prepare_trades_to_sell_all(market, debug=debug)
764 Trade.prepare_orders(compute_value="average")
765 print("------------------")
766 for currency, balance in Balance.known_balances.items():
767 print(balance)
768 print("------------------")
769 Trade.print_all_with_order()
770 print("------------------")
771 Trade.run_orders()
772 Trade.follow_orders()
773
774def process_sell_all_buy(market, base_currency="BTC", debug=False):
775 Balance.prepare_trades(market, debug=debug)
776 Trade.prepare_orders()
777 print("------------------")
778 for currency, balance in Balance.known_balances.items():
779 print(balance)
780 print("------------------")
781 Trade.print_all_with_order()
782 print("------------------")
783 Trade.move_balances(market)
784 Trade.run_orders()
785 Trade.follow_orders()
786
787if __name__ == '__main__': 539if __name__ == '__main__':
788 print_orders(market) 540 from market import market
541 h.print_orders(market)
diff --git a/store.py b/store.py
new file mode 100644
index 0000000..4e46878
--- /dev/null
+++ b/store.py
@@ -0,0 +1,102 @@
1import portfolio
2
3__all__ = ["BalanceStore", "TradeStore"]
4
5class BalanceStore:
6 all = {}
7
8 @classmethod
9 def currencies(cls):
10 return cls.all.keys()
11
12 @classmethod
13 def in_currency(cls, other_currency, market, compute_value="average", type="total"):
14 amounts = {}
15 for currency, balance in cls.all.items():
16 other_currency_amount = getattr(balance, type)\
17 .in_currency(other_currency, market, compute_value=compute_value)
18 amounts[currency] = other_currency_amount
19 return amounts
20
21 @classmethod
22 def fetch_balances(cls, market):
23 all_balances = market.fetch_all_balances()
24 for currency, balance in all_balances.items():
25 if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \
26 currency in cls.all:
27 cls.all[currency] = portfolio.Balance(currency, balance)
28
29 @classmethod
30 def dispatch_assets(cls, amount, repartition=None):
31 if repartition is None:
32 repartition = portfolio.Portfolio.repartition()
33 sum_ratio = sum([v[0] for k, v in repartition.items()])
34 amounts = {}
35 for currency, (ptt, trade_type) in repartition.items():
36 amounts[currency] = ptt * amount / sum_ratio
37 if trade_type == "short":
38 amounts[currency] = - amounts[currency]
39 if currency not in BalanceStore.all:
40 cls.all[currency] = portfolio.Balance(currency, {})
41 return amounts
42
43class TradeStore:
44 all = []
45 debug = False
46
47 @classmethod
48 def compute_trades(cls, values_in_base, new_repartition, only=None, market=None, debug=False):
49 cls.debug = cls.debug or debug
50 base_currency = sum(values_in_base.values()).currency
51 for currency in BalanceStore.currencies():
52 if currency == base_currency:
53 continue
54 value_from = values_in_base.get(currency, portfolio.Amount(base_currency, 0))
55 value_to = new_repartition.get(currency, portfolio.Amount(base_currency, 0))
56 if value_from.value * value_to.value < 0:
57 trade_1 = portfolio.Trade(value_from, portfolio.Amount(base_currency, 0), currency, market=market)
58 if only is None or trade_1.action == only:
59 cls.all.append(trade_1)
60 trade_2 = portfolio.Trade(portfolio.Amount(base_currency, 0), value_to, currency, market=market)
61 if only is None or trade_2.action == only:
62 cls.all.append(trade_2)
63 else:
64 trade = portfolio.Trade(
65 value_from,
66 value_to,
67 currency,
68 market=market
69 )
70 if only is None or trade.action == only:
71 cls.all.append(trade)
72
73 @classmethod
74 def prepare_orders(cls, only=None, compute_value="default"):
75 for trade in cls.all:
76 if only is None or trade.action == only:
77 trade.prepare_order(compute_value=compute_value)
78
79 @classmethod
80 def print_all_with_order(cls):
81 for trade in cls.all:
82 trade.print_with_order()
83
84 @classmethod
85 def run_orders(cls):
86 for order in cls.all_orders(state="pending"):
87 order.run()
88
89 @classmethod
90 def all_orders(cls, state=None):
91 all_orders = sum(map(lambda v: v.orders, cls.all), [])
92 if state is None:
93 return all_orders
94 else:
95 return list(filter(lambda o: o.status == state, all_orders))
96
97 @classmethod
98 def update_all_orders_status(cls):
99 for order in cls.all_orders(state="open"):
100 order.get_status()
101
102
diff --git a/test.py b/test.py
index b27eb74..aae1dc8 100644
--- a/test.py
+++ b/test.py
@@ -5,6 +5,7 @@ from unittest import mock
5import requests 5import requests
6import requests_mock 6import requests_mock
7from io import StringIO 7from io import StringIO
8import helper
8 9
9class WebMockTestCase(unittest.TestCase): 10class WebMockTestCase(unittest.TestCase):
10 import time 11 import time
@@ -15,16 +16,18 @@ class WebMockTestCase(unittest.TestCase):
15 self.wm.start() 16 self.wm.start()
16 17
17 self.patchers = [ 18 self.patchers = [
18 mock.patch.multiple(portfolio.Balance, known_balances={}), 19 mock.patch.multiple(portfolio.BalanceStore,
20 all={},),
21 mock.patch.multiple(portfolio.TradeStore,
22 all=[],
23 debug=False),
19 mock.patch.multiple(portfolio.Portfolio, data=None, liquidities={}), 24 mock.patch.multiple(portfolio.Portfolio, data=None, liquidities={}),
20 mock.patch.multiple(portfolio.Trade,
21 ticker_cache={},
22 ticker_cache_timestamp=self.time.time(),
23 fees_cache={},
24 debug=False,
25 trades=[]),
26 mock.patch.multiple(portfolio.Computation, 25 mock.patch.multiple(portfolio.Computation,
27 computations=portfolio.Computation.computations) 26 computations=portfolio.Computation.computations),
27 mock.patch.multiple(helper,
28 fees_cache={},
29 ticker_cache={},
30 ticker_cache_timestamp=self.time.time()),
28 ] 31 ]
29 for patcher in self.patchers: 32 for patcher in self.patchers:
30 patcher.start() 33 patcher.start()
@@ -149,12 +152,12 @@ class AmountTest(WebMockTestCase):
149 self.assertEqual(amount, amount.in_currency("ETC", None)) 152 self.assertEqual(amount, amount.in_currency("ETC", None))
150 153
151 ticker_mock = unittest.mock.Mock() 154 ticker_mock = unittest.mock.Mock()
152 with mock.patch.object(portfolio.Trade, 'get_ticker', new=ticker_mock): 155 with mock.patch.object(helper, 'get_ticker', new=ticker_mock):
153 ticker_mock.return_value = None 156 ticker_mock.return_value = None
154 157
155 self.assertRaises(Exception, amount.in_currency, "ETH", None) 158 self.assertRaises(Exception, amount.in_currency, "ETH", None)
156 159
157 with mock.patch.object(portfolio.Trade, 'get_ticker', new=ticker_mock): 160 with mock.patch.object(helper, 'get_ticker', new=ticker_mock):
158 ticker_mock.return_value = { 161 ticker_mock.return_value = {
159 "bid": D("0.2"), 162 "bid": D("0.2"),
160 "ask": D("0.4"), 163 "ask": D("0.4"),
@@ -361,37 +364,6 @@ class AmountTest(WebMockTestCase):
361 self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1)) 364 self.assertEqual("Amount(32.00000000 BTX -> Amount(12000.00000000 USDT -> Amount(0.10000000 BTC)))", repr(amount1))
362 365
363class BalanceTest(WebMockTestCase): 366class BalanceTest(WebMockTestCase):
364 def setUp(self):
365 super(BalanceTest, self).setUp()
366
367 self.fetch_balance = {
368 "ETC": {
369 "exchange_free": 0,
370 "exchange_used": 0,
371 "exchange_total": 0,
372 "margin_total": 0,
373 },
374 "USDT": {
375 "exchange_free": D("6.0"),
376 "exchange_used": D("1.2"),
377 "exchange_total": D("7.2"),
378 "margin_total": 0,
379 },
380 "XVG": {
381 "exchange_free": 16,
382 "exchange_used": 0,
383 "exchange_total": 16,
384 "margin_total": 0,
385 },
386 "XMR": {
387 "exchange_free": 0,
388 "exchange_used": 0,
389 "exchange_total": 0,
390 "margin_total": D("-1.0"),
391 "margin_free": 0,
392 },
393 }
394
395 def test_values(self): 367 def test_values(self):
396 balance = portfolio.Balance("BTC", { 368 balance = portfolio.Balance("BTC", {
397 "exchange_total": "0.65", 369 "exchange_total": "0.65",
@@ -426,91 +398,100 @@ class BalanceTest(WebMockTestCase):
426 self.assertEqual(portfolio.D("0.4"), balance.margin_lending_fees.value) 398 self.assertEqual(portfolio.D("0.4"), balance.margin_lending_fees.value)
427 self.assertEqual("USDT", balance.margin_lending_fees.currency) 399 self.assertEqual("USDT", balance.margin_lending_fees.currency)
428 400
429 @mock.patch.object(portfolio.Trade, "get_ticker") 401 def test__repr(self):
430 def test_in_currency(self, get_ticker): 402 self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX])",
431 portfolio.Balance.known_balances = { 403 repr(portfolio.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 })))
432 "BTC": portfolio.Balance("BTC", { 404 balance = portfolio.Balance("BTX", { "exchange_total": 3,
433 "total": "0.65", 405 "exchange_used": 1, "exchange_free": 2 })
434 "exchange_total":"0.65", 406 self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance))
435 "exchange_free": "0.35",
436 "exchange_used": "0.30"}),
437 "ETH": portfolio.Balance("ETH", {
438 "total": 3,
439 "exchange_total": 3,
440 "exchange_free": 3,
441 "exchange_used": 0}),
442 }
443 market = mock.Mock()
444 get_ticker.return_value = {
445 "bid": D("0.09"),
446 "ask": D("0.11"),
447 "average": D("0.1"),
448 }
449 407
450 amounts = portfolio.Balance.in_currency("BTC", market) 408 balance = portfolio.Balance("BTX", { "margin_total": 3,
451 self.assertEqual("BTC", amounts["ETH"].currency) 409 "margin_borrowed": 1, "margin_free": 2 })
452 self.assertEqual(D("0.65"), amounts["BTC"].value) 410 self.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX + borrowed 1.00000000 BTX = 3.00000000 BTX])", repr(balance))
453 self.assertEqual(D("0.30"), amounts["ETH"].value)
454 411
455 amounts = portfolio.Balance.in_currency("BTC", market, compute_value="bid") 412 balance = portfolio.Balance("BTX", { "margin_total": -3,
456 self.assertEqual(D("0.65"), amounts["BTC"].value) 413 "margin_borrowed_base_price": D("0.1"),
457 self.assertEqual(D("0.27"), amounts["ETH"].value) 414 "margin_borrowed_base_currency": "BTC",
415 "margin_lending_fees": D("0.002") })
416 self.assertEqual("Balance(BTX Margin: [-3.00000000 BTX @@ 0.10000000 BTC/0.00200000 BTC])", repr(balance))
458 417
459 amounts = portfolio.Balance.in_currency("BTC", market, compute_value="bid", type="exchange_used") 418class HelperTest(WebMockTestCase):
460 self.assertEqual(D("0.30"), amounts["BTC"].value) 419 def test_get_ticker(self):
461 self.assertEqual(0, amounts["ETH"].value) 420 market = mock.Mock()
421 market.fetch_ticker.side_effect = [
422 { "bid": 1, "ask": 3 },
423 helper.ExchangeError("foo"),
424 { "bid": 10, "ask": 40 },
425 helper.ExchangeError("foo"),
426 helper.ExchangeError("foo"),
427 ]
462 428
463 def test_currencies(self): 429 ticker = helper.get_ticker("ETH", "ETC", market)
464 portfolio.Balance.known_balances = { 430 market.fetch_ticker.assert_called_with("ETH/ETC")
465 "BTC": portfolio.Balance("BTC", { 431 self.assertEqual(1, ticker["bid"])
466 "total": "0.65", 432 self.assertEqual(3, ticker["ask"])
467 "exchange_total":"0.65", 433 self.assertEqual(2, ticker["average"])
468 "exchange_free": "0.35", 434 self.assertFalse(ticker["inverted"])
469 "exchange_used": "0.30"}),
470 "ETH": portfolio.Balance("ETH", {
471 "total": 3,
472 "exchange_total": 3,
473 "exchange_free": 3,
474 "exchange_used": 0}),
475 }
476 self.assertListEqual(["BTC", "ETH"], list(portfolio.Balance.currencies()))
477 435
478 @mock.patch.object(portfolio.market, "fetch_all_balances") 436 ticker = helper.get_ticker("ETH", "XVG", market)
479 def test_fetch_balances(self, fetch_all_balances): 437 self.assertEqual(0.0625, ticker["average"])
480 fetch_all_balances.return_value = self.fetch_balance 438 self.assertTrue(ticker["inverted"])
439 self.assertIn("original", ticker)
440 self.assertEqual(10, ticker["original"]["bid"])
481 441
482 portfolio.Balance.fetch_balances(portfolio.market) 442 ticker = helper.get_ticker("XVG", "XMR", market)
483 self.assertNotIn("ETC", portfolio.Balance.currencies()) 443 self.assertIsNone(ticker)
484 self.assertListEqual(["USDT", "XVG", "XMR"], list(portfolio.Balance.currencies()))
485 444
486 portfolio.Balance.known_balances["ETC"] = portfolio.Balance("ETC", { 445 market.fetch_ticker.assert_has_calls([
487 "exchange_total": "1", "exchange_free": "0", 446 mock.call("ETH/ETC"),
488 "exchange_used": "1" }) 447 mock.call("ETH/XVG"),
489 portfolio.Balance.fetch_balances(portfolio.market) 448 mock.call("XVG/ETH"),
490 self.assertEqual(0, portfolio.Balance.known_balances["ETC"].total) 449 mock.call("XVG/XMR"),
491 self.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(portfolio.Balance.currencies())) 450 mock.call("XMR/XVG"),
451 ])
492 452
493 @mock.patch.object(portfolio.Portfolio, "repartition") 453 market2 = mock.Mock()
494 @mock.patch.object(portfolio.market, "fetch_all_balances") 454 market2.fetch_ticker.side_effect = [
495 def test_dispatch_assets(self, fetch_all_balances, repartition): 455 { "bid": 1, "ask": 3 },
496 fetch_all_balances.return_value = self.fetch_balance 456 { "bid": 1.2, "ask": 3.5 },
497 portfolio.Balance.fetch_balances(portfolio.market) 457 ]
458 ticker1 = helper.get_ticker("ETH", "ETC", market2)
459 ticker2 = helper.get_ticker("ETH", "ETC", market2)
460 ticker3 = helper.get_ticker("ETC", "ETH", market2)
461 market2.fetch_ticker.assert_called_once_with("ETH/ETC")
462 self.assertEqual(1, ticker1["bid"])
463 self.assertDictEqual(ticker1, ticker2)
464 self.assertDictEqual(ticker1, ticker3["original"])
498 465
499 self.assertNotIn("XEM", portfolio.Balance.currencies()) 466 ticker4 = helper.get_ticker("ETH", "ETC", market2, refresh=True)
467 ticker5 = helper.get_ticker("ETH", "ETC", market2)
468 self.assertEqual(1.2, ticker4["bid"])
469 self.assertDictEqual(ticker4, ticker5)
500 470
501 repartition.return_value = { 471 market3 = mock.Mock()
502 "XEM": (D("0.75"), "long"), 472 market3.fetch_ticker.side_effect = [
503 "BTC": (D("0.26"), "long"), 473 { "bid": 1, "ask": 3 },
504 } 474 { "bid": 1.2, "ask": 3.5 },
475 ]
476 ticker6 = helper.get_ticker("ETH", "ETC", market3)
477 helper.ticker_cache_timestamp -= 4
478 ticker7 = helper.get_ticker("ETH", "ETC", market3)
479 helper.ticker_cache_timestamp -= 2
480 ticker8 = helper.get_ticker("ETH", "ETC", market3)
481 self.assertDictEqual(ticker6, ticker7)
482 self.assertEqual(1.2, ticker8["bid"])
505 483
506 amounts = portfolio.Balance.dispatch_assets(portfolio.Amount("BTC", "10.1")) 484 def test_fetch_fees(self):
507 self.assertIn("XEM", portfolio.Balance.currencies()) 485 market = mock.Mock()
508 self.assertEqual(D("2.6"), amounts["BTC"].value) 486 market.fetch_fees.return_value = "Foo"
509 self.assertEqual(D("7.5"), amounts["XEM"].value) 487 self.assertEqual("Foo", helper.fetch_fees(market))
488 market.fetch_fees.assert_called_once()
489 self.assertEqual("Foo", helper.fetch_fees(market))
490 market.fetch_fees.assert_called_once()
510 491
511 @mock.patch.object(portfolio.Portfolio, "repartition") 492 @mock.patch.object(portfolio.Portfolio, "repartition")
512 @mock.patch.object(portfolio.Trade, "get_ticker") 493 @mock.patch.object(helper, "get_ticker")
513 @mock.patch.object(portfolio.Trade, "compute_trades") 494 @mock.patch.object(portfolio.TradeStore, "compute_trades")
514 def test_prepare_trades(self, compute_trades, get_ticker, repartition): 495 def test_prepare_trades(self, compute_trades, get_ticker, repartition):
515 repartition.return_value = { 496 repartition.return_value = {
516 "XEM": (D("0.75"), "long"), 497 "XEM": (D("0.75"), "long"),
@@ -541,7 +522,7 @@ class BalanceTest(WebMockTestCase):
541 "total": D("10000.0") 522 "total": D("10000.0")
542 }, 523 },
543 } 524 }
544 portfolio.Balance.prepare_trades(market) 525 helper.prepare_trades(market)
545 compute_trades.assert_called() 526 compute_trades.assert_called()
546 527
547 call = compute_trades.call_args 528 call = compute_trades.call_args
@@ -552,8 +533,8 @@ class BalanceTest(WebMockTestCase):
552 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value) 533 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value)
553 534
554 @mock.patch.object(portfolio.Portfolio, "repartition") 535 @mock.patch.object(portfolio.Portfolio, "repartition")
555 @mock.patch.object(portfolio.Trade, "get_ticker") 536 @mock.patch.object(helper, "get_ticker")
556 @mock.patch.object(portfolio.Trade, "compute_trades") 537 @mock.patch.object(portfolio.TradeStore, "compute_trades")
557 def test_update_trades(self, compute_trades, get_ticker, repartition): 538 def test_update_trades(self, compute_trades, get_ticker, repartition):
558 repartition.return_value = { 539 repartition.return_value = {
559 "XEM": (D("0.75"), "long"), 540 "XEM": (D("0.75"), "long"),
@@ -584,7 +565,7 @@ class BalanceTest(WebMockTestCase):
584 "total": D("10000.0") 565 "total": D("10000.0")
585 }, 566 },
586 } 567 }
587 portfolio.Balance.update_trades(market) 568 helper.update_trades(market)
588 compute_trades.assert_called() 569 compute_trades.assert_called()
589 570
590 call = compute_trades.call_args 571 call = compute_trades.call_args
@@ -595,8 +576,8 @@ class BalanceTest(WebMockTestCase):
595 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value) 576 self.assertEqual(D("0.7575"), call[0][1]["XEM"].value)
596 577
597 @mock.patch.object(portfolio.Portfolio, "repartition") 578 @mock.patch.object(portfolio.Portfolio, "repartition")
598 @mock.patch.object(portfolio.Trade, "get_ticker") 579 @mock.patch.object(helper, "get_ticker")
599 @mock.patch.object(portfolio.Trade, "compute_trades") 580 @mock.patch.object(portfolio.TradeStore, "compute_trades")
600 def test_prepare_trades_to_sell_all(self, compute_trades, get_ticker, repartition): 581 def test_prepare_trades_to_sell_all(self, compute_trades, get_ticker, repartition):
601 def _get_ticker(c1, c2, market): 582 def _get_ticker(c1, c2, market):
602 if c1 == "USDT" and c2 == "BTC": 583 if c1 == "USDT" and c2 == "BTC":
@@ -621,7 +602,7 @@ class BalanceTest(WebMockTestCase):
621 "total": D("10000.0") 602 "total": D("10000.0")
622 }, 603 },
623 } 604 }
624 portfolio.Balance.prepare_trades_to_sell_all(market) 605 helper.prepare_trades_to_sell_all(market)
625 repartition.assert_not_called() 606 repartition.assert_not_called()
626 compute_trades.assert_called() 607 compute_trades.assert_called()
627 608
@@ -631,89 +612,243 @@ class BalanceTest(WebMockTestCase):
631 self.assertEqual(D("0.01"), call[0][0]["XVG"].value) 612 self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
632 self.assertEqual(D("1.01"), call[0][1]["BTC"].value) 613 self.assertEqual(D("1.01"), call[0][1]["BTC"].value)
633 614
634 def test__repr(self): 615 @unittest.skip("TODO")
635 self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX])", 616 def test_follow_orders(self):
636 repr(portfolio.Balance("BTX", { "exchange_free": 2, "exchange_total": 2 }))) 617 pass
637 balance = portfolio.Balance("BTX", { "exchange_total": 3,
638 "exchange_used": 1, "exchange_free": 2 })
639 self.assertEqual("Balance(BTX Exch: [✔2.00000000 BTX + ❌1.00000000 BTX = 3.00000000 BTX])", repr(balance))
640 618
641 balance = portfolio.Balance("BTX", { "margin_total": 3,
642 "margin_borrowed": 1, "margin_free": 2 })
643 self.assertEqual("Balance(BTX Margin: [✔2.00000000 BTX + borrowed 1.00000000 BTX = 3.00000000 BTX])", repr(balance))
644 619
645 balance = portfolio.Balance("BTX", { "margin_total": -3, 620class TradeStoreTest(WebMockTestCase):
646 "margin_borrowed_base_price": D("0.1"), 621 @unittest.skip("TODO")
647 "margin_borrowed_base_currency": "BTC", 622 def test_compute_trades(self):
648 "margin_lending_fees": D("0.002") }) 623 pass
649 self.assertEqual("Balance(BTX Margin: [-3.00000000 BTX @@ 0.10000000 BTC/0.00200000 BTC])", repr(balance))
650 624
651class TradeTest(WebMockTestCase): 625 def test_prepare_orders(self):
626 trade_mock1 = mock.Mock()
627 trade_mock2 = mock.Mock()
652 628
653 def test_get_ticker(self): 629 portfolio.TradeStore.all.append(trade_mock1)
630 portfolio.TradeStore.all.append(trade_mock2)
631
632 portfolio.TradeStore.prepare_orders()
633 trade_mock1.prepare_order.assert_called_with(compute_value="default")
634 trade_mock2.prepare_order.assert_called_with(compute_value="default")
635
636 portfolio.TradeStore.prepare_orders(compute_value="bla")
637 trade_mock1.prepare_order.assert_called_with(compute_value="bla")
638 trade_mock2.prepare_order.assert_called_with(compute_value="bla")
639
640 trade_mock1.prepare_order.reset_mock()
641 trade_mock2.prepare_order.reset_mock()
642
643 trade_mock1.action = "foo"
644 trade_mock2.action = "bar"
645 portfolio.TradeStore.prepare_orders(only="bar")
646 trade_mock1.prepare_order.assert_not_called()
647 trade_mock2.prepare_order.assert_called_with(compute_value="default")
648
649 def test_print_all_with_order(self):
650 trade_mock1 = mock.Mock()
651 trade_mock2 = mock.Mock()
652 trade_mock3 = mock.Mock()
653 portfolio.TradeStore.all = [trade_mock1, trade_mock2, trade_mock3]
654
655 portfolio.TradeStore.print_all_with_order()
656
657 trade_mock1.print_with_order.assert_called()
658 trade_mock2.print_with_order.assert_called()
659 trade_mock3.print_with_order.assert_called()
660
661 @mock.patch.object(portfolio.TradeStore, "all_orders")
662 def test_run_orders(self, all_orders):
663 order_mock1 = mock.Mock()
664 order_mock2 = mock.Mock()
665 order_mock3 = mock.Mock()
666 all_orders.return_value = [order_mock1, order_mock2, order_mock3]
667 portfolio.TradeStore.run_orders()
668 all_orders.assert_called_with(state="pending")
669
670 order_mock1.run.assert_called()
671 order_mock2.run.assert_called()
672 order_mock3.run.assert_called()
673
674 def test_all_orders(self):
675 trade_mock1 = mock.Mock()
676 trade_mock2 = mock.Mock()
677
678 order_mock1 = mock.Mock()
679 order_mock2 = mock.Mock()
680 order_mock3 = mock.Mock()
681
682 trade_mock1.orders = [order_mock1, order_mock2]
683 trade_mock2.orders = [order_mock3]
684
685 order_mock1.status = "pending"
686 order_mock2.status = "open"
687 order_mock3.status = "open"
688
689 portfolio.TradeStore.all.append(trade_mock1)
690 portfolio.TradeStore.all.append(trade_mock2)
691
692 orders = portfolio.TradeStore.all_orders()
693 self.assertEqual(3, len(orders))
694
695 open_orders = portfolio.TradeStore.all_orders(state="open")
696 self.assertEqual(2, len(open_orders))
697 self.assertEqual([order_mock2, order_mock3], open_orders)
698
699 @mock.patch.object(portfolio.TradeStore, "all_orders")
700 def test_update_all_orders_status(self, all_orders):
701 order_mock1 = mock.Mock()
702 order_mock2 = mock.Mock()
703 order_mock3 = mock.Mock()
704 all_orders.return_value = [order_mock1, order_mock2, order_mock3]
705 portfolio.TradeStore.update_all_orders_status()
706 all_orders.assert_called_with(state="open")
707
708 order_mock1.get_status.assert_called()
709 order_mock2.get_status.assert_called()
710 order_mock3.get_status.assert_called()
711
712
713class BalanceStoreTest(WebMockTestCase):
714 def setUp(self):
715 super(BalanceStoreTest, self).setUp()
716
717 self.fetch_balance = {
718 "ETC": {
719 "exchange_free": 0,
720 "exchange_used": 0,
721 "exchange_total": 0,
722 "margin_total": 0,
723 },
724 "USDT": {
725 "exchange_free": D("6.0"),
726 "exchange_used": D("1.2"),
727 "exchange_total": D("7.2"),
728 "margin_total": 0,
729 },
730 "XVG": {
731 "exchange_free": 16,
732 "exchange_used": 0,
733 "exchange_total": 16,
734 "margin_total": 0,
735 },
736 "XMR": {
737 "exchange_free": 0,
738 "exchange_used": 0,
739 "exchange_total": 0,
740 "margin_total": D("-1.0"),
741 "margin_free": 0,
742 },
743 }
744
745 @mock.patch.object(helper, "get_ticker")
746 def test_in_currency(self, get_ticker):
747 portfolio.BalanceStore.all = {
748 "BTC": portfolio.Balance("BTC", {
749 "total": "0.65",
750 "exchange_total":"0.65",
751 "exchange_free": "0.35",
752 "exchange_used": "0.30"}),
753 "ETH": portfolio.Balance("ETH", {
754 "total": 3,
755 "exchange_total": 3,
756 "exchange_free": 3,
757 "exchange_used": 0}),
758 }
654 market = mock.Mock() 759 market = mock.Mock()
655 market.fetch_ticker.side_effect = [ 760 get_ticker.return_value = {
656 { "bid": 1, "ask": 3 }, 761 "bid": D("0.09"),
657 portfolio.ExchangeError("foo"), 762 "ask": D("0.11"),
658 { "bid": 10, "ask": 40 }, 763 "average": D("0.1"),
659 portfolio.ExchangeError("foo"), 764 }
660 portfolio.ExchangeError("foo"),
661 ]
662 765
663 ticker = portfolio.Trade.get_ticker("ETH", "ETC", market) 766 amounts = portfolio.BalanceStore.in_currency("BTC", market)
664 market.fetch_ticker.assert_called_with("ETH/ETC") 767 self.assertEqual("BTC", amounts["ETH"].currency)
665 self.assertEqual(1, ticker["bid"]) 768 self.assertEqual(D("0.65"), amounts["BTC"].value)
666 self.assertEqual(3, ticker["ask"]) 769 self.assertEqual(D("0.30"), amounts["ETH"].value)
667 self.assertEqual(2, ticker["average"])
668 self.assertFalse(ticker["inverted"])
669 770
670 ticker = portfolio.Trade.get_ticker("ETH", "XVG", market) 771 amounts = portfolio.BalanceStore.in_currency("BTC", market, compute_value="bid")
671 self.assertEqual(0.0625, ticker["average"]) 772 self.assertEqual(D("0.65"), amounts["BTC"].value)
672 self.assertTrue(ticker["inverted"]) 773 self.assertEqual(D("0.27"), amounts["ETH"].value)
673 self.assertIn("original", ticker)
674 self.assertEqual(10, ticker["original"]["bid"])
675 774
676 ticker = portfolio.Trade.get_ticker("XVG", "XMR", market) 775 amounts = portfolio.BalanceStore.in_currency("BTC", market, compute_value="bid", type="exchange_used")
677 self.assertIsNone(ticker) 776 self.assertEqual(D("0.30"), amounts["BTC"].value)
777 self.assertEqual(0, amounts["ETH"].value)
678 778
679 market.fetch_ticker.assert_has_calls([ 779 def test_fetch_balances(self):
680 mock.call("ETH/ETC"), 780 market = mock.Mock()
681 mock.call("ETH/XVG"), 781 market.fetch_all_balances.return_value = self.fetch_balance
682 mock.call("XVG/ETH"),
683 mock.call("XVG/XMR"),
684 mock.call("XMR/XVG"),
685 ])
686 782
687 market2 = mock.Mock() 783 portfolio.BalanceStore.fetch_balances(market)
688 market2.fetch_ticker.side_effect = [ 784 self.assertNotIn("ETC", portfolio.BalanceStore.currencies())
689 { "bid": 1, "ask": 3 }, 785 self.assertListEqual(["USDT", "XVG", "XMR"], list(portfolio.BalanceStore.currencies()))
690 { "bid": 1.2, "ask": 3.5 },
691 ]
692 ticker1 = portfolio.Trade.get_ticker("ETH", "ETC", market2)
693 ticker2 = portfolio.Trade.get_ticker("ETH", "ETC", market2)
694 ticker3 = portfolio.Trade.get_ticker("ETC", "ETH", market2)
695 market2.fetch_ticker.assert_called_once_with("ETH/ETC")
696 self.assertEqual(1, ticker1["bid"])
697 self.assertDictEqual(ticker1, ticker2)
698 self.assertDictEqual(ticker1, ticker3["original"])
699 786
700 ticker4 = portfolio.Trade.get_ticker("ETH", "ETC", market2, refresh=True) 787 portfolio.BalanceStore.all["ETC"] = portfolio.Balance("ETC", {
701 ticker5 = portfolio.Trade.get_ticker("ETH", "ETC", market2) 788 "exchange_total": "1", "exchange_free": "0",
702 self.assertEqual(1.2, ticker4["bid"]) 789 "exchange_used": "1" })
703 self.assertDictEqual(ticker4, ticker5) 790 portfolio.BalanceStore.fetch_balances(market)
791 self.assertEqual(0, portfolio.BalanceStore.all["ETC"].total)
792 self.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(portfolio.BalanceStore.currencies()))
704 793
705 market3 = mock.Mock() 794 @mock.patch.object(portfolio.Portfolio, "repartition")
706 market3.fetch_ticker.side_effect = [ 795 def test_dispatch_assets(self, repartition):
707 { "bid": 1, "ask": 3 }, 796 market = mock.Mock()
708 { "bid": 1.2, "ask": 3.5 }, 797 market.fetch_all_balances.return_value = self.fetch_balance
709 ] 798 portfolio.BalanceStore.fetch_balances(market)
710 ticker6 = portfolio.Trade.get_ticker("ETH", "ETC", market3) 799
711 portfolio.Trade.ticker_cache_timestamp -= 4 800 self.assertNotIn("XEM", portfolio.BalanceStore.currencies())
712 ticker7 = portfolio.Trade.get_ticker("ETH", "ETC", market3) 801
713 portfolio.Trade.ticker_cache_timestamp -= 2 802 repartition.return_value = {
714 ticker8 = portfolio.Trade.get_ticker("ETH", "ETC", market3) 803 "XEM": (D("0.75"), "long"),
715 self.assertDictEqual(ticker6, ticker7) 804 "BTC": (D("0.26"), "long"),
716 self.assertEqual(1.2, ticker8["bid"]) 805 }
806
807 amounts = portfolio.BalanceStore.dispatch_assets(portfolio.Amount("BTC", "10.1"))
808 self.assertIn("XEM", portfolio.BalanceStore.currencies())
809 self.assertEqual(D("2.6"), amounts["BTC"].value)
810 self.assertEqual(D("7.5"), amounts["XEM"].value)
811
812 def test_currencies(self):
813 portfolio.BalanceStore.all = {
814 "BTC": portfolio.Balance("BTC", {
815 "total": "0.65",
816 "exchange_total":"0.65",
817 "exchange_free": "0.35",
818 "exchange_used": "0.30"}),
819 "ETH": portfolio.Balance("ETH", {
820 "total": 3,
821 "exchange_total": 3,
822 "exchange_free": 3,
823 "exchange_used": 0}),
824 }
825 self.assertListEqual(["BTC", "ETH"], list(portfolio.BalanceStore.currencies()))
826
827class ComputationTest(WebMockTestCase):
828 def test_compute_value(self):
829 compute = mock.Mock()
830 portfolio.Computation.compute_value("foo", "buy", compute_value=compute)
831 compute.assert_called_with("foo", "ask")
832
833 compute.reset_mock()
834 portfolio.Computation.compute_value("foo", "sell", compute_value=compute)
835 compute.assert_called_with("foo", "bid")
836
837 compute.reset_mock()
838 portfolio.Computation.compute_value("foo", "ask", compute_value=compute)
839 compute.assert_called_with("foo", "ask")
840
841 compute.reset_mock()
842 portfolio.Computation.compute_value("foo", "bid", compute_value=compute)
843 compute.assert_called_with("foo", "bid")
844
845 compute.reset_mock()
846 portfolio.Computation.computations["test"] = compute
847 portfolio.Computation.compute_value("foo", "bid", compute_value="test")
848 compute.assert_called_with("foo", "bid")
849
850
851class TradeTest(WebMockTestCase):
717 852
718 def test_values_assertion(self): 853 def test_values_assertion(self):
719 value_from = portfolio.Amount("BTC", "1.0") 854 value_from = portfolio.Amount("BTC", "1.0")
@@ -736,14 +871,6 @@ class TradeTest(WebMockTestCase):
736 trade = portfolio.Trade(value_from, value_to, "ETH") 871 trade = portfolio.Trade(value_from, value_to, "ETH")
737 self.assertEqual(0, trade.value_from.linked_to) 872 self.assertEqual(0, trade.value_from.linked_to)
738 873
739 def test_fetch_fees(self):
740 market = mock.Mock()
741 market.fetch_fees.return_value = "Foo"
742 self.assertEqual("Foo", portfolio.Trade.fetch_fees(market))
743 market.fetch_fees.assert_called_once()
744 self.assertEqual("Foo", portfolio.Trade.fetch_fees(market))
745 market.fetch_fees.assert_called_once()
746
747 def test_action(self): 874 def test_action(self):
748 value_from = portfolio.Amount("BTC", "1.0") 875 value_from = portfolio.Amount("BTC", "1.0")
749 value_from.linked_to = portfolio.Amount("ETH", "10.0") 876 value_from.linked_to = portfolio.Amount("ETH", "10.0")
@@ -821,34 +948,6 @@ class TradeTest(WebMockTestCase):
821 948
822 self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount) 949 self.assertEqual(portfolio.Amount("ETH", "0.31"), trade.filled_amount)
823 950
824 def test_prepare_orders(self):
825 trade_mock1 = mock.Mock()
826 trade_mock2 = mock.Mock()
827
828 portfolio.Trade.trades.append(trade_mock1)
829 portfolio.Trade.trades.append(trade_mock2)
830
831 portfolio.Trade.prepare_orders()
832 trade_mock1.prepare_order.assert_called_with(compute_value="default")
833 trade_mock2.prepare_order.assert_called_with(compute_value="default")
834
835 portfolio.Trade.prepare_orders(compute_value="bla")
836 trade_mock1.prepare_order.assert_called_with(compute_value="bla")
837 trade_mock2.prepare_order.assert_called_with(compute_value="bla")
838
839 trade_mock1.prepare_order.reset_mock()
840 trade_mock2.prepare_order.reset_mock()
841
842 trade_mock1.action = "foo"
843 trade_mock2.action = "bar"
844 portfolio.Trade.prepare_orders(only="bar")
845 trade_mock1.prepare_order.assert_not_called()
846 trade_mock2.prepare_order.assert_called_with(compute_value="default")
847
848 @unittest.skip("TODO")
849 def test_compute_trades(self):
850 pass
851
852 @unittest.skip("TODO") 951 @unittest.skip("TODO")
853 def test_prepare_order(self): 952 def test_prepare_order(self):
854 pass 953 pass
@@ -857,77 +956,6 @@ class TradeTest(WebMockTestCase):
857 def test_update_order(self): 956 def test_update_order(self):
858 pass 957 pass
859 958
860 @unittest.skip("TODO")
861 def test_follow_orders(self):
862 pass
863
864 @unittest.skip("TODO")
865 def test_move_balances(self):
866 pass
867
868 def test_all_orders(self):
869 trade_mock1 = mock.Mock()
870 trade_mock2 = mock.Mock()
871
872 order_mock1 = mock.Mock()
873 order_mock2 = mock.Mock()
874 order_mock3 = mock.Mock()
875
876 trade_mock1.orders = [order_mock1, order_mock2]
877 trade_mock2.orders = [order_mock3]
878
879 order_mock1.status = "pending"
880 order_mock2.status = "open"
881 order_mock3.status = "open"
882
883 portfolio.Trade.trades.append(trade_mock1)
884 portfolio.Trade.trades.append(trade_mock2)
885
886 orders = portfolio.Trade.all_orders()
887 self.assertEqual(3, len(orders))
888
889 open_orders = portfolio.Trade.all_orders(state="open")
890 self.assertEqual(2, len(open_orders))
891 self.assertEqual([order_mock2, order_mock3], open_orders)
892
893 @mock.patch.object(portfolio.Trade, "all_orders")
894 def test_run_orders(self, all_orders):
895 order_mock1 = mock.Mock()
896 order_mock2 = mock.Mock()
897 order_mock3 = mock.Mock()
898 all_orders.return_value = [order_mock1, order_mock2, order_mock3]
899 portfolio.Trade.run_orders()
900 all_orders.assert_called_with(state="pending")
901
902 order_mock1.run.assert_called()
903 order_mock2.run.assert_called()
904 order_mock3.run.assert_called()
905
906 @mock.patch.object(portfolio.Trade, "all_orders")
907 def test_update_all_orders_status(self, all_orders):
908 order_mock1 = mock.Mock()
909 order_mock2 = mock.Mock()
910 order_mock3 = mock.Mock()
911 all_orders.return_value = [order_mock1, order_mock2, order_mock3]
912 portfolio.Trade.update_all_orders_status()
913 all_orders.assert_called_with(state="open")
914
915 order_mock1.get_status.assert_called()
916 order_mock2.get_status.assert_called()
917 order_mock3.get_status.assert_called()
918
919 def test_print_all_with_order(self):
920 trade_mock1 = mock.Mock()
921 trade_mock2 = mock.Mock()
922 trade_mock3 = mock.Mock()
923 portfolio.Trade.trades = [trade_mock1, trade_mock2, trade_mock3]
924
925 portfolio.Trade.print_all_with_order()
926
927 trade_mock1.print_with_order.assert_called()
928 trade_mock2.print_with_order.assert_called()
929 trade_mock3.print_with_order.assert_called()
930
931 @mock.patch('sys.stdout', new_callable=StringIO) 959 @mock.patch('sys.stdout', new_callable=StringIO)
932 def test_print_with_order(self, mock_stdout): 960 def test_print_with_order(self, mock_stdout):
933 value_from = portfolio.Amount("BTC", "0.5") 961 value_from = portfolio.Amount("BTC", "0.5")
@@ -951,28 +979,6 @@ class TradeTest(WebMockTestCase):
951 self.assertEqual("\tMock 1", out[1]) 979 self.assertEqual("\tMock 1", out[1])
952 self.assertEqual("\tMock 2", out[2]) 980 self.assertEqual("\tMock 2", out[2])
953 981
954 def test_compute_value(self):
955 compute = mock.Mock()
956 portfolio.Trade.compute_value("foo", "buy", compute_value=compute)
957 compute.assert_called_with("foo", "ask")
958
959 compute.reset_mock()
960 portfolio.Trade.compute_value("foo", "sell", compute_value=compute)
961 compute.assert_called_with("foo", "bid")
962
963 compute.reset_mock()
964 portfolio.Trade.compute_value("foo", "ask", compute_value=compute)
965 compute.assert_called_with("foo", "ask")
966
967 compute.reset_mock()
968 portfolio.Trade.compute_value("foo", "bid", compute_value=compute)
969 compute.assert_called_with("foo", "bid")
970
971 compute.reset_mock()
972 portfolio.Computation.computations["test"] = compute
973 portfolio.Trade.compute_value("foo", "bid", compute_value="test")
974 compute.assert_called_with("foo", "bid")
975
976 def test__repr(self): 982 def test__repr(self):
977 value_from = portfolio.Amount("BTC", "0.5") 983 value_from = portfolio.Amount("BTC", "0.5")
978 value_from.linked_to = portfolio.Amount("ETH", "10.0") 984 value_from.linked_to = portfolio.Amount("ETH", "10.0")
@@ -1045,7 +1051,7 @@ class AcceptanceTest(WebMockTestCase):
1045 "ask": D("0.0012") 1051 "ask": D("0.0012")
1046 } 1052 }
1047 if symbol == "USDT/BTC": 1053 if symbol == "USDT/BTC":
1048 raise portfolio.ExchangeError 1054 raise helper.ExchangeError
1049 if symbol == "BTC/USDT": 1055 if symbol == "BTC/USDT":
1050 return { 1056 return {
1051 "symbol": "BTC/USDT", 1057 "symbol": "BTC/USDT",
@@ -1059,15 +1065,15 @@ class AcceptanceTest(WebMockTestCase):
1059 market.fetch_ticker.side_effect = fetch_ticker 1065 market.fetch_ticker.side_effect = fetch_ticker
1060 with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition): 1066 with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition):
1061 # Action 1 1067 # Action 1
1062 portfolio.Balance.prepare_trades(market) 1068 helper.prepare_trades(market)
1063 1069
1064 balances = portfolio.Balance.known_balances 1070 balances = portfolio.BalanceStore.all
1065 self.assertEqual(portfolio.Amount("ETH", 1), balances["ETH"].total) 1071 self.assertEqual(portfolio.Amount("ETH", 1), balances["ETH"].total)
1066 self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total) 1072 self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total)
1067 self.assertEqual(portfolio.Amount("XVG", 1000), balances["XVG"].total) 1073 self.assertEqual(portfolio.Amount("XVG", 1000), balances["XVG"].total)
1068 1074
1069 1075
1070 trades = portfolio.Trade.trades 1076 trades = TradeStore.all
1071 self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades[0].value_from) 1077 self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades[0].value_from)
1072 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[0].value_to) 1078 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades[0].value_to)
1073 self.assertEqual("dispose", trades[0].action) 1079 self.assertEqual("dispose", trades[0].action)
@@ -1123,7 +1129,7 @@ class AcceptanceTest(WebMockTestCase):
1123 market.order_precision.return_value = 8 1129 market.order_precision.return_value = 8
1124 1130
1125 # Action 3 1131 # Action 3
1126 portfolio.Trade.run_orders() 1132 portfolio.TradeStore.run_orders()
1127 1133
1128 self.assertEqual("open", all_orders[0].status) 1134 self.assertEqual("open", all_orders[0].status)
1129 self.assertEqual("open", all_orders[1].status) 1135 self.assertEqual("open", all_orders[1].status)
@@ -1131,7 +1137,7 @@ class AcceptanceTest(WebMockTestCase):
1131 market.fetch_order.return_value = { "status": "closed" } 1137 market.fetch_order.return_value = { "status": "closed" }
1132 with mock.patch.object(portfolio.time, "sleep") as sleep: 1138 with mock.patch.object(portfolio.time, "sleep") as sleep:
1133 # Action 4 1139 # Action 4
1134 portfolio.Trade.follow_orders(verbose=False) 1140 helper.follow_orders(verbose=False)
1135 1141
1136 sleep.assert_called_with(30) 1142 sleep.assert_called_with(30)
1137 1143
@@ -1164,16 +1170,16 @@ class AcceptanceTest(WebMockTestCase):
1164 1170
1165 with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition): 1171 with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition):
1166 # Action 5 1172 # Action 5
1167 portfolio.Balance.update_trades(market, only="buy", compute_value="average") 1173 helper.update_trades(market, only="buy", compute_value="average")
1168 1174
1169 balances = portfolio.Balance.known_balances 1175 balances = portfolio.BalanceStore.all
1170 self.assertEqual(portfolio.Amount("ETH", 1 / D("3")), balances["ETH"].total) 1176 self.assertEqual(portfolio.Amount("ETH", 1 / D("3")), balances["ETH"].total)
1171 self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total) 1177 self.assertEqual(portfolio.Amount("ETC", 4), balances["ETC"].total)
1172 self.assertEqual(portfolio.Amount("BTC", D("0.134")), balances["BTC"].total) 1178 self.assertEqual(portfolio.Amount("BTC", D("0.134")), balances["BTC"].total)
1173 self.assertEqual(portfolio.Amount("XVG", 0), balances["XVG"].total) 1179 self.assertEqual(portfolio.Amount("XVG", 0), balances["XVG"].total)
1174 1180
1175 1181
1176 trades = portfolio.Trade.trades 1182 trades = TradeStore.all
1177 self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades["ETH"].value_from) 1183 self.assertEqual(portfolio.Amount("BTC", D("0.15")), trades["ETH"].value_from)
1178 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades["ETH"].value_to) 1184 self.assertEqual(portfolio.Amount("BTC", D("0.05")), trades["ETH"].value_to)
1179 self.assertEqual("sell", trades["ETH"].action) 1185 self.assertEqual("sell", trades["ETH"].action)
@@ -1232,11 +1238,11 @@ class AcceptanceTest(WebMockTestCase):
1232 1238
1233 # Action 7 1239 # Action 7
1234 # TODO 1240 # TODO
1235 # portfolio.Trade.run_orders() 1241 # portfolio.TradeStore.run_orders()
1236 1242
1237 with mock.patch.object(portfolio.time, "sleep") as sleep: 1243 with mock.patch.object(portfolio.time, "sleep") as sleep:
1238 # Action 8 1244 # Action 8
1239 portfolio.Trade.follow_orders(verbose=False) 1245 helper.follow_orders(verbose=False)
1240 1246
1241 sleep.assert_called_with(30) 1247 sleep.assert_called_with(30)
1242 1248