]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/commitdiff
Merge branch 'dev' v1.5
authorIsmaël Bouya <ismael.bouya@normalesup.org>
Tue, 1 May 2018 15:35:55 +0000 (17:35 +0200)
committerIsmaël Bouya <ismael.bouya@normalesup.org>
Tue, 1 May 2018 15:35:55 +0000 (17:35 +0200)
ccxt_wrapper.py
main.py
market.py
portfolio.py
store.py
tests/test_ccxt_wrapper.py
tests/test_main.py
tests/test_market.py
tests/test_portfolio.py
tests/test_store.py

index d2c9b4ce26550b0074f6632ddc2bdc972eb17bd2..c4aa94d6cbe7ccfb5c9c38447bc9b59162a1bbe0 100644 (file)
@@ -66,6 +66,13 @@ class poloniexE(poloniex):
     def nanoseconds():
         return int(time.time() * 1000000000)
 
+    def is_dust_trade(self, amount, rate):
+        if abs(amount) < decimal.Decimal("0.000001"):
+            return True
+        if abs(amount * rate) < decimal.Decimal("0.0001"):
+            return True
+        return False
+
     def fetch_margin_balance(self):
         """
         portfolio.market.privatePostGetMarginPosition({"currencyPair": "BTC_DASH"})
@@ -232,39 +239,9 @@ class poloniexE(poloniex):
 
         return all_balances
 
-    def create_exchange_order(self, symbol, type, side, amount, price=None, params={}):
-        return super().create_order(symbol, type, side, amount, price=price, params=params)
-
-    def create_margin_order(self, symbol, type, side, amount, price=None, lending_rate=None, params={}):
-        if type == 'market':
-            raise ExchangeError(self.id + ' allows limit orders only')
-        self.load_markets()
-        method = 'privatePostMargin' + self.capitalize(side)
-        market = self.market(symbol)
-        price = float(price)
-        amount = float(amount)
-        if lending_rate is not None:
-            params = self.extend({"lendingRate": lending_rate}, params)
-        response = getattr(self, method)(self.extend({
-            'currencyPair': market['id'],
-            'rate': self.price_to_precision(symbol, price),
-            'amount': self.amount_to_precision(symbol, amount),
-        }, params))
-        timestamp = self.milliseconds()
-        order = self.parse_order(self.extend({
-            'timestamp': timestamp,
-            'status': 'open',
-            'type': type,
-            'side': side,
-            'price': price,
-            'amount': amount,
-        }, response), market)
-        id = order['id']
-        self.orders[id] = order
-        return self.extend({'info': response}, order)
-
     def order_precision(self, symbol):
-        return 8
+        self.load_markets()
+        return self.markets[symbol]['precision']['price']
 
     def transfer_balance(self, currency, amount, from_account, to_account):
         result = self.privatePostTransferBalance({
@@ -382,14 +359,49 @@ class poloniexE(poloniex):
 
     def create_order(self, symbol, type, side, amount, price=None, account="exchange", lending_rate=None, params={}):
         """
-        Wrapped to handle margin and exchange accounts
+        Wrapped to handle margin and exchange accounts, and get decimals
         """
+        if type == 'market':
+            raise ExchangeError(self.id + ' allows limit orders only')
+        self.load_markets()
         if account == "exchange":
-            return self.create_exchange_order(symbol, type, side, amount, price=price, params=params)
+            method = 'privatePost' + self.capitalize(side)
         elif account == "margin":
-            return self.create_margin_order(symbol, type, side, amount, price=price, lending_rate=lending_rate, params=params)
+            method = 'privatePostMargin' + self.capitalize(side)
+            if lending_rate is not None:
+                params = self.extend({"lendingRate": lending_rate}, params)
         else:
             raise NotImplementedError
+        market = self.market(symbol)
+        response = getattr(self, method)(self.extend({
+            'currencyPair': market['id'],
+            'rate': self.price_to_precision(symbol, price),
+            'amount': self.amount_to_precision(symbol, amount),
+        }, params))
+        timestamp = self.milliseconds()
+        order = self.parse_order(self.extend({
+            'timestamp': timestamp,
+            'status': 'open',
+            'type': type,
+            'side': side,
+            'price': price,
+            'amount': amount,
+        }, response), market)
+        id = order['id']
+        self.orders[id] = order
+        return self.extend({'info': response}, order)
+
+    def price_to_precision(self, symbol, price):
+        """
+        Wrapped to avoid float
+        """
+        return ('{:.' + str(self.markets[symbol]['precision']['price']) + 'f}').format(price).rstrip("0").rstrip(".")
+
+    def amount_to_precision(self, symbol, amount):
+        """
+        Wrapped to avoid float
+        """
+        return ('{:.' + str(self.markets[symbol]['precision']['amount']) + 'f}').format(amount).rstrip("0").rstrip(".")
 
     def common_currency_code(self, currency):
         """
