diff options
Diffstat (limited to 'store.py')
-rw-r--r-- | store.py | 128 |
1 files changed, 107 insertions, 21 deletions
@@ -309,30 +309,110 @@ class TradeStore: | |||
309 | for order in self.all_orders(state="open"): | 309 | for order in self.all_orders(state="open"): |
310 | order.get_status() | 310 | order.get_status() |
311 | 311 | ||
312 | class NoopLock: | ||
313 | def __enter__(self, *args): | ||
314 | pass | ||
315 | def __exit__(self, *args): | ||
316 | pass | ||
317 | |||
318 | class LockedVar: | ||
319 | def __init__(self, value): | ||
320 | self.lock = NoopLock() | ||
321 | self.val = value | ||
322 | |||
323 | def start_lock(self): | ||
324 | import threading | ||
325 | self.lock = threading.Lock() | ||
326 | |||
327 | def set(self, value): | ||
328 | with self.lock: | ||
329 | self.val = value | ||
330 | |||
331 | def get(self, key=None): | ||
332 | with self.lock: | ||
333 | if key is not None and isinstance(self.val, dict): | ||
334 | return self.val.get(key) | ||
335 | else: | ||
336 | return self.val | ||
337 | |||
338 | def __getattr__(self, key): | ||
339 | with self.lock: | ||
340 | return getattr(self.val, key) | ||
341 | |||
312 | class Portfolio: | 342 | class Portfolio: |
313 | URL = "https://cryptoportfolio.io/wp-content/uploads/portfolio/json/cryptoportfolio.json" | 343 | URL = "https://cryptoportfolio.io/wp-content/uploads/portfolio/json/cryptoportfolio.json" |
314 | liquidities = {} | 344 | data = LockedVar(None) |
315 | data = None | 345 | liquidities = LockedVar({}) |
316 | last_date = None | 346 | last_date = LockedVar(None) |
317 | report = ReportStore(None) | 347 | report = LockedVar(ReportStore(None)) |
348 | worker = None | ||
349 | worker_started = False | ||
350 | worker_notify = None | ||
351 | callback = None | ||
352 | |||
353 | @classmethod | ||
354 | def start_worker(cls, poll=30): | ||
355 | import threading | ||
356 | |||
357 | cls.worker = threading.Thread(name="portfolio", daemon=True, | ||
358 | target=cls.wait_for_notification, kwargs={"poll": poll}) | ||
359 | cls.worker_notify = threading.Event() | ||
360 | cls.callback = threading.Event() | ||
361 | |||
362 | cls.last_date.start_lock() | ||
363 | cls.liquidities.start_lock() | ||
364 | cls.report.start_lock() | ||
365 | |||
366 | cls.worker_started = True | ||
367 | cls.worker.start() | ||
368 | |||
369 | @classmethod | ||
370 | def is_worker_thread(cls): | ||
371 | if cls.worker is None: | ||
372 | return False | ||
373 | else: | ||
374 | import threading | ||
375 | return cls.worker == threading.current_thread() | ||
376 | |||
377 | @classmethod | ||
378 | def wait_for_notification(cls, poll=30): | ||
379 | if not cls.is_worker_thread(): | ||
380 | raise RuntimeError("This method needs to be ran with the worker") | ||
381 | while cls.worker_started: | ||
382 | cls.worker_notify.wait() | ||
383 | cls.worker_notify.clear() | ||
384 | cls.report.print_log("Fetching cryptoportfolio") | ||
385 | cls.get_cryptoportfolio(refetch=True) | ||
386 | cls.callback.set() | ||
387 | time.sleep(poll) | ||
318 | 388 | ||
319 | @classmethod | 389 | @classmethod |
320 | def wait_for_recent(cls, delta=4): | 390 | def notify_and_wait(cls): |
391 | cls.callback.clear() | ||
392 | cls.worker_notify.set() | ||
393 | cls.callback.wait() | ||
394 | |||
395 | @classmethod | ||
396 | def wait_for_recent(cls, delta=4, poll=30): | ||
321 | cls.get_cryptoportfolio() | 397 | cls.get_cryptoportfolio() |
322 | while cls.last_date is None or datetime.now() - cls.last_date > timedelta(delta): | 398 | while cls.last_date.get() is None or datetime.now() - cls.last_date.get() > timedelta(delta): |
323 | time.sleep(30) | 399 | if cls.worker is None: |
324 | cls.report.print_log("Attempt to fetch up-to-date cryptoportfolio") | 400 | time.sleep(poll) |
401 | cls.report.print_log("Attempt to fetch up-to-date cryptoportfolio") | ||
325 | cls.get_cryptoportfolio(refetch=True) | 402 | cls.get_cryptoportfolio(refetch=True) |
326 | 403 | ||
327 | @classmethod | 404 | @classmethod |
328 | def repartition(cls, liquidity="medium"): | 405 | def repartition(cls, liquidity="medium"): |
329 | cls.get_cryptoportfolio() | 406 | cls.get_cryptoportfolio() |
330 | liquidities = cls.liquidities[liquidity] | 407 | liquidities = cls.liquidities.get(liquidity) |
331 | return liquidities[cls.last_date] | 408 | return liquidities[cls.last_date.get()] |
332 | 409 | ||
333 | @classmethod | 410 | @classmethod |
334 | def get_cryptoportfolio(cls, refetch=False): | 411 | def get_cryptoportfolio(cls, refetch=False): |
335 | if cls.data is not None and not refetch: | 412 | if cls.data.get() is not None and not refetch: |
413 | return | ||
414 | if cls.worker is not None and not cls.is_worker_thread(): | ||
415 | cls.notify_and_wait() | ||
336 | return | 416 | return |
337 | try: | 417 | try: |
338 | r = requests.get(cls.URL) | 418 | r = requests.get(cls.URL) |
@@ -342,11 +422,12 @@ class Portfolio: | |||
342 | cls.report.log_error("get_cryptoportfolio", exception=e) | 422 | cls.report.log_error("get_cryptoportfolio", exception=e) |
343 | return | 423 | return |
344 | try: | 424 | try: |
345 | cls.data = r.json(parse_int=D, parse_float=D) | 425 | cls.data.set(r.json(parse_int=D, parse_float=D)) |
346 | cls.parse_cryptoportfolio() | 426 | cls.parse_cryptoportfolio() |
347 | except (JSONDecodeError, SimpleJSONDecodeError): | 427 | except (JSONDecodeError, SimpleJSONDecodeError): |
348 | cls.data = None | 428 | cls.data.set(None) |
349 | cls.liquidities = {} | 429 | cls.last_date.set(None) |
430 | cls.liquidities.set({}) | ||
350 | 431 | ||
351 | @classmethod | 432 | @classmethod |
352 | def parse_cryptoportfolio(cls): | 433 | def parse_cryptoportfolio(cls): |
@@ -366,6 +447,8 @@ class Portfolio: | |||
366 | return clean_weights_ | 447 | return clean_weights_ |
367 | 448 | ||
368 | def parse_weights(portfolio_hash): | 449 | def parse_weights(portfolio_hash): |
450 | if "weights" not in portfolio_hash: | ||
451 | return {} | ||
369 | weights_hash = portfolio_hash["weights"] | 452 | weights_hash = portfolio_hash["weights"] |
370 | weights = {} | 453 | weights = {} |
371 | for i in range(len(weights_hash["_row"])): | 454 | for i in range(len(weights_hash["_row"])): |
@@ -375,13 +458,16 @@ class Portfolio: | |||
375 | map(clean_weights(i), weights_hash.items()))) | 458 | map(clean_weights(i), weights_hash.items()))) |
376 | return weights | 459 | return weights |
377 | 460 | ||
378 | high_liquidity = parse_weights(cls.data["portfolio_1"]) | 461 | high_liquidity = parse_weights(cls.data.get("portfolio_1")) |
379 | medium_liquidity = parse_weights(cls.data["portfolio_2"]) | 462 | medium_liquidity = parse_weights(cls.data.get("portfolio_2")) |
380 | 463 | ||
381 | cls.liquidities = { | 464 | cls.liquidities.set({ |
382 | "medium": medium_liquidity, | 465 | "medium": medium_liquidity, |
383 | "high": high_liquidity, | 466 | "high": high_liquidity, |
384 | } | 467 | }) |
385 | cls.last_date = max(max(medium_liquidity.keys()), max(high_liquidity.keys())) | 468 | cls.last_date.set(max( |
469 | max(medium_liquidity.keys(), default=datetime(1, 1, 1)), | ||
470 | max(high_liquidity.keys(), default=datetime(1, 1, 1)) | ||
471 | )) | ||
386 | 472 | ||
387 | 473 | ||