bw-essentials-core 0.1.6__tar.gz → 0.1.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bw-essentials-core might be problematic. Click here for more details.
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/PKG-INFO +1 -1
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/constants/services.py +10 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/portfolio_catalogue.py +56 -1
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/user_portfolio.py +165 -1
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials_core.egg-info/PKG-INFO +1 -1
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/setup.py +1 -1
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/README.md +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/__init__.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/constants/__init__.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/data_loch/__init__.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/data_loch/data_loch.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/email_client/__init__.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/email_client/email_client.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/notifications/__init__.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/notifications/teams_notification_schemas.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/notifications/teams_notifications.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/s3_utils/__init__.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/s3_utils/s3_utils.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/__init__.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/api_client.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/broker.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/market_pricer.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/master_data.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/model_portfolio_reporting.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/notification.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/portfolio_content.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/trade_placement.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/user_app.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/user_portfolio_reporting.py +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials_core.egg-info/SOURCES.txt +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials_core.egg-info/dependency_links.txt +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials_core.egg-info/requires.txt +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials_core.egg-info/top_level.txt +0 -0
- {bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/setup.cfg +0 -0
|
@@ -20,3 +20,13 @@ class Services(Enum):
|
|
|
20
20
|
PROMETHEUS_USER_APP = "Prometheus_User_App"
|
|
21
21
|
PORTFOLIO_CATALOGUE = "Portfolio_Catalogue"
|
|
22
22
|
PORTFOLIO_CONTENT = "Portfolio_Content"
|
|
23
|
+
|
|
24
|
+
class PortfolioStatus(Enum):
|
|
25
|
+
"""
|
|
26
|
+
Enum representing Status of Basket.
|
|
27
|
+
"""
|
|
28
|
+
ACTIVE = "active"
|
|
29
|
+
INACTIVE = "inactive"
|
|
30
|
+
PAUSED = "paused"
|
|
31
|
+
|
|
32
|
+
ACTIVE_FOR_SUBSCRIBED_USER = (ACTIVE, PAUSED)
|
{bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/portfolio_catalogue.py
RENAMED
|
@@ -48,7 +48,7 @@ Example
|
|
|
48
48
|
import logging
|
|
49
49
|
from typing import Optional, Dict, Any
|
|
50
50
|
|
|
51
|
-
from bw_essentials.constants.services import Services
|
|
51
|
+
from bw_essentials.constants.services import Services, PortfolioStatus
|
|
52
52
|
from bw_essentials.services.api_client import ApiClient
|
|
53
53
|
|
|
54
54
|
logger = logging.getLogger(__name__)
|
|
@@ -78,6 +78,7 @@ class PortfolioCatalogue(ApiClient):
|
|
|
78
78
|
self.name = Services.PORTFOLIO_CATALOGUE.value
|
|
79
79
|
self.urls = {
|
|
80
80
|
"rebalance": "rebalance",
|
|
81
|
+
"get_rebalance": "rebalance"
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
def create_rebalance(self, json: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
@@ -102,3 +103,57 @@ class PortfolioCatalogue(ApiClient):
|
|
|
102
103
|
)
|
|
103
104
|
logger.info("%s", data)
|
|
104
105
|
return data
|
|
106
|
+
|
|
107
|
+
def rebalance(self, portfolio_id, status):
|
|
108
|
+
"""Fetch the latest rebalance record for a portfolio.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
portfolio_id : str
|
|
113
|
+
Portfolio identifier.
|
|
114
|
+
status : str
|
|
115
|
+
Portfolio status filter.
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
dict
|
|
120
|
+
The first record from the service response's 'data' list.
|
|
121
|
+
"""
|
|
122
|
+
logger.info("In - rebalance portfolio_id=%s status=%s", portfolio_id, status)
|
|
123
|
+
data = self._get(
|
|
124
|
+
url=self.base_url,
|
|
125
|
+
endpoint=self.urls["get_rebalance"],
|
|
126
|
+
params={'portfolioId': portfolio_id, 'status': status}
|
|
127
|
+
)
|
|
128
|
+
logger.info("rebalance response: %s", data)
|
|
129
|
+
return data.get('data')[0]
|
|
130
|
+
|
|
131
|
+
def get_rebalance_due_date(self, portfolio_id, status):
|
|
132
|
+
"""Return the next rebalance date for an active portfolio, else None.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
portfolio_id : str
|
|
137
|
+
Portfolio identifier.
|
|
138
|
+
status : str
|
|
139
|
+
Portfolio status.
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
str | None
|
|
144
|
+
The next rebalance date string if available; otherwise None.
|
|
145
|
+
"""
|
|
146
|
+
logger.info(
|
|
147
|
+
"In - get_rebalance_due_date portfolio_id=%s status=%s",
|
|
148
|
+
portfolio_id,
|
|
149
|
+
status,
|
|
150
|
+
)
|
|
151
|
+
if status in PortfolioStatus.ACTIVE_FOR_SUBSCRIBED_USER.value:
|
|
152
|
+
rebalance_history = self.rebalance(portfolio_id, status)
|
|
153
|
+
next_rebalance_date = (
|
|
154
|
+
rebalance_history.get('nextRebalanceDate') if rebalance_history else None
|
|
155
|
+
)
|
|
156
|
+
logger.info("nextRebalanceDate=%s", next_rebalance_date)
|
|
157
|
+
return next_rebalance_date
|
|
158
|
+
logger.info("get_rebalance_due_date not applicable for status=%s", status)
|
|
159
|
+
return None
|
{bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/user_portfolio.py
RENAMED
|
@@ -43,7 +43,9 @@ class UserPortfolio(ApiClient):
|
|
|
43
43
|
"skip": "userportfolio/order/skip/basket",
|
|
44
44
|
"create_order_instructions": "userportfolio/order/order-instruction",
|
|
45
45
|
"basket_details": "userportfolio/basket",
|
|
46
|
-
"broker_users_holdings": "holding/{}/users"
|
|
46
|
+
"broker_users_holdings": "holding/{}/users",
|
|
47
|
+
"user_portfolio_thresholds": "alerts/portfolio/thresholds",
|
|
48
|
+
"holding_thresholds": "alerts/holding/thresholds"
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
def create_holding_transaction(self, payload):
|
|
@@ -404,3 +406,165 @@ class UserPortfolio(ApiClient):
|
|
|
404
406
|
endpoint = self.urls.get('broker_users_holdings').format(broker)
|
|
405
407
|
response = self._get(url=self.base_url, endpoint=endpoint)
|
|
406
408
|
return response.get('data')
|
|
409
|
+
|
|
410
|
+
def create_user_portfolio_threshold(self, payload):
|
|
411
|
+
"""Create a *User Portfolio* threshold.
|
|
412
|
+
|
|
413
|
+
Parameters
|
|
414
|
+
----------
|
|
415
|
+
payload : dict | str
|
|
416
|
+
JSON-serialisable dictionary (or raw JSON string) containing the
|
|
417
|
+
following **required** keys.
|
|
418
|
+
|
|
419
|
+
* **portfolio_type** (str) – One of the portfolio types accepted by
|
|
420
|
+
the User-Portfolio service (e.g. ``USER_PORTFOLIO`` or ``BASKET``).
|
|
421
|
+
* **portfolio_id** (str) – Unique identifier of the portfolio
|
|
422
|
+
entity.
|
|
423
|
+
* **side** (str) – Either ``LONG`` or ``SHORT``.
|
|
424
|
+
* **threshold_type** (str) – Threshold category, e.g. ``PT`` or
|
|
425
|
+
``SL``.
|
|
426
|
+
* **status** (str) – Initial status, typically ``ACTIVE``.
|
|
427
|
+
|
|
428
|
+
**Optional** keys:
|
|
429
|
+
|
|
430
|
+
* **target_pct** (Decimal | float) – Percent based trigger level.
|
|
431
|
+
* **target_value** (Decimal | float) – Absolute money value trigger.
|
|
432
|
+
* **source** (str) – Origin of the instruction (``user``, ``admin`` …).
|
|
433
|
+
|
|
434
|
+
Returns
|
|
435
|
+
-------
|
|
436
|
+
dict
|
|
437
|
+
A dictionary representing the newly-created threshold (same shape
|
|
438
|
+
as the backend response ``data`` field).
|
|
439
|
+
"""
|
|
440
|
+
logger.info(f"In create_user_portfolio_threshold {payload =}")
|
|
441
|
+
data = self._post(url=self.base_url,
|
|
442
|
+
endpoint=self.urls.get("user_portfolio_thresholds"),
|
|
443
|
+
data=payload)
|
|
444
|
+
logger.info(f"{data =}")
|
|
445
|
+
return data.get("data")
|
|
446
|
+
|
|
447
|
+
def get_user_portfolio_thresholds(self, params=None):
|
|
448
|
+
"""Retrieve *User Portfolio* thresholds.
|
|
449
|
+
|
|
450
|
+
Parameters
|
|
451
|
+
----------
|
|
452
|
+
params : dict | None, optional
|
|
453
|
+
Query-string parameters used as filters; accepted keys mirror the
|
|
454
|
+
columns of ``UserPortfolioThreshold`` (e.g. ``portfolio_type``,
|
|
455
|
+
``portfolio_id``, ``status`` …). Passing ``None`` performs an
|
|
456
|
+
unfiltered list retrieval.
|
|
457
|
+
|
|
458
|
+
Returns
|
|
459
|
+
-------
|
|
460
|
+
list[dict]
|
|
461
|
+
List of serialised thresholds ordered by ``-effective_from``.
|
|
462
|
+
"""
|
|
463
|
+
logger.info(f"In get_user_portfolio_thresholds {params =}")
|
|
464
|
+
data = self._get(url=self.base_url,
|
|
465
|
+
endpoint=self.urls.get("user_portfolio_thresholds"),
|
|
466
|
+
params=params)
|
|
467
|
+
logger.info(f"{data =}")
|
|
468
|
+
return data.get("data")
|
|
469
|
+
|
|
470
|
+
def update_user_portfolio_threshold(self, payload):
|
|
471
|
+
"""Update an existing *User Portfolio* threshold.
|
|
472
|
+
|
|
473
|
+
The request is forwarded verbatim to ``PUT /portfolio/thresholds``.
|
|
474
|
+
|
|
475
|
+
Parameters
|
|
476
|
+
----------
|
|
477
|
+
payload : dict | str
|
|
478
|
+
Dictionary (or JSON string) that **must** include:
|
|
479
|
+
|
|
480
|
+
* **id** (int) – Primary key of the threshold to update.
|
|
481
|
+
|
|
482
|
+
Any of the following *optional* fields may also be supplied; only
|
|
483
|
+
those present will be updated:
|
|
484
|
+
|
|
485
|
+
* **target_pct** (Decimal | float)
|
|
486
|
+
* **status** (str)
|
|
487
|
+
* **source** (str)
|
|
488
|
+
|
|
489
|
+
Returns
|
|
490
|
+
-------
|
|
491
|
+
dict
|
|
492
|
+
The updated threshold as returned by the backend (``data`` field).
|
|
493
|
+
"""
|
|
494
|
+
logger.info(f"In update_user_portfolio_threshold {payload =}")
|
|
495
|
+
data = self._put(url=self.base_url,
|
|
496
|
+
endpoint=self.urls.get("user_portfolio_thresholds"),
|
|
497
|
+
json=payload)
|
|
498
|
+
logger.info(f"{data =}")
|
|
499
|
+
return data.get("data")
|
|
500
|
+
|
|
501
|
+
def create_holding_threshold(self, payload):
|
|
502
|
+
"""Create a *Holding* threshold.
|
|
503
|
+
|
|
504
|
+
Parameters
|
|
505
|
+
----------
|
|
506
|
+
payload : dict | str
|
|
507
|
+
Data required by ``POST /holding/thresholds``. Mandatory keys:
|
|
508
|
+
|
|
509
|
+
* **holding_type** (str)
|
|
510
|
+
* **holding_id** (str)
|
|
511
|
+
* **side** (str)
|
|
512
|
+
* **threshold** (Decimal | float)
|
|
513
|
+
* **effective_from** (datetime-iso-str)
|
|
514
|
+
|
|
515
|
+
Optional: **source** (str)
|
|
516
|
+
|
|
517
|
+
Returns
|
|
518
|
+
-------
|
|
519
|
+
dict
|
|
520
|
+
Newly created Holding-threshold representation.
|
|
521
|
+
"""
|
|
522
|
+
logger.info(f"In create_holding_threshold {payload =}")
|
|
523
|
+
data = self._post(url=self.base_url,
|
|
524
|
+
endpoint=self.urls.get("holding_thresholds"),
|
|
525
|
+
data=payload)
|
|
526
|
+
logger.info(f"{data =}")
|
|
527
|
+
return data.get("data")
|
|
528
|
+
|
|
529
|
+
def get_holding_thresholds(self, params=None):
|
|
530
|
+
"""Retrieve *Holding* thresholds with optional filters.
|
|
531
|
+
|
|
532
|
+
Parameters
|
|
533
|
+
----------
|
|
534
|
+
params : dict | None, optional
|
|
535
|
+
Filter parameters (``holding_type``, ``holding_id`` …). ``None``
|
|
536
|
+
results in an unfiltered list.
|
|
537
|
+
|
|
538
|
+
Returns
|
|
539
|
+
-------
|
|
540
|
+
list[dict]
|
|
541
|
+
Serialised Holding-thresholds ordered by ``-effective_from``.
|
|
542
|
+
"""
|
|
543
|
+
logger.info(f"In get_holding_thresholds {params =}")
|
|
544
|
+
data = self._get(url=self.base_url,
|
|
545
|
+
endpoint=self.urls.get("holding_thresholds"),
|
|
546
|
+
params=params)
|
|
547
|
+
logger.info(f"{data =}")
|
|
548
|
+
return data.get("data")
|
|
549
|
+
|
|
550
|
+
def update_holding_threshold(self, payload):
|
|
551
|
+
"""Update an existing *Holding* threshold.
|
|
552
|
+
|
|
553
|
+
Parameters
|
|
554
|
+
----------
|
|
555
|
+
payload : dict | str
|
|
556
|
+
Must include the primary key ``id`` and any fields to be changed
|
|
557
|
+
(e.g. ``threshold``, ``status`` or ``effective_to``).
|
|
558
|
+
|
|
559
|
+
Returns
|
|
560
|
+
-------
|
|
561
|
+
dict
|
|
562
|
+
Updated Holding-threshold object as returned by the backend.
|
|
563
|
+
"""
|
|
564
|
+
logger.info(f"In update_holding_threshold {payload =}")
|
|
565
|
+
data = self._put(url=self.base_url,
|
|
566
|
+
endpoint=self.urls.get("holding_thresholds"),
|
|
567
|
+
json=payload)
|
|
568
|
+
logger.info(f"{data =}")
|
|
569
|
+
return data.get("data")
|
|
570
|
+
|
|
@@ -10,7 +10,7 @@ from setuptools import setup, find_packages
|
|
|
10
10
|
|
|
11
11
|
setup(
|
|
12
12
|
name="bw-essentials-core",
|
|
13
|
-
version="0.1.
|
|
13
|
+
version="0.1.8",
|
|
14
14
|
author="InvestorAI",
|
|
15
15
|
author_email="support+tech@investorai.in",
|
|
16
16
|
description="Reusable utilities for S3, email, Data Loch, Microsoft Teams Notifications and more.",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/email_client/__init__.py
RENAMED
|
File without changes
|
{bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/email_client/email_client.py
RENAMED
|
File without changes
|
{bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/notifications/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/market_pricer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/notification.py
RENAMED
|
File without changes
|
{bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/portfolio_content.py
RENAMED
|
File without changes
|
{bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials/services/trade_placement.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials_core.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials_core.egg-info/requires.txt
RENAMED
|
File without changes
|
{bw_essentials_core-0.1.6 → bw_essentials_core-0.1.8}/bw_essentials_core.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|