diff --git a/main.py b/main.py
index 13c22409188a34bd10138b308d766287ada0671b..a4612075e53cae929e4b5906b61d8a630345ac85 100644 (file)
--- a/main.py
+++ b/main.py
@@ -63,7 +63,7 @@ def get_user_market(config_path, user_id, debug=False):
     if debug:
         args.append("--debug")
     args = parse_args(args)
-    pg_config = parse_config(args)
+    pg_config, redis_config = parse_config(args)
     market_id, market_config, user_id = list(fetch_markets(pg_config, str(user_id)))[0]
     return market.Market.from_config(market_config, args,
             pg_config=pg_config, market_id=market_id,
index eff670cab0b5660fe0de2e37289cffb847160371..fc6f9f667fb320e469f7fa103d398d2c40d184b2 100644 (file)
--- a/market.py
+++ b/market.py
@@ -391,14 +391,14 @@ class Processor:
         process_name = "process_{}__{}_{}".format(scenario_name, step["number"], step["name"])
         self.market.report.log_stage("{}_begin".format(process_name))
         if "begin" in step.get("fetch_balances", []):
-            self.market.balances.fetch_balances(tag="{}_begin".format(process_name))
+            self.market.balances.fetch_balances(tag="{}_begin".format(process_name), log_tickers=True)
 
         for action in self.ordered_actions:
             if action in step:
                 self.run_action(action, step[action], kwargs)
 
         if "end" in step.get("fetch_balances", []):
-            self.market.balances.fetch_balances(tag="{}_end".format(process_name))
+            self.market.balances.fetch_balances(tag="{}_end".format(process_name), log_tickers=True)
         self.market.report.log_stage("{}_end".format(process_name))
 
     def method_arguments(self, action):
index 1067b0b0e50084283efe3c7d61477312ae9336d8..d8a546584afc4549be442e1593030ec82c5f0eef 100644 (file)
@@ -320,8 +320,6 @@ class Trade:
             ticker = ticker["original"]
         rate = Computation.compute_value(ticker, self.order_action(), compute_value=compute_value)
 
-        # FIXME: Dust amount should be removed from there if they werent
-        # honored in other sales
         delta_in_base = abs(self.delta)
         # 9 BTC's worth of move (10 - 1 or 1 - 10 depending on case)
 
@@ -580,12 +578,12 @@ class Order:
         self.fetch_mouvements()
 
         self.mark_disappeared_order()
-
+        self.mark_dust_amount_remaining_order()
         self.mark_finished_order()
-        # FIXME: consider open order with dust remaining as closed
 
-    def dust_amount_remaining(self):
-        return self.remaining_amount() < Amount(self.amount.currency, D("0.001"))
+    def mark_dust_amount_remaining_order(self):
+        if self.market.ccxt.is_dust_trade(self.remaining_amount().value, self.rate):
+            self.status = "closed_dust_remaining"
 
     def remaining_amount(self):
         return self.amount - self.filled_amount()
@@ -618,7 +616,7 @@ class Order:
             self.market.report.log_debug_action("Mark {} as cancelled".format(self))
             self.status = "canceled"
             return
-        if self.open and self.id is not None:
+        if (self.status == "closed_dust_remaining" or self.open) and self.id is not None:
             try:
                 self.market.ccxt.cancel_order(self.id)
             except OrderNotFound as e: # Closed inbetween
@@ -650,7 +648,6 @@ class Order:
             return True
 
         similar_trades = self.market.ccxt.fetch_my_trades(symbol=symbol, since=start_timestamp)
-        # FIXME: use set instead of sorted(list(...))
         for order_id in sorted(list(map(lambda x: x["order"], similar_trades))):
             trades = list(filter(lambda x: x["order"] == order_id, similar_trades))
             if any(x["timestamp"] < start_timestamp for x in trades):
index 072d3a2f02ccbc7dd17c1e92b51a786f57d7c6a5..cd0bf7babe2e7240588f4605571365f98096305b 100644 (file)
--- a/store.py
+++ b/store.py
@@ -98,7 +98,8 @@ class ReportStore:
             "args": args,
             })
 
