aboutsummaryrefslogtreecommitdiff
path: root/portfolio.py
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2018-02-11 13:36:54 +0100
committerIsmaël Bouya <ismael.bouya@normalesup.org>2018-02-11 13:36:54 +0100
commit1aa7d4fa2ec3c2b3268bef31a666ca6e1aaa6563 (patch)
treef47f804070e935bf8fdf621cc59f4320606cf46f /portfolio.py
parent6ca5a1ec669593fa915a2824efca068c975f9caa (diff)
downloadTrader-1aa7d4fa2ec3c2b3268bef31a666ca6e1aaa6563.tar.gz
Trader-1aa7d4fa2ec3c2b3268bef31a666ca6e1aaa6563.tar.zst
Trader-1aa7d4fa2ec3c2b3268bef31a666ca6e1aaa6563.zip
Add Makefile and test coverage
Fix order preparation and add tests for the step Separate tests between acceptance and unit Add more tests
Diffstat (limited to 'portfolio.py')
-rw-r--r--portfolio.py98
1 files changed, 57 insertions, 41 deletions
diff --git a/portfolio.py b/portfolio.py
index efd9b84..b629966 100644
--- a/portfolio.py
+++ b/portfolio.py
@@ -149,7 +149,7 @@ class Amount:
149 149
150 def __floordiv__(self, value): 150 def __floordiv__(self, value):
151 if not isinstance(value, (int, float, D)): 151 if not isinstance(value, (int, float, D)):
152 raise TypeError("Amount may only be multiplied by integers") 152 raise TypeError("Amount may only be divided by numbers")
153 return Amount(self.currency, self.value / value) 153 return Amount(self.currency, self.value / value)
154 154
155 def __truediv__(self, value): 155 def __truediv__(self, value):
@@ -290,11 +290,10 @@ class Trade:
290 else: 290 else:
291 return "long" 291 return "long"
292 292
293 @property 293 def filled_amount(self, in_base_currency=False):
294 def filled_amount(self):
295 filled_amount = 0 294 filled_amount = 0
296 for order in self.orders: 295 for order in self.orders:
297 filled_amount += order.filled_amount 296 filled_amount += order.filled_amount(in_base_currency=in_base_currency)
298 return filled_amount 297 return filled_amount
299 298
300 def update_order(self, order, tick): 299 def update_order(self, order, tick):
@@ -329,54 +328,69 @@ class Trade:
329 if inverted: 328 if inverted:
330 ticker = ticker["original"] 329 ticker = ticker["original"]
331 rate = Computation.compute_value(ticker, self.order_action(inverted), compute_value=compute_value) 330 rate = Computation.compute_value(ticker, self.order_action(inverted), compute_value=compute_value)
332 # 0.1
333 331
334 delta_in_base = abs(self.value_from - self.value_to) 332 delta_in_base = abs(self.value_from - self.value_to)
335 # 9 BTC's worth of move (10 - 1 or 1 - 10 depending on case) 333 # 9 BTC's worth of move (10 - 1 or 1 - 10 depending on case)
336 334
337 if not inverted: 335 if not inverted:
338 currency = self.base_currency 336 base_currency = self.base_currency
339 # BTC 337 # BTC
340 if self.action == "dispose": 338 if self.action == "dispose":
341 # I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it 339 filled = self.filled_amount(in_base_currency=False)
342 # At rate 1 Foo = 0.1 BTC 340 delta = delta_in_base.in_currency(self.currency, self.market, rate=1/self.value_from.rate)
343 value_from = self.value_from.linked_to 341 # I have 10 BTC worth of FOO, and I want to sell 9 BTC
344 # value_from = 100 FOO 342 # worth of it, computed first with rate 10 FOO = 1 BTC.
345 value_to = self.value_to.in_currency(self.currency, self.market, rate=1/self.value_from.rate) 343 # -> I "sell" "90" FOO at proposed rate "rate".
346 # value_to = 10 FOO (1 BTC * 1/0.1) 344
347 delta = abs(value_to - value_from) 345 delta = delta - filled
348 # delta = 90 FOO 346 # I already sold 60 FOO, 30 left
349 # Action: "sell" "90 FOO" at rate "0.1" "BTC" on "market"
350
351 # Note: no rounding error possible: if we have value_to == 0, then delta == value_from
352 else: 347 else:
353 delta = delta_in_base.in_currency(self.currency, self.market, rate=1/rate) 348 filled = self.filled_amount(in_base_currency=True)
354 # I want to buy 9 / 0.1 FOO 349 delta = (delta_in_base - filled).in_currency(self.currency, self.market, rate=1/rate)
355 # Action: "buy" "90 FOO" at rate "0.1" "BTC" on "market" 350 # I want to buy 9 BTC worth of FOO, computed with rate
351 # 10 FOO = 1 BTC
352 # -> I "buy" "9 / rate" FOO at proposed rate "rate"
353
354 # I already bought 3 / rate FOO, 6 / rate left
356 else: 355 else:
357 currency = self.currency 356 base_currency = self.currency
358 # FOO 357 # FOO
359 delta = delta_in_base 358 if self.action == "dispose":
360 # sell: 359 filled = self.filled_amount(in_base_currency=True)
361 # I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it 360 # Base is FOO
362 # At rate 1 Foo = 0.1 BTC 361
363 # Action: "buy" "9 BTC" at rate "1/0.1" "FOO" on market 362 delta = (delta_in_base.in_currency(self.currency, self.market, rate=1/self.value_from.rate)
364 # buy: 363 - filled).in_currency(self.base_currency, self.market, rate=1/rate)
365 # I want to buy 9 / 0.1 FOO 364 # I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it
366 # Action: "sell" "9 BTC" at rate "1/0.1" "FOO" on "market" 365 # computed at rate 1 Foo = 0.01 BTC
367 if self.value_to == 0: 366 # Computation says I should sell it at 125 FOO / BTC
368 rate = self.value_from.linked_to.value / self.value_from.value 367 # -> delta_in_base = 9 BTC
369 # Recompute the rate to avoid any rounding error 368 # -> delta = (9 * 1/0.01 FOO) * 1/125 = 7.2 BTC
369 # Action: "buy" "7.2 BTC" at rate "125" "FOO" on market
370
371 # I already bought 300/125 BTC, only 600/125 left
372 else:
373 filled = self.filled_amount(in_base_currency=False)
374 # Base is FOO
375
376 delta = delta_in_base
377 # I have 1 BTC worth of FOO, and I want to buy 9 BTC worth of it
378 # At rate 100 Foo / BTC
379 # Computation says I should buy it at 125 FOO / BTC
380 # -> delta_in_base = 9 BTC
381 # Action: "sell" "9 BTC" at rate "125" "FOO" on market
382
383 delta = delta - filled
384 # I already sold 4 BTC, only 5 left
370 385
371 close_if_possible = (self.value_to == 0) 386 close_if_possible = (self.value_to == 0)
372 387
373 if delta <= self.filled_amount: 388 if delta <= 0:
374 print("Less to do than already filled: {} <= {}".format(delta, 389 print("Less to do than already filled: {}".format(delta))
375 self.filled_amount))
376 return 390 return
377 391
378 self.orders.append(Order(self.order_action(inverted), 392 self.orders.append(Order(self.order_action(inverted),
379 delta - self.filled_amount, rate, currency, self.trade_type, 393 delta, rate, base_currency, self.trade_type,
380 self.market, self, close_if_possible=close_if_possible)) 394 self.market, self, close_if_possible=close_if_possible))
381 395
382 def __repr__(self): 396 def __repr__(self):
@@ -497,15 +511,17 @@ class Order:
497 def remaining_amount(self): 511 def remaining_amount(self):
498 if self.status == "open": 512 if self.status == "open":
499 self.fetch() 513 self.fetch()
500 return self.amount - self.filled_amount 514 return self.amount - self.filled_amount()
501 515
502 @property 516 def filled_amount(self, in_base_currency=False):
503 def filled_amount(self):
504 if self.status == "open": 517 if self.status == "open":
505 self.fetch() 518 self.fetch()
506 filled_amount = Amount(self.amount.currency, 0) 519 filled_amount = 0
507 for mouvement in self.mouvements: 520 for mouvement in self.mouvements:
508 filled_amount += mouvement.total 521 if in_base_currency:
522 filled_amount += mouvement.total_in_base
523 else:
524 filled_amount += mouvement.total
509 return filled_amount 525 return filled_amount
510 526
511 def fetch_mouvements(self): 527 def fetch_mouvements(self):