]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git/blobdiff - test.py
Move request wrapper to ccxt
[perso/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Trader.git] / test.py
diff --git a/test.py b/test.py
index f61e739d9a3e664cd30bfca1d185d13846c46cee..18616c1c848620d93a19d61c72b4edcd7c773ea1 100644 (file)
--- a/test.py
+++ b/test.py
@@ -23,6 +23,9 @@ for test_type in limits:
 class WebMockTestCase(unittest.TestCase):
     import time
 
+    def market_args(self, debug=False, quiet=False):
+        return type('Args', (object,), { "debug": debug, "quiet": quiet })()
+
     def setUp(self):
         super(WebMockTestCase, self).setUp()
         self.wm = requests_mock.Mocker()
@@ -67,6 +70,18 @@ class poloniexETest(unittest.TestCase):
         self.wm.stop()
         super(poloniexETest, self).tearDown()
 
+    def test__init(self):
+        with mock.patch("market.ccxt.poloniexE.session") as session:
+            session.request.return_value = "response"
+            ccxt = market.ccxt.poloniexE()
+            ccxt._market = mock.Mock
+            ccxt._market.report = mock.Mock()
+
+            ccxt.session.request("GET", "URL", data="data",
+                    headers="headers")
+            ccxt._market.report.log_http_request.assert_called_with('GET', 'URL', 'data',
+                    'headers', 'response')
+
     def test_nanoseconds(self):
         with mock.patch.object(market.ccxt.time, "time") as time:
             time.return_value = 123456.7890123456
@@ -77,6 +92,58 @@ class poloniexETest(unittest.TestCase):
             time.return_value = 123456.7890123456
             self.assertEqual(123456789012345, self.s.nonce())
 
+    def test_request(self):
+        with mock.patch.object(market.ccxt.poloniex, "request") as request,\
+                mock.patch("market.ccxt.retry_call") as retry_call:
+            with self.subTest(wrapped=True):
+                with self.subTest(desc="public"):
+                    self.s.request("foo")
+                    retry_call.assert_called_with(request,
+                            delay=1, tries=10, fargs=["foo"],
+                            fkwargs={'api': 'public', 'method': 'GET', 'params': {}, 'headers': None, 'body': None},
+                            exceptions=(market.ccxt.RequestTimeout,))
+                    request.assert_not_called()
+
+                with self.subTest(desc="private GET"):
+                    self.s.request("foo", api="private")
+                    retry_call.assert_called_with(request,
+                            delay=1, tries=10, fargs=["foo"],
+                            fkwargs={'api': 'private', 'method': 'GET', 'params': {}, 'headers': None, 'body': None},
+                            exceptions=(market.ccxt.RequestTimeout,))
+                    request.assert_not_called()
+
+                with self.subTest(desc="private POST regexp"):
+                    self.s.request("returnFoo", api="private", method="POST")
+                    retry_call.assert_called_with(request,
+                            delay=1, tries=10, fargs=["returnFoo"],
+                            fkwargs={'api': 'private', 'method': 'POST', 'params': {}, 'headers': None, 'body': None},
+                            exceptions=(market.ccxt.RequestTimeout,))
+                    request.assert_not_called()
+
+                with self.subTest(desc="private POST non-regexp"):
+                    self.s.request("getMarginPosition", api="private", method="POST")
+                    retry_call.assert_called_with(request,
+                            delay=1, tries=10, fargs=["getMarginPosition"],
+                            fkwargs={'api': 'private', 'method': 'POST', 'params': {}, 'headers': None, 'body': None},
+                            exceptions=(market.ccxt.RequestTimeout,))
+                    request.assert_not_called()
+            retry_call.reset_mock()
+            request.reset_mock()
+            with self.subTest(wrapped=False):
+                with self.subTest(desc="private POST non-matching regexp"):
+                    self.s.request("marginBuy", api="private", method="POST")
+                    request.assert_called_with("marginBuy",
+                            api="private", method="POST", params={},
+                            headers=None, body=None)
+                    retry_call.assert_not_called()
+
+                with self.subTest(desc="private POST non-matching non-regexp"):
+                    self.s.request("closeMarginPositionOther", api="private", method="POST")
+                    request.assert_called_with("closeMarginPositionOther",
+                            api="private", method="POST", params={},
+                            headers=None, body=None)
+                    retry_call.assert_not_called()
+
     def test_order_precision(self):
         self.assertEqual(8, self.s.order_precision("FOO"))
 
