aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2018-01-21 15:17:48 +0100
committerIsmaël Bouya <ismael.bouya@normalesup.org>2018-01-21 15:37:24 +0100
commitdeb8924cc60f0d64575657399f4fe112ff1cfb31 (patch)
tree66dbef501b99f962882fce9ac0f67855650674cd
parentf0e4f1828138f86cee54d9f8c4414228840207aa (diff)
downloadTrader-deb8924cc60f0d64575657399f4fe112ff1cfb31.tar.gz
Trader-deb8924cc60f0d64575657399f4fe112ff1cfb31.tar.zst
Trader-deb8924cc60f0d64575657399f4fe112ff1cfb31.zip
Add compute value lambdas for currency conversion
-rw-r--r--portfolio.py51
-rw-r--r--test.py30
2 files changed, 63 insertions, 18 deletions
diff --git a/portfolio.py b/portfolio.py
index 4991dce..5211dd8 100644
--- a/portfolio.py
+++ b/portfolio.py
@@ -89,14 +89,14 @@ class Amount:
89 self.ticker_cache = {} 89 self.ticker_cache = {}
90 self.ticker_cache_timestamp = time.time() 90 self.ticker_cache_timestamp = time.time()
91 91
92 def in_currency(self, other_currency, market, action="average"): 92 def in_currency(self, other_currency, market, action=None, compute_value="average"):
93 if other_currency == self.currency: 93 if other_currency == self.currency:
94 return self 94 return self
95 asset_ticker = Trade.get_ticker(self.currency, other_currency, market) 95 asset_ticker = Trade.get_ticker(self.currency, other_currency, market)
96 if asset_ticker is not None: 96 if asset_ticker is not None:
97 return Amount( 97 return Amount(
98 other_currency, 98 other_currency,
99 self.value * asset_ticker[action], 99 self.value * Trade.compute_value(asset_ticker, action, compute_value=compute_value),
100 linked_to=self, 100 linked_to=self,
101 ticker=asset_ticker) 101 ticker=asset_ticker)
102 else: 102 else:
@@ -175,12 +175,12 @@ class Balance:
175 return cls(currency, hash_["total"], hash_["free"], hash_["used"]) 175 return cls(currency, hash_["total"], hash_["free"], hash_["used"])
176 176
177 @classmethod 177 @classmethod
178 def in_currency(cls, other_currency, market, action="average", type="total"): 178 def in_currency(cls, other_currency, market, compute_value="average", type="total"):
179 amounts = {} 179 amounts = {}
180 for currency in cls.known_balances: 180 for currency in cls.known_balances:
181 balance = cls.known_balances[currency] 181 balance = cls.known_balances[currency]
182 other_currency_amount = getattr(balance, type)\ 182 other_currency_amount = getattr(balance, type)\
183 .in_currency(other_currency, market, action=action) 183 .in_currency(other_currency, market, compute_value=compute_value)
184 amounts[currency] = other_currency_amount 184 amounts[currency] = other_currency_amount
185 return amounts 185 return amounts
186 186
@@ -213,16 +213,34 @@ class Balance:
213 return amounts 213 return amounts
214 214
215 @classmethod 215 @classmethod
216 def prepare_trades(cls, market, base_currency="BTC"): 216 def prepare_trades(cls, market, base_currency="BTC", compute_value=None):
217 cls.fetch_balances(market) 217 cls.fetch_balances(market)
218 values_in_base = cls.in_currency(base_currency, market) 218 values_in_base = cls.in_currency(base_currency, market)
219 total_base_value = sum(values_in_base.values()) 219 total_base_value = sum(values_in_base.values())
220 new_repartition = cls.dispatch_assets(total_base_value) 220 new_repartition = cls.dispatch_assets(total_base_value)
221 Trade.compute_trades(values_in_base, new_repartition, market=market) 221 # Recompute it in case we have new currencies
222 values_in_base = cls.in_currency(base_currency, market)
223 Trade.compute_trades(values_in_base, new_repartition, market=market, compute_value=compute_value)
222 224
223 def __repr__(self): 225 def __repr__(self):
224 return "Balance({} [{}/{}/{}])".format(self.currency, str(self.free), str(self.used), str(self.total)) 226 return "Balance({} [{}/{}/{}])".format(self.currency, str(self.free), str(self.used), str(self.total))
225 227
228class Computation:
229 def average_inverse(ticker, action):
230 if ticker["inverted"]:
231 return 1/ticker["original"]["average"]
232 else:
233 return ticker["average"]
234
235 computations = {
236 "default": lambda x, y: x[y],
237 "average_inverse": average_inverse,
238 "average": lambda x, y: x["average"],
239 "bid": lambda x, y: x["bid"],
240 "ask": lambda x, y: x["ask"],
241 }
242
243
226class Trade: 244class Trade:
227 trades = {} 245 trades = {}
228 246
@@ -282,7 +300,7 @@ class Trade:
282 return cls.get_ticker(c1, c2, market) 300 return cls.get_ticker(c1, c2, market)
283 301
284 @classmethod 302 @classmethod
285 def compute_trades(cls, values_in_base, new_repartition, market=None): 303 def compute_trades(cls, values_in_base, new_repartition, market=None, compute_value=None):
286 base_currency = sum(values_in_base.values()).currency 304 base_currency = sum(values_in_base.values()).currency
287 for currency in Balance.currencies(): 305 for currency in Balance.currencies():
288 if currency == base_currency: 306 if currency == base_currency:
@@ -293,7 +311,8 @@ class Trade:
293 currency, 311 currency,
294 market=market 312 market=market
295 ) 313 )
296 cls.trades[currency].prepare_order() 314 if compute_value is not None:
315 cls.trades[currency].prepare_order(compute_value=compute_value)
297 return cls.trades 316 return cls.trades
298 317
299 @property 318 @property
@@ -314,7 +333,7 @@ class Trade:
314 else: 333 else:
315 return "bid" if not inverted else "ask" 334 return "bid" if not inverted else "ask"
316 335
317 def prepare_order(self): 336 def prepare_order(self, compute_value="default"):
318 if self.action is None: 337 if self.action is None:
319 return 338 return
320 ticker = self.value_from.ticker 339 ticker = self.value_from.ticker
@@ -322,7 +341,9 @@ class Trade:
322 341
323 if not inverted: 342 if not inverted:
324 value_from = self.value_from.linked_to 343 value_from = self.value_from.linked_to
325 value_to = self.value_to.in_currency(self.currency, self.market) 344 # The ticker will most probably be inverted, but we want the average
345 # of the initial value
346 value_to = self.value_to.in_currency(self.currency, self.market, compute_value="average_inverse")
326 delta = abs(value_to - value_from) 347 delta = abs(value_to - value_from)
327 currency = self.base_currency 348 currency = self.base_currency
328 else: 349 else:
@@ -330,11 +351,17 @@ class Trade:
330 delta = abs(self.value_to - self.value_from) 351 delta = abs(self.value_to - self.value_from)
331 currency = self.currency 352 currency = self.currency
332 353
333 rate = ticker[self.order_action(inverted)] 354 rate = Trade.compute_value(ticker, self.order_action(inverted), compute_value=compute_value)
334 355
335 self.orders.append(Order(self.order_action(inverted), delta, rate, currency)) 356 self.orders.append(Order(self.order_action(inverted), delta, rate, currency))
336 357
337 @classmethod 358 @classmethod
359 def compute_value(cls, ticker, action, compute_value="default"):
360 if type(compute_value) == str:
361 compute_value = Computation.computations[compute_value]
362 return compute_value(ticker, action)
363
364 @classmethod
338 def all_orders(cls): 365 def all_orders(cls):
339 return sum(map(lambda v: v.orders, cls.trades.values()), []) 366 return sum(map(lambda v: v.orders, cls.trades.values()), [])
340 367
@@ -401,7 +428,7 @@ class Order:
401 return self.status 428 return self.status
402 429
403def print_orders(market, base_currency="BTC"): 430def print_orders(market, base_currency="BTC"):
404 Balance.prepare_trades(market, base_currency=base_currency) 431 Balance.prepare_trades(market, base_currency=base_currency, compute_value="average")
405 for currency, balance in Balance.known_balances.items(): 432 for currency, balance in Balance.known_balances.items():
406 print(balance) 433 print(balance)
407 for currency, trade in Trade.trades.items(): 434 for currency, trade in Trade.trades.items():
diff --git a/test.py b/test.py
index fde3a06..a9baadf 100644
--- a/test.py
+++ b/test.py
@@ -22,6 +22,8 @@ class AmountTest(unittest.TestCase):
22 22
23 with mock.patch.object(portfolio.Trade, 'get_ticker', new=ticker_mock): 23 with mock.patch.object(portfolio.Trade, 'get_ticker', new=ticker_mock):
24 ticker_mock.return_value = { 24 ticker_mock.return_value = {
25 "bid": D("0.2"),
26 "ask": D("0.4"),
25 "average": D("0.3"), 27 "average": D("0.3"),
26 "foo": "bar", 28 "foo": "bar",
27 } 29 }
@@ -32,6 +34,12 @@ class AmountTest(unittest.TestCase):
32 self.assertEqual(amount, converted_amount.linked_to) 34 self.assertEqual(amount, converted_amount.linked_to)
33 self.assertEqual("bar", converted_amount.ticker["foo"]) 35 self.assertEqual("bar", converted_amount.ticker["foo"])
34 36
37 converted_amount = amount.in_currency("ETH", None, action="bid", compute_value="default")
38 self.assertEqual(D("2"), converted_amount.value)
39
40 converted_amount = amount.in_currency("ETH", None, compute_value="ask")
41 self.assertEqual(D("4"), converted_amount.value)
42
35 def test__abs(self): 43 def test__abs(self):
36 amount = portfolio.Amount("SC", -120) 44 amount = portfolio.Amount("SC", -120)
37 self.assertEqual(120, abs(amount).value) 45 self.assertEqual(120, abs(amount).value)
@@ -295,11 +303,11 @@ class BalanceTest(unittest.TestCase):
295 self.assertEqual(D("0.65"), amounts["BTC"].value) 303 self.assertEqual(D("0.65"), amounts["BTC"].value)
296 self.assertEqual(D("0.30"), amounts["ETH"].value) 304 self.assertEqual(D("0.30"), amounts["ETH"].value)
297 305
298 amounts = portfolio.Balance.in_currency("BTC", market, action="bid") 306 amounts = portfolio.Balance.in_currency("BTC", market, compute_value="bid")
299 self.assertEqual(D("0.65"), amounts["BTC"].value) 307 self.assertEqual(D("0.65"), amounts["BTC"].value)
300 self.assertEqual(D("0.27"), amounts["ETH"].value) 308 self.assertEqual(D("0.27"), amounts["ETH"].value)
301 309
302 amounts = portfolio.Balance.in_currency("BTC", market, action="bid", type="used") 310 amounts = portfolio.Balance.in_currency("BTC", market, compute_value="bid", type="used")
303 self.assertEqual(D("0.30"), amounts["BTC"].value) 311 self.assertEqual(D("0.30"), amounts["BTC"].value)
304 self.assertEqual(0, amounts["ETH"].value) 312 self.assertEqual(0, amounts["ETH"].value)
305 313
@@ -344,10 +352,16 @@ class BalanceTest(unittest.TestCase):
344 "XEM": 7500, 352 "XEM": 7500,
345 "BTC": 2500, 353 "BTC": 2500,
346 } 354 }
347 get_ticker.side_effect = [ 355 def _get_ticker(c1, c2, market):
348 { "average": D("0.0001") }, 356 if c1 == "USDT" and c2 == "BTC":
349 { "average": D("0.000001") } 357 return { "average": D("0.0001") }
350 ] 358 if c1 == "XVG" and c2 == "BTC":
359 return { "average": D("0.000001") }
360 if c1 == "XEM" and c2 == "BTC":
361 return { "average": D("0.001") }
362 raise Exception("Should be called with {}, {}".format(c1, c2))
363 get_ticker.side_effect = _get_ticker
364
351 market = mock.Mock() 365 market = mock.Mock()
352 market.fetch_balance.return_value = { 366 market.fetch_balance.return_value = {
353 "USDT": { 367 "USDT": {
@@ -493,6 +507,10 @@ class TradeTest(unittest.TestCase):
493 pass 507 pass
494 508
495 @unittest.skip("TODO") 509 @unittest.skip("TODO")
510 def test_compute_value(self):
511 pass
512
513 @unittest.skip("TODO")
496 def test__repr(self): 514 def test__repr(self):
497 pass 515 pass
498 516