-    def log_balances(self, tag=None):
+    def log_balances(self, tag=None, tickers=None,
+            ticker_currency=None, compute_value=None, type=None):
         self.print_log("[Balance]")
         for currency, balance in self.market.balances.all.items():
             self.print_log("\t{}".format(balance))
@@ -109,11 +110,22 @@ class ReportStore:
                 "balances": self.market.balances.as_json()
                 }
 
+        if tickers is not None:
+            log["tickers"] = self._ticker_hash(tickers, ticker_currency,
+                    compute_value, type)
+
         self.add_log(log.copy())
         self.add_redis_status(log)
 
     def log_tickers(self, amounts, other_currency,
             compute_value, type):
+        log = self._ticker_hash(amounts, other_currency, compute_value,
+                type)
+        log["type"] = "tickers"
+
+        self.add_log(log)
+
+    def _ticker_hash(self, amounts, other_currency, compute_value, type):
         values = {}
         rates = {}
         if callable(compute_value):
@@ -122,8 +134,7 @@ class ReportStore:
         for currency, amount in amounts.items():
             values[currency] = amount.as_json()["value"]
             rates[currency] = amount.rate
-        log = {
-                "type": "tickers",
+        return {
                 "compute_value": compute_value,
                 "balance_type": type,
                 "currency": other_currency,
@@ -132,9 +143,6 @@ class ReportStore:
                 "total": sum(amounts.values()).as_json()["value"]
                 }
 
-        self.add_log(log.copy())
-        self.add_redis_status(log)
-
     def log_dispatch(self, amount, amounts, liquidity, repartition):
         self.add_log({
             "type": "dispatch",
@@ -294,13 +302,20 @@ class BalanceStore:
                 compute_value, type)
         return amounts
 
-    def fetch_balances(self, tag=None):
+    def fetch_balances(self, tag=None, log_tickers=False,
+            ticker_currency="BTC", ticker_compute_value="average", ticker_type="total"):
         all_balances = self.market.ccxt.fetch_all_balances()
         for currency, balance in all_balances.items():
             if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \
                     currency in self.all:
                 self.all[currency] = portfolio.Balance(currency, balance)
-        self.market.report.log_balances(tag=tag)
+        if log_tickers:
+            tickers = self.in_currency(ticker_currency, compute_value=ticker_compute_value, type=ticker_type)
+            self.market.report.log_balances(tag=tag,
+                    tickers=tickers, ticker_currency=ticker_currency,
+                    compute_value=ticker_compute_value, type=ticker_type)
+        else:
+            self.market.report.log_balances(tag=tag)
 
     def dispatch_assets(self, amount, liquidity="medium", repartition=None):
         if repartition is None:
index 10e334daee0d102aa3997d46201da8c6ee9cc40d..c326f0a202dae9d2755a4fb79eba2a87e4bdd067 100644 (file)
@@ -110,7 +110,23 @@ class poloniexETest(unittest.TestCase):
                     retry_call.assert_not_called()
 
     def test_order_precision(self):
-        self.assertEqual(8, self.s.order_precision("FOO"))
+        self.s.markets = {
+                "FOO": {
+                    "precision": {
+                        "price": 5,
+                        "amount": 6,
+                        }
+                    },
+                "BAR": {
+                    "precision": {
+                        "price": 7,
+                        "amount": 8,
+                        }
+                    }
+                }
+        with mock.patch.object(self.s, "load_markets") as load_markets:
+            self.assertEqual(5, self.s.order_precision("FOO"))
+            load_markets.assert_called_once()
 
     def test_transfer_balance(self):
         with self.subTest(success=True),\
@@ -167,33 +183,6 @@ class poloniexETest(unittest.TestCase):
                     }
             self.assertEqual(expected, self.s.margin_summary())
 
-    def test_create_order(self):
-        with mock.patch.object(self.s, "create_exchange_order") as exchange,\
-                mock.patch.object(self.s, "create_margin_order") as margin:
-            with self.subTest(account="unspecified"):
-                self.s.create_order("symbol", "type", "side", "amount", price="price", lending_rate="lending_rate", params="params")
-                exchange.assert_called_once_with("symbol", "type", "side", "amount", price="price", params="params")
-                margin.assert_not_called()
-                exchange.reset_mock()
-                margin.reset_mock()
-
-            with self.subTest(account="exchange"):
-                self.s.create_order("symbol", "type", "side", "amount", account="exchange", price="price", lending_rate="lending_rate", params="params")
-                exchange.assert_called_once_with("symbol", "type", "side", "amount", price="price", params="params")
-                margin.assert_not_called()
-                exchange.reset_mock()
-                margin.reset_mock()
-
-            with self.subTest(account="margin"):
-                self.s.create_order("symbol", "type", "side", "amount", account="margin", price="price", lending_rate="lending_rate", params="params")
-                margin.assert_called_once_with("symbol", "type", "side", "amount", lending_rate="lending_rate", price="price", params="params")
-                exchange.assert_not_called()
-                exchange.reset_mock()
-                margin.reset_mock()
-
-            with self.subTest(account="unknown"), self.assertRaises(NotImplementedError):
-                self.s.create_order("symbol", "type", "side", "amount", account="unknown")
-
     def test_parse_ticker(self):
         ticker = {
                 "high24hr": "12",
@@ -439,11 +428,13 @@ class poloniexETest(unittest.TestCase):
             self.assertEqual(expected_doge, result["DOGE"])
             self.assertEqual(expected_btc, result["BTC"])
 
-    def test_create_margin_order(self):
-        with self.assertRaises(market.ExchangeError):
-            self.s.create_margin_order("FOO", "market", "buy", "10")
+    def test_create_order(self):
+        with self.subTest(type="market"),\
+                self.assertRaises(market.ExchangeError):
+            self.s.create_order("FOO", "market", "buy", "10")
 
-        with mock.patch.object(self.s, "load_markets") as load_markets,\
+        with self.subTest(type="limit", account="margin"),\
+                mock.patch.object(self.s, "load_markets") as load_markets,\
                 mock.patch.object(self.s, "privatePostMarginBuy") as margin_buy,\
                 mock.patch.object(self.s, "privatePostMarginSell") as margin_sell,\
                 mock.patch.object(self.s, "market") as market_mock,\
@@ -460,26 +451,101 @@ class poloniexETest(unittest.TestCase):
             ptp.return_value = D("0.1")
             atp.return_value = D("12")
 
-            order = self.s.create_margin_order("BTC_ETC", "margin", "buy", "12", price="0.1")
+            order = self.s.create_order("BTC_ETC", "limit", "buy", "12",
+                    account="margin", price="0.1")
             self.assertEqual(123, order["id"])
             margin_buy.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12")})
             margin_sell.assert_not_called()
             margin_buy.reset_mock()
             margin_sell.reset_mock()
 
-            order = self.s.create_margin_order("BTC_ETC", "margin", "sell", "12", lending_rate="0.01", price="0.1")
+            order = self.s.create_order("BTC_ETC", "limit", "sell",
+                    "12", account="margin", lending_rate="0.01", price="0.1")
             self.assertEqual(456, order["id"])
             margin_sell.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12"), "lendingRate": "0.01"})
             margin_buy.assert_not_called()
 
-    def test_create_exchange_order(self):
-        with mock.patch.object(market.ccxt.poloniex, "create_order") as create_order:
-            self.s.create_order("symbol", "type", "side", "amount", price="price", params="params")
+        with self.subTest(type="limit", account="exchange"),\
+                mock.patch.object(self.s, "load_markets") as load_markets,\
+                mock.patch.object(self.s, "privatePostBuy") as exchange_buy,\
+                mock.patch.object(self.s, "privatePostSell") as exchange_sell,\
+                mock.patch.object(self.s, "market") as market_mock,\
+                mock.patch.object(self.s, "price_to_precision") as ptp,\
+                mock.patch.object(self.s, "amount_to_precision") as atp:
+
+            exchange_buy.return_value = {
+                    "orderNumber": 123
+                    }
+            exchange_sell.return_value = {
+                    "orderNumber": 456
+                    }
+            market_mock.return_value = { "id": "BTC_ETC", "symbol": "BTC_ETC" }
+            ptp.return_value = D("0.1")
+            atp.return_value = D("12")
+
+            order = self.s.create_order("BTC_ETC", "limit", "buy", "12",
+                    account="exchange", price="0.1")
+            self.assertEqual(123, order["id"])
+            exchange_buy.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12")})
+            exchange_sell.assert_not_called()
+            exchange_buy.reset_mock()
+            exchange_sell.reset_mock()
 
-            create_order.assert_called_once_with("symbol", "type", "side", "amount", price="price", params="params")
+            order = self.s.create_order("BTC_ETC", "limit", "sell",
+                    "12", account="exchange", lending_rate="0.01", price="0.1")
+            self.assertEqual(456, order["id"])
+            exchange_sell.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12")})
+            exchange_buy.assert_not_called()
+
+        with self.subTest(account="unknown"), self.assertRaises(NotImplementedError),\
+                mock.patch.object(self.s, "load_markets") as load_markets:
+            self.s.create_order("symbol", "type", "side", "amount", account="unknown")
 
     def test_common_currency_code(self):
         self.assertEqual("FOO", self.s.common_currency_code("FOO"))
 
     def test_currency_id(self):
         self.assertEqual("FOO", self.s.currency_id("FOO"))
+
+    def test_amount_to_precision(self):
+        self.s.markets = {
+                "FOO": {
+                    "precision": {
+                        "price": 5,
+                        "amount": 6,
+                        }
+                    },
+                "BAR": {
+                    "precision": {
+                        "price": 7,
+                        "amount": 8,
+                        }
+                    }
+                }
+        self.assertEqual("0.0001", self.s.amount_to_precision("FOO", D("0.0001")))
+        self.assertEqual("0.0000001", self.s.amount_to_precision("BAR", D("0.0000001")))
+        self.assertEqual("0.000001", self.s.amount_to_precision("FOO", D("0.000001")))
+
+    def test_price_to_precision(self):
+        self.s.markets = {
+                "FOO": {
+                    "precision": {
+                        "price": 5,
+                        "amount": 6,
+                        }
+                    },
+                "BAR": {
+                    "precision": {
+                        "price": 7,
+                        "amount": 8,
+                        }
+                    }
+                }
+        self.assertEqual("0.0001", self.s.price_to_precision("FOO", D("0.0001")))
+        self.assertEqual("0.0000001", self.s.price_to_precision("BAR", D("0.0000001")))
+        self.assertEqual("0", self.s.price_to_precision("FOO", D("0.000001")))
+
+    def test_is_dust_trade(self):
+        self.assertTrue(self.s.is_dust_trade(D("0.0000009"), D("1000")))
+        self.assertTrue(self.s.is_dust_trade(D("0.000001"), D("10")))
+        self.assertFalse(self.s.is_dust_trade(D("0.000001"), D("100")))
index b650870ba59729ab9ff55bd78fbb390009cf8259..55b1382551aa34da39ec9b35fcd8b4bd7e8c7ee5 100644 (file)
@@ -103,7 +103,7 @@ class MainTest(WebMockTestCase):
                 mock.patch("main.parse_config") as main_parse_config:
             with self.subTest(debug=False):
                 main_parse_args.return_value = self.market_args()
-                main_parse_config.return_value = "pg_config"
+                main_parse_config.return_value = ["pg_config", "redis_config"]
                 main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3)]
                 m = main.get_user_market("config_path.ini", 1)
 