@@ -1092,7 +1159,7 @@ class MarketTest(WebMockTestCase):
         self.ccxt = mock.Mock(spec=market.ccxt.poloniexE)
 
     def test_values(self):
-        m = market.Market(self.ccxt)
+        m = market.Market(self.ccxt, self.market_args())
 
         self.assertEqual(self.ccxt, m.ccxt)
         self.assertFalse(m.debug)
@@ -1104,28 +1171,30 @@ class MarketTest(WebMockTestCase):
         self.assertEqual(m, m.balances.market)
         self.assertEqual(m, m.ccxt._market)
 
-        m = market.Market(self.ccxt, debug=True)
+        m = market.Market(self.ccxt, self.market_args(debug=True))
         self.assertTrue(m.debug)
 
-        m = market.Market(self.ccxt, debug=False)
+        m = market.Market(self.ccxt, self.market_args(debug=False))
         self.assertFalse(m.debug)
 
+        with mock.patch("market.ReportStore") as report_store:
+            with self.subTest(quiet=False):
+                m = market.Market(self.ccxt, self.market_args(quiet=False))
+                report_store.assert_called_with(m, verbose_print=True)
+            with self.subTest(quiet=True):
+                m = market.Market(self.ccxt, self.market_args(quiet=True))
+                report_store.assert_called_with(m, verbose_print=False)
+
     @mock.patch("market.ccxt")
     def test_from_config(self, ccxt):
         with mock.patch("market.ReportStore"):
             ccxt.poloniexE.return_value = self.ccxt
-            self.ccxt.session.request.return_value = "response"
 
-            m = market.Market.from_config({"key": "key", "secred": "secret"})
+            m = market.Market.from_config({"key": "key", "secred": "secret"}, self.market_args())
 
             self.assertEqual(self.ccxt, m.ccxt)
 
-            self.ccxt.session.request("GET", "URL", data="data",
-                    headers="headers")
-            m.report.log_http_request.assert_called_with('GET', 'URL', 'data',
-                    'headers', 'response')
-
-        m = market.Market.from_config({"key": "key", "secred": "secret"}, debug=True)
+        m = market.Market.from_config({"key": "key", "secred": "secret"}, self.market_args(debug=True))
         self.assertEqual(True, m.debug)
 
     def test_get_tickers(self):
@@ -1134,7 +1203,7 @@ class MarketTest(WebMockTestCase):
                 market.NotSupported
                 ]
 
-        m = market.Market(self.ccxt)
+        m = market.Market(self.ccxt, self.market_args())
         self.assertEqual("tickers", m.get_tickers())
         self.assertEqual("tickers", m.get_tickers())
         self.ccxt.fetch_tickers.assert_called_once()
@@ -1147,7 +1216,7 @@ class MarketTest(WebMockTestCase):
                     "ETH/ETC": { "bid": 1, "ask": 3 },
                     "XVG/ETH": { "bid": 10, "ask": 40 },
                     }
-            m = market.Market(self.ccxt)
+            m = market.Market(self.ccxt, self.market_args())
 
             ticker = m.get_ticker("ETH", "ETC")
             self.assertEqual(1, ticker["bid"])
@@ -1175,7 +1244,7 @@ class MarketTest(WebMockTestCase):
                     market.ExchangeError("foo"),
                     ]
 
-            m = market.Market(self.ccxt)
+            m = market.Market(self.ccxt, self.market_args())
 
             ticker = m.get_ticker("ETH", "ETC")
             self.ccxt.fetch_ticker.assert_called_with("ETH/ETC")
@@ -1195,7 +1264,7 @@ class MarketTest(WebMockTestCase):
             self.assertIsNone(ticker)
 
     def test_fetch_fees(self):
-        m = market.Market(self.ccxt)
+        m = market.Market(self.ccxt, self.market_args())
         self.ccxt.fetch_fees.return_value = "Foo"
         self.assertEqual("Foo", m.fetch_fees())
         self.ccxt.fetch_fees.assert_called_once()
