from .helper import *
import market, store, portfolio
import datetime
@unittest.skipUnless("unit" in limits, "Unit skipped")
class MarketTest(WebMockTestCase):
def setUp(self):
super().setUp()
self.ccxt = mock.Mock(spec=market.ccxt.poloniexE)
def test_values(self):
m = market.Market(self.ccxt, self.market_args())
self.assertEqual(self.ccxt, m.ccxt)
self.assertFalse(m.debug)
self.assertIsInstance(m.report, market.ReportStore)
self.assertIsInstance(m.trades, market.TradeStore)
self.assertIsInstance(m.balances, market.BalanceStore)
self.assertEqual(m, m.report.market)
self.assertEqual(m, m.trades.market)
self.assertEqual(m, m.balances.market)
self.assertEqual(m, m.ccxt._market)
m = market.Market(self.ccxt, self.market_args(debug=True))
self.assertTrue(m.debug)
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)
report_store().log_market.assert_called_once()
report_store.reset_mock()
with self.subTest(quiet=True):
m = market.Market(self.ccxt, self.market_args(quiet=True))
report_store.assert_called_with(m, verbose_print=False)
report_store().log_market.assert_called_once()
@mock.patch("market.ccxt")
def test_from_config(self, ccxt):
with mock.patch("market.ReportStore"):
ccxt.poloniexE.return_value = self.ccxt
m = market.Market.from_config({"key": "key", "secred": "secret"}, self.market_args())
self.assertEqual(self.ccxt, m.ccxt)
m = market.Market.from_config({"key": "key", "secred": "secret"}, self.market_args(debug=True))
self.assertEqual(True, m.debug)
def test_get_tickers(self):
self.ccxt.fetch_tickers.side_effect = [
"tickers",
market.NotSupported
]
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()
self.assertIsNone(m.get_tickers(refresh=self.time.time()))
def test_get_ticker(self):
with self.subTest(get_tickers=True):
self.ccxt.fetch_tickers.return_value = {
"ETH/ETC": { "bid": 1, "ask": 3 },
"XVG/ETH": { "bid": 10, "ask": 40 },
}
m = market.Market(self.ccxt, self.market_args())
ticker = m.get_ticker("ETH", "ETC")
self.assertEqual(1, ticker["bid"])
self.assertEqual(3, ticker["ask"])
self.assertEqual(2, ticker["average"])
self.assertFalse(ticker["inverted"])
ticker = m.get_ticker("ETH", "XVG")
self.assertEqual(0.0625, ticker["average"])
self.assertTrue(ticker["inverted"])
self.assertIn("original", ticker)
self.assertEqual(10, ticker["original"]["bid"])
self.assertEqual(25, ticker["original"]["average"])
ticker = m.get_ticker("XVG", "XMR")
self.assertIsNone(ticker)
with self.subTest(get_tickers=False):
self.ccxt.fetch_tickers.return_value = None
self.ccxt.fetch_ticker.side_effect = [
{ "bid": 1, "ask": 3 },
market.ExchangeError("foo"),
{ "bid": 10, "ask": 40 },
market.ExchangeError("foo"),
market.ExchangeError("foo"),
]
m = market.Market(self.ccxt, self.market_args())
ticker = m.get_ticker("ETH", "ETC")
self.ccxt.fetch_ticker.assert_called_with("ETH/ETC")
self.assertEqual(1, ticker["bid"])
self.assertEqual(3, ticker["ask"])
self.assertEqual(2, ticker["average"])
self.assertFalse(ticker["inverted"])
ticker = m.get_ticker("ETH", "XVG")
self.assertEqual(0.0625, ticker["average"])
self.assertTrue(ticker["inverted"])
self.assertIn("original", ticker)
self.assertEqual(10, ticker["original"]["bid"])
self.assertEqual(25, ticker["original"]["average"])
ticker = m.get_ticker("XVG", "XMR")
self.assertIsNone(ticker)
def test_fetch_fees(self):
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()
self.ccxt.reset_mock()
self.assertEqual("Foo", m.fetch_fees())
self.ccxt.fetch_fees.assert_not_called()
@mock.patch.object(market.Portfolio, "repartition")
@mock.patch.object(market.Market, "get_ticker")
@mock.patch.object(market.TradeStore, "compute_trades")
def test_prepare_trades(self, compute_trades, get_ticker, repartition):
repartition.return_value = {
"XEM": (D("0.75"), "long"),
"BTC": (D("0.25"), "long"),
}
def _get_ticker(c1, c2):
if c1 == "USDT" and c2 == "BTC":
return { "average": D("0.0001") }
if c1 == "XVG" and c2 == "BTC":
return { "average": D("0.000001") }
self.fail("Should be called with {}, {}".format(c1, c2))
get_ticker.side_effect = _get_ticker
with mock.patch("market.ReportStore"):
m = market.Market(self.ccxt, self.market_args())
self.ccxt.fetch_all_balances.return_value = {
"USDT": {
"exchange_free": D("10000.0"),
"exchange_used": D("0.0"),
"exchange_total": D("10000.0"),
"total": D("10000.0")
},
"XVG": {
"exchange_free": D("10000.0"),
"exchange_used": D("0.0"),
"exchange_total": D("10000.0"),
"total": D("10000.0")
},
}
m.balances.fetch_balances(tag="tag")
m.prepare_trades()
compute_trades.assert_called()
call = compute_trades.call_args
self.assertEqual(1, call[0][0]["USDT"].value)
self.assertEqual(D("0.01"), call[0][0]["XVG"].value)
self.assertEqual(D("0.2525"), call[0][1]["BTC"].value)
self.assertEqual(D("0.7575"), call[0][1]["XEM"].value)
m.report.log_stage.assert_called_once_with("prepare_trades",
base_currency='BTC', compute_value='average',
liquidity='medium', only=None, repartition=None)
m.report.log_balances.assert_called_once_with(tag="tag")
@mock.patch.object(market.time, "sleep")
@mock.patch.object(market.TradeStore, "all_orders")
def test_follow_orders(self, all_orders, time_mock):
for debug, sleep in [
(False, None), (True, None),
(False, 12), (True, 12)]:
with self.subTest(sleep=sleep, debug=debug), \
mock.patch("market.ReportStore"):
m = market.Market(self.ccxt, self.market_args(debug=debug))
order_mock1 = mock.Mock()
order_mock2 = mock.Mock()
order_mock3 = mock.Mock()
all_orders.side_effect = [
[order_mock1, order_mock2],
[order_mock1, order_mock2],
[order_mock1, order_mock3],
[order_mock1, order_mock3],
[order_mock1, order_mock3],
[order_mock1, order_mock3],
[]
]
order_mock1.get_status.side_effect = ["open", "open", "closed"]
order_mock2.get_status.side_effect = ["open"]
order_mock3.get_status.side_effect = ["open", "closed"]
order_mock1.trade = mock.Mock()
order_mock2.trade = mock.Mock()
order_mock3.trade = mock.Mock()
m.follow_orders(sleep=sleep)
order_mock1.trade.update_order.assert_any_call(order_mock1, 1)
order_mock1.trade.update_order.assert_any_call(order_mock1, 2)
self.assertEqual(2, order_mock1.trade.update_order.call_count)
self.assertEqual(3, order_mock1.get_status.call_count)
order_mock2.trade.update_order.assert_any_call(order_mock2, 1)
self.assertEqual(1, order_mock2.trade.update_order.call_count)
self.assertEqual(1, order_mock2.get_status.call_count)
order_mock3.trade.update_order.assert_any_call(order_mock3, 2)
self.assertEqual(1, order_mock3.trade.update_order.call_count)
self.assertEqual(2, order_mock3.get_status.call_count)
m.report.log_stage.assert_called()
calls = [
mock.call("follow_orders_begin"),
mock.call("follow_orders_tick_1"),
mock.call("follow_orders_tick_2"),
mock.call("follow_orders_tick_3"),
mock.call("follow_orders_end"),
]
m.report.log_stage.assert_has_calls(calls)
m.report.log_orders.assert_called()
self.assertEqual(3, m.report.log_orders.call_count)
calls = [
mock.call([order_mock1, order_mock2], tick=1),
mock.call([order_mock1, order_mock3], tick=2),
mock.call([order_mock1, order_mock3], tick=3),
]
m.report.log_orders.assert_has_calls(calls)
calls = [
mock.call(order_mock1, 3, finished=True),
mock.call(order_mock3, 3, finished=True),
]
m.report.log_order.assert_has_calls(calls)
if sleep is None:
if debug:
m.report.log_debug_action.assert_called_with("Set follow_orders tick to 7s")
time_mock.assert_called_with(7)
else:
time_mock.assert_called_with(30)
else:
time_mock.assert_called_with(sleep)
with self.subTest("disappearing order"), \
mock.patch("market.ReportStore"):
all_orders.reset_mock()
m = market.Market(self.ccxt, self.market_args())
order_mock1 = mock.Mock()
order_mock2 = mock.Mock()
all_orders.side_effect = [
[order_mock1, order_mock2],
[order_mock1, order_mock2],
[order_mock1, order_mock2],
[order_mock1, order_mock2],
[]
]
order_mock1.get_status.side_effect = ["open", "closed"]
order_mock2.get_status.side_effect = ["open", "error_disappeared"]
order_mock1.trade = mock.Mock()
trade_mock = mock.Mock()
order_mock2.trade = trade_mock
trade_mock.tick_actions_recreate.return_value = "tick1"
m.follow_orders()
trade_mock.tick_actions_recreate.assert_called_once_with(2)
trade_mock.prepare_order.assert_called_once_with(compute_value="tick1")
m.report.log_error.assert_called_once_with("follow_orders", message=mock.ANY)
@mock.patch.object(market.BalanceStore, "fetch_balances")
def test_move_balance(self, fetch_balances):
for debug in [True, False]:
with self.subTest(debug=debug),\
mock.patch("market.ReportStore"):
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")
value_to = portfolio.Amount("BTC", "10.0")
trade1 = portfolio.Trade(value_from, value_to, "ETH", m)
value_from = portfolio.Amount("BTC", "0.0")
value_from.linked_to = portfolio.Amount("ETH", "0.0")
value_to = portfolio.Amount("BTC", "-3.0")
trade2 = portfolio.Trade(value_from, value_to, "ETH", m)
value_from = portfolio.Amount("USDT", "0.0")
value_from.linked_to = portfolio.Amount("XVG", "0.0")
value_to = portfolio.Amount("USDT", "-50.0")
trade3 = portfolio.Trade(value_from, value_to, "XVG", m)
m.trades.all = [trade1, trade2, trade3]
balance1 = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" })
balance2 = portfolio.Balance("USDT", { "margin_in_position": "100", "margin_available": "50" })
balance3 = portfolio.Balance("ETC", { "margin_in_position": "10", "margin_available": "15" })
m.balances.all = {"BTC": balance1, "USDT": balance2, "ETC": balance3}
m.move_balances()
fetch_balances.assert_called_with()
m.report.log_move_balances.assert_called_once()
if debug:
m.report.log_debug_action.assert_called()
self.assertEqual(3, m.report.log_debug_action.call_count)
else:
self.ccxt.transfer_balance.assert_any_call("BTC", 3, "exchange", "margin")
self.ccxt.transfer_balance.assert_any_call("USDT", 100, "exchange", "margin")
self.ccxt.transfer_balance.assert_any_call("ETC", 5, "margin", "exchange")
m.report.reset_mock()
fetch_balances.reset_mock()
with self.subTest(retry=True):
with mock.patch("market.ReportStore"):
m = market.Market(self.ccxt, self.market_args())
value_from = portfolio.Amount("BTC", "0.0")
value_from.linked_to = portfolio.Amount("ETH", "0.0")
value_to = portfolio.Amount("BTC", "-3.0")
trade = portfolio.Trade(value_from, value_to, "ETH", m)
m.trades.all = [trade]
balance = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" })
m.balances.all = {"BTC": balance}
m.ccxt.transfer_balance.side_effect = [
market.ccxt.RequestTimeout,
market.ccxt.InvalidNonce,
True
]
m.move_balances()
self.ccxt.transfer_balance.assert_has_calls([
mock.call("BTC", 3, "exchange", "margin"),
mock.call("BTC", 3, "exchange", "margin"),
mock.call("BTC", 3, "exchange", "margin")
])
self.assertEqual(3, fetch_balances.call_count)
m.report.log_error.assert_called_with(mock.ANY, message="Retrying", exception=mock.ANY)
self.assertEqual(3, m.report.log_move_balances.call_count)
self.ccxt.transfer_balance.reset_mock()
m.report.reset_mock()
fetch_balances.reset_mock()
with self.subTest(retry=True, too_much=True):
with mock.patch("market.ReportStore"):
m = market.Market(self.ccxt, self.market_args())
value_from = portfolio.Amount("BTC", "0.0")
value_from.linked_to = portfolio.Amount("ETH", "0.0")
value_to = portfolio.Amount("BTC", "-3.0")
trade = portfolio.Trade(value_from, value_to, "ETH", m)
m.trades.all = [trade]
balance = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" })
m.balances.all = {"BTC": balance}
m.ccxt.transfer_balance.side_effect = [
market.ccxt.RequestTimeout,
market.ccxt.RequestTimeout,
market.ccxt.RequestTimeout,
market.ccxt.RequestTimeout,
market.ccxt.RequestTimeout,
]
with self.assertRaises(market.ccxt.RequestTimeout):
m.move_balances()
self.ccxt.transfer_balance.reset_mock()
m.report.reset_mock()
fetch_balances.reset_mock()
with self.subTest(retry=True, partial_result=True):
with mock.patch("market.ReportStore"):
m = market.Market(self.ccxt, self.market_args())
value_from = portfolio.Amount("BTC", "1.0")
value_from.linked_to = portfolio.Amount("ETH", "10.0")
value_to = portfolio.Amount("BTC", "10.0")
trade1 = portfolio.Trade(value_from, value_to, "ETH", m)
value_from = portfolio.Amount("BTC", "0.0")
value_from.linked_to = portfolio.Amount("ETH", "0.0")
value_to = portfolio.Amount("BTC", "-3.0")
trade2 = portfolio.Trade(value_from, value_to, "ETH", m)
value_from = portfolio.Amount("USDT", "0.0")
value_from.linked_to = portfolio.Amount("XVG", "0.0")
value_to = portfolio.Amount("USDT", "-50.0")
trade3 = portfolio.Trade(value_from, value_to, "XVG", m)
m.trades.all = [trade1, trade2, trade3]
balance1 = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "0" })
balance2 = portfolio.Balance("USDT", { "margin_in_position": "100", "margin_available": "50" })
balance3 = portfolio.Balance("ETC", { "margin_in_position": "10", "margin_available": "15" })
m.balances.all = {"BTC": balance1, "USDT": balance2, "ETC": balance3}
call_counts = { "BTC": 0, "USDT": 0, "ETC": 0 }
def _transfer_balance(currency, amount, from_, to_):
call_counts[currency] += 1
if currency == "BTC":
m.balances.all["BTC"] = portfolio.Balance("BTC", { "margin_in_position": "0", "margin_available": "3" })
if currency == "USDT":
if call_counts["USDT"] == 1:
raise market.ccxt.RequestTimeout
else:
m.balances.all["USDT"] = portfolio.Balance("USDT", { "margin_in_position": "100", "margin_available": "150" })
if currency == "ETC":
m.balances.all["ETC"] = portfolio.Balance("ETC", { "margin_in_position": "10", "margin_available": "10" })
m.ccxt.transfer_balance.side_effect = _transfer_balance
m.move_balances()
self.ccxt.transfer_balance.assert_has_calls([
mock.call("BTC", 3, "exchange", "margin"),
mock.call('USDT', 100, 'exchange', 'margin'),
mock.call('USDT', 100, 'exchange', 'margin'),
mock.call("ETC", 5, "margin", "exchange")
])
self.assertEqual(2, fetch_balances.call_count)
m.report.log_error.assert_called_with(mock.ANY, message="Retrying", exception=mock.ANY)
self.assertEqual(2, m.report.log_move_balances.call_count)
m.report.log_move_balances.asser_has_calls([
mock.call(
{
'BTC': portfolio.Amount("BTC", "3"),
'USDT': portfolio.Amount("USDT", "150"),
'ETC': portfolio.Amount("ETC", "10"),
},
{
'BTC': portfolio.Amount("BTC", "3"),
'USDT': portfolio.Amount("USDT", "100"),
}),
mock.call(
{
'BTC': portfolio.Amount("BTC", "3"),
'USDT': portfolio.Amount("USDT", "150"),
'ETC': portfolio.Amount("ETC", "10"),
},
{
'BTC': portfolio.Amount("BTC", "0"),
'USDT': portfolio.Amount("USDT", "100"),
'ETC': portfolio.Amount("ETC", "-5"),
}),
])
def test_store_file_report(self):
file_open = mock.mock_open()
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('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(report_db=False), 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_report.assert_not_called()
db_report.assert_not_called()
report.reset_mock()
m = market.Market(self.ccxt, self.market_args(report_db=False, 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, "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)
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(report_db=True, report_path="present"), user_id=1)
with self.subTest(file="present", pg_config=None, report_db=True),\
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, "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)
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(report_db=True), 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, "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)
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(report_db=True, report_path="present"),
pg_config="pg_config", user_id=1)
with self.subTest(file="present", 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, "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)
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, 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,\
mock.patch.object(m.trades, "prepare_orders") as prepare_orders:
m.print_orders()
log_stage.assert_called_with("print_orders")
fetch_balances.assert_called_with(tag="print_orders")
prepare_trades.assert_called_with(base_currency="BTC",
compute_value="average")
prepare_orders.assert_called_with(compute_value="average")
def test_print_balances(self):
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,\
mock.patch.object(m.balances, "fetch_balances") as fetch_balances,\
mock.patch.object(m.report, "print_log") as print_log:
in_currency.return_value = {
"BTC": portfolio.Amount("BTC", "0.65"),
"ETH": portfolio.Amount("BTC", "0.3"),
}
m.print_balances()
log_stage.assert_called_once_with("print_balances")
fetch_balances.assert_called_with()
print_log.assert_has_calls([
mock.call("total:"),
mock.call(portfolio.Amount("BTC", "0.95")),
])
@mock.patch("market.Processor.process")
@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, self.market_args())
with self.subTest(before=False, after=False):
m.process(None)
process.assert_not_called()
store_report.assert_called_once()
log_error.assert_not_called()
process.reset_mock()
log_error.reset_mock()
store_report.reset_mock()
with self.subTest(before=True, after=False):
m.process(None, before=True)
process.assert_called_once_with("sell_all", steps="before")
store_report.assert_called_once()
log_error.assert_not_called()
process.reset_mock()
log_error.reset_mock()
store_report.reset_mock()
with self.subTest(before=False, after=True):
m.process(None, after=True)
process.assert_called_once_with("sell_all", steps="after")
store_report.assert_called_once()
log_error.assert_not_called()
process.reset_mock()
log_error.reset_mock()
store_report.reset_mock()
with self.subTest(before=True, after=True):
m.process(None, before=True, after=True)
process.assert_has_calls([
mock.call("sell_all", steps="before"),
mock.call("sell_all", steps="after"),
])
store_report.assert_called_once()
log_error.assert_not_called()
process.reset_mock()
log_error.reset_mock()
store_report.reset_mock()
with self.subTest(action="print_balances"),\
mock.patch.object(m, "print_balances") as print_balances:
m.process(["print_balances"])
process.assert_not_called()
log_error.assert_not_called()
store_report.assert_called_once()
print_balances.assert_called_once_with()
log_error.reset_mock()
store_report.reset_mock()
with self.subTest(action="print_orders"),\
mock.patch.object(m, "print_orders") as print_orders,\
mock.patch.object(m, "print_balances") as print_balances:
m.process(["print_orders", "print_balances"])
process.assert_not_called()
log_error.assert_not_called()
store_report.assert_called_once()
print_orders.assert_called_once_with()
print_balances.assert_called_once_with()
log_error.reset_mock()
store_report.reset_mock()
with self.subTest(action="unknown"):
m.process(["unknown"])
log_error.assert_called_once_with("market_process", message="Unknown action unknown")
store_report.assert_called_once()
log_error.reset_mock()
store_report.reset_mock()
with self.subTest(unhandled_exception=True):
process.side_effect = Exception("bouh")
m.process(None, before=True)
log_error.assert_called_with("market_process", exception=mock.ANY)
store_report.assert_called_once()
@unittest.skipUnless("unit" in limits, "Unit skipped")
class ProcessorTest(WebMockTestCase):
def test_values(self):
processor = market.Processor(self.m)
self.assertEqual(self.m, processor.market)
def test_run_action(self):
processor = market.Processor(self.m)
with mock.patch.object(processor, "parse_args") as parse_args:
method_mock = mock.Mock()
parse_args.return_value = [method_mock, { "foo": "bar" }]
processor.run_action("foo", "bar", "baz")
parse_args.assert_called_with("foo", "bar", "baz")
method_mock.assert_called_with(foo="bar")
processor.run_action("wait_for_recent", "bar", "baz")
method_mock.assert_called_with(foo="bar")
def test_select_step(self):
processor = market.Processor(self.m)
scenario = processor.scenarios["sell_all"]
self.assertEqual(scenario, processor.select_steps(scenario, "all"))
self.assertEqual(["all_sell"], list(map(lambda x: x["name"], processor.select_steps(scenario, "before"))))
self.assertEqual(["wait", "all_buy"], list(map(lambda x: x["name"], processor.select_steps(scenario, "after"))))
self.assertEqual(["wait"], list(map(lambda x: x["name"], processor.select_steps(scenario, 2))))
self.assertEqual(["wait"], list(map(lambda x: x["name"], processor.select_steps(scenario, "wait"))))
with self.assertRaises(TypeError):
processor.select_steps(scenario, ["wait"])
@mock.patch("market.Processor.process_step")
def test_process(self, process_step):
processor = market.Processor(self.m)
processor.process("sell_all", foo="bar")
self.assertEqual(3, process_step.call_count)
steps = list(map(lambda x: x[1][1]["name"], process_step.mock_calls))
scenario_names = list(map(lambda x: x[1][0], process_step.mock_calls))
kwargs = list(map(lambda x: x[1][2], process_step.mock_calls))
self.assertEqual(["all_sell", "wait", "all_buy"], steps)
self.assertEqual(["sell_all", "sell_all", "sell_all"], scenario_names)
self.assertEqual([{"foo":"bar"}, {"foo":"bar"}, {"foo":"bar"}], kwargs)
process_step.reset_mock()
processor.process("sell_needed", steps=["before", "after"])
self.assertEqual(3, process_step.call_count)
def test_method_arguments(self):
ccxt = mock.Mock(spec=market.ccxt.poloniexE)
m = market.Market(ccxt, self.market_args())
processor = market.Processor(m)
method, arguments = processor.method_arguments("wait_for_recent")
self.assertEqual(market.Portfolio.wait_for_recent, method)
self.assertEqual(["delta", "poll"], arguments)
method, arguments = processor.method_arguments("prepare_trades")
self.assertEqual(m.prepare_trades, method)
self.assertEqual(['base_currency', 'liquidity', 'compute_value', 'repartition', 'only'], arguments)
method, arguments = processor.method_arguments("prepare_orders")
self.assertEqual(m.trades.prepare_orders, method)
method, arguments = processor.method_arguments("move_balances")
self.assertEqual(m.move_balances, method)
method, arguments = processor.method_arguments("run_orders")
self.assertEqual(m.trades.run_orders, method)
method, arguments = processor.method_arguments("follow_orders")
self.assertEqual(m.follow_orders, method)
method, arguments = processor.method_arguments("close_trades")
self.assertEqual(m.trades.close_trades, method)
def test_process_step(self):
processor = market.Processor(self.m)
with mock.patch.object(processor, "run_action") as run_action:
step = processor.scenarios["sell_needed"][1]
processor.process_step("foo", step, {"foo":"bar"})
self.m.report.log_stage.assert_has_calls([
mock.call("process_foo__1_sell_begin"),
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"),
])
self.assertEqual(5, run_action.call_count)
run_action.assert_has_calls([
mock.call('prepare_trades', {}, {'foo': 'bar'}),
mock.call('prepare_orders', {'only': 'dispose', 'compute_value': 'average'}, {'foo': 'bar'}),
mock.call('run_orders', {}, {'foo': 'bar'}),
mock.call('follow_orders', {}, {'foo': 'bar'}),
mock.call('close_trades', {}, {'foo': 'bar'}),
])
self.m.reset_mock()
with mock.patch.object(processor, "run_action") as run_action:
step = processor.scenarios["sell_needed"][0]
processor.process_step("foo", step, {"foo":"bar"})
self.m.balances.fetch_balances.assert_not_called()
def test_parse_args(self):
processor = market.Processor(self.m)
with mock.patch.object(processor, "method_arguments") as method_arguments:
method_mock = mock.Mock()
method_arguments.return_value = [
method_mock,
["foo2", "foo"]
]
method, args = processor.parse_args("action", {"foo": "bar", "foo2": "bar"}, {"foo": "bar2", "bla": "bla"})
self.assertEqual(method_mock, method)
self.assertEqual({"foo": "bar2", "foo2": "bar"}, args)
with mock.patch.object(processor, "method_arguments") as method_arguments:
method_mock = mock.Mock()
method_arguments.return_value = [
method_mock,
["repartition"]
]
method, args = processor.parse_args("action", {"repartition": { "base_currency": 1 }}, {})
self.assertEqual(1, len(args["repartition"]))
self.assertIn("BTC", args["repartition"])
with mock.patch.object(processor, "method_arguments") as method_arguments:
method_mock = mock.Mock()
method_arguments.return_value = [
method_mock,
["repartition", "base_currency"]
]
method, args = processor.parse_args("action", {"repartition": { "base_currency": 1 }}, {"base_currency": "USDT"})
self.assertEqual(1, len(args["repartition"]))
self.assertIn("USDT", args["repartition"])
with mock.patch.object(processor, "method_arguments") as method_arguments:
method_mock = mock.Mock()
method_arguments.return_value = [
method_mock,
["repartition", "base_currency"]
]
method, args = processor.parse_args("action", {"repartition": { "ETH": 1 }}, {"base_currency": "USDT"})
self.assertEqual(1, len(args["repartition"]))
self.assertIn("ETH", args["repartition"])