@@ -114,7 +114,7 @@ class MainTest(WebMockTestCase):
             main_parse_args.reset_mock()
             with self.subTest(debug=True):
                 main_parse_args.return_value = self.market_args(debug=True)
-                main_parse_config.return_value = "pg_config"
+                main_parse_config.return_value = ["pg_config", "redis_config"]
                 main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3)]
                 m = main.get_user_market("config_path.ini", 1, debug=True)
 
index 53630b70f11edeb97d37d0271c73c146acd77345..6a3322c579d43815fb15b8da538063427936a814 100644 (file)
@@ -993,8 +993,8 @@ class ProcessorTest(WebMockTestCase):
                 mock.call("process_foo__1_sell_end"),
                 ])
             self.m.balances.fetch_balances.assert_has_calls([
-                mock.call(tag="process_foo__1_sell_begin"),
-                mock.call(tag="process_foo__1_sell_end"),
+                mock.call(tag="process_foo__1_sell_begin", log_tickers=True),
+                mock.call(tag="process_foo__1_sell_end", log_tickers=True),
                 ])
 
             self.assertEqual(5, run_action.call_count)
index 4d789965673b9090c0df2ac7d74c0cd81e46d4ce..969f5d4caf2f4a6f0dc1b5b1705f8d879da4730c 100644 (file)
@@ -823,14 +823,16 @@ class OrderTest(WebMockTestCase):
             order.cancel()
             self.m.ccxt.cancel_order.assert_not_called()
 