@@ -1222,7 +1291,7 @@ class MarketTest(WebMockTestCase):
         get_ticker.side_effect = _get_ticker
 
         with mock.patch("market.ReportStore"):
-            m = market.Market(self.ccxt)
+            m = market.Market(self.ccxt, self.market_args())
             self.ccxt.fetch_all_balances.return_value = {
                     "USDT": {
                         "exchange_free": D("10000.0"),
@@ -1262,7 +1331,7 @@ class MarketTest(WebMockTestCase):
                 (False, 12), (True, 12)]:
             with self.subTest(sleep=sleep, debug=debug), \
                     mock.patch("market.ReportStore"):
-                m = market.Market(self.ccxt, debug=debug)
+                m = market.Market(self.ccxt, self.market_args(debug=debug))
 
                 order_mock1 = mock.Mock()
                 order_mock2 = mock.Mock()
@@ -1339,7 +1408,7 @@ class MarketTest(WebMockTestCase):
         for debug in [True, False]:
             with self.subTest(debug=debug),\
                     mock.patch("market.ReportStore"):
-                m = market.Market(self.ccxt, debug=debug)
+                m = market.Market(self.ccxt, self.market_args(debug=debug))
 
                 value_from = portfolio.Amount("BTC", "1.0")
                 value_from.linked_to = portfolio.Amount("ETH", "10.0")
@@ -1375,51 +1444,135 @@ class MarketTest(WebMockTestCase):
                     self.ccxt.transfer_balance.assert_any_call("USDT", 100, "exchange", "margin")
                     self.ccxt.transfer_balance.assert_any_call("ETC", 5, "margin", "exchange")
 
-    def test_store_report(self):
-
+    def test_store_file_report(self):
         file_open = mock.mock_open()
-        m = market.Market(self.ccxt, user_id=1)
-        with self.subTest(file=None),\
+        m = market.Market(self.ccxt, self.market_args(), report_path="present", user_id=1)
+        with self.subTest(file="present"),\
+                mock.patch("market.open", file_open),\
+                mock.patch.object(m, "report") as report,\
+                mock.patch.object(market, "datetime") as time_mock:
+
+            report.print_logs = [[time_mock.now(), "Foo"], [time_mock.now(), "Bar"]]
+            report.to_json.return_value = "json_content"
+
+            m.store_file_report(datetime.datetime(2018, 2, 25))
+
+            file_open.assert_any_call("present/2018-02-25T00:00:00_1.json", "w")
+            file_open.assert_any_call("present/2018-02-25T00:00:00_1.log", "w")
+            file_open().write.assert_any_call("json_content")
+            file_open().write.assert_any_call("Foo\nBar")
+            m.report.to_json.assert_called_once_with()
+
+        m = market.Market(self.ccxt, self.market_args(), report_path="error", user_id=1)
+        with self.subTest(file="error"),\
+                mock.patch("market.open") as file_open,\
                 mock.patch.object(m, "report") as report,\
-                mock.patch("market.open", file_open):
+                mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
+            file_open.side_effect = FileNotFoundError
+
+            m.store_file_report(datetime.datetime(2018, 2, 25))
+
+            self.assertRegex(stdout_mock.getvalue(), "impossible to store report file: FileNotFoundError;")
+
+    @mock.patch.object(market, "psycopg2")
+    def test_store_database_report(self, psycopg2):
+        connect_mock = mock.Mock()
+        cursor_mock = mock.MagicMock()
+
+        connect_mock.cursor.return_value = cursor_mock
+        psycopg2.connect.return_value = connect_mock
+        m = market.Market(self.ccxt, self.market_args(),
+                pg_config={"config": "pg_config"}, user_id=1)
+        cursor_mock.fetchone.return_value = [42]
+
+        with self.subTest(error=False),\
+                mock.patch.object(m, "report") as report:
+            report.to_json_array.return_value = [
+                    ("date1", "type1", "payload1"),
+                    ("date2", "type2", "payload2"),
+                    ]
+            m.store_database_report(datetime.datetime(2018, 3, 24))
+            connect_mock.assert_has_calls([
+                mock.call.cursor(),
+                mock.call.cursor().execute('INSERT INTO reports("date", "market_config_id", "debug") VALUES (%s, %s, %s) RETURNING id;', (datetime.datetime(2018, 3, 24), None, False)),
+                mock.call.cursor().fetchone(),
+                mock.call.cursor().execute('INSERT INTO report_lines("date", "report_id", "type", "payload") VALUES (%s, %s, %s, %s);', ('date1', 42, 'type1', 'payload1')),
+                mock.call.cursor().execute('INSERT INTO report_lines("date", "report_id", "type", "payload") VALUES (%s, %s, %s, %s);', ('date2', 42, 'type2', 'payload2')),
+                mock.call.commit(),
+                mock.call.cursor().close(),
+                mock.call.close()
+                ])
+
+        connect_mock.reset_mock()
+        with self.subTest(error=True),\
+                mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
+            psycopg2.connect.side_effect = Exception("Bouh")
+            m.store_database_report(datetime.datetime(2018, 3, 24))
+            self.assertEqual(stdout_mock.getvalue(), "impossible to store report to database: Exception; Bouh\n")
+
+    def test_store_report(self):
+        m = market.Market(self.ccxt, self.market_args(), user_id=1)
+        with self.subTest(file=None, pg_config=None),\
+                mock.patch.object(m, "report") as report,\
+                mock.patch.object(m, "store_database_report") as db_report,\
+                mock.patch.object(m, "store_file_report") as file_report:
             m.store_report()
             report.merge.assert_called_with(store.Portfolio.report)
-            file_open.assert_not_called()
+
+            file_report.assert_not_called()
+            db_report.assert_not_called()
 
         report.reset_mock()
-        file_open = mock.mock_open()
-        m = market.Market(self.ccxt, report_path="present", user_id=1)
-        with self.subTest(file="present"),\
-                mock.patch("market.open", file_open),\
+        m = market.Market(self.ccxt, self.market_args(), report_path="present", user_id=1)
+        with self.subTest(file="present", pg_config=None),\
                 mock.patch.object(m, "report") as report,\
+                mock.patch.object(m, "store_file_report") as file_report,\
+                mock.patch.object(m, "store_database_report") as db_report,\
                 mock.patch.object(market, "datetime") as time_mock:
 
             time_mock.now.return_value = datetime.datetime(2018, 2, 25)
-            report.to_json.return_value = "json_content"
 
             m.store_report()
 
-            file_open.assert_any_call("present/2018-02-25T00:00:00_1.json", "w")
-            file_open().write.assert_called_once_with("json_content")
-            m.report.to_json.assert_called_once_with()
             report.merge.assert_called_with(store.Portfolio.report)
+            file_report.assert_called_once_with(datetime.datetime(2018, 2, 25))
+            db_report.assert_not_called()
 
         report.reset_mock()
+        m = market.Market(self.ccxt, self.market_args(), pg_config="present", user_id=1)
+        with self.subTest(file=None, pg_config="present"),\
+                mock.patch.object(m, "report") as report,\
+                mock.patch.object(m, "store_file_report") as file_report,\
+                mock.patch.object(m, "store_database_report") as db_report,\
+                mock.patch.object(market, "datetime") as time_mock:
 
-        m = market.Market(self.ccxt, report_path="error", user_id=1)
-        with self.subTest(file="error"),\
-                mock.patch("market.open") as file_open,\
+            time_mock.now.return_value = datetime.datetime(2018, 2, 25)
+
+            m.store_report()
+
+            report.merge.assert_called_with(store.Portfolio.report)
+            file_report.assert_not_called()
+            db_report.assert_called_once_with(datetime.datetime(2018, 2, 25))
+
+        report.reset_mock()
+        m = market.Market(self.ccxt, self.market_args(),
+                pg_config="pg_config", report_path="present", user_id=1)
+        with self.subTest(file="present", pg_config="present"),\
                 mock.patch.object(m, "report") as report,\
-                mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
-            file_open.side_effect = FileNotFoundError
+                mock.patch.object(m, "store_file_report") as file_report,\
+                mock.patch.object(m, "store_database_report") as db_report,\
+                mock.patch.object(market, "datetime") as time_mock:
+
+            time_mock.now.return_value = datetime.datetime(2018, 2, 25)
 
             m.store_report()
 
             report.merge.assert_called_with(store.Portfolio.report)
-            self.assertRegex(stdout_mock.getvalue(), "impossible to store report file: FileNotFoundError;")
+            file_report.assert_called_once_with(datetime.datetime(2018, 2, 25))
+            db_report.assert_called_once_with(datetime.datetime(2018, 2, 25))
 
     def test_print_orders(self):
-        m = market.Market(self.ccxt)
+        m = market.Market(self.ccxt, self.market_args())
         with mock.patch.object(m.report, "log_stage") as log_stage,\
                 mock.patch.object(m.balances, "fetch_balances") as fetch_balances,\
                 mock.patch.object(m, "prepare_trades") as prepare_trades,\
@@ -1433,7 +1586,7 @@ class MarketTest(WebMockTestCase):
             prepare_orders.assert_called_with(compute_value="average")
 
     def test_print_balances(self):
-        m = market.Market(self.ccxt)
+        m = market.Market(self.ccxt, self.market_args())
 
         with mock.patch.object(m.balances, "in_currency") as in_currency,\
                 mock.patch.object(m.report, "log_stage") as log_stage,\
@@ -1458,7 +1611,7 @@ class MarketTest(WebMockTestCase):
     @mock.patch("market.ReportStore.log_error")
     @mock.patch("market.Market.store_report")
     def test_process(self, store_report, log_error, process):
-        m = market.Market(self.ccxt)
+        m = market.Market(self.ccxt, self.market_args())
         with self.subTest(before=False, after=False):
             m.process(None)
 
@@ -2007,16 +2160,20 @@ class TradeTest(WebMockTestCase):
         value_to = portfolio.Amount("BTC", "1.0")
         trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
 
-        self.assertEqual("buy", trade.order_action(False))
-        self.assertEqual("sell", trade.order_action(True))
+        trade.inverted = False
+        self.assertEqual("buy", trade.order_action())
+        trade.inverted = True
+        self.assertEqual("sell", trade.order_action())
 
         value_from = portfolio.Amount("BTC", "0")
         value_from.linked_to = portfolio.Amount("ETH", "0")
         value_to = portfolio.Amount("BTC", "-1.0")
         trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
 
-        self.assertEqual("sell", trade.order_action(False))
-        self.assertEqual("buy", trade.order_action(True))
+        trade.inverted = False
+        self.assertEqual("sell", trade.order_action())
+        trade.inverted = True
+        self.assertEqual("buy", trade.order_action())
 
     def test_trade_type(self):
         value_from = portfolio.Amount("BTC", "0.5")
@@ -2034,26 +2191,59 @@ class TradeTest(WebMockTestCase):
         self.assertEqual("short", trade.trade_type)
 
     def test_is_fullfiled(self):
-        value_from = portfolio.Amount("BTC", "0.5")
-        value_from.linked_to = portfolio.Amount("ETH", "10.0")
-        value_to = portfolio.Amount("BTC", "1.0")
-        trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
+        with self.subTest(inverted=False):
+            value_from = portfolio.Amount("BTC", "0.5")
+            value_from.linked_to = portfolio.Amount("ETH", "10.0")
+            value_to = portfolio.Amount("BTC", "1.0")
+            trade = portfolio.Trade(value_from, value_to, "ETH", self.m)
 
-        order1 = mock.Mock()
-        order1.filled_amount.return_value = portfolio.Amount("BTC", "0.3")
+            order1 = mock.Mock()
+            order1.filled_amount.return_value = portfolio.Amount("BTC", "0.3")
 
-        order2 = mock.Mock()
-        order2.filled_amount.return_value = portfolio.Amount("BTC", "0.01")
-        trade.orders.append(order1)
-        trade.orders.append(order2)
+            order2 = mock.Mock()
+            order2.filled_amount.return_value = portfolio.Amount("BTC", "0.01")
+            trade.orders.append(order1)
+            trade.orders.append(order2)
+
+            self.assertFalse(trade.is_fullfiled)
+
+            order3 = mock.Mock()
+            order3.filled_amount.return_value = portfolio.Amount("BTC", "0.19")
+            trade.orders.append(order3)
+
+            self.assertTrue(trade.is_fullfiled)
+
+            order1.filled_amount.assert_called_with(in_base_currency=True)
+            order2.filled_amount.assert_called_with(in_base_currency=True)
+            order3.filled_amount.assert_called_with(in_base_currency=True)
+
+        with self.subTest(inverted=True):
+            value_from = portfolio.Amount("BTC", "0.5")
+            value_from.linked_to = portfolio.Amount("USDT", "1000.0")
+            value_to = portfolio.Amount("BTC", "1.0")
+            trade = portfolio.Trade(value_from, value_to, "USDT", self.m)
+            trade.inverted = True
 
-        self.assertFalse(trade.is_fullfiled)
+            order1 = mock.Mock()
+            order1.filled_amount.return_value = portfolio.Amount("BTC", "0.3")
 
-        order3 = mock.Mock()
-        order3.filled_amount.return_value = portfolio.Amount("BTC", "0.19")
-        trade.orders.append(order3)
+            order2 = mock.Mock()
+            order2.filled_amount.return_value = portfolio.Amount("BTC", "0.01")
+            trade.orders.append(order1)
+            trade.orders.append(order2)
+
+            self.assertFalse(trade.is_fullfiled)
+
+            order3 = mock.Mock()
+            order3.filled_amount.return_value = portfolio.Amount("BTC", "0.19")
+            trade.orders.append(order3)
+
+            self.assertTrue(trade.is_fullfiled)
+
+            order1.filled_amount.assert_called_with(in_base_currency=False)
+            order2.filled_amount.assert_called_with(in_base_currency=False)
+            order3.filled_amount.assert_called_with(in_base_currency=False)
 
-        self.assertTrue(trade.is_fullfiled)
 
     def test_filled_amount(self):
         value_from = portfolio.Amount("BTC", "0.5")
@@ -2713,7 +2903,8 @@ class OrderTest(WebMockTestCase):
         self.m.report.log_debug_action.assert_called_once()
 
     @mock.patch.object(portfolio.Order, "fetch_mouvements")
-    def test_fetch(self, fetch_mouvements):
+    @mock.patch.object(portfolio.Order, "mark_finished_order")
+    def test_fetch(self, mark_finished_order, fetch_mouvements):
         order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
                 D("0.1"), "BTC", "long", self.m, "trade")
         order.id = 45
