From 350ed24de673dc125be9e2fdecb0f1abc7835b41 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Isma=C3=ABl=20Bouya?= Date: Sat, 3 Feb 2018 21:31:29 +0100 Subject: [PATCH] Work in progress to use shorts --- market.py | 92 +++++++++++++++++++++++++++++++++++ portfolio.py | 132 +++++++++++++++++++++++++++++-------------------- test.py | 135 +++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 270 insertions(+), 89 deletions(-) diff --git a/market.py b/market.py index 1601e2d..1e1e083 100644 --- a/market.py +++ b/market.py @@ -4,6 +4,7 @@ import decimal def exchange_sum(self, *args): return sum([arg for arg in args if isinstance(arg, (float, int, decimal.Decimal))]) ccxt.Exchange.sum = exchange_sum + def poloniex_fetch_balance(self, params={}): self.load_markets() balances = self.privatePostReturnCompleteBalances(self.extend({ @@ -25,6 +26,39 @@ def poloniex_fetch_balance(self, params={}): return self.parse_balance(result) ccxt.poloniex.fetch_balance = poloniex_fetch_balance +def poloniex_fetch_margin_balances(self): + positions = self.privatePostGetMarginPosition({"currencyPair": "all"}) + parsed = {} + for symbol, position in positions.items(): + if position["type"] == "none": + continue + base_currency, currency = symbol.split("_") + parsed[currency] = { + "amount": decimal.Decimal(position["amount"]), + "borrowedPrice": decimal.Decimal(position["basePrice"]), + "lendingFees": decimal.Decimal(position["lendingFees"]), + "pl": decimal.Decimal(position["pl"]), + "liquidationPrice": decimal.Decimal(position["liquidationPrice"]), + "type": position["type"], + "total": decimal.Decimal(position["total"]), + "base_currency": base_currency, + } + return parsed +ccxt.poloniex.fetch_margin_balances = poloniex_fetch_margin_balances + +def poloniex_fetch_balance_with_margin(self, params={}): + exchange_balance = self.fetch_balance(params=params) + margin_balances = self.fetch_margin_balances() + + for currency, balance in margin_balances.items(): + assert exchange_balance[currency]["total"] == 0 + assert balance["type"] == "short" + exchange_balance[currency]["total"] = balance["amount"] + exchange_balance[currency]["marginPosition"] = balance + return exchange_balance +ccxt.poloniex.fetch_balance_with_margin = poloniex_fetch_balance_with_margin + + def poloniex_fetch_balance_per_type(self): balances = self.privatePostReturnAvailableAccountBalances() result = {'info': balances} @@ -92,6 +126,7 @@ def poloniex_create_margin_order(self, symbol, type, side, amount, price=None, l id = order['id'] self.orders[id] = order return self.extend({'info': response}, order) +ccxt.poloniex.create_margin_order = poloniex_create_margin_order def poloniex_create_order(self, symbol, type, side, amount, price=None, account="exchange", lending_rate=None, params={}): if account == "exchange": @@ -100,8 +135,65 @@ def poloniex_create_order(self, symbol, type, side, amount, price=None, account= return self.create_margin_order(symbol, type, side, amount, price=price, lending_rate=lending_rate, params=params) else: raise NotImplementedError + +def poloniex_order_precision(self, symbol): + return 8 + ccxt.poloniex.create_exchange_order = ccxt.poloniex.create_order ccxt.poloniex.create_order = poloniex_create_order +ccxt.poloniex.order_precision = poloniex_order_precision + +def poloniex_transfer_balance(self, currency, amount, from_account, to_account): + result = self.privatePostTransferBalance({ + "currency": currency, + "amount": amount, + "fromAccount": from_account, + "toAccount": to_account, + "confirmed": 1}) + return result["success"] == 1 +ccxt.poloniex.transfer_balance = poloniex_transfer_balance + +# portfolio.market.create_order("DASH/BTC", "limit", "sell", 0.1, price=0.06828800, account="margin") + +# portfolio.market.privatePostReturnTradableBalances() +# Returns tradable balances in margin +# 'BTC_DASH': {'BTC': '0.01266999', 'DASH': '0.08574839'}, +# Je peux emprunter jusqu’à 0.08574839 DASH ou 0.01266999 BTC (une position est +# déjà ouverte) +# 'BTC_CLAM': {'BTC': '0.00585143', 'CLAM': '7.79300395'}, +# Je peux emprunter 7.7 CLAM pour les vendre contre des BTC, ou emprunter +# 0.00585143 BTC pour acheter des CLAM + +# portfolio.market.privatePostReturnMarginAccountSummary() +# Returns current informations for margin +# {'currentMargin': '1.49680968', -> marge (ne doit pas descendre sous 20% / 0.2) +# = netValue / totalBorrowedValue +# 'lendingFees': '0.00000000', -> fees totaux +# 'netValue': '0.01008254', -> balance + plus-value +# 'pl': '0.00008254', -> plus value latente (somme des positions) +# 'totalBorrowedValue': '0.00673602', -> valeur en BTC empruntée +# 'totalValue': '0.01000000'} -> valeur totale en compte + + +# portfolio.market.privatePostGetMarginPosition({"currencyPair": "BTC_DASH"}) +# See DASH/BTC positions +# {'amount': '-0.10000000', -> DASH empruntés +# 'basePrice': '0.06818560', -> à ce prix là (0.06828800 demandé * (1-0.15%)) +# 'lendingFees': '0.00000000', -> ce que je dois à mon créditeur +# 'liquidationPrice': '0.15107132', -> prix auquel ça sera liquidé (dépend de ce que j’ai déjà sur mon compte margin) +# 'pl': '-0.00000371', -> plus-value latente si je rachète tout de suite (négatif = perdu) +# 'total': '0.00681856', -> valeur totale empruntée en BTC +# 'type': 'short'} + + +# closeMarginPosition({"currencyPair": "BTC_DASH"}) : fermer la position au prix +# du marché +# Nécessaire à la fin +# portfolio.market.create_order("DASH/BTC", "limit", "buy", 0.1, price=0.06726487, account="margin") + +# portfolio.market.fetch_balance_per_type() +# Ne suffit pas pour calculer les positions: ne contient que les 0.01 envoyés +# TODO: vérifier si fetch_balance marque ces 0.01 comme disponibles -> oui market = ccxt.poloniex({ "apiKey": "XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX", diff --git a/portfolio.py b/portfolio.py index 3257bcf..d9d2d4d 100644 --- a/portfolio.py +++ b/portfolio.py @@ -1,6 +1,6 @@ from ccxt import ExchangeError import time -from decimal import Decimal as D +from decimal import Decimal as D, ROUND_DOWN # Put your poloniex api key in market.py from market import market @@ -10,7 +10,7 @@ class Portfolio: data = None @classmethod - def repartition_pertenthousand(cls, liquidity="medium"): + def repartition(cls, liquidity="medium"): cls.parse_cryptoportfolio() liquidities = cls.liquidities[liquidity] cls.last_date = sorted(liquidities.keys())[-1] @@ -40,7 +40,7 @@ class Portfolio: cls.get_cryptoportfolio() def filter_weights(weight_hash): - if weight_hash[1] == 0: + if weight_hash[1][0] == 0: return False if weight_hash[0] == "_row": return False @@ -48,15 +48,13 @@ class Portfolio: def clean_weights(i): def clean_weights_(h): - if isinstance(h[1][i], str): - return [h[0], h[1][i]] + if h[0].endswith("s"): + return [h[0][0:-1], (h[1][i], "short")] else: - return [h[0], int(h[1][i] * 10000)] + return [h[0], (h[1][i], "long")] return clean_weights_ def parse_weights(portfolio_hash): - # FIXME: we'll need shorts at some point - assert all(map(lambda x: x == "long", portfolio_hash["holding"]["direction"])) weights_hash = portfolio_hash["weights"] weights = {} for i in range(len(weights_hash["_row"])): @@ -105,6 +103,9 @@ class Amount: else: raise Exception("This asset is not available in the chosen market") + def __round__(self, n=8): + return Amount(self.currency, self.value.quantize(D(1)/D(10**n), rounding=ROUND_DOWN)) + def __abs__(self): return Amount(self.currency, abs(self.value)) @@ -196,35 +197,50 @@ class Balance: for key in hash_: if key in ["info", "free", "used", "total"]: continue - if hash_[key]["total"] > 0 or key in cls.known_balances: + if hash_[key]["total"] != 0 or key in cls.known_balances: cls.known_balances[key] = cls.from_hash(key, hash_[key]) @classmethod def fetch_balances(cls, market): cls._fill_balances(market.fetch_balance()) return cls.known_balances + # FIXME:Separate balances per trade type and in position + # Need to check how balances in position are represented + @classmethod def dispatch_assets(cls, amount, repartition=None): if repartition is None: - repartition = Portfolio.repartition_pertenthousand() - sum_pertenthousand = sum([v for k, v in repartition.items()]) + repartition = Portfolio.repartition() + sum_ratio = sum([v[0] for k, v in repartition.items()]) amounts = {} - for currency, ptt in repartition.items(): - amounts[currency] = ptt * amount / sum_pertenthousand + for currency, (ptt, trade_type) in repartition.items(): + amounts[currency] = ptt * amount / sum_ratio if currency not in cls.known_balances: cls.known_balances[currency] = cls(currency, 0, 0, 0) return amounts + @classmethod + def dispatch_trade_types(cls, repartition=None): + if repartition is None: + repartition = Portfolio.repartition() + trade_types = {} + for currency, (ptt, trade_type) in repartition.items(): + trade_types[currency] = trade_type + return trade_types + # FIXME: once we know the repartition and sold everything, we can move + # the necessary part to the margin account + @classmethod def prepare_trades(cls, market, base_currency="BTC", compute_value="average"): cls.fetch_balances(market) values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value) total_base_value = sum(values_in_base.values()) new_repartition = cls.dispatch_assets(total_base_value) + trade_types = cls.dispatch_trade_types() # Recompute it in case we have new currencies values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value) - Trade.compute_trades(values_in_base, new_repartition, market=market) + Trade.compute_trades(values_in_base, new_repartition, trade_types, market=market) @classmethod def update_trades(cls, market, base_currency="BTC", compute_value="average", only=None): @@ -232,15 +248,17 @@ class Balance: values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value) total_base_value = sum(values_in_base.values()) new_repartition = cls.dispatch_assets(total_base_value) - Trade.compute_trades(values_in_base, new_repartition, only=only, market=market) + trade_types = cls.dispatch_trade_types() + Trade.compute_trades(values_in_base, new_repartition, trade_types, only=only, market=market) @classmethod def prepare_trades_to_sell_all(cls, market, base_currency="BTC", compute_value="average"): cls.fetch_balances(market) values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value) total_base_value = sum(values_in_base.values()) - new_repartition = cls.dispatch_assets(total_base_value, repartition={ base_currency: 1 }) - Trade.compute_trades(values_in_base, new_repartition, market=market) + new_repartition = cls.dispatch_assets(total_base_value, repartition={ base_currency: (1, "long") }) + trade_types = cls.dispatch_trade_types() + Trade.compute_trades(values_in_base, new_repartition, trade_types, market=market) def __repr__(self): return "Balance({} [{}/{}/{}])".format(self.currency, str(self.free), str(self.used), str(self.total)) @@ -253,16 +271,16 @@ class Computation: "ask": lambda x, y: x["ask"], } - class Trade: trades = {} - def __init__(self, value_from, value_to, currency, market=None): + def __init__(self, value_from, value_to, currency, trade_type, market=None): # We have value_from of currency, and want to finish with value_to of # that currency. value_* may not be in currency's terms self.currency = currency self.value_from = value_from self.value_to = value_to + self.trade_type = trade_type self.orders = [] self.market = market assert self.value_from.currency == self.value_to.currency @@ -313,7 +331,7 @@ class Trade: return cls.get_ticker(c1, c2, market) @classmethod - def compute_trades(cls, values_in_base, new_repartition, only=None, market=None): + def compute_trades(cls, values_in_base, new_repartition, trade_types, only=None, market=None): base_currency = sum(values_in_base.values()).currency for currency in Balance.currencies(): if currency == base_currency: @@ -322,6 +340,7 @@ class Trade: values_in_base.get(currency, Amount(base_currency, 0)), new_repartition.get(currency, Amount(base_currency, 0)), currency, + trade_types.get(currency, "long"), market=market ) if only is None or trade.action == only: @@ -347,25 +366,30 @@ class Trade: return "sell" def order_action(self, inverted): - if self.value_from < self.value_to: - return "buy" if not inverted else "sell" + # a xor b xor c + if (self.trade_type == "short") != ((self.value_from < self.value_to) != inverted): + return "buy" else: - return "sell" if not inverted else "buy" + return "sell" def prepare_order(self, compute_value="default"): if self.action is None: return - ticker = self.value_from.ticker + ticker = Trade.get_ticker(self.currency, self.base_currency, self.market) inverted = ticker["inverted"] if inverted: ticker = ticker["original"] rate = Trade.compute_value(ticker, self.order_action(inverted), compute_value=compute_value) # 0.1 + # FIXME: optimize if value_to == 0 or value_from == 0?) + delta_in_base = abs(self.value_from - self.value_to) # 9 BTC's worth of move (10 - 1 or 1 - 10 depending on case) if not inverted: + currency = self.base_currency + # BTC if self.action == "sell": # I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it # At rate 1 Foo = 0.1 BTC @@ -382,35 +406,26 @@ class Trade: delta = delta_in_base.in_currency(self.currency, self.market, rate=1/rate) # I want to buy 9 / 0.1 FOO # Action: "buy" "90 FOO" at rate "0.1" "BTC" on "market" - - # FIXME: Need to round up to the correct amount of FOO in case - # we want to use all BTC - currency = self.base_currency - # BTC else: - if self.action == "sell": - # I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it - # At rate 1 Foo = 0.1 BTC - delta = delta_in_base - # Action: "buy" "9 BTC" at rate "1/0.1" "FOO" on market - - # FIXME: Need to round up to the correct amount of FOO in case - # we want to sell all - else: - delta = delta_in_base - # I want to buy 9 / 0.1 FOO - # Action: "sell" "9 BTC" at rate "1/0.1" "FOO" on "market" - - # FIXME: Need to round up to the correct amount of FOO in case - # we want to use all BTC - currency = self.currency # FOO + delta = delta_in_base + # sell: + # I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it + # At rate 1 Foo = 0.1 BTC + # Action: "buy" "9 BTC" at rate "1/0.1" "FOO" on market + # buy: + # I want to buy 9 / 0.1 FOO + # Action: "sell" "9 BTC" at rate "1/0.1" "FOO" on "market" - self.orders.append(Order(self.order_action(inverted), delta, rate, currency, self.market)) + self.orders.append(Order(self.order_action(inverted), delta, rate, currency, self.trade_type, self.market)) @classmethod def compute_value(cls, ticker, action, compute_value="default"): + if action == "buy": + action = "ask" + if action == "sell": + action = "bid" if isinstance(compute_value, str): compute_value = Computation.computations[compute_value] return compute_value(ticker, action) @@ -450,11 +465,12 @@ class Trade: order.get_status() def __repr__(self): - return "Trade({} -> {} in {}, {})".format( + return "Trade({} -> {} in {}, {} {})".format( self.value_from, self.value_to, self.currency, - self.action) + self.action, + self.trade_type) @classmethod def print_all_with_order(cls): @@ -467,25 +483,33 @@ class Trade: print("\t", order, sep="") class Order: - def __init__(self, action, amount, rate, base_currency, market, account="exchange"): + def __init__(self, action, amount, rate, base_currency, trade_type, market): self.action = action self.amount = amount self.rate = rate self.base_currency = base_currency self.market = market - self.account = account + self.trade_type = trade_type self.result = None self.status = "pending" def __repr__(self): - return "Order({} {} at {} {} [{}])".format( + return "Order({} {} {} at {} {} [{}])".format( self.action, + self.trade_type, self.amount, self.rate, self.base_currency, self.status ) + @property + def account(self): + if self.trade_type == "long": + return "exchange" + else: + return "margin" + @property def pending(self): return self.status == "pending" @@ -496,13 +520,15 @@ class Order: def run(self, debug=False): symbol = "{}/{}".format(self.amount.currency, self.base_currency) - amount = self.amount.value + amount = round(self.amount, self.market.order_precision(symbol)).value if debug: print("market.create_order('{}', 'limit', '{}', {}, price={}, account={})".format( symbol, self.action, amount, self.rate, self.account)) else: try: + if self.action == "sell" and self.trade_type == "short": + assert self.market.transfer_balance(self.base_currency, amount * self.rate, "exchange", "margin") self.result = self.market.create_order(symbol, 'limit', self.action, amount, price=self.rate, account=self.account) self.status = "open" except Exception as e: @@ -527,7 +553,7 @@ def print_orders(market, base_currency="BTC"): Trade.prepare_orders(compute_value="average") for currency, balance in Balance.known_balances.items(): print(balance) - portfolio.Trade.print_all_with_order() + Trade.print_all_with_order() def make_orders(market, base_currency="BTC"): Balance.prepare_trades(market, base_currency=base_currency) diff --git a/test.py b/test.py index 8a6ba50..8240eb4 100644 --- a/test.py +++ b/test.py @@ -174,7 +174,7 @@ class PortfolioTest(unittest.TestCase): with open("test_portfolio.json") as example: import json - self.json_response = json.load(example) + self.json_response = json.load(example, parse_int=portfolio.D, parse_float=portfolio.D) self.patcher = mock.patch.multiple(portfolio.Portfolio, data=None, liquidities={}) self.patcher.start() @@ -220,29 +220,63 @@ class PortfolioTest(unittest.TestCase): self.assertEqual(10, len(liquidities["medium"].keys())) self.assertEqual(10, len(liquidities["high"].keys())) - expected = {'BTC': 2857, 'DGB': 1015, 'DOGE': 1805, 'SC': 623, 'ZEC': 3701} + expected = { + 'BTC': (D("0.2857"), "long"), + 'DGB': (D("0.1015"), "long"), + 'DOGE': (D("0.1805"), "long"), + 'SC': (D("0.0623"), "long"), + 'ZEC': (D("0.3701"), "long"), + } self.assertDictEqual(expected, liquidities["high"]['2018-01-08']) - expected = {'ETC': 1000, 'FCT': 1000, 'GAS': 1000, 'NAV': 1000, 'OMG': 1000, 'OMNI': 1000, 'PPC': 1000, 'RIC': 1000, 'VIA': 1000, 'XCP': 1000} + expected = { + 'BTC': (D("1.1102e-16"), "long"), + 'ETC': (D("0.1"), "long"), + 'FCT': (D("0.1"), "long"), + 'GAS': (D("0.1"), "long"), + 'NAV': (D("0.1"), "long"), + 'OMG': (D("0.1"), "long"), + 'OMNI': (D("0.1"), "long"), + 'PPC': (D("0.1"), "long"), + 'RIC': (D("0.1"), "long"), + 'VIA': (D("0.1"), "long"), + 'XCP': (D("0.1"), "long"), + } self.assertDictEqual(expected, liquidities["medium"]['2018-01-08']) # It doesn't refetch the data when available portfolio.Portfolio.parse_cryptoportfolio() mock_get.assert_called_once_with() - portfolio.Portfolio.data["portfolio_1"]["holding"]["direction"][3] = "short" - self.assertRaises(AssertionError, portfolio.Portfolio.parse_cryptoportfolio) - @mock.patch.object(portfolio.Portfolio, "get_cryptoportfolio") - def test_repartition_pertenthousand(self, mock_get): + def test_repartition(self, mock_get): mock_get.side_effect = self.fill_data - expected_medium = {'USDT': 1000, 'ETC': 1000, 'FCT': 1000, 'OMG': 1000, 'STEEM': 1000, 'STRAT': 1000, 'XEM': 1000, 'XMR': 1000, 'XVC': 1000, 'ZRX': 1000} - expected_high = {'USDT': 1226, 'BTC': 1429, 'ETC': 1127, 'ETH': 1569, 'FCT': 3341, 'GAS': 1308} + expected_medium = { + 'BTC': (D("1.1102e-16"), "long"), + 'USDT': (D("0.1"), "long"), + 'ETC': (D("0.1"), "long"), + 'FCT': (D("0.1"), "long"), + 'OMG': (D("0.1"), "long"), + 'STEEM': (D("0.1"), "long"), + 'STRAT': (D("0.1"), "long"), + 'XEM': (D("0.1"), "long"), + 'XMR': (D("0.1"), "long"), + 'XVC': (D("0.1"), "long"), + 'ZRX': (D("0.1"), "long"), + } + expected_high = { + 'USDT': (D("0.1226"), "long"), + 'BTC': (D("0.1429"), "long"), + 'ETC': (D("0.1127"), "long"), + 'ETH': (D("0.1569"), "long"), + 'FCT': (D("0.3341"), "long"), + 'GAS': (D("0.1308"), "long"), + } - self.assertEqual(expected_medium, portfolio.Portfolio.repartition_pertenthousand()) - self.assertEqual(expected_medium, portfolio.Portfolio.repartition_pertenthousand(liquidity="medium")) - self.assertEqual(expected_high, portfolio.Portfolio.repartition_pertenthousand(liquidity="high")) + self.assertEqual(expected_medium, portfolio.Portfolio.repartition()) + self.assertEqual(expected_medium, portfolio.Portfolio.repartition(liquidity="medium")) + self.assertEqual(expected_high, portfolio.Portfolio.repartition(liquidity="high")) def tearDown(self): self.patcher.stop() @@ -339,7 +373,7 @@ class BalanceTest(unittest.TestCase): self.assertEqual(0, portfolio.Balance.known_balances["ETC"].total) self.assertListEqual(["USDT", "XVG", "ETC"], list(portfolio.Balance.currencies())) - @mock.patch.object(portfolio.Portfolio, "repartition_pertenthousand") + @mock.patch.object(portfolio.Portfolio, "repartition") @mock.patch.object(portfolio.market, "fetch_balance") def test_dispatch_assets(self, fetch_balance, repartition): fetch_balance.return_value = self.fetch_balance @@ -348,8 +382,8 @@ class BalanceTest(unittest.TestCase): self.assertNotIn("XEM", portfolio.Balance.currencies()) repartition.return_value = { - "XEM": 7500, - "BTC": 2600, + "XEM": (D("0.75"), "long"), + "BTC": (D("0.26"), "long"), } amounts = portfolio.Balance.dispatch_assets(portfolio.Amount("BTC", "10.1")) @@ -357,13 +391,13 @@ class BalanceTest(unittest.TestCase): self.assertEqual(D("2.6"), amounts["BTC"].value) self.assertEqual(D("7.5"), amounts["XEM"].value) - @mock.patch.object(portfolio.Portfolio, "repartition_pertenthousand") + @mock.patch.object(portfolio.Portfolio, "repartition") @mock.patch.object(portfolio.Trade, "get_ticker") @mock.patch.object(portfolio.Trade, "compute_trades") def test_prepare_trades(self, compute_trades, get_ticker, repartition): repartition.return_value = { - "XEM": 7500, - "BTC": 2500, + "XEM": (D("0.75"), "long"), + "BTC": (D("0.25"), "long"), } def _get_ticker(c1, c2, market): if c1 == "USDT" and c2 == "BTC": @@ -587,11 +621,12 @@ class AcceptanceTest(unittest.TestCase): }, } repartition = { - "ETH": 2500, - "ETC": 2500, - "BTC": 4000, - "BTD": 500, - "USDT": 500, + "ETH": (D("0.25"), "long"), + "ETC": (D("0.25"), "long"), + "BTC": (D("0.4"), "long"), + "BTD": (D("0.01"), "short"), + "B2X": (D("0.04"), "long"), + "USDT": (D("0.05"), "long"), } def fetch_ticker(symbol): @@ -619,6 +654,12 @@ class AcceptanceTest(unittest.TestCase): "bid": D("0.0008"), "ask": D("0.0012") } + if symbol == "B2X/BTC": + return { + "symbol": "B2X/BTC", + "bid": D("0.0008"), + "ask": D("0.0012") + } if symbol == "USDT/BTC": raise portfolio.ExchangeError if symbol == "BTC/USDT": @@ -632,7 +673,7 @@ class AcceptanceTest(unittest.TestCase): market = mock.Mock() market.fetch_balance.return_value = fetch_balance market.fetch_ticker.side_effect = fetch_ticker - with mock.patch.object(portfolio.Portfolio, "repartition_pertenthousand", return_value=repartition): + with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition): # Action 1 portfolio.Balance.prepare_trades(market) @@ -654,9 +695,13 @@ class AcceptanceTest(unittest.TestCase): self.assertNotIn("BTC", trades) self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["BTD"].value_from) - self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades["BTD"].value_to) + self.assertEqual(portfolio.Amount("BTC", D("0.002")), trades["BTD"].value_to) self.assertEqual("buy", trades["BTD"].action) + self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["B2X"].value_from) + self.assertEqual(portfolio.Amount("BTC", D("0.008")), trades["B2X"].value_to) + self.assertEqual("buy", trades["B2X"].action) + self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["USDT"].value_from) self.assertEqual(portfolio.Amount("BTC", D("0.01")), trades["USDT"].value_to) self.assertEqual("buy", trades["USDT"].action) @@ -680,7 +725,7 @@ class AcceptanceTest(unittest.TestCase): self.assertEqual("limit", type) if symbol == "ETH/BTC": self.assertEqual("sell", action) - self.assertEqual(2, 3*amount) + self.assertEqual(D('0.66666666'), amount) self.assertEqual(D("0.14014"), price) elif symbol == "XVG/BTC": self.assertEqual("sell", action) @@ -693,6 +738,7 @@ class AcceptanceTest(unittest.TestCase): "id": symbol, } market.create_order.side_effect = create_order + market.order_precision.return_value = 8 # Action 3 portfolio.Trade.run_orders() @@ -734,7 +780,7 @@ class AcceptanceTest(unittest.TestCase): } market.fetch_balance.return_value = fetch_balance - with mock.patch.object(portfolio.Portfolio, "repartition_pertenthousand", return_value=repartition): + with mock.patch.object(portfolio.Portfolio, "repartition", return_value=repartition): # Action 5 portfolio.Balance.update_trades(market, only="buy", compute_value="average") @@ -757,9 +803,13 @@ class AcceptanceTest(unittest.TestCase): self.assertNotIn("BTC", trades) self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["BTD"].value_from) - self.assertEqual(portfolio.Amount("BTC", D("0.0097")), trades["BTD"].value_to) + self.assertEqual(portfolio.Amount("BTC", D("0.00194")), trades["BTD"].value_to) self.assertEqual("buy", trades["BTD"].action) + self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["B2X"].value_from) + self.assertEqual(portfolio.Amount("BTC", D("0.00776")), trades["B2X"].value_to) + self.assertEqual("buy", trades["B2X"].action) + self.assertEqual(portfolio.Amount("BTC", D("0.00")), trades["USDT"].value_from) self.assertEqual(portfolio.Amount("BTC", D("0.0097")), trades["USDT"].value_to) self.assertEqual("buy", trades["USDT"].action) @@ -772,21 +822,34 @@ class AcceptanceTest(unittest.TestCase): portfolio.Trade.prepare_orders(only="buy", compute_value=lambda x, y: x["ask"]) all_orders = portfolio.Trade.all_orders(state="pending") - self.assertEqual(3, len(all_orders)) - self.assertEqual(portfolio.Amount("ETC", D("38.5")/3), all_orders[0].amount) + self.assertEqual(4, len(all_orders)) + self.assertEqual(portfolio.Amount("ETC", D("12.83333333")), round(all_orders[0].amount)) self.assertEqual(D("0.003"), all_orders[0].rate) self.assertEqual("buy", all_orders[0].action) + self.assertEqual("long", all_orders[0].trade_type) - self.assertEqual(portfolio.Amount("BTD", D("24.25")/3), all_orders[1].amount) + self.assertEqual(portfolio.Amount("BTD", D("1.61666666")), round(all_orders[1].amount)) self.assertEqual(D("0.0012"), all_orders[1].rate) - self.assertEqual("buy", all_orders[1].action) + self.assertEqual("sell", all_orders[1].action) + self.assertEqual("short", all_orders[1].trade_type) + + diff = portfolio.Amount("B2X", D("19.4")/3) - all_orders[2].amount + self.assertAlmostEqual(0, diff.value) + self.assertEqual(D("0.0012"), all_orders[2].rate) + self.assertEqual("buy", all_orders[2].action) + self.assertEqual("long", all_orders[2].trade_type) + + self.assertEqual(portfolio.Amount("BTC", D("0.0097")), all_orders[3].amount) + self.assertEqual(D("16000"), all_orders[3].rate) + self.assertEqual("sell", all_orders[3].action) + self.assertEqual("long", all_orders[3].trade_type) - self.assertEqual(portfolio.Amount("BTC", D("0.0097")), all_orders[2].amount) - self.assertEqual(D("16000"), all_orders[2].rate) - self.assertEqual("sell", all_orders[2].action) + # Action 7 + # TODO + # portfolio.Trade.run_orders() with mock.patch.object(portfolio.time, "sleep") as sleep: - # Action 7 + # Action 8 portfolio.Trade.follow_orders(verbose=False) sleep.assert_called_with(30) -- 2.41.0