-    def test_dust_amount_remaining(self):
+    def test_mark_dust_amount_remaining(self):
         order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                 D("0.1"), "BTC", "long", self.m, "trade")
-        order.remaining_amount = mock.Mock(return_value=portfolio.Amount("ETH", 1))
-        self.assertFalse(order.dust_amount_remaining())
+        self.m.ccxt.is_dust_trade.return_value = False
+        order.mark_dust_amount_remaining_order()
+        self.assertEqual("pending", order.status)
 
-        order.remaining_amount = mock.Mock(return_value=portfolio.Amount("ETH", D("0.0001")))
-        self.assertTrue(order.dust_amount_remaining())
+        self.m.ccxt.is_dust_trade.return_value = True
+        order.mark_dust_amount_remaining_order()
+        self.assertEqual("closed_dust_remaining", order.status)
 
     @mock.patch.object(portfolio.Order, "fetch")
     @mock.patch.object(portfolio.Order, "filled_amount", return_value=portfolio.Amount("ETH", 1))
@@ -965,6 +967,7 @@ class OrderTest(WebMockTestCase):
                     "status": "foo",
                     "datetime": "timestamp"
                     }
+            self.m.ccxt.is_dust_trade.return_value = False
             order.fetch()
 
             self.m.ccxt.fetch_order.assert_called_once_with(45)