@@ -2723,6 +2914,7 @@ class OrderTest(WebMockTestCase):
             self.m.report.log_debug_action.assert_called_once()
             self.m.report.log_debug_action.reset_mock()
             self.m.ccxt.fetch_order.assert_not_called()
+            mark_finished_order.assert_not_called()
             fetch_mouvements.assert_not_called()
 
         with self.subTest(debug=False):
@@ -2739,17 +2931,19 @@ class OrderTest(WebMockTestCase):
             self.assertEqual("timestamp", order.timestamp)
             self.assertEqual(1, len(order.results))
             self.m.report.log_debug_action.assert_not_called()
+            mark_finished_order.assert_called_once()
 
+            mark_finished_order.reset_mock()
             with self.subTest(missing_order=True):
                 self.m.ccxt.fetch_order.side_effect = [
                         portfolio.OrderNotCached,
                         ]
                 order.fetch()
                 self.assertEqual("closed_unknown", order.status)
+                mark_finished_order.assert_called_once()
 
     @mock.patch.object(portfolio.Order, "fetch")
-    @mock.patch.object(portfolio.Order, "mark_finished_order")
-    def test_get_status(self, mark_finished_order, fetch):
+    def test_get_status(self, fetch):
         with self.subTest(debug=True):
             self.m.debug = True
             order = portfolio.Order("buy", portfolio.Amount("ETH", 10),
@@ -2768,10 +2962,8 @@ class OrderTest(WebMockTestCase):
                 return update_status
             fetch.side_effect = _fetch(order)
             self.assertEqual("open", order.get_status())
-            mark_finished_order.assert_not_called()
             fetch.assert_called_once()
 
-        mark_finished_order.reset_mock()
         fetch.reset_mock()
         with self.subTest(debug=False, finished=True):
             self.m.debug = False
@@ -2783,7 +2975,6 @@ class OrderTest(WebMockTestCase):
                 return update_status
             fetch.side_effect = _fetch(order)
             self.assertEqual("closed", order.get_status())
-            mark_finished_order.assert_called_once()
             fetch.assert_called_once()
 
     def test_run(self):
@@ -2978,15 +3169,18 @@ class ReportStoreTest(WebMockTestCase):
 
         self.assertEqual(3, len(report_store1.logs))
         self.assertEqual(["1", "2", "3"], list(map(lambda x: x["stage"], report_store1.logs)))
+        self.assertEqual(6, len(report_store1.print_logs))
 
     def test_print_log(self):
         report_store = market.ReportStore(self.m)
         with self.subTest(verbose=True),\
+                mock.patch.object(store, "datetime") as time_mock,\
                 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
+            time_mock.now.return_value = datetime.datetime(2018, 2, 25, 2, 20, 10)
             report_store.set_verbose(True)
             report_store.print_log("Coucou")
             report_store.print_log(portfolio.Amount("BTC", 1))
-            self.assertEqual(stdout_mock.getvalue(), "Coucou\n1.00000000 BTC\n")
+            self.assertEqual(stdout_mock.getvalue(), "2018-02-25 02:20:10: Coucou\n2018-02-25 02:20:10: 1.00000000 BTC\n")
 
         with self.subTest(verbose=False),\
                 mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock:
@@ -2995,6 +3189,14 @@ class ReportStoreTest(WebMockTestCase):
             report_store.print_log(portfolio.Amount("BTC", 1))
             self.assertEqual(stdout_mock.getvalue(), "")
 
+    def test_default_json_serial(self):
+        report_store = market.ReportStore(self.m)
+
+        self.assertEqual("2018-02-24T00:00:00",
+                report_store.default_json_serial(portfolio.datetime(2018, 2, 24)))
+        self.assertEqual("1.00000000 BTC",
+                report_store.default_json_serial(portfolio.Amount("BTC", 1)))
+
     def test_to_json(self):
         report_store = market.ReportStore(self.m)
         report_store.logs.append({"foo": "bar"})
@@ -3004,6 +3206,20 @@ class ReportStoreTest(WebMockTestCase):
         report_store.logs.append({"amount": portfolio.Amount("BTC", 1)})
         self.assertEqual('[\n  {\n    "foo": "bar"\n  },\n  {\n    "date": "2018-02-24T00:00:00"\n  },\n  {\n    "amount": "1.00000000 BTC"\n  }\n]', report_store.to_json())
 
+    def test_to_json_array(self):
+        report_store = market.ReportStore(self.m)
+        report_store.logs.append({
+            "date": "date1", "type": "type1", "foo": "bar", "bla": "bla"
+            })
+        report_store.logs.append({
+            "date": "date2", "type": "type2", "foo": "bar", "bla": "bla"
+            })
+        logs = list(report_store.to_json_array())
+
+        self.assertEqual(2, len(logs))
+        self.assertEqual(("date1", "type1", '{\n  "foo": "bar",\n  "bla": "bla"\n}'), logs[0])
+        self.assertEqual(("date2", "type2", '{\n  "foo": "bar",\n  "bla": "bla"\n}'), logs[1])
+
     @mock.patch.object(market.ReportStore, "print_log")
     @mock.patch.object(market.ReportStore, "add_log")
     def test_log_stage(self, add_log, print_log):
@@ -3497,7 +3713,7 @@ class MainTest(WebMockTestCase):
                 mock.patch("main.parse_config") as main_parse_config:
             with self.subTest(debug=False):
                 main_parse_config.return_value = ["pg_config", "report_path"]
-                main_fetch_markets.return_value = [({"key": "market_config"},)]
+                main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3)]
                 m = main.get_user_market("config_path.ini", 1)
 
                 self.assertIsInstance(m, market.Market)
