aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2018-05-03 00:22:33 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2018-05-03 00:22:33 +0200
commit9b69786341d14fd4327b117a12437fd1650cd965 (patch)
treea534f8d64d247d50929f78c92c74acbe4131a1d2
parent40d0fa279e0745b33676f21cdc8b496ebd301cf8 (diff)
downloadTrader-9b69786341d14fd4327b117a12437fd1650cd965.tar.gz
Trader-9b69786341d14fd4327b117a12437fd1650cd965.tar.zst
Trader-9b69786341d14fd4327b117a12437fd1650cd965.zip
Include current portfolio currencies when printing balances
-rw-r--r--market.py9
-rw-r--r--store.py23
-rw-r--r--tests/test_market.py9
-rw-r--r--tests/test_store.py87
4 files changed, 108 insertions, 20 deletions
diff --git a/market.py b/market.py
index 6edd605..5876071 100644
--- a/market.py
+++ b/market.py
@@ -255,6 +255,7 @@ class Processor:
255 "name": "print_balances", 255 "name": "print_balances",
256 "number": 1, 256 "number": 1,
257 "fetch_balances": ["begin"], 257 "fetch_balances": ["begin"],
258 "fetch_balances_args": { "add_portfolio": True },
258 "print_tickers": { "base_currency": "BTC" }, 259 "print_tickers": { "base_currency": "BTC" },
259 } 260 }
260 ], 261 ],
@@ -386,15 +387,19 @@ class Processor:
386 def process_step(self, scenario_name, step, kwargs): 387 def process_step(self, scenario_name, step, kwargs):
387 process_name = "process_{}__{}_{}".format(scenario_name, step["number"], step["name"]) 388 process_name = "process_{}__{}_{}".format(scenario_name, step["number"], step["name"])
388 self.market.report.log_stage("{}_begin".format(process_name)) 389 self.market.report.log_stage("{}_begin".format(process_name))
390
391 fetch_args = step.get("fetch_balances_args", {})
389 if "begin" in step.get("fetch_balances", []): 392 if "begin" in step.get("fetch_balances", []):
390 self.market.balances.fetch_balances(tag="{}_begin".format(process_name), log_tickers=True) 393 self.market.balances.fetch_balances(tag="{}_begin".format(process_name),
394 log_tickers=True, **fetch_args)
391 395
392 for action in self.ordered_actions: 396 for action in self.ordered_actions:
393 if action in step: 397 if action in step:
394 self.run_action(action, step[action], kwargs) 398 self.run_action(action, step[action], kwargs)
395 399
396 if "end" in step.get("fetch_balances", []): 400 if "end" in step.get("fetch_balances", []):
397 self.market.balances.fetch_balances(tag="{}_end".format(process_name), log_tickers=True) 401 self.market.balances.fetch_balances(tag="{}_end".format(process_name),
402 log_tickers=True, **fetch_args)
398 self.market.report.log_stage("{}_end".format(process_name)) 403 self.market.report.log_stage("{}_end".format(process_name))
399 404
400 def method_arguments(self, action): 405 def method_arguments(self, action):
diff --git a/store.py b/store.py
index 81828f0..76cfec8 100644
--- a/store.py
+++ b/store.py
@@ -303,13 +303,16 @@ class BalanceStore:
303 compute_value, type) 303 compute_value, type)
304 return amounts 304 return amounts
305 305
306 def fetch_balances(self, tag=None, log_tickers=False, 306 def fetch_balances(self, tag=None, add_portfolio=False, log_tickers=False,
307 ticker_currency="BTC", ticker_compute_value="average", ticker_type="total"): 307 ticker_currency="BTC", ticker_compute_value="average", ticker_type="total"):
308 all_balances = self.market.ccxt.fetch_all_balances() 308 all_balances = self.market.ccxt.fetch_all_balances()
309 for currency, balance in all_balances.items(): 309 for currency, balance in all_balances.items():
310 if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \ 310 if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \
311 currency in self.all: 311 currency in self.all:
312 self.all[currency] = portfolio.Balance(currency, balance) 312 self.all[currency] = portfolio.Balance(currency, balance)
313 if add_portfolio:
314 for currency in Portfolio.repartition(from_cache=True):
315 self.all.setdefault(currency, portfolio.Balance(currency, {}))
313 if log_tickers: 316 if log_tickers:
314 tickers = self.in_currency(ticker_currency, compute_value=ticker_compute_value, type=ticker_type) 317 tickers = self.in_currency(ticker_currency, compute_value=ticker_compute_value, type=ticker_type)
315 self.market.report.log_balances(tag=tag, 318 self.market.report.log_balances(tag=tag,
@@ -509,7 +512,9 @@ class Portfolio:
509 cls.get_cryptoportfolio(refetch=True) 512 cls.get_cryptoportfolio(refetch=True)
510 513
511 @classmethod 514 @classmethod
512 def repartition(cls, liquidity="medium"): 515 def repartition(cls, liquidity="medium", from_cache=False):
516 if from_cache:
517 cls.retrieve_cryptoportfolio()
513 cls.get_cryptoportfolio() 518 cls.get_cryptoportfolio()
514 liquidities = cls.liquidities.get(liquidity) 519 liquidities = cls.liquidities.get(liquidity)
515 return liquidities[cls.last_date.get()] 520 return liquidities[cls.last_date.get()]
@@ -538,6 +543,20 @@ class Portfolio:
538 cls.liquidities.set({}) 543 cls.liquidities.set({})
539 544
540 @classmethod 545 @classmethod
546 def retrieve_cryptoportfolio(cls):
547 if dbs.redis_connected():
548 repartition = dbs.redis.get("/cryptoportfolio/repartition/latest")
549 date = dbs.redis.get("/cryptoportfolio/repartition/date")
550 if date is not None and repartition is not None:
551 date = datetime.datetime.strptime(date.decode(), "%Y-%m-%d")
552 repartition = json.loads(repartition, parse_int=D, parse_float=D)
553 repartition = { k: { date: v } for k, v in repartition.items() }
554
555 cls.data.set("")
556 cls.last_date.set(date)
557 cls.liquidities.set(repartition)
558
559 @classmethod
541 def store_cryptoportfolio(cls): 560 def store_cryptoportfolio(cls):
542 if dbs.redis_connected(): 561 if dbs.redis_connected():
543 hash_ = {} 562 hash_ = {}
diff --git a/tests/test_market.py b/tests/test_market.py
index ab3cd5e..46fad53 100644
--- a/tests/test_market.py
+++ b/tests/test_market.py
@@ -1032,6 +1032,15 @@ class ProcessorTest(WebMockTestCase):
1032 processor.process_step("foo", step, {"foo":"bar"}) 1032 processor.process_step("foo", step, {"foo":"bar"})
1033 self.m.balances.fetch_balances.assert_not_called() 1033 self.m.balances.fetch_balances.assert_not_called()
1034 1034
1035 self.m.reset_mock()
1036 with mock.patch.object(processor, "run_action") as run_action:
1037 step = processor.scenarios["print_balances"][0]
1038
1039 processor.process_step("foo", step, {"foo":"bar"})
1040 self.m.balances.fetch_balances.assert_called_once_with(
1041 add_portfolio=True, log_tickers=True,
1042 tag='process_foo__1_print_balances_begin')
1043
1035 def test_parse_args(self): 1044 def test_parse_args(self):
1036 processor = market.Processor(self.m) 1045 processor = market.Processor(self.m)
1037 1046
diff --git a/tests/test_store.py b/tests/test_store.py
index d0f7755..ee7e063 100644
--- a/tests/test_store.py
+++ b/tests/test_store.py
@@ -391,6 +391,18 @@ class BalanceStoreTest(WebMockTestCase):
391 tag=None, ticker_currency='FOO', tickers='tickers', 391 tag=None, ticker_currency='FOO', tickers='tickers',
392 type='type') 392 type='type')
393 393
394 balance_store = market.BalanceStore(self.m)
395
396 with self.subTest(add_portfolio=True),\
397 mock.patch.object(market.Portfolio, "repartition") as repartition:
398 repartition.return_value = {
399 "DOGE": D("0.5"),
400 "USDT": D("0.5"),
401 }
402 balance_store.fetch_balances(add_portfolio=True)
403 self.assertListEqual(["USDT", "XVG", "XMR", "DOGE"], list(balance_store.currencies()))
404
405
394 @mock.patch.object(market.Portfolio, "repartition") 406 @mock.patch.object(market.Portfolio, "repartition")
395 def test_dispatch_assets(self, repartition): 407 def test_dispatch_assets(self, repartition):
396 self.m.ccxt.fetch_all_balances.return_value = self.fetch_balance 408 self.m.ccxt.fetch_all_balances.return_value = self.fetch_balance
@@ -1259,24 +1271,67 @@ class PortfolioTest(WebMockTestCase):
1259 mock.call("/cryptoportfolio/repartition/date", "2018-03-08"), 1271 mock.call("/cryptoportfolio/repartition/date", "2018-03-08"),
1260 ]) 1272 ])
1261 1273
1262 @mock.patch.object(market.Portfolio, "get_cryptoportfolio") 1274 @mock.patch.object(store.dbs, "redis_connected")
1263 def test_repartition(self, get_cryptoportfolio): 1275 @mock.patch.object(store.dbs, "redis")
1264 market.Portfolio.liquidities = store.LockedVar({ 1276 def test_retrieve_cryptoportfolio(self, redis, redis_connected):
1265 "medium": { 1277 with self.subTest(redis_connected=False):
1266 "2018-03-01": "medium_2018-03-01", 1278 redis_connected.return_value = False
1267 "2018-03-08": "medium_2018-03-08", 1279 store.Portfolio.retrieve_cryptoportfolio()
1268 }, 1280 redis.get.assert_not_called()
1269 "high": { 1281 self.assertIsNone(store.Portfolio.data.get())
1270 "2018-03-01": "high_2018-03-01", 1282
1271 "2018-03-08": "high_2018-03-08", 1283 with self.subTest(redis_connected=True, value=None):
1284 redis_connected.return_value = True
1285 redis.get.return_value = None
1286 store.Portfolio.retrieve_cryptoportfolio()
1287 self.assertEqual(2, redis.get.call_count)
1288
1289 redis.reset_mock()
1290 with self.subTest(redis_connected=True, value="present"):
1291 redis_connected.return_value = True
1292 redis.get.side_effect = [
1293 b'{ "medium": "medium_repartition", "high": "high_repartition" }',
1294 b"2018-03-08"
1295 ]
1296 store.Portfolio.retrieve_cryptoportfolio()
1297 self.assertEqual(2, redis.get.call_count)
1298 self.assertEqual(datetime.datetime(2018,3,8), store.Portfolio.last_date.get())
1299 self.assertEqual("", store.Portfolio.data.get())
1300 expected_liquidities = {
1301 'high': { datetime.datetime(2018, 3, 8): 'high_repartition' },
1302 'medium': { datetime.datetime(2018, 3, 8): 'medium_repartition' },
1272 } 1303 }
1273 }) 1304 self.assertEqual(expected_liquidities, store.Portfolio.liquidities.get())
1274 market.Portfolio.last_date = store.LockedVar("2018-03-08") 1305
1306 @mock.patch.object(market.Portfolio, "get_cryptoportfolio")
1307 @mock.patch.object(market.Portfolio, "retrieve_cryptoportfolio")
1308 def test_repartition(self, retrieve_cryptoportfolio, get_cryptoportfolio):
1309 with self.subTest(from_cache=False):
1310 market.Portfolio.liquidities = store.LockedVar({
1311 "medium": {
1312 "2018-03-01": "medium_2018-03-01",
1313 "2018-03-08": "medium_2018-03-08",
1314 },
1315 "high": {
1316 "2018-03-01": "high_2018-03-01",
1317 "2018-03-08": "high_2018-03-08",
1318 }
1319 })
1320 market.Portfolio.last_date = store.LockedVar("2018-03-08")
1321
1322 self.assertEqual("medium_2018-03-08", market.Portfolio.repartition())
1323 get_cryptoportfolio.assert_called_once_with()
1324 retrieve_cryptoportfolio.assert_not_called()
1325 self.assertEqual("medium_2018-03-08", market.Portfolio.repartition(liquidity="medium"))
1326 self.assertEqual("high_2018-03-08", market.Portfolio.repartition(liquidity="high"))
1327
1328 retrieve_cryptoportfolio.reset_mock()
1329 get_cryptoportfolio.reset_mock()
1275 1330
1276 self.assertEqual("medium_2018-03-08", market.Portfolio.repartition()) 1331 with self.subTest(from_cache=True):
1277 get_cryptoportfolio.assert_called_once_with() 1332 self.assertEqual("medium_2018-03-08", market.Portfolio.repartition(from_cache=True))
1278 self.assertEqual("medium_2018-03-08", market.Portfolio.repartition(liquidity="medium")) 1333 get_cryptoportfolio.assert_called_once_with()
1279 self.assertEqual("high_2018-03-08", market.Portfolio.repartition(liquidity="high")) 1334 retrieve_cryptoportfolio.assert_called_once_with()
1280 1335
1281 @mock.patch.object(market.time, "sleep") 1336 @mock.patch.object(market.time, "sleep")
1282 @mock.patch.object(market.Portfolio, "get_cryptoportfolio") 1337 @mock.patch.object(market.Portfolio, "get_cryptoportfolio")