index df113b706483780028b9177a05d537c56335b5cc..12999d36e1b7e9bd64f0d7eda8ad002dd14b8411 100644 (file)
@@ -369,17 +369,27 @@ class BalanceStoreTest(WebMockTestCase):
 
         balance_store = market.BalanceStore(self.m)
 
-        balance_store.fetch_balances()
-        self.assertNotIn("ETC", balance_store.currencies())
-        self.assertListEqual(["USDT", "XVG", "XMR"], list(balance_store.currencies()))
-
-        balance_store.all["ETC"] = portfolio.Balance("ETC", {
-            "exchange_total": "1", "exchange_free": "0",
-            "exchange_used": "1" })
-        balance_store.fetch_balances(tag="foo")
-        self.assertEqual(0, balance_store.all["ETC"].total)
-        self.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(balance_store.currencies()))
-        self.m.report.log_balances.assert_called_with(tag="foo")
+        with self.subTest(log_tickers=False):
+            balance_store.fetch_balances()
+            self.assertNotIn("ETC", balance_store.currencies())
+            self.assertListEqual(["USDT", "XVG", "XMR"], list(balance_store.currencies()))
+
+            balance_store.all["ETC"] = portfolio.Balance("ETC", {
+                "exchange_total": "1", "exchange_free": "0",
+                "exchange_used": "1" })
+            balance_store.fetch_balances(tag="foo")
+            self.assertEqual(0, balance_store.all["ETC"].total)
+            self.assertListEqual(["USDT", "XVG", "XMR", "ETC"], list(balance_store.currencies()))
+            self.m.report.log_balances.assert_called_with(tag="foo")
+
+        with self.subTest(log_tickers=True),\
+                mock.patch.object(balance_store, "in_currency") as in_currency:
+            in_currency.return_value = "tickers"
+            balance_store.fetch_balances(log_tickers=True, ticker_currency="FOO",
+                    ticker_compute_value="compute", ticker_type="type")
+            self.m.report.log_balances.assert_called_with(compute_value='compute',
+                    tag=None, ticker_currency='FOO', tickers='tickers',
+                    type='type')
 
     @mock.patch.object(market.Portfolio, "repartition")
     def test_dispatch_assets(self, repartition):