@@ -3505,7 +3721,7 @@ class MainTest(WebMockTestCase):
 
             with self.subTest(debug=True):
                 main_parse_config.return_value = ["pg_config", "report_path"]
-                main_fetch_markets.return_value = [({"key": "market_config"},)]
+                main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3)]
                 m = main.get_user_market("config_path.ini", 1, debug=True)
 
                 self.assertIsInstance(m, market.Market)
@@ -3524,16 +3740,16 @@ class MainTest(WebMockTestCase):
             args_mock.after = "after"
             self.assertEqual("", stdout_mock.getvalue())
 
-            main.process("config", 1, "report_path", args_mock)
+            main.process("config", 3, 1, args_mock, "report_path", "pg_config")
 
             market_mock.from_config.assert_has_calls([
-                mock.call("config", debug="debug", user_id=1, report_path="report_path"),
+                mock.call("config", args_mock, pg_config="pg_config", market_id=3, user_id=1, report_path="report_path"),
                 mock.call().process("action", before="before", after="after"),
                 ])
 
             with self.subTest(exception=True):
                 market_mock.from_config.side_effect = Exception("boo")
-                main.process("config", 1, "report_path", args_mock)
+                main.process(3, "config", 1, "report_path", args_mock, "pg_config")
                 self.assertEqual("Exception: boo\n", stdout_mock.getvalue())
 
     def test_main(self):
