diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-05-03 00:22:33 +0200 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-05-03 00:22:33 +0200 |
commit | 9b69786341d14fd4327b117a12437fd1650cd965 (patch) | |
tree | a534f8d64d247d50929f78c92c74acbe4131a1d2 | |
parent | 40d0fa279e0745b33676f21cdc8b496ebd301cf8 (diff) | |
download | Trader-9b69786341d14fd4327b117a12437fd1650cd965.tar.gz Trader-9b69786341d14fd4327b117a12437fd1650cd965.tar.zst Trader-9b69786341d14fd4327b117a12437fd1650cd965.zip |
Include current portfolio currencies when printing balances
-rw-r--r-- | market.py | 9 | ||||
-rw-r--r-- | store.py | 23 | ||||
-rw-r--r-- | tests/test_market.py | 9 | ||||
-rw-r--r-- | tests/test_store.py | 87 |
4 files changed, 108 insertions, 20 deletions
@@ -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): |
@@ -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") |