@@ -586,27 +596,77 @@ class ReportStoreTest(WebMockTestCase):
         self.m.balances.as_json.return_value = "json"
         self.m.balances.all = { "FOO": "bar", "BAR": "baz" }
 
-        report_store.log_balances(tag="tag")
-        print_log.assert_has_calls([
-            mock.call("[Balance]"),
-            mock.call("\tbar"),
-            mock.call("\tbaz"),
-            ])
-        add_log.assert_called_once_with({
-            'type': 'balance',
-            'balances': 'json',
-            'tag': 'tag'
-            })
-        add_redis_status.assert_called_once_with({
-            'type': 'balance',
-            'balances': 'json',
-            'tag': 'tag'
-            })
+        with self.subTest(tickers=None):
+            report_store.log_balances(tag="tag")
+            print_log.assert_has_calls([
+                mock.call("[Balance]"),
+                mock.call("\tbar"),
+                mock.call("\tbaz"),
+                ])
+            add_log.assert_called_once_with({
+                'type': 'balance',
+                'balances': 'json',
+                'tag': 'tag'
+                })
+            add_redis_status.assert_called_once_with({
+                'type': 'balance',
+                'balances': 'json',
+                'tag': 'tag'
+                })
+        add_log.reset_mock()
+        add_redis_status.reset_mock()
+        with self.subTest(tickers="present"):
+            amounts = {
+                    "BTC": portfolio.Amount("BTC", 10),
+                    "ETH": portfolio.Amount("BTC", D("0.3"))
+                    }
+            amounts["ETH"].rate = D("0.1")
+
+            report_store.log_balances(tag="tag", tickers=amounts,
+                    ticker_currency="BTC", compute_value="default",
+                    type="total")
+            add_log.assert_called_once_with({
+                'type': 'balance',
+                'balances': 'json',
+                'tag': 'tag',
+                'tickers': {
+                    'compute_value': 'default',
+                    'balance_type': 'total',
+                    'currency': 'BTC',
+                    'balances': {
+                        'BTC': D('10'),
+                        'ETH': D('0.3')
+                        },
+                    'rates': {
+                        'BTC': None,
+                        'ETH': D('0.1')
+                        },
+                    'total': D('10.3')
+                    },
+                })
+            add_redis_status.assert_called_once_with({
+                'type': 'balance',
+                'balances': 'json',
+                'tag': 'tag',
+                'tickers': {
+                    'compute_value': 'default',
+                    'balance_type': 'total',
+                    'currency': 'BTC',
+                    'balances': {
+                        'BTC': D('10'),
+                        'ETH': D('0.3')
+                        },
+                    'rates': {
+                        'BTC': None,
+                        'ETH': D('0.1')
+                        },
+                    'total': D('10.3')
+                    },
+                })
 
     @mock.patch.object(market.ReportStore, "print_log")
     @mock.patch.object(market.ReportStore, "add_log")
-    @mock.patch.object(market.ReportStore, "add_redis_status")
-    def test_log_tickers(self, add_redis_status, add_log, print_log):
+    def test_log_tickers(self, add_log, print_log):
         report_store = market.ReportStore(self.m)
         amounts = {
                 "BTC": portfolio.Amount("BTC", 10),
@@ -631,21 +691,6 @@ class ReportStoreTest(WebMockTestCase):
                 },
             'total': D('10.3')
             })
-        add_redis_status.assert_called_once_with({
-            'type': 'tickers',
-            'compute_value': 'default',
-            'balance_type': 'total',
-            'currency': 'BTC',
-            'balances': {
-                'BTC': D('10'),
-                'ETH': D('0.3')
-                },
-            'rates': {
-                'BTC': None,
-                'ETH': D('0.1')
-                },
-            'total': D('10.3')
-            })
 
         add_log.reset_mock()
         compute_value = lambda x: x["bid"]