@@ -3551,7 +3767,7 @@ class MainTest(WebMockTestCase):
 
                 parse_config.return_value = ["pg_config", "report_path"]
 
-                fetch_markets.return_value = [["config1", 1], ["config2", 2]]
+                fetch_markets.return_value = [[3, "config1", 1], [1, "config2", 2]]
 
                 main.main(["Foo", "Bar"])
 
@@ -3561,8 +3777,8 @@ class MainTest(WebMockTestCase):
 
                 self.assertEqual(2, process.call_count)
                 process.assert_has_calls([
-                    mock.call("config1", 1, "report_path", args_mock),
-                    mock.call("config2", 2, "report_path", args_mock),
+                    mock.call("config1", 3, 1, args_mock, "report_path", "pg_config"),
+                    mock.call("config2", 1, 2, args_mock, "report_path", "pg_config"),
                     ])
         with self.subTest(parallel=True):
             with mock.patch("main.parse_args") as parse_args,\
@@ -3579,7 +3795,7 @@ class MainTest(WebMockTestCase):
 
                 parse_config.return_value = ["pg_config", "report_path"]
 
-                fetch_markets.return_value = [["config1", 1], ["config2", 2]]
+                fetch_markets.return_value = [[3, "config1", 1], [1, "config2", 2]]
 
                 main.main(["Foo", "Bar"])
 
@@ -3591,9 +3807,9 @@ class MainTest(WebMockTestCase):
                 self.assertEqual(2, process.call_count)
                 process.assert_has_calls([
                     mock.call.__bool__(),
-                    mock.call("config1", 1, "report_path", args_mock),
+                    mock.call("config1", 3, 1, args_mock, "report_path", "pg_config"),
                     mock.call.__bool__(),
-                    mock.call("config2", 2, "report_path", args_mock),
+                    mock.call("config2", 1, 2, args_mock, "report_path", "pg_config"),
                     ])
 
     @mock.patch.object(main.sys, "exit")
@@ -3679,7 +3895,7 @@ class MainTest(WebMockTestCase):
             rows = list(main.fetch_markets({"foo": "bar"}, None))
 
             psycopg2.connect.assert_called_once_with(foo="bar")
-            cursor_mock.execute.assert_called_once_with("SELECT config,user_id FROM market_configs")
+            cursor_mock.execute.assert_called_once_with("SELECT id,config,user_id FROM market_configs")
 
             self.assertEqual(["row_1", "row_2"], rows)
 
@@ -3689,7 +3905,7 @@ class MainTest(WebMockTestCase):
             rows = list(main.fetch_markets({"foo": "bar"}, 1))
 
             psycopg2.connect.assert_called_once_with(foo="bar")
-            cursor_mock.execute.assert_called_once_with("SELECT config,user_id FROM market_configs WHERE user_id = %s", 1)
+            cursor_mock.execute.assert_called_once_with("SELECT id,config,user_id FROM market_configs WHERE user_id = %s", 1)
 
             self.assertEqual(["row_1", "row_2"], rows)
 
@@ -3753,7 +3969,7 @@ class ProcessorTest(WebMockTestCase):
 
     def test_method_arguments(self):
         ccxt = mock.Mock(spec=market.ccxt.poloniexE)
-        m = market.Market(ccxt)
+        m = market.Market(ccxt, self.market_args())
 
         processor = market.Processor(m)